///|
pub(all) struct ExperimentConfig {
name : String
grid : Grid1D
steps : Int
dt : Double
} derive(Debug, ToJson)
///|
pub fn ExperimentConfig::new(
name : String,
grid : Grid1D,
steps : Int,
dt : Double,
) -> ExperimentConfig {
{ name, grid, steps: steps.max(0), dt }
}
///|
pub(all) struct ExperimentSample {
step : Int
time : Double
particles : Int
total_charge : Double
kinetic_energy : Double
field_energy : Double
total_energy : Double
charge_error : Double
} derive(Debug, ToJson)
///|
pub(all) struct ExperimentReport {
name : String
samples : Array[ExperimentSample]
final_state : PicState
} derive(Debug, ToJson)
///|
fn experiment_sample(
step : Int,
state : PicState,
initial_charge : Double,
) -> ExperimentSample {
let diagnostics = PicDiagnostics::from_state(state)
{
step,
time: diagnostics.time,
particles: diagnostics.particles,
total_charge: diagnostics.total_charge,
kinetic_energy: diagnostics.kinetic_energy,
field_energy: diagnostics.field_energy,
total_energy: diagnostics.total_energy(),
charge_error: (diagnostics.total_charge - initial_charge).abs(),
}
}
///|
pub fn run_experiment(config : ExperimentConfig) -> ExperimentReport {
let particles = two_stream_particles(
config.grid.cells,
config.grid.length,
1.0e5,
)
let initial = PicState::new(config.grid, particles)
let initial_charge = total_particle_charge(initial.particles)
let samples : Array[ExperimentSample] = []
let mut state = initial
samples.push(experiment_sample(0, state, initial_charge))
for step in 0.. String {
let output = StringBuilder()
output.write_string(
"step,time,particles,total_charge,kinetic_energy,field_energy,total_energy,charge_error\n",
)
for sample in report.samples {
output.write_string(
"\{sample.step},\{sample.time},\{sample.particles},\{sample.total_charge},\{sample.kinetic_energy},\{sample.field_energy},\{sample.total_energy},\{sample.charge_error}\n",
)
}
output.to_string()
}
///|
pub fn experiment_summary(report : ExperimentReport) -> String {
let output = StringBuilder()
output.write_string("experiment=\{report.name}\n")
output.write_string("samples=\{report.samples.length()}\n")
output.write_string("final_time=\{report.final_state.time}\n")
output.write_string(
"final_particles=\{report.final_state.particles.length()}\n",
)
output.to_string()
}
///|
pub fn experiment_charge_error(report : ExperimentReport) -> Double {
if report.samples.length() == 0 {
0.0
} else {
report.samples[report.samples.length() - 1].charge_error
}
}
///|
pub fn experiment_energy_drift(report : ExperimentReport) -> Double {
if report.samples.length() < 2 {
0.0
} else {
let first = report.samples[0].total_energy
let last = report.samples[report.samples.length() - 1].total_energy
safe_ratio((last - first).abs(), first.abs().max(1.0e-30), 0.0)
}
}
///|
pub fn experiment_times(report : ExperimentReport) -> Array[Double] {
report.samples.map(fn(sample) { sample.time })
}
///|
pub fn experiment_particle_counts(report : ExperimentReport) -> Array[Int] {
report.samples.map(fn(sample) { sample.particles })
}
///|
pub fn experiment_energy_series(report : ExperimentReport) -> Array[Double] {
report.samples.map(fn(sample) { sample.total_energy })
}
///|
pub fn experiment_passes_charge_tolerance(
report : ExperimentReport,
tolerance : Double,
) -> Bool {
experiment_charge_error(report) <= tolerance
}
///|
pub fn experiment_passes_energy_tolerance(
report : ExperimentReport,
tolerance : Double,
) -> Bool {
experiment_energy_drift(report) <= tolerance
}