This vignette demonstrates multistage test (MST) assembly under three design strategies:
Bottom-up strategy: Constraints are primarily enforced at the module level
Top-down strategy: Constraints are primarily enforced at the pathway level
Hybrid strategy: Combines module-level control (bottom-up strength) and pathway or panel-level balancing (top-down strength)
The examples mirror demonstration examples from the Rmst
package (https://github.com/xluo11/Rmst/tree/master), using the
same item pool and specifications. To ensure fast vignette rendering and
CRAN compatibility, the full assembly process is not executed here. The
assembled panel shown below was precomputed and saved as a package
dataset.
The purpose of this vignette is not only to reproduce assembly results, but to highlight:
Flexible specification of constraints across multiple hierarchical levels
Clear translation of design specifications into linear constraints
Transparent workflow using mstATA
The pool contains: 400 dichotomous 3PL items; 20 GPCM items and 20 GRM items. 127 items for content 1, 96 items for content 2, 103 items for content 3 and 114 items for content 4.
Each item includes: categorical attribute (model, content area) and continuous attribute (IRT parameters, response time).
data("Rmst_pool")
PoolSize<-nrow(Rmst_pool)
content_categories<-c("1","2","3","4")
item_par_cols<-list("3PL"=c("a","b","c"),
"GPCM" = c("alpha","delta1","delta2","delta3"),
"GRM" = c("alpha","beta1","beta2"))| Attribute | Mean | SD | Min | Max |
|---|---|---|---|---|
| 3PL: a | 0.91 | 0.19 | 0.55 | 2.01 |
| 3PL: b | -0.03 | 0.71 | -2.04 | 2.09 |
| 3PL: c | 0.10 | 0.04 | 0.02 | 0.20 |
| GRM: alpha | 0.94 | 0.16 | 0.72 | 1.26 |
| GRM: beta1 | -0.62 | 0.46 | -1.45 | 0.19 |
| GRM: beta2 | 0.23 | 0.59 | -0.78 | 1.36 |
| GPCM: alpha | 0.93 | 0.15 | 0.62 | 1.21 |
| GPCM: delta1 | 0.16 | 0.82 | -1.30 | 2.24 |
| GPCM: delta2 | -0.08 | 0.98 | -1.41 | 2.11 |
| GPCM: delta3 | 0.39 | 1.08 | -1.71 | 2.36 |
| Response Time | 56.17 | 15.37 | 22.00 | 129.00 |
Assemble 2 panels of 1-2 MST using the top-down design approach. Each route includes 40 items, and no item reuse is allowed. Maximize TIF over the [-1.64, 0] for the route 1M-2E and over [0, 1.64] for the route 1M-2H in hopes of covering the region [-1.64, 1.64] (90% of the population) jointly with adequate test information. To achieve a more balanced routing distribution, anchor the TIF intersection of Module 2E and 2M at 0.
As for non-statistical constraints, each route is required to have 36 3PL items, 2 GPCM items, and 2 GRM items. Also, each route includes 9 to 11 items in each of the four content domains and has an average response time of 60 +/- 4 seconds per item.
### Step 1: Prepare the Item Pool
theta_values<-unique(c(seq(-1.64,0,length.out = 3),
seq(0,1.64,length.out = 3)))
theta_iif<-compute_iif(Rmst_pool,
item_par_cols = item_par_cols,
theta = theta_values,model_col = "model",
D = 1)
Rmst_pool[,paste0("iif(theta=",theta_values,")")]<-theta_iif
# check attribute availability
item_cat_values<-vapply(content_categories,FUN = function(cat_level)
get_attribute_val(Rmst_pool,attribute = "content",cat_level = cat_level),
FUN.VALUE = integer(PoolSize))
# number of items in each category
apply(item_cat_values,2,sum)
#> 1 2 3 4
#> 127 96 103 114
### Step 2: Specify the MST Structure: 40 items in each pathway, routing decision point is 0.
TD_12 <- mst_design(itempool = Rmst_pool,item_id_col = "Item_id",
design = "1-2",rdps = list(0),pathway_length = 40)
### Step 3: Identify hierarchical requirements
# mst structure constraints: pathway length, at least one item per module,
# routing decision point is 0.
# item reusage: no item reuse within a panel, no item reuse across 2 panels.
# num of items from different IRT model per pathway: 36 3PL items, 2 GPCM items, and 2 GRM items
# num of items from each content domain per pathway: 9 to 11 items in each domain.
# average response time: each pathway has 60 +/- 4 seconds per item.
# TIF: three equal interval points in [-1.64, 0] for the route 1M-2E
# and in [0, 1.64] for the route 1M-2H
### Step 4: Translate specifications
TD12_structure<-mst_structure_con(x = TD_12)
TD12_itemtype<-test_itemcat_con(x = TD_12,attribute = "model",
cat_levels = c("3PL","GPCM","GRM"),
operator = "=",
target_num = c(36,2,2),
which_pathway = 1:2)
TD12_content<-test_itemcat_range_con(x = TD_12,attribute = "content",
cat_levels = content_categories,
min = 9,max = 11,
which_pathway = 1:2)
TD12_time<-test_itemquant_range_con(x = TD_12,attribute = "time",
min = 56*40,max = 64*40,
which_pathway = 1:2)
TD12_noreuse<-panel_itemreuse_con(x = TD_12,overlap = FALSE)
TD12_obj1<-objective_term(x = TD_12,attribute = "iif(theta=-1.64)",
applied_level = "Pathway-level",
which_pathway = 1,sense = "max")
TD12_obj2<-objective_term(x = TD_12,attribute = "iif(theta=-0.82)",
applied_level = "Pathway-level",
which_pathway = 1,sense = "max")
TD12_obj3<-objective_term(x = TD_12,attribute = "iif(theta=0)",
applied_level = "Pathway-level",
which_pathway = 1,sense = "max")
TD12_obj4<-objective_term(x = TD_12,attribute = "iif(theta=0)",
applied_level = "Pathway-level",
which_pathway = 2,sense = "max")
TD12_obj5<-objective_term(x = TD_12,attribute = "iif(theta=0.82)",
applied_level = "Pathway-level",
which_pathway = 2,sense = "max")
TD12_obj6<-objective_term(x = TD_12,attribute = "iif(theta=1.64)",
applied_level = "Pathway-level",
which_pathway = 2,sense = "max")
TD12_obj<-capped_maximin_obj(x = TD_12,
multiple_terms = list(TD12_obj1,TD12_obj2,TD12_obj3,
TD12_obj4,TD12_obj5,TD12_obj6))
TD12_onepanel<-onepanel_spec(x = TD_12,
constraints = list(TD12_structure,TD12_noreuse,
TD12_itemtype,TD12_content,
TD12_time),
objective = TD12_obj)
TD12_model<-multipanel_spec(x = TD_12,panel_model = TD12_onepanel,
num_panels = 2,
global_min_use = 0,global_max_use = 1)
# It is not executed in the vignette to avoid long build times.
# \dontrun{
# ### Step 5: Execute assembly via solver
# TD12_result<-solve_model(model_spec = TD12_model,solver = "HiGHS",time_limit = 5*60)
# TD12_panel<-assembled_panel(x = TD_12,result = TD12_result)
# ### Step 6: Diagnose infeasible model
# # There is an optimal solution. Skip this step.
# }# ### Step 7: Evaluate panel
# Instead, we load a precomputed result:
data("TD12_panel")
# number of items in each content domain
report_test_itemcat(assembled_panel = TD12_panel,
attribute = "content",
cat_levels = content_categories,
which_pathway = 1:2)
#> panel_id pathway_id 1 2 3 4
#> 1 Panel_1 M-E 11 9 9 11
#> 2 Panel_1 M-H 11 11 9 9
#> 11 Panel_2 M-E 11 10 9 10
#> 21 Panel_2 M-H 11 9 9 11
# number of items in each model
report_test_itemcat(assembled_panel = TD12_panel,
attribute = "model",
cat_levels = c("3PL","GPCM","GRM"),
which_pathway = 1:2)
#> panel_id pathway_id 3PL GPCM GRM
#> 1 Panel_1 M-E 36 2 2
#> 2 Panel_1 M-H 36 2 2
#> 11 Panel_2 M-E 36 2 2
#> 21 Panel_2 M-H 36 2 2
# average time in each pathway per item
report_test_itemquant(assembled_panel = TD12_panel,
attribute = "time",
statistic = "average",
which_pathway = 1:2)
#> panel_id pathway_id attribute average
#> 1 Panel_1 M-E time 56.350
#> 2 Panel_1 M-H time 57.650
#> 3 Panel_2 M-E time 56.025
#> 4 Panel_2 M-H time 57.750
# TIF at [-1.64.0] for the route 1M-2E
report_test_tif(assembled_panel = TD12_panel,
theta = seq(-1.64,0,length.out = 3),
item_par_cols = item_par_cols,
model_col = "model",
which_pathway = 1)
#> panel_id theta information pathway_id
#> 1 Panel_1 -1.64 7.517750 M-E
#> 2 Panel_1 -0.82 9.200638 M-E
#> 3 Panel_1 0.00 7.521433 M-E
#> 4 Panel_2 -1.64 7.533666 M-E
#> 5 Panel_2 -0.82 9.232393 M-E
#> 6 Panel_2 0.00 7.540688 M-E
# TIF at [0,1.64] for the route 1M-2H
report_test_tif(assembled_panel = TD12_panel,
theta = seq(0,1.64,length.out = 3),
item_par_cols = item_par_cols,
model_col = "model",
which_pathway = 2)
#> panel_id theta information pathway_id
#> 1 Panel_1 0.00 7.601128 M-H
#> 2 Panel_1 0.82 9.169080 M-H
#> 3 Panel_1 1.64 7.560521 M-H
#> 4 Panel_2 0.00 7.537336 M-H
#> 5 Panel_2 0.82 9.221848 M-H
#> 6 Panel_2 1.64 7.711617 M-H
# TIF plots
plot_panel_tif(assembled_panel = TD12_panel,
item_par_cols = item_par_cols,
model_col = "model",
theta = seq(-3,3,0.1),
unit = "pathway",mode = "within_panel")# analytic evaluation
TD12_precision<-analytic_mst_precision(design = "1-2",assembled_panel = TD12_panel,
item_par_cols = item_par_cols,
model_col = "model",
theta = seq(-3,3,0.1),
range_tcc = c(-5,5),
rdps = list(0))
TD12_bias_CSEM_panel1<-TD12_precision$Panel_1$eval_tb
TD12_bias_CSEM_panel2<-TD12_precision$Panel_2$eval_tb
TD12_bias_CSEM_panel1[,"Panel"]<-"Panel_1"
TD12_bias_CSEM_panel2[,"Panel"]<-"Panel_2"
TD12_bias_CSEM<-rbind(TD12_bias_CSEM_panel1,TD12_bias_CSEM_panel2)
TD12_bias_CSEM$Panel<-as.factor(TD12_bias_CSEM$Panel)
ggplot(TD12_bias_CSEM, aes(x = theta, y = bias, color = Panel)) +
geom_line() +
geom_point(size = 1.5) +
labs(
x = expression(theta),
y = "Bias",
color = "Panel"
) +
theme_bw()ggplot(TD12_bias_CSEM, aes(x = theta, y = csem, color = Panel)) +
geom_line() +
geom_point(size = 1.5) +
labs(
x = expression(theta),
y = "CSEM",
color = "Panel"
) +
theme_bw()Assemble 2 panels of 1-3 MST using the bottom-up design approach. Each module includes 20 items, and no item reuse is allowed. Maximize TIF over the [-1.96, -0.65] for the easy module, over [-0.65, 0.65] for the moderate modules, and over [0.65, 1.96] for the hard module. In addition, each module is required to comply with the following constraints: 15 3PL items, 2 to 3 GPCM items, and 2 to 3 GRM items; 4 to 6 items in each of the four content domains; 56 to 64 seconds of response time per item
### Step 1: Prepare the Item Pool
theta_values<-unique(c(seq(-0.65,0.65,length.out = 3),
seq(-1.96,-0.65,length.out = 3),
seq(0.65,1.96,length.out = 3)))
theta_iif<-compute_iif(Rmst_pool,
item_par_cols = item_par_cols,
theta = theta_values,model_col = "model",
D = 1)
Rmst_pool[,paste0("iif(theta=",theta_values,")")]<-theta_iif
### Step 2: Specify the MST Structure: 20 items in each model. no RDP specified
BU_13 <- mst_design(itempool = Rmst_pool,item_id_col = "Item_id",
design = "1-3",rdps = NULL,module_length = c(20,20,20,20))
### Step 3: Identify hierarchical requirements
# mst structure constraints: module length
# item reusage: no item reuse within a panel, no item reuse across 2 panels.
# TIF: three equal interval points in [-1.96, -0.65] for the 2E module,
# in [-0.65, 0.65] for 1M and 2M modules,
# and in [0.65, 1.96] for the 2H module.
# number of items from different IRT model:
# each module has 15 3PL items, 2 to 3 GPCM items, and 2 to 3 GRM items
# number of items from each content domain:
# each module includes 4 to 6 items in each domain.
# average response time: each module has 60 +/- 4 seconds per item.
### Step 4: Translate specifications
BU13_structure<-mst_structure_con(x = BU_13)
#> 'ModuleLength' present in design; ignoring stage-level bounds for length.
BU13_obj1<-objective_term(x = BU_13,attribute = "iif(theta=-0.65)",
applied_level = "Module-level",
which_module = 1,sense = "max")
BU13_obj2<-objective_term(x = BU_13,attribute = "iif(theta=0)",
applied_level = "Module-level",
which_module = 1,sense = "max")
BU13_obj3<-objective_term(x = BU_13,attribute = "iif(theta=0.65)",
applied_level = "Module-level",
which_module = 1,sense = "max")
BU13_obj4<-objective_term(x = BU_13,attribute = "iif(theta=-1.96)",
applied_level = "Module-level",
which_module = 2,sense = "max")
BU13_obj5<-objective_term(x = BU_13,attribute = "iif(theta=-1.305)",
applied_level = "Module-level",
which_module = 2,sense = "max")
BU13_obj6<-objective_term(x = BU_13,attribute = "iif(theta=-0.65)",
applied_level = "Module-level",
which_module = 2,sense = "max")
BU13_obj7<-objective_term(x = BU_13,attribute = "iif(theta=-0.65)",
applied_level = "Module-level",
which_module = 3,sense = "max")
BU13_obj8<-objective_term(x = BU_13,attribute = "iif(theta=0)",
applied_level = "Module-level",
which_module = 3,sense = "max")
BU13_obj9<-objective_term(x = BU_13,attribute = "iif(theta=0.65)",
applied_level = "Module-level",
which_module = 3,sense = "max")
BU13_obj10<-objective_term(x = BU_13,attribute = "iif(theta=0.65)",
applied_level = "Module-level",
which_module = 4,sense = "max")
BU13_obj11<-objective_term(x = BU_13,attribute = "iif(theta=1.305)",
applied_level = "Module-level",
which_module = 4,sense = "max")
BU13_obj12<-objective_term(x = BU_13,attribute = "iif(theta=1.96)",
applied_level = "Module-level",
which_module = 4,sense = "max")
BU13_obj<-capped_maximin_obj(x = BU_13,
multiple_terms = list(BU13_obj1,BU13_obj2,BU13_obj3,
BU13_obj4,BU13_obj5,BU13_obj6,
BU13_obj7,BU13_obj8,BU13_obj9,
BU13_obj10,BU13_obj11,BU13_obj12))
BU13_itemtype<-test_itemcat_range_con(x = BU_13,attribute = "model",
cat_levels = c("3PL","GPCM","GRM"),
min = c(15,2,2),max = c(15,3,3),
which_module = 1:4)
BU13_content<-test_itemcat_range_con(x = BU_13,attribute = "content",
cat_levels = content_categories,
min = 4,max = 6,
which_module = 1:4)
BU13_time<-test_itemquant_range_con(x = BU_13,attribute = "time",
min = 56*20,max = 64*20,
which_module = 1:4)
BU13_noreuse<-panel_itemreuse_con(x = BU_13,overlap = FALSE)
BU13_onepanel<-onepanel_spec(x = BU_13,
constraints = list(BU13_structure,BU13_noreuse,
BU13_itemtype,BU13_content,BU13_time),
objective = BU13_obj)
BU13_model<-multipanel_spec(x = BU_13,panel_model = BU13_onepanel,
num_panels = 2,
global_min_use = 0,global_max_use = 1)
# \dontrun{
# ### Step 5: Execute assembly via solver
# BU13_result<-solve_model(model_spec = BU13_model,solver = "HiGHS",time_limit = 5*60)
# BU13_panel<-assembled_panel(x = BU_13,result = BU13_result)
# ### Step 6: Diagnose infeasible model
# There is an optimal solution. Skip this step.
# }data("BU13_panel")
### Step 7: Evaluate panel
report_test_itemcat(assembled_panel = BU13_panel,
attribute = "content",
cat_levels = content_categories,
which_module = 1:4)
#> panel_id module_id 1 2 3 4
#> 1 Panel_1 S1M 4 6 5 5
#> 2 Panel_1 S2E 5 5 5 5
#> 3 Panel_1 S2M 6 4 6 4
#> 4 Panel_1 S2H 6 4 5 5
#> 11 Panel_2 S1M 4 6 5 5
#> 21 Panel_2 S2E 4 6 4 6
#> 31 Panel_2 S2M 4 6 4 6
#> 41 Panel_2 S2H 5 5 4 6
report_test_itemcat(assembled_panel = BU13_panel,
attribute = "model",
cat_levels = c("3PL","GPCM","GRM"),
which_module = 1:4)
#> panel_id module_id 3PL GPCM GRM
#> 1 Panel_1 S1M 15 2 3
#> 2 Panel_1 S2E 15 2 3
#> 3 Panel_1 S2M 15 3 2
#> 4 Panel_1 S2H 15 3 2
#> 11 Panel_2 S1M 15 2 3
#> 21 Panel_2 S2E 15 2 3
#> 31 Panel_2 S2M 15 3 2
#> 41 Panel_2 S2H 15 3 2
# average time in each module per item
report_test_itemquant(assembled_panel = BU13_panel,
attribute = "time",
statistic = "average",
which_module = 1:4)
#> panel_id module_id attribute average
#> 1 Panel_1 S1M time 56.70
#> 2 Panel_1 S2E time 56.00
#> 3 Panel_1 S2M time 56.05
#> 4 Panel_1 S2H time 57.75
#> 5 Panel_2 S1M time 56.05
#> 6 Panel_2 S2E time 56.45
#> 7 Panel_2 S2M time 56.00
#> 8 Panel_2 S2H time 56.10
# TIF at [-0.65, 0.65] for 1M and 2M modules
report_test_tif(assembled_panel = BU13_panel,
theta = seq(-0.65,0.65,length.out = 3),
item_par_cols = item_par_cols,
model_col = "model",which_module = c(1,3))
#> panel_id theta information module_id
#> 1 Panel_1 -0.65 3.922799 S1M
#> 2 Panel_1 0.00 4.842350 S1M
#> 3 Panel_1 0.65 4.795111 S1M
#> 4 Panel_1 -0.65 4.224004 S2M
#> 5 Panel_1 0.00 4.884430 S2M
#> 6 Panel_1 0.65 4.114863 S2M
#> 7 Panel_2 -0.65 3.931018 S1M
#> 8 Panel_2 0.00 4.886804 S1M
#> 9 Panel_2 0.65 3.896316 S1M
#> 10 Panel_2 -0.65 4.154348 S2M
#> 11 Panel_2 0.00 4.887273 S2M
#> 12 Panel_2 0.65 3.999800 S2M
# TIF at [-1.96, -0.65] for the 2E module
report_test_tif(assembled_panel = BU13_panel,
theta = seq(-1.96,-0.65,length.out = 3),
item_par_cols = item_par_cols,
model_col = "model",which_module = 2)
#> panel_id theta information module_id
#> 1 Panel_1 -1.960 3.897463 S2E
#> 2 Panel_1 -1.305 4.886115 S2E
#> 3 Panel_1 -0.650 4.899734 S2E
#> 4 Panel_2 -1.960 3.903641 S2E
#> 5 Panel_2 -1.305 4.900072 S2E
#> 6 Panel_2 -0.650 4.911936 S2E
# TIF at [0.65, 1.96] for the 2H module
report_test_tif(assembled_panel = BU13_panel,
theta = seq(0.65,1.96,length.out = 3),
item_par_cols = item_par_cols,
model_col = "model",which_module = 4)
#> panel_id theta information module_id
#> 1 Panel_1 0.650 4.894815 S2H
#> 2 Panel_1 1.305 4.581293 S2H
#> 3 Panel_1 1.960 3.922658 S2H
#> 4 Panel_2 0.650 4.863998 S2H
#> 5 Panel_2 1.305 4.868411 S2H
#> 6 Panel_2 1.960 3.901820 S2H
plot_panel_tif(assembled_panel = BU13_panel,
item_par_cols = item_par_cols,
model_col = "model",theta = seq(-3,3,0.1),unit = "module",mode = "within_panel")Assemble 2 panels of 1-2-3 MST using the top-down approach. Two routes with capricious ability change (1M-2E-3H & 1M-2H-3E) are blocked. Items are allowed to be used up to four times. Each route needs to meet the following criteria:
Maximize TIF over [-1.96, -0.64] for the easy route (1M-2E-3E), over [-0.64, 0.64] for the moderate routes (1M-2E-3M & 1M-2H-3M), and over [0.64, 1.96] for the hard route (1M-2H-3H) Include a total of 40 items: 34 to 38 3PL items, 1 to 3 GPCM items, and 1 to 3 GRM items. 8 to 12 items in each of the four content domains. 56 to 64 seconds of response time per item. To yield decent routing accuracy, the first two stages are required to include at least 8 items.
### Step 1: Prepare the Item Pool
theta_values<-unique(c(seq(-0.64,0.64,length.out = 3),
seq(-1.96,-0.64,length.out = 3),
seq(0.64,1.96,length.out = 3)))
theta_iif<-compute_iif(Rmst_pool,
item_par_cols = item_par_cols,
theta = theta_values,model_col = "model",
D = 1)
Rmst_pool[,paste0("iif(theta=",theta_values,")")]<-theta_iif
### Step 2: Specify the MST Structure: 40 items in each pathway, routing decision point is 0.
TD_123 <- mst_design(itempool = Rmst_pool,item_id_col = "Item_id",
design = "1-2-3",rdps = NULL,exclude_pathways = c("1-1-3","1-2-1"),
pathway_length = 40)
### Step 3: Identify hierarchical requirements
# mst structure constraints: pathway length, minimum stage length in stage 1 and 2.
# no 1M-2E-3H & 1M-2H-3E pathways.
# item reusage: no items reused within a pathway, an item can be
# at most selected four times across 2 panels.
# TIF: three equal interval points in [-1.96, -0.64] for the easy route (1M-2E-3E),
# over [-0.64, 0.64] for the moderate routes (1M-2E-3M & 1M-2H-3M),
# and over [0.64, 1.96] for the hard route (1M-2H-3H)
# number of items from different IRT model: each pathway has 34 to 38 3PL items,
# 1 to 3 GPCM items, and 1 to 3 GRM items.
# number of items from each content domain: each pathway includes 8 to 12 items in each domain.
# average response time: each pathway has 60 +/- 4 seconds per item.
### Step 4: Translate specifications
TD123_structure<-mst_structure_con(x = TD_123,
stage_length_bound = data.frame(stage = c(1,2,3),
min = c(8,8,NA_integer_),
max = rep(NA_integer_,3)))
TD123_obj1<-objective_term(x = TD_123,attribute = "iif(theta=-1.96)",
applied_level = "Pathway-level",
which_pathway = 1,sense = "max")
TD123_obj2<-objective_term(x = TD_123,attribute = "iif(theta=-1.3)",
applied_level = "Pathway-level",
which_pathway = 1,sense = "max")
TD123_obj3<-objective_term(x = TD_123,attribute = "iif(theta=-0.64)",
applied_level = "Pathway-level",
which_pathway = 1,sense = "max")
TD123_obj4<-objective_term(x = TD_123,attribute = "iif(theta=-0.64)",
applied_level = "Pathway-level",
which_pathway = 2,sense = "max")
TD123_obj5<-objective_term(x = TD_123,attribute = "iif(theta=0)",
applied_level = "Pathway-level",
which_pathway = 2,sense = "max")
TD123_obj6<-objective_term(x = TD_123,attribute = "iif(theta=0.64)",
applied_level = "Pathway-level",
which_pathway = 2,sense = "max")
TD123_obj7<-objective_term(x = TD_123,attribute = "iif(theta=-0.64)",
applied_level = "Pathway-level",
which_pathway = 3,sense = "max")
TD123_obj8<-objective_term(x = TD_123,attribute = "iif(theta=0)",
applied_level = "Pathway-level",
which_pathway = 3,sense = "max")
TD123_obj9<-objective_term(x = TD_123,attribute = "iif(theta=0.64)",
applied_level = "Pathway-level",
which_pathway = 3,sense = "max")
TD123_obj10<-objective_term(x = TD_123,attribute = "iif(theta=0.64)",
applied_level = "Pathway-level",
which_pathway = 4,sense = "max")
TD123_obj11<-objective_term(x = TD_123,attribute = "iif(theta=1.3)",
applied_level = "Pathway-level",
which_pathway = 4,sense = "max")
TD123_obj12<-objective_term(x = TD_123,attribute = "iif(theta=1.96)",
applied_level = "Pathway-level",
which_pathway = 4,sense = "max")
TD123_obj<-capped_maximin_obj(x = TD_123,
multiple_terms = list(TD123_obj1,TD123_obj2,TD123_obj3,
TD123_obj4,TD123_obj5,TD123_obj6,
TD123_obj7,TD123_obj8,TD123_obj9,
TD123_obj10,TD123_obj11,TD123_obj12))
TD123_itemtype<-test_itemcat_range_con(x = TD_123,attribute = "model",
cat_levels = c("3PL","GPCM","GRM"),
min = c(34,1,1),max = c(38,3,3),
which_pathway = 1:4)
TD123_content<-test_itemcat_range_con(x = TD_123,attribute = "content",
cat_levels = content_categories,
min = 8,max = 12,
which_pathway = 1:4)
TD123_time<-test_itemquant_range_con(x = TD_123,attribute = "time",
min = 56*40,max = 64*40,
which_pathway = 1:4)
TD123_noreuse<-panel_itemreuse_con(x = TD_123,overlap = TRUE)
TD123_onepanel<-onepanel_spec(x = TD_123,
constraints = list(TD123_structure,TD123_noreuse,
TD123_itemtype,TD123_content,TD123_time),
objective = TD123_obj)
TD123_model<-multipanel_spec(x = TD_123,panel_model = TD123_onepanel,
num_panels = 2,global_max_use = 4)
# \dontrun{
# ### Step 5: Execute assembly via solver
# TD123_result<-solve_model(model_spec = TD123_model,solver = "HiGHS",time_limit = 5*60)
# TD123_panel<-assembled_panel(x = TD_123,result = TD123_result)
# ### Step 6: Diagnose infeasible model
# There is an optimal solution. Skip this step.
# }data("TD123_panel")
### Step 7: Evaluate panel
report_test_itemcat(assembled_panel = TD123_panel,
attribute = "content",
cat_levels = content_categories,
which_pathway = 1:4)
#> panel_id pathway_id 1 2 3 4
#> 1 Panel_1 M-E-E 12 9 8 11
#> 2 Panel_1 M-E-M 12 8 8 12
#> 3 Panel_1 M-H-M 9 11 8 12
#> 4 Panel_1 M-H-H 11 9 11 9
#> 11 Panel_2 M-E-E 10 12 8 10
#> 21 Panel_2 M-E-M 12 8 8 12
#> 31 Panel_2 M-H-M 9 11 8 12
#> 41 Panel_2 M-H-H 11 9 11 9
report_test_itemcat(assembled_panel = TD123_panel,
attribute = "model",
cat_levels = c("3PL","GPCM","GRM"),
which_pathway = 1:4)
#> panel_id pathway_id 3PL GPCM GRM
#> 1 Panel_1 M-E-E 36 1 3
#> 2 Panel_1 M-E-M 36 1 3
#> 3 Panel_1 M-H-M 36 1 3
#> 4 Panel_1 M-H-H 34 3 3
#> 11 Panel_2 M-E-E 35 2 3
#> 21 Panel_2 M-E-M 35 2 3
#> 31 Panel_2 M-H-M 36 1 3
#> 41 Panel_2 M-H-H 34 3 3
# average time in each module per item
report_test_itemquant(assembled_panel = TD123_panel,
attribute = "time",
statistic = "average",
which_pathway = 1:4)
#> panel_id pathway_id attribute average
#> 1 Panel_1 M-E-E time 56.375
#> 2 Panel_1 M-E-M time 56.425
#> 3 Panel_1 M-H-M time 56.250
#> 4 Panel_1 M-H-H time 56.100
#> 5 Panel_2 M-E-E time 56.100
#> 6 Panel_2 M-E-M time 56.150
#> 7 Panel_2 M-H-M time 56.225
#> 8 Panel_2 M-H-H time 56.100
# TIF at [-1.96, -0.64] for the easy route (1M-2E-3E)
report_test_tif(assembled_panel = TD123_panel,
theta = seq(-1.96,-0.64,length.out = 3),
item_par_cols = item_par_cols,
model_col = "model",which_pathway = 1)
#> panel_id theta information pathway_id
#> 1 Panel_1 -1.96 5.639209 M-E-E
#> 2 Panel_1 -1.30 6.856428 M-E-E
#> 3 Panel_1 -0.64 7.113380 M-E-E
#> 4 Panel_2 -1.96 5.756675 M-E-E
#> 5 Panel_2 -1.30 7.070713 M-E-E
#> 6 Panel_2 -0.64 7.450101 M-E-E
# TIF at [-0.64, 0.64] for the moderate routes (1M-2E-3M & 1M-2H-3M),
report_test_tif(assembled_panel = TD123_panel,
theta = seq(-0.64,0.64,length.out = 3),
item_par_cols = item_par_cols,
model_col = "model",which_pathway = 2:3)
#> panel_id theta information pathway_id
#> 1 Panel_1 -0.64 6.128133 M-E-M
#> 2 Panel_1 0.00 5.518658 M-E-M
#> 3 Panel_1 0.64 4.807058 M-E-M
#> 4 Panel_1 -0.64 5.589652 M-H-M
#> 5 Panel_1 0.00 4.967843 M-H-M
#> 6 Panel_1 0.64 4.285901 M-H-M
#> 7 Panel_2 -0.64 6.519357 M-E-M
#> 8 Panel_2 0.00 6.487888 M-E-M
#> 9 Panel_2 0.64 5.959664 M-E-M
#> 10 Panel_2 -0.64 5.627857 M-H-M
#> 11 Panel_2 0.00 5.057230 M-H-M
#> 12 Panel_2 0.64 4.421342 M-H-M
# TIF at [0.64, 1.96] for the hard route (1M-2H-3H)
report_test_tif(assembled_panel = TD123_panel,
theta = seq(0.64,1.96,length.out = 3),
item_par_cols = item_par_cols,
model_col = "model",which_pathway = 4)
#> panel_id theta information pathway_id
#> 1 Panel_1 0.64 7.693210 M-H-H
#> 2 Panel_1 1.30 7.202137 M-H-H
#> 3 Panel_1 1.96 6.231615 M-H-H
#> 4 Panel_2 0.64 7.693210 M-H-H
#> 5 Panel_2 1.30 7.202137 M-H-H
#> 6 Panel_2 1.96 6.231615 M-H-H
plot_panel_tif(assembled_panel = TD123_panel,
item_par_cols = item_par_cols,
model_col = "model",theta = seq(-3,3,0.1),
unit = "pathway",mode = "within_panel")In the Rmst package demonstration of the hybrid assembly for an MST 1–3 design, the example appears to use an item pool generated with a seed different from the default setting. When running the same demonstration code with the default seed, the solver returns “No solution for the LP model.” Therefore, the Hybrid 1–3 panel was assembled using specifications that differ from those in the Rmst package demonstration.
Assemble 2 panels of 1-3 MST using the extended-hybrid assembly approach. Each module includes 20 items, and no item reuse is allowed. Maximize TIF over the [-1.96, -0.65] for the easy pathways, over [-0.65, 0.65] for the moderate pathways, and over [0.65, 1.96] for the hard pathways. In addition, each module is required to comply with the following constraints: 15 3PL items, 2 to 3 GPCM items, and 2 to 3 GRM items; 4 to 6 items in each of the four content domains; each pathway is required to have 56 to 64 seconds of response time per item
### Step 1: Prepare the Item Pool
theta_values<-unique(c(seq(-0.65,0.65,length.out = 3),
seq(-1.96,-0.65,length.out = 3),
seq(0.65,1.96,length.out = 3)))
theta_iif<-compute_iif(Rmst_pool,
item_par_cols = item_par_cols,
theta = theta_values,model_col = "model",
D = 1)
Rmst_pool[,paste0("iif(theta=",theta_values,")")]<-theta_iif
### Step 2: Specify the MST Structure: 20 items in each pathway
Hy_13 <- mst_design(itempool = Rmst_pool,item_id_col = "Item_id",
design = "1-3",rdps = NULL,
module_length = c(20,20,20,20))
### Step 3: Identify hierarchical requirements
# mst structure constraints: module length
# item reusage: no item reuse within a panel, no item reuse across 2 panels.
# TIF: minimum TIF of 6 at three equal interval points in [-1.28, 1.28] for
# routing module, minimum TIF of 8 at -1.28 for S2E, 0 for S2M, 1.28 for S2H modules
# number of items from each content domain: each pathway includes 4 to 6 items in each domain.
# TIF: three equal interval points in [-1.96, -0.65] for the S1R-S2E pathways,
# in [-0.65, 0.65] for S1R-S2M pathways,
# and in [0.65, 1.96] for S1R-S2H pathways.
# number of items from different IRT model:
# each module has 15 3PL items, 2 to 3 GPCM items, and 2 to 3 GRM items
# number of items from each content domain:
# each module includes 4 to 6 items in each domain.
# average response time: each pathway has 60 +/- 4 seconds per item.
### Step 4: Translate specifications
Hy13_structure<-mst_structure_con(x = Hy_13)
#> 'ModuleLength' present in design; ignoring stage-level bounds for length.
Hy13_noreuse<-panel_itemreuse_con(x = Hy_13,overlap = FALSE)
Hy13_itemtype<-test_itemcat_range_con(x = Hy_13,attribute = "model",
cat_levels = c("3PL","GPCM","GRM"),
min = c(15,2,2),max = c(15,3,3),
which_module = 1:4)
Hy13_content<-test_itemcat_range_con(x = Hy_13,attribute = "content",
cat_levels = content_categories,
min = 4,max = 6,
which_module = 1:4)
Hy13_time<-test_itemquant_range_con(x = Hy_13,attribute = "time",
min = 56*40,max = 64*40,
which_pathway = 1:3)
Hy13_obj1<-objective_term(x = Hy_13,attribute = "iif(theta=-0.65)",
applied_level = "Pathway-level",
which_pathway = 1,sense = "max")
Hy13_obj2<-objective_term(x = Hy_13,attribute = "iif(theta=0)",
applied_level = "Pathway-level",
which_pathway = 1,sense = "max")
Hy13_obj3<-objective_term(x = Hy_13,attribute = "iif(theta=0.65)",
applied_level = "Pathway-level",
which_pathway = 1,sense = "max")
Hy13_obj4<-objective_term(x = Hy_13,attribute = "iif(theta=-1.96)",
applied_level = "Pathway-level",
which_pathway = 2,sense = "max")
Hy13_obj5<-objective_term(x = Hy_13,attribute = "iif(theta=-1.305)",
applied_level = "Pathway-level",
which_pathway = 2,sense = "max")
Hy13_obj6<-objective_term(x = Hy_13,attribute = "iif(theta=-0.65)",
applied_level = "Pathway-level",
which_pathway = 2,sense = "max")
Hy13_obj7<-objective_term(x = Hy_13,attribute = "iif(theta=-0.65)",
applied_level = "Pathway-level",
which_pathway = 3,sense = "max")
Hy13_obj8<-objective_term(x = Hy_13,attribute = "iif(theta=0)",
applied_level = "Pathway-level",
which_pathway = 3,sense = "max")
Hy13_obj9<-objective_term(x = Hy_13,attribute = "iif(theta=0.65)",
applied_level = "Pathway-level",
which_pathway = 3,sense = "max")
Hy13_obj<-capped_maximin_obj(x = Hy_13,
multiple_terms = list(Hy13_obj1,Hy13_obj2,Hy13_obj3,
Hy13_obj4,Hy13_obj5,Hy13_obj6,
Hy13_obj7,Hy13_obj8,Hy13_obj9))
Hy13_onepanel<-onepanel_spec(x = Hy_13,
constraints = list(Hy13_structure,Hy13_noreuse,
Hy13_itemtype,Hy13_content,
Hy13_time),
objective = Hy13_obj)
Hy13_model<-multipanel_spec(x = Hy_13,panel_model = Hy13_onepanel,
num_panels = 2,global_max_use = 1)
# \dontrun{
# ### Step 5: Execute assembly via solver
# Hy13_result<-solve_model(model_spec = Hy13_model,solver = "HiGHS",time_limit = 5*60)
# Hy13_panel<-assembled_panel(x = Hy_13,result = Hy13_result)
# ### Step 6: Diagnose infeasible model
# There is an optimal solution. Skip this step.
# }data("Hy13_panel")
### Step 7: Evaluate panel
report_test_itemcat(assembled_panel = Hy13_panel,
attribute = "content",
cat_levels = content_categories,
which_module = 1:4)
#> panel_id module_id 1 2 3 4
#> 1 Panel_1 S1M 4 6 4 6
#> 2 Panel_1 S2E 6 5 4 5
#> 3 Panel_1 S2M 5 5 5 5
#> 4 Panel_1 S2H 4 5 6 5
#> 11 Panel_2 S1M 4 6 4 6
#> 21 Panel_2 S2E 5 4 5 6
#> 31 Panel_2 S2M 4 5 5 6
#> 41 Panel_2 S2H 6 4 6 4
report_test_itemcat(assembled_panel = Hy13_panel,
attribute = "model",
cat_levels = c("3PL","GPCM","GRM"),
which_module = 1:4)
#> panel_id module_id 3PL GPCM GRM
#> 1 Panel_1 S1M 15 2 3
#> 2 Panel_1 S2E 15 3 2
#> 3 Panel_1 S2M 15 2 3
#> 4 Panel_1 S2H 15 3 2
#> 11 Panel_2 S1M 15 2 3
#> 21 Panel_2 S2E 15 3 2
#> 31 Panel_2 S2M 15 2 3
#> 41 Panel_2 S2H 15 3 2
report_test_itemquant(assembled_panel = Hy13_panel,
attribute = "time",
statistic = "average",
which_pathway = 1:3)
#> panel_id pathway_id attribute average
#> 1 Panel_1 M-E time 56.175
#> 2 Panel_1 M-M time 56.225
#> 3 Panel_1 M-H time 56.575
#> 4 Panel_2 M-E time 56.675
#> 5 Panel_2 M-M time 56.200
#> 6 Panel_2 M-H time 56.700
plot_panel_tif(assembled_panel = Hy13_panel,
item_par_cols = item_par_cols,
model_col = "model",theta = seq(-3,3,0.1),
unit = "pathway",
mode = "within_panel")This vignette demonstrates how the mstATA package
can be used to assemble multistage testing (MST) panels using three
common strategies: bottom-up,
top-down, and hybrid assembly. The
examples illustrate how the package provides flexibility in constructing
linear constraints for specification requirements that may be enforced
at different levels of the test design, including the
module, pathway,
panel, and solution levels. By
formulating these requirements within a unified mixed-integer linear
programming (MILP) framework, mstATA enables test
developers to adapt assembly strategies to different operational needs
while maintaining transparent and reproducible specifications.
Note: Additional examples comparing bottom-up, top-down, and hybrid assembly strategies under the same set of specifications (but enforced at different levels) can be found in the author’s PhD dissertation, which will be available through ProQuest in May 2026.