Getting started with shieldR

Your first experiment

This tutorial takes you from a neutral scenario to saved, reproducible results. Only the installed shieldR package is needed for the code below. Small batch and replication counts keep the tutorial quick; they are teaching choices and are not a study design or a precision justification.

The package implements a fixed five-leg research model. Its teaching scenarios use synthetic route and condition inputs. The default examples disable exposure and item-condition hazards so that network mechanics are easy to inspect.

scenario <- example_scenario(N = 4L)
scenario$graph
#>    leg         from           to route travel_min exposure_weight
#> 1   L1       Source   Collection     A          1               0
#> 2   L1       Source   Collection     B          1               0
#> 3   L1       Source   Collection     C          1               0
#> 4   L2   Collection Transfer-A/B     A          1               0
#> 5   L2   Collection Transfer-A/B     B          1               0
#> 6   L2   Collection Transfer-A/B     C          1               0
#> 7   L3 Transfer-A/B   Stage1-a/b     A          1               0
#> 8   L3 Transfer-A/B   Stage1-a/b     B          1               0
#> 9   L3 Transfer-A/B   Stage1-a/b     C          1               0
#> 10  L4   Stage1-a/b       Stage2     A          1               0
#> 11  L4   Stage1-a/b       Stage2     B          1               0
#> 12  L4   Stage1-a/b       Stage2     C          1               0
#> 13  L5       Stage2       Stage3     A          1               0
#> 14  L5       Stage2       Stage3     B          1               0
#> 15  L5       Stage2       Stage3     C          1               0
#>    vehicle_fraction leg_i route_i
#> 1                 1     1       1
#> 2                 1     1       2
#> 3                 1     1       3
#> 4                 1     2       1
#> 5                 1     2       2
#> 6                 1     2       3
#> 7                 1     3       1
#> 8                 1     3       2
#> 9                 1     3       3
#> 10                1     4       1
#> 11                1     4       2
#> 12                1     4       3
#> 13                1     5       1
#> 14                1     5       2
#> 15                1     5       3
run <- simulate_shield(scenario, "P0", n_rep = 3L, seed = 20260512L)
summarize_shield(run)
#>             endpoint n missing      median          q1          q3
#> 1      viable_stage1 3       0  1.00000000  1.00000000  1.00000000
#> 2      viable_stage2 3       0  1.00000000  1.00000000  1.00000000
#> 3      viable_stage3 3       0  1.00000000  1.00000000  1.00000000
#> 4       t_stage2_med 3       0 11.00000000 11.00000000 11.00000000
#> 5       t_stage3_med 3       0 14.00000000 14.00000000 14.00000000
#> 6            util_L1 3       0  0.06666667  0.06666667  0.06666667
#> 7            util_L2 3       0  0.06666667  0.06666667  0.06666667
#> 8            util_L3 3       0  0.06666667  0.06666667  0.06666667
#> 9            util_L4 3       0  0.06666667  0.06666667  0.06666667
#> 10           util_L5 3       0  0.06666667  0.06666667  0.06666667
#> 11        n_dispatch 3       0 20.00000000 20.00000000 20.00000000
#> 12      n_disruption 3       0  0.00000000  0.00000000  0.00000000
#> 13 n_disruption_loss 3       0  0.00000000  0.00000000  0.00000000
#> 14         n_pending 3       0  0.00000000  0.00000000  0.00000000
#> 15         hold_mean 3       0  0.00000000  0.00000000  0.00000000
#> 16         hold_rate 3       0  0.00000000  0.00000000  0.00000000

Interpret the endpoints

The statistical unit is the batch replication. The primary endpoint, viable_stage2, measures arrival viable at Stage 2 by the administrative horizon. Arrival is recorded before unloading. Item-condition hazard ends there, while downstream transport continues to Stage 3. Conditional arrival-time medians remain missing when no item arrives; they are never replaced by zero.

The constrained example uses fewer vehicles and a shorter horizon. Its purpose is to illustrate that failure to arrive is an endpoint failure even without a loss.

