///|
priv struct DesignRng {
mut state : Int64
}
///|
fn rng(seed : Int32) -> DesignRng raise DesignError {
if seed < 1 || seed > 2147483646 {
raise Invalid("seed must be 1..2147483646")
}
{ state: seed.to_int64(), }
}
///|
fn draw(random : DesignRng, bound : Int32) -> Int32 {
let limit = 2147483646L / bound.to_int64() * bound.to_int64()
let mut value = limit
while value >= limit {
random.state = random.state * 16807L % 2147483647L
value = random.state - 1L
}
(value % bound.to_int64()).to_int()
}
///|
fn shuffled(runs : Array[Run], random : DesignRng) -> Array[Run] {
let result = runs.copy()
for i = result.length() - 1; i > 0; i = i - 1 {
let j = draw(random, i + 1)
let tmp = result[i]
result[i] = result[j]
result[j] = tmp
}
result
}
///|
pub fn randomize(
p : Plan,
seed : Int32,
within_blocks~ : Bool = false,
) -> Plan raise DesignError {
validate_plan(p)
let random = rng(seed)
if !within_blocks {
return plan(p.factors, shuffled(p.runs, random))
}
let blocks : Array[Int32] = []
for r in p.runs {
if !blocks.contains(r.block) {
blocks.push(r.block)
}
}
if blocks.length() > 64 {
raise Budget("within-block randomization supports <=64 blocks")
}
let runs = []
for block in blocks {
let group = p.runs.filter(fn(r) { r.block == block })
for r in shuffled(group, random) {
runs.push(r)
}
}
plan(p.factors, runs)
}
///|
pub fn balanced_blocks(
p : Plan,
count : Int32,
seed : Int32,
) -> Plan raise DesignError {
validate_plan(p)
if count < 1 || count > 64 || count > p.runs.length() {
raise Budget("block count must be 1..min(64,runs)")
}
let mixed = randomize(p, seed)
let runs = [ for i, r in mixed.runs => { ..r, block: i % count + 1, } ]
plan(p.factors, runs)
}
///|
pub fn block_counts(p : Plan) -> Array[(Int32, Int32)] raise DesignError {
validate_plan(p)
let ids : Array[Int32] = []
let counts : Array[Int32] = []
for r in p.runs {
let mut index = -1
for i, id in ids {
if id == r.block {
index = i
break
}
}
if index < 0 {
ids.push(r.block)
counts.push(1)
} else {
counts[index] += 1
}
}
[
for i, id in ids => (id, counts[i])
]
}