///|
pub(all) struct ReproducibilityManifest {
name : String
seed : Int
cells : Int
particles : Int
steps : Int
dt : Double
shape : String
boundary : String
toolchain : String
} derive(Debug, ToJson)
///|
pub(all) struct ResultFingerprint {
sample_count : Int
work_units : Int
charge_error : Double
energy_drift : Double
checksum : Int
} derive(Debug, ToJson)
///|
pub fn manifest_from_scenario(
scenario : SimulationScenario,
toolchain : String,
) -> ReproducibilityManifest {
{
name: scenario.name,
seed: scenario.seed,
cells: scenario.grid.cells,
particles: scenario.particles,
steps: scenario.steps,
dt: scenario.dt,
shape: shape_name(scenario.shape),
boundary: boundary_name(scenario.boundary),
toolchain,
}
}
///|
pub fn boundary_name(boundary : Boundary1D) -> String {
match boundary {
Periodic => "periodic"
Reflecting => "reflecting"
Absorbing => "absorbing"
}
}
///|
pub fn manifest_seed(manifest : ReproducibilityManifest) -> Int {
deterministic_seed(
manifest.name,
manifest.seed + manifest.cells + manifest.particles + manifest.steps,
)
}
///|
pub fn manifest_work_units(manifest : ReproducibilityManifest) -> Int {
manifest.cells * manifest.particles * manifest.steps
}
///|
pub fn manifest_to_csv(manifest : ReproducibilityManifest) -> String {
let output = StringBuilder()
output.write_string("field,value\n")
output.write_string("name,\{manifest.name}\n")
output.write_string("seed,\{manifest.seed}\n")
output.write_string("cells,\{manifest.cells}\n")
output.write_string("particles,\{manifest.particles}\n")
output.write_string("steps,\{manifest.steps}\n")
output.write_string("dt,\{manifest.dt}\n")
output.write_string("shape,\{manifest.shape}\n")
output.write_string("boundary,\{manifest.boundary}\n")
output.write_string("toolchain,\{manifest.toolchain}\n")
output.to_string()
}
///|
pub fn fingerprint_integer(value : Double) -> Int {
let scaled = (value.abs() * 1000000.0).to_int()
scaled % 2147483647
}
///|
pub fn fingerprint_values(values : ArrayView[Double]) -> Int {
let mut hash = 17
for value in values {
hash = (hash * 31 + fingerprint_integer(value)) % 2147483647
}
hash.abs()
}
///|
pub fn fingerprint_diagnostics(values : ArrayView[PicDiagnostics]) -> Int {
let energies = values.map(fn(value) { value.total_energy() })
let charges = values.map(fn(value) { value.total_charge })
(fingerprint_values(energies) * 31 + fingerprint_values(charges)) % 2147483647
}
///|
pub fn fingerprint_from_scenario(
scenario : SimulationScenario,
) -> ResultFingerprint {
let diagnostics = scenario_trace(scenario)
let energies = diagnostics.map(fn(value) { value.total_energy() })
let charges = diagnostics.map(fn(value) { value.total_charge })
{
sample_count: diagnostics.length(),
work_units: scenario_work_units(scenario),
charge_error: if charges.length() < 2 {
0.0
} else {
relative_change(charges[0], charges[charges.length() - 1])
},
energy_drift: if energies.length() < 2 {
0.0
} else {
relative_change(energies[0], energies[energies.length() - 1])
},
checksum: fingerprint_diagnostics(diagnostics),
}
}
///|
pub fn fingerprint_to_csv(fingerprint : ResultFingerprint) -> String {
"sample_count,work_units,charge_error,energy_drift,checksum\n\{fingerprint.sample_count},\{fingerprint.work_units},\{fingerprint.charge_error},\{fingerprint.energy_drift},\{fingerprint.checksum}\n"
}
///|
pub fn fingerprints_equal(
first : ResultFingerprint,
second : ResultFingerprint,
) -> Bool {
first.sample_count == second.sample_count &&
first.work_units == second.work_units &&
first.checksum == second.checksum &&
(first.charge_error - second.charge_error).abs() < 1.0e-12 &&
(first.energy_drift - second.energy_drift).abs() < 1.0e-12
}
///|
pub fn manifest_fingerprint(
scenario : SimulationScenario,
toolchain : String,
) -> String {
let manifest = manifest_from_scenario(scenario, toolchain)
let fingerprint = fingerprint_from_scenario(scenario)
let output = StringBuilder()
output.write_string(manifest_to_csv(manifest))
output.write_string("\n")
output.write_string(fingerprint_to_csv(fingerprint))
output.to_string()
}
///|
pub fn reproducible_run(scenario : SimulationScenario) -> Bool {
fingerprints_equal(
fingerprint_from_scenario(scenario),
fingerprint_from_scenario(scenario),
)
}
///|
pub fn compare_scenarios(
first : SimulationScenario,
second : SimulationScenario,
) -> Bool {
first.grid.cells == second.grid.cells &&
first.particles == second.particles &&
first.steps == second.steps &&
(first.dt - second.dt).abs() < 1.0e-15 &&
first.shape == second.shape &&
first.boundary == second.boundary
}
///|
pub fn scenario_manifest_summary(scenario : SimulationScenario) -> String {
manifest_to_csv(manifest_from_scenario(scenario, "stable"))
}
///|
pub fn benchmark_fingerprint_suite(
suite : BenchmarkSuite,
) -> Array[ResultFingerprint] {
suite.records.map(fn(record) {
{
sample_count: record.steps + 1,
work_units: record.work_units,
charge_error: record.charge_error,
energy_drift: record.energy_drift,
checksum: fingerprint_integer(record.charge_error) +
fingerprint_integer(record.energy_drift),
}
})
}
///|
pub fn fingerprint_suite_to_csv(suite : BenchmarkSuite) -> String {
let output = StringBuilder()
output.write_string(
"benchmark,sample_count,work_units,charge_error,energy_drift,checksum\n",
)
let fingerprints = benchmark_fingerprint_suite(suite)
for i in 0.. Int {
scenario.steps + 1
}
///|
pub fn expected_work_units(scenario : SimulationScenario) -> Int {
scenario.grid.cells * scenario.particles * scenario.steps
}
///|
pub fn expected_output_schema() -> String {
"name,cells,particles,steps,work_units,final_particles,charge_error,energy_drift"
}
///|
pub fn result_is_well_formed(
scenario : SimulationScenario,
fingerprint : ResultFingerprint,
) -> Bool {
fingerprint.sample_count == expected_sample_count(scenario) &&
fingerprint.work_units == expected_work_units(scenario) &&
fingerprint.checksum >= 0
}
///|
pub fn reproducibility_report(
scenario : SimulationScenario,
toolchain : String,
) -> String {
let fingerprint = fingerprint_from_scenario(scenario)
let output = StringBuilder()
output.write_string("reproducible=\{reproducible_run(scenario)}\n")
output.write_string(
"well_formed=\{result_is_well_formed(scenario, fingerprint)}\n",
)
output.write_string("\{manifest_fingerprint(scenario, toolchain)}")
output.to_string()
}