limited <- example_scenario("constrained", N = 4L)
short_run <- simulate_shield(limited, n_rep = 3L)
summarize_shield(short_run)
#>             endpoint n missing     median         q1         q3
#> 1      viable_stage1 3       0  1.0000000  1.0000000  1.0000000
#> 2      viable_stage2 3       0  0.5000000  0.5000000  0.5000000
#> 3      viable_stage3 3       0  0.0000000  0.0000000  0.0000000
#> 4       t_stage2_med 3       0 11.0000000 11.0000000 11.0000000
#> 5       t_stage3_med 0       3         NA         NA         NA
#> 6            util_L1 3       0  0.6666667  0.6666667  0.6666667
#> 7            util_L2 3       0  0.6666667  0.6666667  0.6666667
#> 8            util_L3 3       0  0.5000000  0.5000000  0.5000000
#> 9            util_L4 3       0  0.2500000  0.2500000  0.2500000
#> 10           util_L5 3       0  0.0000000  0.0000000  0.0000000
#> 11        n_dispatch 3       0 14.0000000 14.0000000 14.0000000
#> 12      n_disruption 3       0  0.0000000  0.0000000  0.0000000
#> 13 n_disruption_loss 3       0  0.0000000  0.0000000  0.0000000
#> 14         n_pending 3       0  4.0000000  4.0000000  4.0000000
#> 15         hold_mean 3       0  0.0000000  0.0000000  0.0000000
#> 16         hold_rate 3       0  0.0000000  0.0000000  0.0000000

Construct an explicit scenario

The fixed topology is:

Source -> Collection -> Transfer-A / Transfer-B -> Stage1-a / Stage1-b -> Stage2 -> Stage3
        L1       L2                L3                L4       L5

Each leg offers routes A, B and C. Parallel destinations and route labels are separate concepts: choosing a route does not itself identify the destination. Vehicles carry one item and are shared within the documented leg pools.

To define an experiment, pass your own graph and hazard, observation and policy settings to shield_parameters(). The documented graph schema fixes five legs and three routes per leg. All event durations are whole minutes. Defaults are synthetic teaching values, not calibrated logistics parameters.

scenario$graph includes derived indices. Select only the seven input columns before reconstructing or exporting a graph for later use:

graph_columns <- c("leg", "from", "to", "route", "travel_min",
                   "exposure_weight", "vehicle_fraction")
graph <- scenario$graph[graph_columns]
rebuilt <- shield_parameters(graph = graph, N = 4L, horizon = 60L)
stopifnot(identical(rebuilt, scenario))
Column Contract
leg One of L1 to L5, with exactly one row for each route
from, to The fixed node-group names shown by example_scenario()$graph
route A, B or C; 15 distinct leg-route rows in total
travel_min Strictly positive whole minutes
exposure_weight Finite number in the closed interval from zero to one
vehicle_fraction Strictly positive, finite multiplier of batch size, constant within a leg

The constructor also checks observation probabilities, class counts, hazards, and duration constraints. Reconstruct from inputs when changing a scenario; editing a derived field such as V or leg_time leaves related fields stale. The following catches an intentionally invalid graph to show the diagnostic:

invalid_graph <- graph
invalid_graph$travel_min[1] <- 0
tryCatch(
  shield_parameters(invalid_graph),
  error = function(e) conditionMessage(e)
)
#> [1] "graph numeric values are outside the documented domains"

Time-varying logistics-item specifications

An item has a true condition and an observed specification. These need not agree: observations can be noisy, missing, delayed or retained from an earlier time. The basic model updates item classes and viability each minute. The optional with_item_model() extension adds a dimensionless condition index, a degradation indicator and finite-capacity processing at Stage 2. Processing can stabilize an item; residual degradation and other losses can continue afterwards.

Specification Interpretation
fragility Positive class-specific weights controlling susceptibility to loss
obs_condition_index Latest delivered, noisy condition measurement; higher is better in this synthetic model
obs_degradation Latest observed degradation flag
obs_time, obs_missing Measurement time and whether the latest observation was missing
refresh, delivery Observation interval and delivery delay in minutes
useful_updates Whether later observations are delivered; does not stop underlying condition changes

