tests/testthat/test-bipartite-layout.R

test_that("bipartite layout with rows orientation works", {
  cg <- caugi(A %-->% X, A %-->% Y, B %-->% X, B %-->% Y)
  partition <- c(TRUE, TRUE, FALSE, FALSE)

  layout <- caugi_layout_bipartite(cg, partition, orientation = "rows")

  expect_equal(nrow(layout), 4)
  expect_equal(layout$name, c("A", "B", "X", "Y"))

  # A and B should be at y=1 (top row)
  expect_equal(layout$y[layout$name %in% c("A", "B")], c(1, 1))

  # X and Y should be at y=0 (bottom row)
  expect_equal(layout$y[layout$name %in% c("X", "Y")], c(0, 0))
})

test_that("bipartite layout with columns orientation works", {
  cg <- caugi(A %-->% X, A %-->% Y, B %-->% X, B %-->% Y)
  partition <- c(TRUE, TRUE, FALSE, FALSE)

  layout <- caugi_layout_bipartite(cg, partition, orientation = "columns")

  expect_equal(nrow(layout), 4)
  expect_equal(layout$name, c("A", "B", "X", "Y"))

  # A and B should be at x=1 (right column)
  expect_equal(layout$x[layout$name %in% c("A", "B")], c(1, 1))

  # X and Y should be at x=0 (left column)
  expect_equal(layout$x[layout$name %in% c("X", "Y")], c(0, 0))
})

test_that("bipartite layout via caugi_layout with rows", {
  cg <- caugi(A %-->% X, A %-->% Y, B %-->% X, B %-->% Y)
  partition <- c(TRUE, TRUE, FALSE, FALSE)

  layout <- caugi_layout(
    cg,
    method = "bipartite",
    partition = partition,
    orientation = "rows"
  )

  expect_equal(nrow(layout), 4)
  # A and B should be at y=1 (top row)
  expect_equal(layout$y[layout$name %in% c("A", "B")], c(1, 1))
  # X and Y should be at y=0 (bottom row)
  expect_equal(layout$y[layout$name %in% c("X", "Y")], c(0, 0))
})

test_that("bipartite layout via caugi_layout with columns", {
  cg <- caugi(A %-->% X, A %-->% Y, B %-->% X, B %-->% Y)
  partition <- c(TRUE, TRUE, FALSE, FALSE)

  layout <- caugi_layout(
    cg,
    method = "bipartite",
    partition = partition,
    orientation = "columns"
  )

  expect_equal(nrow(layout), 4)
  # A and B should be at x=1 (right column)
  expect_equal(layout$x[layout$name %in% c("A", "B")], c(1, 1))
  # X and Y should be at x=0 (left column)
  expect_equal(layout$x[layout$name %in% c("X", "Y")], c(0, 0))
})

test_that("bipartite layout auto-detects partition", {
  cg <- caugi(A %-->% X, B %-->% X, C %-->% Y)

  # Should auto-detect: A, B, C have no incoming edges (partition = TRUE)
  # X, Y have incoming edges (partition = FALSE)
  layout <- caugi_layout_bipartite(cg, orientation = "rows")

  expect_equal(nrow(layout), 5)
  # A, B, C should be in top row (y=1)
  expect_equal(layout$y[layout$name %in% c("A", "B", "C")], c(1, 1, 1))
  # X, Y should be in bottom row (y=0)
  expect_equal(layout$y[layout$name %in% c("X", "Y")], c(0, 0))
})

test_that("bipartite layout works on chain graph with auto-detection", {
  cg <- caugi(A %-->% B, B %-->% C, C %-->% D)

  # A has no incoming edges (partition = TRUE), others do (partition = FALSE)
  layout <- caugi_layout_bipartite(cg, orientation = "rows")

  expect_equal(nrow(layout), 4)
  expect_equal(layout$y[layout$name == "A"], 1) # Top row
  # Bottom row
  expect_equal(layout$y[layout$name %in% c("B", "C", "D")], c(0, 0, 0))
})

test_that("partition must be logical", {
  cg <- caugi(A %-->% B)

  expect_error(
    caugi_layout_bipartite(cg, partition = c(1, 0)),
    "partition must be a logical vector"
  )
})

test_that("partition length must match node count", {
  cg <- caugi(A %-->% B, B %-->% C)

  expect_error(
    caugi_layout_bipartite(cg, partition = c(TRUE, FALSE)),
    "partition length.*does not match number of nodes"
  )
})

test_that("both partitions must be non-empty", {
  cg <- caugi(A %-->% B, B %-->% C)

  expect_error(
    caugi_layout(
      cg,
      method = "bipartite",
      partition = c(TRUE, TRUE, TRUE),
      orientation = "rows"
    ),
    "Both partitions must be non-empty"
  )
})

test_that("bipartite layout works with single node per partition", {
  cg <- caugi(A %-->% B)
  partition <- c(TRUE, FALSE)

  layout <- caugi_layout_bipartite(cg, partition, orientation = "rows")

  # Both nodes should be centered horizontally
  expect_equal(layout$x, c(0.5, 0.5))
  expect_equal(layout$y, c(1, 0))
})

test_that("bipartite layout handles uneven partitions", {
  cg <- caugi(A %-->% X, B %-->% X, C %-->% X)
  partition <- c(TRUE, TRUE, TRUE, FALSE)

  layout <- caugi_layout_bipartite(cg, partition, orientation = "rows")

  # Three nodes in top row
  expect_equal(sum(layout$y == 1), 3)
  # One node in bottom row
  expect_equal(sum(layout$y == 0), 1)
})

