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.00000000The 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.0000000The 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:
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+00Both 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.
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 TRUEThe 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 TRUEHere 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.
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"| 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 |
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