///|
priv struct Measurement {
case_name : String
point_count : Int32
constraint_count : Int32
seed : Int32
repetition : Int32
elapsed_us : Double
status : String
error : String
iterations : Int32
variables : Int32
equations : Int32
local_dof : Int32
dependent_equations : Int32
max_tolerance_ratio : Double
max_length_error : Double
residual_ratios : Array[Double]
warmup_statuses : Array[String]
initial_points : Array[Array[Double]]
points : Array[Array[Double]]
retained_points : Array[Array[Double]]
} derive(ToJson)
///|
fn random(state : Array[Int32]) -> Double {
let value = 16807 * (state[0] % 127773) - 2836 * (state[0] / 127773)
state[0] = if value > 0 { value } else { value + 2147483647 }
state[0].to_double() / 2147483647.0
}
///|
fn target(i : Int32) -> @sketch.Point {
{ x: i.to_double(), y: if i % 2 == 0 { 0.0 } else { 2.0 }, }
}
///|
fn length(a : @sketch.Point, b : @sketch.Point) -> Double {
@math.hypot(b.x - a.x, b.y - a.y)
}
///|
fn stored(
s : @sketch.Sketch,
ids : Array[@sketch.PointId],
) -> Array[Array[Double]] {
ids.map(id => {
let p = s.point(id).unwrap()
[p.x, p.y]
})
}
///|
fn run_case(
name : String,
count : Int32,
warmups : Int32,
repetitions : Int32,
) -> Unit {
let s = @sketch.Sketch::new()
let seed = 20261022 + count * 31
let state = [seed]
for i = 0; i < 5; i = i + 1 {
random(state) |> ignore
}
let targets = Array::makei(count, i => target(i))
let ids = targets.map(p => {
s.add_point(
p.x + 0.05 * (2.0 * random(state) - 1.0),
p.y + 0.05 * (2.0 * random(state) - 1.0),
)
})
// Two positioned vertices, all adjacent edges, all two-hop diagonals and
// one closing edge: exactly 2*count distinct relations, not duplicate fixes.
s.constrain(Fix(ids[0], targets[0].x, targets[0].y)) |> ignore
s.constrain(Fix(ids[1], targets[1].x, targets[1].y)) |> ignore
for i = 1; i < count; i = i + 1 {
s.constrain(
Distance(ids[i - 1], ids[i], length(targets[i - 1], targets[i])),
)
|> ignore
}
for i = 2; i < count; i = i + 1 {
s.constrain(
Distance(ids[i - 2], ids[i], length(targets[i - 2], targets[i])),
)
|> ignore
}
s.constrain(
Distance(ids[0], ids[count - 1], length(targets[0], targets[count - 1])),
)
|> ignore
let initial_points = stored(s, ids)
// solve() never applies a solution. Every warmup/repetition therefore starts
// from the same immutable initial geometry, not an already-solved sketch.
let warmup_statuses = []
for i = 0; i < warmups; i = i + 1 {
let status = match s.solve() {
Err(_) => "Error"
Ok(solution) =>
match solution.report.status {
Satisfied => "Satisfied"
Stalled => "Stalled"
IterationLimit => "IterationLimit"
}
}
warmup_statuses.push(status)
}
for repetition = 0; repetition < repetitions; repetition = repetition + 1 {
let start = @clock.monotonic_clock_start()
let result = s.solve()
let elapsed_us = @clock.monotonic_clock_end(start)
let row : Measurement = {
case_name: name,
point_count: count,
constraint_count: count * 2,
seed,
repetition,
elapsed_us,
status: "Error",
error: "",
iterations: 0,
variables: 0,
equations: 0,
local_dof: 0,
dependent_equations: 0,
max_tolerance_ratio: 0.0,
max_length_error: 0.0,
residual_ratios: [],
warmup_statuses,
initial_points,
points: [],
retained_points: stored(s, ids),
}
let row = match result {
Err(error) => { ..row, error: error.message(), }
Ok(solution) => {
let report = solution.report
let mut max_length_error = 0.0
for r in report.residuals {
if r.length_error > max_length_error {
max_length_error = r.length_error
}
}
{
..row,
status: match report.status {
Satisfied => "Satisfied"
Stalled => "Stalled"
IterationLimit => "IterationLimit"
},
iterations: report.iterations,
variables: report.variables,
equations: report.equations,
local_dof: report.local_dof,
dependent_equations: report.dependent_equations,
max_tolerance_ratio: report.max_tolerance_ratio,
max_length_error,
residual_ratios: report.residuals.map(r => r.tolerance_ratio),
points: ids.map(id => {
let p = solution.point(id).unwrap()
[p.x, p.y]
}),
}
}
}
println(@json.to_json(row).stringify())
}
}
///|
fn main {
let smoke = @env.get_env_var("CAD_SKETCH_BENCH_SMOKE") is Some("1")
let warmups = if smoke { 0 } else { 1 }
let repetitions = if smoke { 1 } else { 3 }
let metadata : Json = {
"kind": "cad_sketch_benchmark",
"schema": 1,
"smoke": smoke,
"warmups": warmups,
"repetitions": repetitions,
"length_tolerance": 1.0e-7,
}
println(metadata.stringify())
run_case("Small", 10, warmups, repetitions)
if !smoke {
run_case("Medium", 30, warmups, repetitions)
run_case("Large", 60, warmups, repetitions)
run_case("Stress", 100, warmups, repetitions)
}
}