///|
pub(all) struct Request {
system : System
box : Array[Interval]
options : Options
}
///|
fn j_object(value : Json) -> Map[String, Json] raise RootError {
match value {
Object(x) => x
_ => raise Invalid("expected JSON object")
}
}
///|
fn j_array(value : Json) -> Array[Json] raise RootError {
match value {
Array(x) => x
_ => raise Invalid("expected JSON array")
}
}
///|
fn j_text(value : Json) -> String raise RootError {
match value {
String(x) => x
_ => raise Invalid("expected JSON string")
}
}
///|
fn j_number(value : Json) -> Double raise RootError {
match value {
Number(x, repr=_) if !x.is_nan() && !x.is_inf() => x
_ => raise Invalid("expected finite JSON number")
}
}
///|
fn j_integer(value : Json, maximum : Int32) -> Int32 raise RootError {
match value {
Number(x, repr=Some(s)) if s.length() > 0 &&
s.length() <= 8 &&
x >= 0.0 &&
x <= maximum.to_double() => {
for i in 0..= 48 && s[i] <= 57 else {
raise Invalid("use a nonnegative decimal integer token")
}
}
x.to_int()
}
_ => raise Invalid("integer outside supported range")
}
}
///|
fn j_required(o : Map[String, Json], key : String) -> Json raise RootError {
match o.get(key) {
Some(x) => x
None => raise Invalid("missing field: " + key)
}
}
///|
fn j_only(o : Map[String, Json], keys : Array[String]) -> Unit raise RootError {
for key, _ in o {
guard keys.contains(key) else { raise Invalid("unknown field: " + key) }
}
}
///|
/// Duplicate decoded keys, nonfinite numbers, unknown fields and oversized inputs are rejected.
pub fn decode_request(source : String) -> Request raise RootError {
guard source.length() <= 262144 else {
raise Invalid("request exceeds 256 Ki UTF-16 units")
}
let value = @json.parse(
source,
max_nesting_depth=24,
reject_duplicate_keys=true,
preserve_number_repr=true,
) catch {
_ =>
raise Invalid(
"invalid JSON, duplicate object key or nesting limit exceeded",
)
}
let o = j_object(value)
j_only(o, ["version", "variables", "equations", "box", "options"])
guard j_integer(j_required(o, "version"), 1) == 1 else {
raise Invalid("unsupported request version")
}
let names = j_array(j_required(o, "variables")).map(j_text)
guard names.length() >= 1 && names.length() <= 6 else {
raise Invalid("require 1..6 variables")
}
let eqs = j_array(j_required(o, "equations"))
guard eqs.length() == names.length() else {
raise Invalid("equation count must equal variable count")
}
let equations = eqs.map(x => parse_polynomial(j_text(x), names))
let system = { names, equations, }
validate_system(system)
let box = j_array(j_required(o, "box")).map(x => {
let a = j_array(x)
guard a.length() == 2 else {
raise Invalid("each interval requires two endpoints")
}
interval(j_number(a[0]), j_number(a[1]))
})
check_box(box, names.length())
let defaults = default_options()
let opt = match o.get("options") {
Some(x) => j_object(x)
None => Map([])
}
j_only(opt, ["tolerance", "max_assessments", "max_depth"])
let options = {
tolerance: match opt.get("tolerance") {
Some(x) => j_number(x)
None => defaults.tolerance
},
max_assessments: match opt.get("max_assessments") {
Some(x) => j_integer(x, 20000)
None => defaults.max_assessments
},
max_depth: match opt.get("max_depth") {
Some(x) => j_integer(x, 80)
None => defaults.max_depth
},
}
validate_options(options)
{ system, box, options, }
}
///|
fn box_json(box : Array[Interval]) -> Json {
Json::array(
box.map(x => Json::array([Json::number(x.lo), Json::number(x.hi)])),
)
}
///|
/// Serialize finite result bounds and the actual certificate inputs.
fn result_json(request : Request, result : SolveResult) -> Json {
let roots = result.roots.map(root => {
let c = root.certificate
let v : Json = {
"enclosure": box_json(root.enclosure),
"certificate": {
"method": "krawczyk_strict_contraction",
"domain": box_json(c.domain),
"image": box_json(c.image),
"center": Json::array(c.center.map(x => Json::number(x))),
"preconditioner": Json::array(
c.preconditioner.map(row => Json::array(row.map(x => Json::number(x)))),
),
"contraction_bound": Json::number(c.q),
},
}
v
})
let unresolved = result.unresolved.map(u => {
let v : Json = { "box": box_json(u.box), "reason": Json::string(u.reason) }
v
})
{
"version": 1,
"variables": Json::array(request.system.names.map(x => Json::string(x))),
"initial_box": box_json(request.box),
"complete": Json::boolean(result.complete),
"tolerance_met": Json::boolean(result.tolerance_met),
"root_count": Json::number(result.roots.length().to_double()),
"roots": Json::array(roots),
"unresolved": Json::array(unresolved),
"stats": {
"assessments": Json::number(result.assessments.to_double()),
"excluded": Json::number(result.excluded.to_double()),
"contractions": Json::number(result.contractions.to_double()),
"splits": Json::number(result.splits.to_double()),
},
}
}
///|
/// Transport-neutral request/response entry point. Errors remain typed.
pub fn solve_json(source : String) -> String raise RootError {
let request = decode_request(source)
result_json(request, solve(request.system, request.box, request.options)).stringify()
}