///|
fn json_field(value : Json, key : String) -> Json? {
if value is Object(o) {
o.get(key)
} else {
None
}
}
///|
fn json_number(object : Json, key : String) -> Double raise GasError {
match json_field(object, key) {
Some(value) =>
match (if value is Number(n, ..) { Some(n) } else { None }) {
Some(n) =>
if finite(n) {
n
} else {
raise Invalid("nonfinite checkpoint number")
}
None => raise Invalid("checkpoint field must be a number: " + key)
}
None => raise Invalid("missing checkpoint field: " + key)
}
}
///|
/// Portable checkpoint v1. Full integrated boundary ledger is included so
/// conservation audits survive restart. It contains no solver configuration.
pub fn Simulation::checkpoint(self : Simulation) -> String raise GasError {
ignore(self.diagnostics())
let states = []
for u in self.cells {
states.push(u.to_json())
}
let document = Json::object({
"schema": Json::number(1.0),
"gamma": Json::number(self.gas.gamma),
"left": Json::number(self.mesh.left),
"right": Json::number(self.mesh.right),
"time": Json::number(self.time),
"steps": Json::number(self.steps.to_double()),
"retries": Json::number(self.retries.to_double()),
"initial_totals": self.initial_totals.to_json(),
"boundary_change": self.boundary_change.to_json(),
"cells": Json::array(states),
})
document.stringify()
}
///|
fn decode_conserved(value : Json) -> Conserved raise GasError {
let object = match (if value is Object(o) { Some(o) } else { None }) {
Some(o) => o
None => raise Invalid("conserved state must be an object")
}
if object.length() != 3 {
raise Invalid("conserved state must contain exactly mass, momentum, energy")
}
{
mass: json_number(value, "mass"),
momentum: json_number(value, "momentum"),
energy: json_number(value, "energy"),
}
}
///|
fn nonnegative_count(value : Double) -> Int32 raise GasError {
if value < 0.0 || value > 1000000000.0 || value.to_int().to_double() != value {
raise Invalid("checkpoint counter outside supported integer range")
}
value.to_int()
}
///|
/// Rejects duplicate keys, unknown/missing fields, nonphysical states and huge input.
pub fn restore_checkpoint(text : String) -> Simulation raise GasError {
if text.length() > 256000000 {
raise Invalid("checkpoint exceeds 256 million code units")
}
let value = @json.parse(
text,
max_nesting_depth=16,
reject_duplicate_keys=true,
) catch {
_ => raise Invalid("invalid checkpoint JSON")
}
let object = match (if value is Object(o) { Some(o) } else { None }) {
Some(o) => o
None => raise Invalid("checkpoint must be an object")
}
if object.length() != 10 {
raise Invalid("checkpoint must contain exactly the v1 fields")
}
if json_number(value, "schema") != 1.0 {
raise Invalid("unsupported checkpoint schema")
}
let gas = Gas::new(json_number(value, "gamma"))
let array = match json_field(value, "cells") {
Some(v) =>
match (if v is Array(a) { Some(a) } else { None }) {
Some(a) => a
None => raise Invalid("cells must be an array")
}
None => raise Invalid("missing cells")
}
let mesh = Mesh::new(
json_number(value, "left"),
json_number(value, "right"),
array.length(),
)
let cells = []
for u in array {
cells.push(decode_conserved(u))
}
let time = json_number(value, "time")
if time < 0.0 {
raise Invalid("negative checkpoint time")
}
let initial_totals = match json_field(value, "initial_totals") {
Some(v) => decode_conserved(v)
None => raise Invalid("missing initial totals")
}
let boundary_change = match json_field(value, "boundary_change") {
Some(v) => decode_conserved(v)
None => raise Invalid("missing boundary ledger")
}
let steps = nonnegative_count(json_number(value, "steps"))
let retries = nonnegative_count(json_number(value, "retries"))
gas.validate_cells(mesh, cells)
let result = {
gas,
mesh,
cells,
time,
steps,
retries,
boundary_change,
initial_totals,
}
ignore(result.conservation_residual())
result
}