The observed fields belong to the internal item_specs state table. They are used by the decision rules; ordinary run rows contain batch-level outcomes. The index equation and its thresholds are synthetic, dimensionless assumptions, not a physical sensor calibration. with_decision_controls(..., item = "initial_observed") freezes the information used for decisions while item conditions continue to evolve. The default item = "updated" uses delivered updates. This separates information freshness from the physical process.

The following explicit specification activates condition changes and finite processing capacity. The coefficients are illustrative and uncalibrated. The processing extension continues to fixed 24-hour and 30-day follow-up times; these are model assumptions, separate from the transport horizon.

item_model <- list(
  degradation_scale = 0.2, early_other = 0.02, late_other = 0.1,
  tau = 40, residual = 0.1,
  prep = c(1, 2, 3), processing = c(2, 3, 4), failure = 0,
  capacity = 1L, fragility = c(1.2, 1, 0.8),
  sensitivity = 0.7, specificity = 0.95, persistence = 1,
  missing = 0, refresh = 2L, delivery = 0L,
  useful_updates = TRUE, item_age = 0
)
dynamic <- with_item_model(example_scenario(N = 4L), item_model)
updated <- with_decision_controls(dynamic, item = "updated")
initial <- with_decision_controls(dynamic, item = "initial_observed")
updated_run <- simulate_shield(updated, "P3", n_rep = 3L, seed = 20260512L)
initial_run <- simulate_shield(initial, "P3", n_rep = 3L, seed = 20260513L)
summarize_shield(updated_run)
#>                     endpoint n missing       median          q1           q3
#> 1              viable_stage1 3       0 1.000000e+00  1.00000000 1.000000e+00
#> 2              viable_stage2 3       0 1.000000e+00  1.00000000 1.000000e+00
#> 3              viable_stage3 3       0 1.000000e+00  1.00000000 1.000000e+00
#> 4               t_stage2_med 3       0 1.450000e+01 14.25000000 1.500000e+01
#> 5               t_stage3_med 3       0 2.650000e+01 26.50000000 2.800000e+01
#> 6                    util_L1 3       0 8.333333e-02  0.07708333 8.333333e-02
#> 7                    util_L2 3       0 7.500000e-02  0.07083333 8.125000e-02
#> 8                    util_L3 3       0 8.333333e-02  0.08333333 8.333333e-02
#> 9                    util_L4 3       0 8.750000e-02  0.08333333 8.958333e-02
#> 10                   util_L5 3       0 6.666667e-02  0.06666667 6.666667e-02
#> 11                n_dispatch 3       0 2.000000e+01 20.00000000 2.000000e+01
#> 12              n_disruption 3       0 0.000000e+00  0.00000000 0.000000e+00
#> 13         n_disruption_loss 3       0 0.000000e+00  0.00000000 0.000000e+00
#> 14                 n_pending 3       0 0.000000e+00  0.00000000 0.000000e+00
#> 15                 hold_mean 3       0 7.000000e-01  0.60000000 8.000000e-01
#> 16                 hold_rate 3       0 4.500000e-01  0.40000000 5.250000e-01
#> 17                viable_30d 3       0 7.500000e-01  0.62500000 8.750000e-01
#> 18                viable_24h 3       0 1.000000e+00  0.87500000 1.000000e+00
#> 19          loss_degradation 3       0 0.000000e+00  0.00000000 1.250000e-01
#> 20                loss_other 3       0 0.000000e+00  0.00000000 2.500000e-01
#> 21           loss_second_hit 3       0 0.000000e+00  0.00000000 0.000000e+00
#> 22      stabilization_access 3       0 1.000000e+00  1.00000000 1.000000e+00
#> 23       never_stabilization 3       0 0.000000e+00  0.00000000 0.000000e+00
#> 24 loss_before_stabilization 3       0 0.000000e+00  0.00000000 0.000000e+00
#> 25       t_stabilization_med 3       0 2.450000e+01 23.75000000 2.750000e+01
#> 26          t_processing_med 3       0 2.100000e+01 20.00000000 2.425000e+01
#> 27    processing_utilization 3       0 3.240741e-04  0.00031250 3.240741e-04
#> 28            exposure_proxy 3       0 0.000000e+00  0.00000000 0.000000e+00
summarize_shield(initial_run)
#>                     endpoint n missing       median           q1           q3
#> 1              viable_stage1 3       0 1.000000e+00 1.000000e+00 1.000000e+00
#> 2              viable_stage2 3       0 1.000000e+00 1.000000e+00 1.000000e+00
#> 3              viable_stage3 3       0 1.000000e+00 1.000000e+00 1.000000e+00
#> 4               t_stage2_med 3       0 1.600000e+01 1.575000e+01 1.625000e+01
#> 5               t_stage3_med 3       0 2.700000e+01 2.675000e+01 2.775000e+01
#> 6                    util_L1 3       0 8.333333e-02 8.125000e-02 8.541667e-02
#> 7                    util_L2 3       0 8.750000e-02 8.333333e-02 9.166667e-02
#> 8                    util_L3 3       0 8.333333e-02 8.333333e-02 8.541667e-02
#> 9                    util_L4 3       0 7.916667e-02 7.916667e-02 8.333333e-02
#> 10                   util_L5 3       0 6.666667e-02 6.666667e-02 6.666667e-02
#> 11                n_dispatch 3       0 2.000000e+01 2.000000e+01 2.000000e+01
#> 12              n_disruption 3       0 0.000000e+00 0.000000e+00 0.000000e+00
#> 13         n_disruption_loss 3       0 0.000000e+00 0.000000e+00 0.000000e+00
#> 14                 n_pending 3       0 0.000000e+00 0.000000e+00 0.000000e+00
#> 15                 hold_mean 3       0 8.500000e-01 8.250000e-01 8.750000e-01
#> 16                 hold_rate 3       0 6.000000e-01 5.750000e-01 6.250000e-01
#> 17                viable_30d 3       0 1.000000e+00 8.750000e-01 1.000000e+00
#> 18                viable_24h 3       0 1.000000e+00 1.000000e+00 1.000000e+00
#> 19          loss_degradation 3       0 0.000000e+00 0.000000e+00 0.000000e+00
#> 20                loss_other 3       0 0.000000e+00 0.000000e+00 1.250000e-01
#> 21           loss_second_hit 3       0 0.000000e+00 0.000000e+00 0.000000e+00
#> 22      stabilization_access 3       0 1.000000e+00 1.000000e+00 1.000000e+00
#> 23       never_stabilization 3       0 0.000000e+00 0.000000e+00 0.000000e+00
#> 24 loss_before_stabilization 3       0 0.000000e+00 0.000000e+00 0.000000e+00
#> 25       t_stabilization_med 3       0 2.500000e+01 2.400000e+01 2.575000e+01
#> 26          t_processing_med 3       0 2.150000e+01 2.075000e+01 2.250000e+01
#> 27    processing_utilization 3       0 3.009259e-04 3.009259e-04 3.240741e-04
#> 28            exposure_proxy 3       0 0.000000e+00 0.000000e+00 0.000000e+00