test_that("plot method accepts bipartite layout as string", {
  cg <- caugi(A %-->% X, B %-->% X)
  partition <- c(TRUE, TRUE, FALSE)

  expect_no_error({
    p <- plot(
      cg,
      layout = "bipartite",
      partition = partition,
      orientation = "rows"
    )
  })
})

test_that("plot method accepts bipartite layout as function", {
  cg <- caugi(A %-->% X, B %-->% X)
  partition <- c(TRUE, TRUE, FALSE)

  expect_no_error({
    p <- plot(
      cg,
      layout = caugi_layout_bipartite,
      partition = partition,
      orientation = "rows"
    )
  })
})

test_that("plot method accepts pre-computed layout data.frame", {
  cg <- caugi(A %-->% B, B %-->% C)
  coords <- caugi_layout_sugiyama(cg)

  expect_no_error({
    p <- plot(cg, layout = coords)
  })
})

test_that("plot method errors on invalid layout type", {
  cg <- caugi(A %-->% B)

  expect_error(
    plot(cg, layout = 123),
    "layout must be a character string, function, or data.frame"
  )
})

test_that("plot method errors on layout data.frame missing columns", {
  cg <- caugi(A %-->% B)

  # Missing 'y' column
  bad_coords1 <- data.frame(name = c("A", "B"), x = c(0, 1))
  expect_error(
    plot(cg, layout = bad_coords1),
    "Layout data.frame is missing required column: y"
  )

  # Missing 'x' and 'y' columns
  bad_coords2 <- data.frame(name = c("A", "B"))
  expect_error(
    plot(cg, layout = bad_coords2),
    "Layout data.frame is missing required columns: x, y"
  )

  # Missing 'name' column
  bad_coords3 <- data.frame(x = c(0, 1), y = c(0, 1))
  expect_error(
    plot(cg, layout = bad_coords3),
    "Layout data.frame is missing required column: name"
  )
})

test_that("plot method errors on non-numeric layout coordinates", {
  cg <- caugi(A %-->% B)

  # Non-numeric x
  bad_coords1 <- data.frame(name = c("A", "B"), x = c("0", "1"), y = c(0, 1))
  expect_error(
    plot(cg, layout = bad_coords1),
    "Layout column 'x' must be numeric"
  )

  # Non-numeric y
  bad_coords2 <- data.frame(name = c("A", "B"), x = c(0, 1), y = c("0", "1"))
  expect_error(
    plot(cg, layout = bad_coords2),
    "Layout column 'y' must be numeric"
  )
})

test_that("plot method errors on layout with NA/NaN/Inf values", {
  cg <- caugi(A %-->% B)

  # NA in x
  bad_coords1 <- data.frame(name = c("A", "B"), x = c(NA, 1), y = c(0, 1))
  expect_error(
    plot(cg, layout = bad_coords1),
    "Layout column 'x' contains NA, NaN, or infinite values"
  )

  # Inf in y
  bad_coords2 <- data.frame(name = c("A", "B"), x = c(0, 1), y = c(0, Inf))
  expect_error(
    plot(cg, layout = bad_coords2),
    "Layout column 'y' contains NA, NaN, or infinite values"
  )

  # NaN in x
  bad_coords3 <- data.frame(name = c("A", "B"), x = c(NaN, 1), y = c(0, 1))
  expect_error(
    plot(cg, layout = bad_coords3),
    "Layout column 'x' contains NA, NaN, or infinite values"
  )
})

test_that("plot method errors on layout with wrong number of nodes", {
  cg <- caugi(A %-->% B, B %-->% C)

  # Too few nodes
  bad_coords <- data.frame(name = c("A", "B"), x = c(0, 1), y = c(0, 1))
  expect_error(
    plot(cg, layout = bad_coords),
    "Layout has 2 rows but graph has 3 nodes"
  )
})

test_that("plot method errors on layout with missing node names", {
  cg <- caugi(A %-->% B, B %-->% C)

  # Missing node C, but has correct row count (due to extra node D)
  bad_coords <- data.frame(
    name = c("A", "B", "D"),
    x = c(0, 1, 2),
    y = c(0, 1, 2)
  )
  expect_error(
    plot(cg, layout = bad_coords),
    "Layout is missing coordinate for node: C"
  )
})

test_that("plot method errors on layout with extra node names", {
  cg <- caugi(A %-->% B)

  # Extra node Z - this will be caught by row count check first
  bad_coords <- data.frame(
    name = c("A", "B", "Z"),
    x = c(0, 1, 2),
    y = c(0, 1, 2)
  )
  expect_error(
    plot(cg, layout = bad_coords),
    "Layout has 3 rows but graph has 2 nodes"
  )

  # Extra node Z with correct row count - should catch unknown node
  bad_coords2 <- data.frame(name = c("Z", "B"), x = c(0, 1), y = c(0, 1))
  expect_error(
    plot(cg, layout = bad_coords2),
    "Layout is missing coordinate for node: A"
  )
})

test_that("bipartite auto-detect fails when all nodes have incoming edges", {
  # Graph where all nodes have incoming edges (cycle)
  cg <- caugi(A %-->% B, B %-->% C, C %-->% A)

  expect_error(
    caugi_layout_bipartite(cg),
    "Could not automatically detect bipartite structure"
  )
})

test_that("bipartite auto-detect fails when no nodes have incoming edges", {
  # Graph with isolated nodes (no edges at all)
  cg <- caugi(A + B + C)

  expect_error(
    caugi_layout_bipartite(cg),
    "Could not automatically detect bipartite structure"
  )
})

Try the caugi package in your browser

Any scripts or data that you put into this service are public.

caugi documentation built on Oct. 28, 2026, 1:07 a.m.