Nothing
pairs_tree <- function(...) {
mpt_tree("pairs", list(
E1 = "c * r",
E2 = "(1 - c) * u^2",
E3 = "(1 - c) * 2 * u * (1 - u)",
E4 = "c * (1 - r) + (1 - c) * (1 - u)^2"
), ...)
}
source_trees <- function() {
list(
mpt_tree("sourceA", list(
A = "D1 * d1 + D1 * (1 - d1) * a + (1 - D1) * b * g",
B = "D1 * (1 - d1) * (1 - a) + (1 - D1) * b * (1 - g)",
N = "(1 - D1) * (1 - b)"
)),
mpt_tree("sourceB", list(
A = "D2 * (1 - d2) * a + (1 - D2) * b * g",
B = "D2 * d2 + D2 * (1 - d2) * (1 - a) + (1 - D2) * b * (1 - g)",
N = "(1 - D2) * (1 - b)"
)),
mpt_tree("new_items", list(A = "b * g", B = "b * (1 - g)", N = "1 - b"))
)
}
oberauer_2019_tree <- function() {
mpt_tree("ss", list(
correct = "Pb + (1 - Pb) * Pi * GcorrPi + (1 - Pb) * (1 - Pi) * GcorrNoPi",
other = paste0(
"(1 - Pb) * Pi * (1 - GcorrPi) + ",
"(1 - Pb) * (1 - Pi) * (1 - GcorrNoPi) * GotherNoPi"
),
npl = "(1 - Pb) * (1 - Pi) * (1 - GcorrNoPi) * (1 - GotherNoPi)"
))
}
oberauer_lin_trees <- function() {
list(
mpt_tree("newdist", list(
corr = "Pi * Pb + Pi * (1 - Pb) * (1 - Pd) * (1/5) + (1 - Pi) * (1/15)",
other = "Pi * (1 - Pb) * (1 - Pd) * (4/5) + (1 - Pi) * (4/15)",
dist = "Pi * (1 - Pb) * Pd + (1 - Pi) * (5/15)",
npl = "(1 - Pi) * (5/15)"
)),
mpt_tree("nodist", list(
corr = "Pi * Pb + Pi * (1 - Pb) * (1/5) + (1 - Pi) * (1/15)",
other = "Pi * (1 - Pb) * (4/5) + (1 - Pi) * (4/15)",
npl = "(1 - Pi) * (10/15)"
), impossible = "dist")
)
}
simplex_trees <- function() {
list(
mpt_tree("sourceA", list(
A = "dA + (1 - dA) * gA", B = "(1 - dA) * gB", New = "(1 - dA) * gNew"
)),
mpt_tree("sourceB", list(
A = "(1 - dB) * gA", B = "dB + (1 - dB) * gB", New = "(1 - dB) * gNew"
)),
mpt_tree("new", list(A = "gA", B = "gB", New = "gNew"))
)
}
# one wide, one narrow leaf label and long edge labels, which near the root
# stick out to the left of steep edges
long_label_tree <- function() {
mpt_tree("long", list(
Correct_Correct_Long_Category_Name =
"Very_Long_Parameter_Name_One * Very_Long_Parameter_Name_Two",
Other_Response_Category_With_A_Long_Name =
"Very_Long_Parameter_Name_One * (1 - Very_Long_Parameter_Name_Two)",
Rest = "1 - Very_Long_Parameter_Name_One"
))
}
# draws the tree on a pdf device and returns where every label starts and ends,
# in inches from the left edge of the plot region, with the plot width
label_extents <- function(tree, width, height, cex = 0.9, pointsize = 12) {
extents <- data.frame()
record <- function(x, y = NULL, labels = y, ...) {
args <- list(...)
usr <- graphics::par("usr")
start <- (x - usr[1]) / (usr[2] - usr[1]) * graphics::par("pin")[1]
size <- graphics::strwidth(labels, units = "inches", cex = args$cex, font = args$font %||% 1)
shift <- if (is.null(args$pos)) {
-size / 2
} else {
args$offset * graphics::strwidth("m", units = "inches", cex = args$cex)
}
extents <<- rbind(extents, data.frame(
kind = if (is.null(args$pos)) "edge" else "leaf",
left = start + shift, right = start + shift + size, plot_width = graphics::par("pin")[1]
))
}
withr::with_pdf(
NULL,
testthat::with_mocked_bindings(plot(tree, cex = cex), text = record, .package = "graphics"),
width = width, height = height, pointsize = pointsize
)
extents
}
# draws the model on a pdf device and returns the pairs of edge labels whose
# boxes overlap, measured in inches and `box` times the text height (the text
# height leaves out descenders and parentheses)
overlapping_edge_labels <- function(model, width, height, cex = 0.9, box = 1.3) {
boxes <- data.frame()
record <- function(x, y = NULL, labels = y, ...) {
args <- list(...)
if (!is.null(args$pos)) {
return(invisible())
}
usr <- graphics::par("usr")
boxes <<- rbind(boxes, data.frame(
x = (x - usr[1]) / (usr[2] - usr[1]) * graphics::par("pin")[1],
y = (y - usr[3]) / (usr[4] - usr[3]) * graphics::par("pin")[2],
width = graphics::strwidth(labels, units = "inches", cex = args$cex),
height = box * graphics::strheight(labels, units = "inches", cex = args$cex),
label = labels
))
}
withr::with_pdf(
NULL,
testthat::with_mocked_bindings(
suppressWarnings(plot(model, cex = cex)), text = record, .package = "graphics"
),
width = width, height = height
)
overlap_x <- outer(boxes$width, boxes$width, `+`) / 2 - abs(outer(boxes$x, boxes$x, `-`))
overlap_y <- outer(boxes$height, boxes$height, `+`) / 2 - abs(outer(boxes$y, boxes$y, `-`))
pairs <- which(overlap_x > 0 & overlap_y > 0 & upper.tri(overlap_x), arr.ind = TRUE)
as.character(paste(boxes$label[pairs[, 1]], "<>", boxes$label[pairs[, 2]])[nrow(pairs) > 0])
}
test_that("branch expressions expand into root-to-leaf paths", {
paths <- .mpt_branch_paths(quote(D + (1 - D) * g))
expect_equal(paths, list("D", c("1 - D", "g")))
# factored subtrees are expanded distributively
paths_factored <- .mpt_branch_paths(quote(Pm * (Pb + (1 - Pb) * 0.25)))
expect_equal(paths_factored, list(c("Pm", "Pb"), c("Pm", "1 - Pb", "0.25")))
})
test_that("a constant factor above 1 and the factors after it form one edge", {
expect_equal(
.mpt_branch_paths(quote((1 - c) * 2 * u * (1 - u))),
list(c("1 - c", "2 * u * (1 - u)"))
)
expect_equal(
.mpt_branch_paths(quote(k * 2 * (1 - u) * v)),
list(c("k", "2 * (1 - u) * v"))
)
# a folded constant counts by its value, not by how it is written
expect_equal(
.mpt_branch_paths(quote(k * (6 / 3) * u)),
list(c("k", "6/3 * u"))
)
})
test_that("a leading constant above 1 makes the whole path one edge", {
expect_equal(
.mpt_branch_paths(quote(2 * (1 - c) * u * (1 - u))),
list("2 * (1 - c) * u * (1 - u)")
)
expect_equal(.mpt_branch_paths(quote(2 * u)), list("2 * u"))
expect_equal(.mpt_branch_paths(quote(2)), list("2"))
})
test_that("a trailing constant above 1 joins the factor before it", {
expect_equal(
.mpt_branch_paths(quote((1 - c) * u * (1 - u) * 2)),
list(c("1 - c", "u", "(1 - u) * 2"))
)
expect_equal(.mpt_branch_paths(quote((1 - c) * 3)), list("(1 - c) * 3"))
})
test_that("a constant between factors takes everything after it", {
expect_equal(
.mpt_branch_paths(quote(u * 2 * (1 - u))),
list(c("u", "2 * (1 - u)"))
)
})
test_that("constants of 1 or less and a variable-free call stay ordinary edges", {
expect_equal(
.mpt_branch_paths(quote(0.2 * x + (1 - x) * (1 / 3))),
list(c("0.2", "x"), c("1 - x", "1/3"))
)
expect_equal(.mpt_branch_paths(quote(1 * x)), list(c("1", "x")))
expect_equal(.mpt_branch_paths(quote(foo() * D)), list(c("foo()", "D")))
})
test_that("pair clustering draws the multiplicity as one edge below 1 - c", {
graph <- .mpt_tree_graph(pairs_tree())
expect_equal(
graph$edges$label,
c("r", "1 - r", "u^2", "2 * u * (1 - u)", "(1 - u)^2", "c", "1 - c")
)
expect_equal(graph$nodes$category, c("E1", "E4", NA, "E2", "E3", "E4", NA, NA))
expect_equal(graph$edges$from, c(3, 3, 7, 7, 7, 8, 8))
expect_equal(graph$edges$to, c(1, 2, 4, 5, 6, 3, 7))
})
test_that("a package tree with the constant written first keeps it in one root edge", {
study <- mpt_tree("study", list(
C = "cp + (1 - cp) * rp * rp",
E = "2 * (1 - cp) * rp * (1 - rp)",
U = "(1 - cp) * (1 - rp) * (1 - rp)"
))
graph <- .mpt_tree_graph(study)
root_edges <- graph$edges[graph$edges$x0 == 0, ]
expect_setequal(root_edges$label, c("cp", "1 - cp", "2 * (1 - cp) * rp * (1 - rp)"))
expect_false("2" %in% graph$edges$label)
})
test_that("plot() warns once per tree when a constant above 1 hides a later process step", {
withr::local_pdf(NULL)
swallowed <- mpt_tree("swallowed", list(
A = "D * 2 * g * (1 - g) * r",
B = "D * 2 * g * (1 - g) * (1 - r)",
C = "D * (g^2 + (1 - g)^2)",
E = "1 - D"
))
messages <- character(0)
withCallingHandlers(
plot(swallowed),
warning = function(w) {
messages <<- c(messages, conditionMessage(w))
invokeRestart("muffleWarning")
}
)
expect_length(messages, 1)
expect_match(messages, "In the tree 'swallowed'", fixed = TRUE)
expect_match(messages, "branch 'A': D * 2 * g * (1 - g) * r", fixed = TRUE)
expect_match(messages, "branch 'B': D * 2 * g * (1 - g) * (1 - r)", fixed = TRUE)
expect_match(messages, "merges the factors after the constant 2", fixed = TRUE)
# the constant splits a factor from its complement
expect_warning(
plot(mpt_tree("split", list(a = "u * 2 * (1 - u)", b = "1 - u"))),
"branch 'a': u * 2 * (1 - u)", fixed = TRUE
)
})
test_that("plot() warns when a quotient hides a complement", {
withr::local_pdf(NULL)
quotient <- mpt_tree("quotient", list(
a = "(1 - Pi) * 1 / 15", b = "Pi + (1 - Pi) * 14 / 15"
))
expect_warning(
plot(quotient),
"branch 'a': (1 - Pi) * 1/15 is one edge", fixed = TRUE
)
# a covariate in the denominator does not normalise the complement
expect_warning(
plot(mpt_tree("covariate", list(a = "(1 - Pi) / N", b = "Pi + (1 - Pi) * (N - 1) / N"))),
"branch 'a': (1 - Pi)/N is one edge", fixed = TRUE
)
# a denominator that normalises only one of two complements hides the other
expect_warning(
plot(mpt_tree("partial", list(a = "(1 - a) * (1 - b) / (a + 1)", b = "1 - a"))),
"is one edge", fixed = TRUE
)
})
test_that("plot() accepts a normalised quotient but still warns on a constant denominator", {
withr::local_pdf(NULL)
luce <- mpt_tree("luce", list(
a = "x * (1 - d) / (x * (1 - d) + y)", b = "y / (x * (1 - d) + y)"
))
expect_silent(plot(luce))
expect_warning(
plot(mpt_tree("constant", list(a = "(1 - Pi) * 1 / 15", b = "Pi + (1 - Pi) * 14 / 15"))),
"is one edge", fixed = TRUE
)
})
test_that("plot() warns when two merged steps after a constant have no complements", {
withr::local_pdf(NULL)
unpaired <- mpt_tree("unpaired", list(
A = "D * 2 * g * r", B = "D * 2 * g * (1 - r)", C = "1 - D"
))
expect_warning(
plot(unpaired),
"branch 'A': D * 2 * g * r, where the drawing merges the factors after the constant 2",
fixed = TRUE
)
})
test_that("a warning names a node reached through a restricted edge by its restricted label", {
withr::local_pdf(NULL)
# the fixture warns on purpose: `a` and `b` write their second factor in a
# different order (`x` against `1 - y`), which pairs the wrong edges
restricted <- mpt(
mpt_tree("restricted", list(
a = "(1 - g) * x * y", b = "(1 - g) * (1 - y) * x", c = "(1 - g) * (1 - x)", d = "g"
)),
restrictions = "g = 0.5"
)
expect_warning(
plot(restricted),
"the edges leaving the node '1 - g (g = 0.5)' do not sum to 1", fixed = TRUE
)
})
test_that("plot() stays silent about constants that merge cleanly", {
withr::local_pdf(NULL)
silent <- list(
pairs_tree(),
mpt_tree("leading", list(a = "2 * (1 - c) * u * (1 - u)", b = "c")),
mpt_tree("trailing", list(a = "(1 - c) * u * (1 - u) * 2", b = "c")),
mpt_tree("single", list(a = "(1 - c) * 3", b = "c")),
mpt_tree("one_step", list(a = "D * 2 * r", b = "1 - D")),
mpt_tree("constants", list(a = "(1 - Pi) * 10 * (1/15)", b = "Pi")),
mpt_tree("folded", list(a = "(1 - Pi) * (1/15)", b = "Pi + (1 - Pi) * (14/15)"))
)
for (tree in silent) {
expect_silent(plot(tree))
}
})
test_that("the Oberauer (2019) tree keeps its structure", {
graph <- .mpt_tree_graph(oberauer_2019_tree())
expect_equal(nrow(graph$nodes), 11)
expect_equal(nrow(graph$edges), 10)
leaves <- graph$nodes[!is.na(graph$nodes$category), ]
expect_equal(
leaves$category,
c("correct", "correct", "other", "correct", "other", "npl")
)
expect_equal(
graph$edges$label[match(leaves$id, graph$edges$to)],
c("Pb", "GcorrPi", "1 - GcorrPi", "GcorrNoPi", "GotherNoPi", "1 - GotherNoPi")
)
})
test_that("node ids identify the nodes and the edges refer to them", {
trees <- c(
mpt_2htm_trees()[1], list(pairs_tree(), mpt_tree("one", list(only = "1")))
)
for (tree in trees) {
graph <- .mpt_tree_graph(tree)
nodes <- graph$nodes
edges <- graph$edges
expect_type(nodes$id, "integer")
expect_equal(nodes$id, seq_len(nrow(nodes)))
expect_true(all(c(edges$from, edges$to) %in% nodes$id))
# a tree: every node but the root has exactly one incoming edge
expect_equal(sort(edges$to), sort(setdiff(nodes$id, setdiff(edges$from, edges$to))))
expect_false(anyDuplicated(edges$to) > 0)
from <- nodes[match(edges$from, nodes$id), ]
to <- nodes[match(edges$to, nodes$id), ]
expect_equal(c(edges$x0, edges$y0), c(from$x, from$y))
expect_equal(c(edges$x1, edges$y1), c(to$x, to$y))
}
})
test_that("the tree graph merges shared path prefixes only", {
graph <- .mpt_tree_graph(mpt_2htm_trees()[[1]])
leaves <- graph$nodes[!is.na(graph$nodes$category), ]
expect_equal(leaves$category, c("old", "old", "new"))
# internal nodes: the root and the shared (1 - D) node
expect_equal(sum(is.na(graph$nodes$category)), 2)
expect_setequal(graph$edges$label, c("D", "1 - D", "g", "1 - g"))
})
test_that("equal terminal weights stay separate leaf edges", {
fan <- mpt_tree("t", list(
a = "m + (1 - m) * (1/3)",
b = "(1 - m) * (1/3)",
c = "(1 - m) * (1/3)"
))
graph <- .mpt_tree_graph(fan)
leaves <- graph$nodes[!is.na(graph$nodes$category), ]
expect_equal(leaves$category, c("a", "a", "b", "c"))
# constant edge labels are rounded for display
expect_equal(sum(graph$edges$label == "0.333"), 3)
})
test_that("a three-level tree has one node per process state and centers its internal nodes", {
tree <- mpt_tree("t", list(
a = "D * b * c",
b = "D * b * (1 - c)",
c = "D * (1 - b)",
d = "1 - D"
))
graph <- .mpt_tree_graph(tree)
# root, the D node, the D > b node, and four leaves
expect_equal(nrow(graph$nodes), 7)
expect_equal(nrow(graph$edges), 6)
leaves <- graph$nodes[!is.na(graph$nodes$category), ]
expect_equal(leaves$category, c("a", "b", "c", "d"))
expect_equal(leaves$y, c(0, -1, -2, -3))
expect_equal(leaves$x, c(3, 3, 2, 1))
expect_true(all(graph$edges$x1 == graph$edges$x0 + 1))
expect_equal(sum(graph$nodes$x == 0), 1)
# the D > b node has leaves at 0 and -1, the D node has that node and a
# leaf at -2, the root has the D node and a leaf at -3
internal <- graph$nodes[is.na(graph$nodes$category), ]
expect_equal(internal$y[order(internal$x)], c(-2.125, -1.25, -0.5))
})
test_that("a node lists its process children in the order they are first mentioned", {
tree <- mpt_tree("t", list(
a = "D * m * x",
b = "D * m * (1 - x)",
c = "(1 - D) * n * y",
d = "(1 - D) * n * (1 - y)"
))
graph <- .mpt_tree_graph(tree)
root_edges <- graph$edges[graph$edges$x0 == 0, ]
expect_equal(root_edges$label[order(-root_edges$y1)], c("D", "1 - D"))
})
test_that("leaves and subtrees keep the order of first mention", {
# source monitoring, new items: the success edge b comes before 1 - b
new_items <- source_trees()[[3]]
graph <- .mpt_tree_graph(new_items)
root_edges <- graph$edges[graph$edges$x0 == 0, ]
expect_equal(root_edges$label[order(-root_edges$y1)], c("b", "1 - b"))
leaves <- graph$nodes[!is.na(graph$nodes$category), ]
expect_equal(leaves$category, c("A", "B", "N"))
leaf_categories <- function(branches) {
nodes <- .mpt_tree_graph(mpt_tree("t", branches))$nodes
nodes$category[!is.na(nodes$category)]
}
# a leaf listed first is drawn first, unless it is the complement of a sibling
expect_equal(leaf_categories(list(z = "g", a = "D * b", b = "D * (1 - b)")), c("z", "a", "b"))
# a leaf mentioned after a subtree is drawn below it
expect_equal(leaf_categories(list(a = "D * b", b = "D * (1 - b)", n = "k")), c("a", "b", "n"))
expect_equal(
leaf_categories(list(d = "1 - D", a = "D * b", b = "D * (1 - b)")),
c("a", "b", "d")
)
})
test_that("a complement edge follows its sibling and other edges keep first-mention order", {
expect_equal(.mpt_sibling_order(c("1 - D", "g", "D"), c(0, 1, 2)), c(2, 3, 1))
# a complement without its sibling, plain symbols and constants keep their rank
expect_equal(.mpt_sibling_order(c("1 - b", "gB", "gA", "0.2"), c(0, 1, 2, 3)), 1:4)
expect_equal(.mpt_sibling_order(c("gA", "gB", "gNew"), c(0, 1, 2)), 1:3)
# repeated siblings: the complement goes below the last X, ties by first mention
expect_equal(.mpt_sibling_order(c("g", "g", "1 - g"), c(0, 1, 2)), 1:3)
expect_equal(.mpt_sibling_order(c("1 - g", "1 - g", "g"), c(2, 1, 0)), c(3, 2, 1))
expect_equal(.mpt_sibling_order(c("2 - D", "D"), c(0, 1)), 1:2)
# redundant parentheses do not hide the pair
expect_equal(.mpt_sibling_order(c("1 - (D)", "D"), c(0, 1)), c(2, 1))
})
test_that("the success edge X is drawn above its complement 1 - X in published trees", {
trees <- c(
mpt_2htm_trees(), source_trees(), oberauer_lin_trees()[1], list(
pairs_tree(),
mpt_tree("three_levels", list(
a = "D * b * c", b = "D * b * (1 - c)", c = "D * (1 - b)", d = "1 - D"
)),
mpt_tree("complement_listed_first", list(
d = "1 - D", a = "D * b", b = "D * (1 - b)"
))
)
)
complement_pairs_checked <- 0
for (tree in trees) {
edges <- .mpt_tree_graph(tree)$edges
for (siblings in split(edges, edges$from)) {
for (label in siblings$label[startsWith(siblings$label, "1 - ")]) {
success <- siblings$y1[siblings$label == substring(label, 5)]
if (length(success) == 0L) next
complement_pairs_checked <- complement_pairs_checked + 1
expect_true(
all(success > siblings$y1[siblings$label == label]),
label = glue("{tree$name}: {substring(label, 5)} above {label}")
)
}
}
}
expect_gt(complement_pairs_checked, 15)
})
test_that("plot methods leave par() as it was, on success and on error", {
tree <- mpt_tree("t", list(a = "D", b = "1 - D"))
withr::local_pdf(NULL)
before <- graphics::par(c("mfrow", "mar"))
plot(tree)
expect_equal(graphics::par(c("mfrow", "mar")), before)
local_mocked_bindings(.mpt_edge_label_positions = function(edges, cex) stop("boom"))
expect_error(plot(tree), "boom")
expect_equal(graphics::par(c("mfrow", "mar")), before)
expect_error(plot(mpt(mpt_2htm_trees(), tree_id = "item_type")), "boom")
expect_equal(graphics::par(c("mfrow", "mar")), before)
})
test_that("plot methods return x invisibly", {
tree <- mpt_tree("t", list(a = "D", b = "1 - D"))
model <- mpt(list(tree), tree_id = "item")
withr::local_pdf(NULL)
tree_result <- withVisible(plot(tree))
expect_identical(tree_result$value, tree)
expect_false(tree_result$visible)
model_result <- withVisible(plot(model))
expect_identical(model_result$value, model)
expect_false(model_result$visible)
})
test_that("namespaced function calls in edge labels plot correctly", {
tree_ns <- mpt_tree("ns_call", list(
A = "stats::plogis(a) * g",
B = "stats::plogis(a) * (1 - g)",
C = "1 - stats::plogis(a)"
))
expect_true(any(grepl("stats::plogis", .mpt_tree_graph(tree_ns)$edges$label)))
withr::local_pdf(NULL)
expect_silent(plot(tree_ns))
})
test_that("variable-free calls that cannot be evaluated fall back to label", {
tree_no_eval <- mpt_tree("no_eval", list(D = "foo() * D", not_D = "1 - D"))
expect_silent(graph <- .mpt_tree_graph(tree_no_eval))
expect_true(any(grepl("foo()", graph$edges$label, fixed = TRUE)))
withr::local_pdf(NULL)
expect_silent(plot(tree_no_eval))
})
test_that("models whose sibling edges sum to 1 plot without a warning", {
withr::local_pdf(NULL)
old_new <- mpt_2htm_trees()
pair_clustering <- list(
pairs_tree(impossible = c("F1", "F2")),
mpt_tree("singletons", list(F1 = "u", F2 = "1 - u"), impossible = c("E1", "E2", "E3", "E4"))
)
models <- list(
two_htm = mpt(old_new, tree_id = "item_type"),
pair_clustering = mpt(pair_clustering, tree_id = "item_type"),
source_monitoring = mpt(source_trees(), tree_id = "source"),
oberauer_2019 = mpt(
oberauer_2019_tree(),
covariates = c("GcorrPi", "GcorrNoPi", "GotherNoPi")
),
oberauer_lin = mpt(oberauer_lin_trees(), tree_id = "tree"),
simplex = mpt(simplex_trees(), tree_id = "source", simplex = list(c("gA", "gB", "gNew"))),
constants_summing_to_1 = mpt(old_new, tree_id = "item_type", restrictions = "g = 0.5"),
equated = mpt(list(
mpt_tree("old", list(old = "Do + (1 - Do) * g", new = "(1 - Do) * (1 - g)")),
mpt_tree("new", list(old = "(1 - Dn) * g", new = "Dn + (1 - Dn) * (1 - g)"))
), tree_id = "item_type", restrictions = "Dn = Do")
)
for (name in names(models)) {
expect_silent(plot(models[[name]]))
}
# a bare tree cannot know the simplex: fans of plain symbols are not checked
expect_silent(plot(simplex_trees()[[1]]))
expect_silent(plot(simplex_trees()[[3]]))
expect_silent(plot(pair_clustering[[1]]))
})
test_that("valid models whose edges are not simple probabilities plot without a warning", {
withr::local_pdf(NULL)
trees <- list(
complement_of_a_product = mpt_tree("t", list(hit = "D * G", miss = "1 - D * G")),
factor_with_a_constant = mpt_tree("t", list(
yes = "(Do) + ((1 - Do) * guess * 0.25)", no = "(1 - Do) * (1 - guess * 0.25)"
)),
multiplicity_first = mpt_tree("study", list(
C = "cp + (1 - cp) * rp * rp",
E = "2 * (1 - cp) * rp * (1 - rp)",
U = "(1 - cp) * (1 - rp) * (1 - rp)"
)),
multiplicity_last = mpt_tree("t", list(
a = "(1 - c) * u^2", b = "(1 - c) * u * (1 - u) * 2", d = "(1 - c) * (1 - u)^2", e = "c"
)),
zero_product = mpt_tree("t", list(x = "0 * D", y = "1 - 0 * D"))
)
for (name in names(trees)) {
expect_silent(plot(trees[[name]]))
if (name != "zero_product") expect_silent(plot(mpt(trees[[name]])))
}
# mpt() refuses a branch that is 0 for every value; the tree still draws
expect_error(mpt(trees$zero_product), "outside \\(0, 1\\]")
in_a_node <- mpt_tree("t", list(
hit = "G * D + (1 - G * D) * gA", other = "(1 - G * D) * gB", miss = "(1 - G * D) * gC"
))
expect_silent(plot(mpt(in_a_node, simplex = list(c("gA", "gB", "gC")))))
# quotient edges are not checked for their sum; the quotient warning is
# tested apart
quotients <- list(
mpt(mpt_tree("t", list(
a = "D + (1 - D) * g / 2", b = "(1 - D) * g / 2", c = "(1 - D) * (1 - g)"
))),
mpt(mpt_tree("t", list(
hit = "D + (1 - D)/ss", miss = "(1 - D) * (1 - 1/ss)"
)), covariates = "ss")
)
for (model in quotients) {
messages <- character(0)
withCallingHandlers(
plot(model),
warning = function(w) {
messages <<- c(messages, conditionMessage(w))
invokeRestart("muffleWarning")
}
)
expect_false(any(grepl("do not sum to 1", messages, fixed = TRUE)))
}
})
test_that("covariates that sum to 1 only in the data are not checked", {
withr::local_pdf(NULL)
fan <- mpt_tree("cued", list(correct = "D + (1 - D) * Gcorr", incorrect = "(1 - D) * Gother"))
model <- suppressWarnings(mpt(fan, covariates = c("Gcorr", "Gother")))
expect_silent(plot(model))
# a covariate with its complement does sum to 1, and is checked with the rest of the node
wrong <- mpt_tree("w", list(
a = "D * Gc", b = "D * (1 - Gc)", c = "D * 0.5", d = "1 - D"
))
expect_warning(
plot(suppressWarnings(mpt(wrong, covariates = "Gc"))),
"the node 'D' do not sum to 1: 'Gc', '1 - Gc', '0.5'",
fixed = TRUE
)
swapped <- mpt_tree("s", list(a = "D * G", b = "(1 - G) * D", c = "1 - D"))
expect_warning(
plot(mpt(swapped, covariates = "G")),
"the root do not sum to 1: 'D', '1 - D', '1 - G'",
fixed = TRUE
)
})
test_that("a node whose edges do not sum to 1 warns once, names the node, and still draws", {
withr::local_pdf(NULL)
mismatch <- mpt_tree("mismatch", list(a = "x * y", b = "(1 - y) * x", c = "1 - x"))
expect_warning(
result <- plot(mismatch),
"In the tree 'mismatch', the edges leaving the root do not sum to 1: 'x', '1 - x', '1 - y'",
fixed = TRUE
)
expect_identical(result, mismatch)
# one warning per offending node, each naming its node and its edges
bad <- mpt_tree("bad", list(
a = "u * v", b = "u * (1 - w)", c = "1 - u", d = "(1 - v) * 0.5", e = "(1 - v) * 0.25"
))
messages <- character(0)
withCallingHandlers(
plot(bad),
warning = function(w) {
messages <<- c(messages, conditionMessage(w))
invokeRestart("muffleWarning")
}
)
expect_length(messages, 3)
expect_match(messages[1], "the node 'u' do not sum to 1: 'v', '1 - w'", fixed = TRUE)
expect_match(messages[2], "the node '1 - v' do not sum to 1: '0.5', '0.25'", fixed = TRUE)
expect_match(messages[3], "the root do not sum to 1: 'u', '1 - u', '1 - v'", fixed = TRUE)
# a deeper node is named by its whole path of edges, from the root down
deep <- mpt_tree("deep", list(
a = "x * y * z", b = "x * y * (1 - w)", c = "x * (1 - y)", d = "1 - x"
))
expect_warning(plot(deep), "the node 'x > y' do not sum to 1: 'z', '1 - w'", fixed = TRUE)
})
test_that("a swapped complement in a published tree is reported", {
withr::local_pdf(NULL)
swapped <- mpt_tree("swapped", list(old = "D + (1 - D) * (1 - g)", new = "(1 - D) * (1 - g)"))
expect_warning(
plot(swapped),
"the node '1 - D' do not sum to 1: '1 - g', '1 - g'",
fixed = TRUE
)
swapped_root <- mpt_tree("swapped_root", list(old = "D + (1 - D) * g", new = "D * (1 - g)"))
expect_warning(plot(swapped_root), "the root do not sum to 1: 'D', 'D', '1 - D'", fixed = TRUE)
})
test_that("a parenthesised complement is classified like the plain one", {
withr::local_pdf(NULL)
expect_silent(plot(mpt_tree("p", list(a = "D", b = "1 - (D)"))))
expect_warning(
plot(mpt_tree("p", list(a = "D", b = "1 - (D)", c = "D"))),
"'D', 'D', '1 - (D)'",
fixed = TRUE
)
})
test_that("sibling sums use the original expressions and a tolerance of 1e-6", {
withr::local_pdf(NULL)
# the edge labels show 0.333, the sum of the expressions is 1 within 1e-6
within_tolerance <- mpt_tree("t", list(
a = "m", b = "(1 - m) * 0.3333333", c = "(1 - m) * 0.3333333", d = "(1 - m) * 0.3333333"
))
expect_silent(plot(within_tolerance))
fraction <- mpt_tree("t", list(
a = "m", b = "(1 - m) * (1/3)", c = "(1 - m) * (1/3)", d = "(1 - m) * (1/3)"
))
expect_silent(plot(fraction))
rounded <- mpt_tree("t", list(
a = "m", b = "(1 - m) * 0.333", c = "(1 - m) * 0.333", d = "(1 - m) * 0.333"
))
expect_warning(plot(rounded), "the node '1 - m' do not sum to 1", fixed = TRUE)
})
test_that("a simplex fan is checked only when the simplex is known", {
tree <- simplex_trees()[[1]]
graph <- .mpt_tree_graph(tree)
expect_silent(.mpt_warn_sibling_sums(graph, "sA", list(c("gA", "gB", "gNew"))))
expect_warning(
.mpt_warn_sibling_sums(graph, "sA", list()),
"'gA', 'gB', 'gNew'",
fixed = TRUE
)
expect_silent(.mpt_warn_sibling_sums(graph, "sA", NULL))
# a group member missing from the tree's edges still gets its test value
partial <- .mpt_tree_graph(mpt_tree("t", list(A = "dA * gA", B = "dA * gB", C = "1 - dA")))
expect_warning(
.mpt_warn_sibling_sums(partial, "t", list(c("gA", "gB", "gNew"))),
"'gA', 'gB'",
fixed = TRUE
)
})
test_that("plot.mpt() checks plain-symbol fans against the model's simplex", {
withr::local_pdf(NULL)
tree <- mpt_tree("s", list(A = "gA", B = "gB", C = "x * gNew", D = "gNew * (1 - x)"))
model <- mpt(tree, simplex = list(c("gA", "gB", "gNew")))
expect_warning(
plot(model),
"the root do not sum to 1: 'gA', 'gB', 'x', 'gNew'",
fixed = TRUE
)
expect_silent(plot(tree))
})
test_that("leaf labels fit inside the plot region when they take less than 90% of its width", {
tree <- long_label_tree()
fits <- logical(0)
overflow <- numeric(0)
settings <- list(
list(7, 7, 12), list(12, 4, 12), list(6, 4, 12), list(5, 3, 12),
list(4, 3, 12), list(2.5, 2.5, 12), list(4, 3, 16)
)
for (setting in settings) {
extents <- label_extents(
tree, setting[[1]], setting[[2]], pointsize = setting[[3]]
)
leaves <- extents[extents$kind == "leaf", ]
widest <- max(leaves$right - leaves$left)
fits <- c(fits, widest <= 0.9 * leaves$plot_width[1])
overflow <- c(overflow, max(leaves$right) / leaves$plot_width[1] - 1)
}
expect_gte(sum(fits), 4)
expect_true(any(!fits))
expect_lte(max(overflow[fits]), 0.02)
})
test_that("edge labels and leaf labels stay inside the plot region", {
extents <- label_extents(long_label_tree(), 10, 7)
expect_gte(min(extents$left), -0.02 * extents$plot_width[1])
expect_lte(max(extents$right), 1.02 * extents$plot_width[1])
expect_equal(sum(extents$kind == "edge"), 4)
})
test_that("labels of neighbouring edges do not overlap in a dense tree", {
# four binary process steps with restricted parameters: the long restriction
# labels of neighbouring edges used to overprint at these sizes
steps <- expand.grid(A = 0:1, B = 0:1, C = 0:1, D = 0:1)
branches <- apply(steps, 1, function(row) {
paste(ifelse(row == 1, names(row), paste0("(1 - ", names(row), ")")), collapse = " * ")
})
dense <- mpt(
mpt_tree("dense", as.list(setNames(branches, paste0("c", seq_along(branches))))),
restrictions = c("B = 0.571", "C = 0.2", "D = 0.125")
)
for (size in list(c(5, 3.5), c(6, 4), c(8, 6))) {
expect_identical(overlapping_edge_labels(dense, size[1], size[2]), character(0))
}
# panels too small for the loose spacing still keep every label readable:
# the boxes are tightened to the text height before they would overprint
for (size in list(c(4, 3), c(5, 3))) {
expect_identical(overlapping_edge_labels(dense, size[1], size[2], box = 1), character(0))
}
})
test_that("the plot window makes room for the farthest edge label and no more", {
# the room a label needs depends on the window it is placed in, so the window
# is found in a few passes; without them the label leaves the plot region
root_long <- mpt_tree("root_long", list(
a = "Very_Long_Parameter_Name_One_abc * g",
b = "Very_Long_Parameter_Name_One_abc * (1 - g)",
c = "1 - Very_Long_Parameter_Name_One_abc"
))
for (size in list(c(5, 4), c(4.5, 3.5), c(4, 3))) {
extents <- label_extents(root_long, size[1], size[2])
leftmost <- min(extents$left[extents$kind == "edge"])
expect_gte(leftmost, -0.02)
expect_lt(leftmost, 0.05)
}
})
test_that("label boxes are pushed apart vertically and a huge tree is left alone", {
y <- .mpt_separate_labels(
x = c(0, 0.5, 5), y = c(1, 1.05, 1), width = c(2, 2, 2), height = c(0.1, 0.1, 0.1)
)
expect_gt(y[2] - y[1], 0.15)
# the lower label keeps its place and the higher one makes room
expect_equal(y[1], 1)
expect_equal(y[3], 1)
})
test_that("a label equally far from a free height above and below goes above", {
# at height 0 the two candidates are exactly symmetric
y <- .mpt_separate_labels(x = c(0, 0), y = c(0, 0), width = c(1, 1), height = c(0.1, 0.1))
expect_equal(y[1], 0)
expect_gt(y[2], 0)
})
test_that("a tree of 2500 labels is placed and one of 2501 is left alone", {
# spread out so that placing is cheap; the labels start above the window, so
# only a placed label ends up inside it
for (n in c(2500L, 2501L)) {
y <- .mpt_separate_labels(
x = seq_len(n) * 10, y = rep(100, n), width = rep(1, n), height = rep(0.1, n),
limits = c(0, 10)
)
expect_equal(y, rep(if (n == 2500L) 9.95 else 100, n), label = n)
}
})
test_that("labels stay inside the plot region and overlap before they leave it", {
n <- 40
y <- .mpt_separate_labels(
x = rep(0, n), y = seq(1, 2, length.out = n), width = rep(1, n), height = rep(0.1, n),
limits = c(0, 2)
)
expect_true(all(y >= 0.05 & y <= 1.95))
# a free height inside the window is taken even when it is farther away
y <- .mpt_separate_labels(
x = c(0, 0), y = c(1.94, 1.94), width = c(1, 1), height = c(0.1, 0.1), limits = c(0, 2)
)
expect_equal(y[1], 1.94)
expect_lt(y[2], 1.94 - 0.1)
})
test_that("a tree of 3000 labels is left alone", {
n <- 3000
expect_identical(
.mpt_separate_labels(rep(0, n), rep(1, n), rep(2, n), rep(0.1, n)),
rep(1, n)
)
})
test_that("edge labels stay inside the plot region of a panel too small for them", {
withr::local_pdf(NULL, width = 4, height = 3)
steps <- expand.grid(A = 0:1, B = 0:1, C = 0:1, D = 0:1)
branches <- apply(steps, 1, function(row) {
paste(ifelse(row == 1, names(row), paste0("(1 - ", names(row), ")")), collapse = " * ")
})
edges <- .mpt_tree_graph(
mpt_tree("dense", as.list(setNames(branches, paste0("c", seq_along(branches)))))
)$edges
edges$label <- paste(edges$label, "(restricted to 0.125)")
graphics::plot.new()
graphics::plot.window(xlim = c(-0.1, 40), ylim = c(-4, 1))
position <- .mpt_edge_label_positions(edges, 0.9)
usr <- graphics::par("usr")
half <- graphics::strheight(edges$label, cex = 0.9) / 2
expect_gte(min(position$y - half), usr[3] - 1e-8)
expect_lte(max(position$y + half), usr[4] + 1e-8)
})
test_that("labels collide by their distance in inches, not in user units", {
withr::local_pdf(NULL)
# two flat edges whose labels are 0.2 inch apart on a window of 1000 user
# units across: far apart in user units, overlapping on the page
edges <- data.frame(
x0 = c(0, 50), x1 = c(100, 150), y0 = c(0, 0), y1 = c(0, 0), label = c("a long label", "a long label")
)
graphics::plot.new()
graphics::plot.window(xlim = c(0, 1000), ylim = c(-5, 5))
position <- .mpt_edge_label_positions(edges, 0.9)
y_per_inch <- diff(graphics::par("usr")[3:4]) / graphics::par("pin")[2]
height <- graphics::strheight("a long label", units = "inches", cex = 0.9)
expect_gte(abs(diff(position$y)) / y_per_inch, 1.5 * height)
})
test_that("labels side by side keep a gap and a crowded column ends up free of overlap", {
# boxes that only touch sideways (x apart = their width) still get a gap
y <- .mpt_separate_labels(x = c(0, 1), y = c(1, 1), width = c(1, 1), height = c(0.1, 0.1))
expect_gt(abs(y[2] - y[1]), 0.1)
# a sideways gap under half a text height does not count as clear
y <- .mpt_separate_labels(x = c(0, 1.02), y = c(1, 1), width = c(1, 1), height = c(0.1, 0.1))
expect_gt(abs(y[2] - y[1]), 0.1)
# a crowd spreads to both sides of where it stands
y <- .mpt_separate_labels(rep(0, 5), 1 + 0:4 * 0.01, rep(1, 5), rep(0.1, 5))
expect_lt(max(abs(y - (1 + 0:4 * 0.01))), 0.4)
# 60 labels on one spot: pairwise pushing never settled on crowds like this
n <- 60
height <- rep(0.1, n)
y <- .mpt_separate_labels(rep(0, n), rep(1, n) + seq_len(n) * 1e-4, rep(1, n), height)
expect_gte(min(diff(sort(y))), 1.5 * 0.1)
})
test_that("a dense tree of hybrid size has no overlapping edge labels where it has room", {
# five binary process steps, 32 leaves, four of them with long restricted
# labels: the size of a three-level hybrid tree, whose labels still overlapped
# in boxes 50% taller than the text
steps <- c("PrItemColor", "PrItemLocation", "PrGuessSecondLure", "PrGuessThirdLure", "PrGuessExtra")
grid <- expand.grid(rep(list(0:1), length(steps)))
names(grid) <- steps
branches <- apply(grid, 1, function(row) {
paste(ifelse(row == 1, names(row), paste0("(1 - ", names(row), ")")), collapse = " * ")
})
dense <- mpt(
mpt_tree("dense", as.list(setNames(branches, paste0("c", seq_along(branches))))),
restrictions = c(
"PrItemLocation = 0.571", "PrGuessSecondLure = 0.2",
"PrGuessThirdLure = 0.125", "PrGuessExtra = 0.25"
)
)
for (size in list(c(7.7, 13.8), c(8, 10), c(10, 14))) {
expect_identical(
overlapping_edge_labels(dense, size[1], size[2], box = 1.5), character(0)
)
}
})
test_that("leaf labels stay inside the plot region where they fit", {
# the leaf padding is a fraction of an inch: the looser 2% tolerance of the
# test above would not see it
tree <- long_label_tree()
for (size in list(c(7, 7), c(6, 4), c(5, 3))) {
extents <- label_extents(tree, size[1], size[2])
leaves <- extents[extents$kind == "leaf", ]
expect_lte(max(leaves$right), leaves$plot_width[1])
}
})
test_that("a label too wide for any window does not stretch the tree past the plot", {
# the room for the label cannot be had: the passes must stop instead of
# adding ever more of it
long_name <- strrep("x", 100)
tree <- mpt_tree("wide", list(
a = paste0("P", long_name, " * g"),
b = paste0("P", long_name, " * (1 - g)"),
c = paste0("1 - P", long_name)
))
for (size in list(c(2, 2), c(4, 3), c(3, 2.5))) {
xs <- NULL
usr <- NULL
withr::with_pdf(
NULL,
testthat::with_mocked_bindings(
plot(tree),
segments = function(x0, y0, x1, y1, ...) {
xs <<- range(c(x0, x1))
usr <<- graphics::par("usr")
},
.package = "graphics"
),
width = size[1], height = size[2]
)
expect_gte(xs[1], usr[1])
expect_lte(xs[2], usr[2])
}
})
test_that("edge labels sit beside their edge on the outer side of a fan", {
withr::local_pdf(NULL)
edges <- .mpt_tree_graph(mpt_tree("t", list(
a = "D", b = "(1 - D) * g", c = "(1 - D) * (1 - g)", d = "(1 - D) * 0.5"
)))$edges
graphics::plot.new()
# unequal user units per inch on the two axes
graphics::plot.window(xlim = c(-0.1, 40), ylim = c(-4, 1))
cex <- 0.9
position <- .mpt_edge_label_positions(edges, cex)
pin <- graphics::par("pin")
usr <- graphics::par("usr")
x_per_inch <- (usr[2] - usr[1]) / pin[1]
y_per_inch <- (usr[4] - usr[3]) / pin[2]
dx <- (edges$x1 - edges$x0) / x_per_inch
dy <- (edges$y1 - edges$y0) / y_per_inch
# distance of the label centre from the (infinite) edge line, in inches
distance <- abs(
(position$x - edges$x0) / x_per_inch * dy - (position$y - edges$y0) / y_per_inch * dx
) / sqrt(dx^2 + dy^2)
width <- graphics::strwidth(edges$label, units = "inches", cex = cex)
height <- graphics::strheight(edges$label, units = "inches", cex = cex)
# the label box extends from its centre by this much towards the line
# (|nx| * w / 2 + |ny| * h / 2), and the gap beyond that is 0.4 label heights
extent <- (abs(dy) * width / 2 + abs(dx) * height / 2) / sqrt(dx^2 + dy^2)
expect_equal(distance, 0.4 * height + extent)
# rising edges carry the label above the line, falling edges below
line_y <- edges$y0 + (position$x - edges$x0) * (edges$y1 - edges$y0) / (edges$x1 - edges$x0)
expect_equal(sign(position$y - line_y), ifelse(edges$y1 >= edges$y0, 1, -1))
})
test_that("plot.mpt() lays the trees out on the expected grid", {
withr::local_pdf(NULL)
grids <- list()
local_mocked_bindings(.mpt_plot_tree = function(x, cex, simplex, covariates, restrictions) {
grids[[length(grids) + 1]] <<- graphics::par("mfrow")
})
for (n_trees in 2:6) {
categories <- paste0(c("a", "b"), rep(seq_len(n_trees), each = 2))
trees <- lapply(seq_len(n_trees), function(i) {
own <- categories[(2 * i - 1):(2 * i)]
mpt_tree(
paste0("t", i), setNames(list("p", "1 - p"), own),
impossible = setdiff(categories, own)
)
})
grids <- list()
plot(mpt(trees, tree_id = "item"))
expect_length(grids, n_trees)
expect_true(all(vapply(grids, identical, logical(1), grids[[1]])), label = n_trees)
expect_equal(
grids[[1]],
list(c(1, 2), c(1, 3), c(2, 2), c(2, 3), c(2, 3))[[n_trees - 1]],
label = glue("grid for {n_trees} trees")
)
}
})
test_that("plot.mpt() leaves a multi-panel layout of the user alone", {
withr::local_pdf(NULL)
grids <- list()
local_mocked_bindings(.mpt_plot_tree = function(x, cex, simplex, covariates, restrictions) {
grids[[length(grids) + 1]] <<- graphics::par("mfrow")
})
trees <- list(
mpt_tree("t1", list(a = "p", b = "1 - p"), impossible = c("c", "d")),
mpt_tree("t2", list(c = "q", d = "1 - q"), impossible = c("a", "b"))
)
graphics::par(mfrow = c(2, 2))
plot(mpt(trees, tree_id = "item"))
expect_equal(grids, list(c(2, 2), c(2, 2)))
expect_equal(graphics::par("mfrow"), c(2, 2))
})
test_that("cex reaches every edge and leaf label and the title shows the tree name", {
withr::local_pdf(NULL)
tree <- mpt_2htm_trees()[[1]]
graph <- .mpt_tree_graph(tree)
texts <- list()
titles <- list()
local_mocked_bindings(
text = function(x, y = NULL, labels = y, ...) {
texts[[length(texts) + 1]] <<- list(labels = labels, cex = list(...)$cex)
},
title = function(...) titles[[length(titles) + 1]] <<- list(...),
.package = "graphics"
)
plot(tree, cex = 1.7)
expect_length(texts, 2)
expect_true(all(vapply(texts, function(call) identical(call$cex, 1.7), logical(1))))
expect_setequal(unlist(lapply(texts, `[[`, "labels")), c(graph$edges$label, "old", "new"))
expect_equal(sum(lengths(lapply(texts, `[[`, "labels"))), nrow(graph$edges) + 3)
expect_length(titles, 1)
expect_equal(titles[[1]]$main, "MPT tree 'old'")
})
test_that("a leaf label wider than the plot still leaves a valid window", {
tree <- mpt_tree("long", list(
Correct_Correct_Long_Category_Name = "p", Other = "1 - p"
))
usr <- NULL
label_positions <- .mpt_edge_label_positions
local_mocked_bindings(.mpt_edge_label_positions = function(edges, cex) {
usr <<- graphics::par("usr")
label_positions(edges, cex)
})
withr::with_pdf(NULL, plot(tree), width = 1.5, height = 1.5)
expect_true(all(is.finite(usr)))
expect_gt(usr[2], usr[1])
})
drawn_labels <- function(x) {
texts <- character(0)
local_mocked_bindings(
text = function(x, y = NULL, labels = y, ...) texts <<- c(texts, labels),
.package = "graphics"
)
withr::with_pdf(NULL, expect_no_warning(plot(x)))
texts
}
alias_trees <- function() {
list(
mpt_tree("old", list(old = "Do + (1 - Do) * g", new = "(1 - Do) * (1 - g)")),
mpt_tree("new", list(old = "(1 - Dn) * g", new = "Dn + (1 - Dn) * (1 - g)"))
)
}
test_that("plot() of a model labels the edges of a fixed parameter with the restriction", {
fixed <- mpt(mpt_2htm_trees(), tree_id = "item_type", restrictions = "g = 0.5")
labels <- drawn_labels(fixed)
expect_contains(labels, c("D", "1 - D", "g = 0.5", "1 - g (g = 0.5)"))
expect_false("0.5" %in% labels)
})
test_that("plot() of a model labels the edges of an equated parameter with the restriction", {
aliased <- mpt(alias_trees(), tree_id = "item_type", restrictions = "Dn = Do")
labels <- drawn_labels(aliased)
expect_contains(labels, c("Do", "1 - Do", "Dn (= Do)", "1 - Dn (Dn = Do)", "g", "1 - g"))
expect_contains(drawn_labels(alias_trees()[[2]]), c("Dn", "1 - Dn"))
})
test_that("a restricted constant keeps the multiplicity edge and the sibling sums intact", {
restricted <- mpt(pairs_tree(), restrictions = "u = 0.5")
expect_contains(drawn_labels(restricted), c(
"u^2 (u = 0.5)", "2 * u * (1 - u) (u = 0.5)", "(1 - u)^2 (u = 0.5)", "c", "1 - c"
))
# the fan a, 1 - b sums to 1 only once b = a
equated <- mpt(mpt_tree("t", list(A = "a", B = "1 - b")), restrictions = "b = a")
expect_contains(drawn_labels(equated), c("a", "1 - b (b = a)"))
# a chain resolves in either order: b = c with c = 0.4 makes the fan c, 1 - b sum to 1
chain_tree <- mpt_tree("t", list(A = "a * c", B = "a * (1 - b)", C = "1 - a"))
for (order in list(c("b = c", "c = 0.4"), c("c = 0.4", "b = c"))) {
expect_contains(drawn_labels(mpt(chain_tree, restrictions = order)), "c = 0.4")
}
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.