Both arms retain the same condition dynamics. Only the information used by the allocation rule differs. A small synthetic comparison does not establish that one information policy is better in a real logistics application.

Compare designs explicitly

Independent policy comparisons use different seeds. compare_policies() returns a Mann-Whitney U comparison, Cliff’s delta and bootstrap intervals for delta and the median difference. It does not perform a preliminary normality test. Its intervals quantify Monte Carlo sampling uncertainty conditional on the model. This short example explicitly reduces bootstrap resamples for speed; study reporting uses the default 10,000.

a <- simulate_shield(scenario, "P0", n_rep = 3L, seed = 20260512L)
b <- simulate_shield(scenario, "P3", n_rep = 3L, seed = 20260512L + 1L)
compare_policies(b$viable_stage2, a$viable_stage2, "P3", "P0", B = 100L)
#>       family arm_x arm_y n_x n_y median_x median_y iqr_x_lo iqr_x_hi iqr_y_lo
#> 1 comparison    P3    P0   3   3        1        1        1        1        1
#>   iqr_y_hi           test   U p_raw p_raw_log10 p_underflow cliff_delta
#> 1        1 Mann-Whitney U 4.5     1           0       FALSE           0
#>   cliff_lwr cliff_upr  cliff_mag med_diff med_diff_lwr med_diff_upr
#> 1         0         0 negligible        0            0            0
#>   med_diff_ci_degenerate
#> 1                   TRUE

The neutral scenario is tied by construction. A point bootstrap interval on a discrete endpoint is flagged rather than interpreted as empirical certainty. For several comparisons, collect p_raw_log10 values in each prespecified family and call adjust_fdr() separately for that family. Very small tails should be reported using log probabilities; the linear field is floored when it cannot represent the computed value.

Paired disruptions-disabled interventions instead use the same seed and replication count. The package aligns rows and checks their replication identities.

on <- simulate_shield(scenario, "P3", "adaptive", n_rep = 3L,
                      disruptions_enabled = TRUE)
off <- simulate_shield(scenario, "P3", "adaptive", n_rep = 3L,
                       disruptions_enabled = FALSE)
compare_intervention(on, off, B = 100L)
#>        endpoint n_pairs                         contrast mean_difference lower
#> 1 viable_stage2       3 no_disruptions minus disruptions               0     0
#>   upper point_interval
#> 1     0           TRUE

Here exposure is zero, so the contrast is zero. In a nonzero disruption risk experiment, the contrast includes downstream changes and horizon effects. It is not a count of direct disruption losses or proof of a better policy.

P3 uses a known disruption-response model and an entropy-regularized decision heuristic. Its comparisons with P1 and P2 do not isolate individual component effects. All assumptions require scientific review for a new application.

The study design tutorial develops independent comparisons, paired interventions, complete comparison-family adjustment, and replay in more detail. The workbench tutorial covers the Shiny interface.

Save results with provenance

CSV is convenient for a table, but it does not preserve the scenario and software attributes needed by compare_intervention(). Save the full run as RDS and write an environment record alongside derived exports. The temporary folder below makes this example self-contained; use a named output directory inside your analysis project for a real study.

output_dir <- tempfile("shieldR-tutorial-")
dir.create(output_dir)
saveRDS(run, file.path(output_dir, "batch-run.rds"))
utils::write.csv(summarize_shield(run),
                 file.path(output_dir, "endpoint-summary.csv"), row.names = FALSE)
writeLines(capture.output(sessionInfo()),
           file.path(output_dir, "sessionInfo.txt"))
restored <- readRDS(file.path(output_dir, "batch-run.rds"))
stopifnot(identical(run, restored))
list.files(output_dir)
#> [1] "batch-run.rds"        "endpoint-summary.csv" "sessionInfo.txt"

Troubleshooting

Symptom Check
Every policy has the same viability The neutral examples have no item-condition or disruption hazards; ties are expected
Viability is below one without any losses Some items may be viable but have not arrived by the administrative horizon
Arrival-time summaries are missing No arrival occurred in one or more replications; retain missingness explicitly
Graph validation fails Keep exactly the seven input columns and all 15 unique leg-route rows
A paired comparison is rejected Keep original run attributes, matching configurations and identical replication seeds
A bootstrap interval is a single point Inspect discreteness and replication counts; this does not establish empirical certainty
The vignette is not installed Install from a built source archive or use build_vignettes = TRUE

Session information

sessionInfo()
#> R version 4.6.2 (2026-10-29)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] shieldR_0.6.0  rmarkdown_2.32
#> 
#> loaded via a namespace (and not attached):
#>  [1] digest_0.6.39    R6_2.6.1         fastmap_1.2.0    xfun_0.62       
#>  [5] maketools_1.3.2  cachem_1.1.0     parallel_4.6.2   knitr_1.52      
#>  [9] htmltools_0.5.9  buildtools_1.0.0 lifecycle_1.0.5  cli_3.6.6       
#> [13] sass_0.4.10      jquerylib_0.1.4  compiler_4.6.2   sys_3.4.3       
#> [17] tools_4.6.2      evaluate_1.0.5   bslib_0.12.0     yaml_2.3.12     
#> [21] otel_0.2.0       jsonlite_2.0.0   rlang_1.3.0