Snapshot WIP: solver HP epic progress, BPHX/HX physics, BMAD skill refresh.
Some checks failed
CI / check (push) Has been cancelled
Some checks failed
CI / check (push) Has been cancelled
Capture uncommitted solver robustness work (regularization, domain errors, linear solver lifecycle, tube DP/MSH), web workbench updates, and synced BMAD skills across IDE agent folders before starting BPHX pressure-drop. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
32
crates/solver/benches/batch_solve.rs
Normal file
32
crates/solver/benches/batch_solve.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! Phase-0 benchmark: sequential batch-solve scaling baseline.
|
||||
//!
|
||||
//! Solves N independent copies of the same reference cycle sequentially. This is
|
||||
//! the Phase-0 *sequential* baseline; any parallel batch path belongs to Epic 4
|
||||
//! and is intentionally out of scope here.
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId};
|
||||
|
||||
mod common;
|
||||
|
||||
fn bench_batch_sequential(c: &mut criterion::Criterion) {
|
||||
let mut group = c.benchmark_group("batch_solve_sequential");
|
||||
for n in [1, 2, 4] {
|
||||
group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, &n| {
|
||||
b.iter(|| {
|
||||
for _ in 0..n {
|
||||
let mut system = common::build_reference_cycle_a();
|
||||
common::solve_reference_system(&mut system);
|
||||
black_box(());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
name = benches;
|
||||
config = common::end_to_end_criterion();
|
||||
targets = bench_batch_sequential
|
||||
}
|
||||
criterion_main!(benches);
|
||||
458
crates/solver/benches/common.rs
Normal file
458
crates/solver/benches/common.rs
Normal file
@@ -0,0 +1,458 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
//! Shared helpers for the `entropyk-solver` Phase-0 Criterion benchmarks.
|
||||
//!
|
||||
//! Provides:
|
||||
//! - A deterministic mock refrigeration cycle for micro-benchmarks (LU solve,
|
||||
//! residual/Jacobian assembly).
|
||||
//! - Three reference cycles built directly from the public component/solver APIs
|
||||
//! so the end-to-end benchmarks exercise real CoolProp solves without paying
|
||||
//! the cost of spawning a CLI process.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use criterion::Criterion;
|
||||
use entropyk_components::port::{Connected, FluidId, Port};
|
||||
use entropyk_components::{
|
||||
Component, ComponentError, Condenser, ConnectedPort, Evaporator, IsenthalpicExpansionValve,
|
||||
IsentropicCompressor, JacobianBuilder, ResidualVector, StateSlice,
|
||||
};
|
||||
use entropyk_core::{Enthalpy, MassFlow, Pressure};
|
||||
use entropyk_fluids::{CoolPropBackend, FluidBackend};
|
||||
use entropyk_solver::system::{System, DEFAULT_MASS_FLOW_SEED_KG_S};
|
||||
use entropyk_solver::{FallbackConfig, FallbackSolver, JacobianMatrix, NewtonConfig, Solver};
|
||||
|
||||
type CP = Port<Connected>;
|
||||
|
||||
// ── Mock components (copied from `refrigeration_cycle_integration.rs`) ───────
|
||||
|
||||
struct MockCompressor {
|
||||
port_suc: CP,
|
||||
port_disc: CP,
|
||||
}
|
||||
|
||||
impl Component for MockCompressor {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
_s: &StateSlice,
|
||||
r: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
r[0] = self.port_disc.pressure().to_pascals()
|
||||
- (self.port_suc.pressure().to_pascals() + 1_000_000.0);
|
||||
r[1] = self.port_disc.enthalpy().to_joules_per_kg()
|
||||
- (self.port_suc.enthalpy().to_joules_per_kg() + 75_000.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_s: &StateSlice,
|
||||
_j: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
fn get_ports(&self) -> &[ConnectedPort] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
|
||||
Ok(vec![
|
||||
MassFlow::from_kg_per_s(0.05),
|
||||
MassFlow::from_kg_per_s(-0.05),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
struct MockCondenser {
|
||||
port_in: CP,
|
||||
port_out: CP,
|
||||
}
|
||||
|
||||
impl Component for MockCondenser {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
_s: &StateSlice,
|
||||
r: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
r[0] = self.port_out.pressure().to_pascals() - self.port_in.pressure().to_pascals();
|
||||
r[1] = self.port_out.enthalpy().to_joules_per_kg()
|
||||
- (self.port_in.enthalpy().to_joules_per_kg() - 225_000.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_s: &StateSlice,
|
||||
_j: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
fn get_ports(&self) -> &[ConnectedPort] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
|
||||
Ok(vec![
|
||||
MassFlow::from_kg_per_s(0.05),
|
||||
MassFlow::from_kg_per_s(-0.05),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
struct MockValve {
|
||||
port_in: CP,
|
||||
port_out: CP,
|
||||
}
|
||||
|
||||
impl Component for MockValve {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
_s: &StateSlice,
|
||||
r: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
r[0] = self.port_out.pressure().to_pascals()
|
||||
- (self.port_in.pressure().to_pascals() - 1_000_000.0);
|
||||
r[1] = self.port_out.enthalpy().to_joules_per_kg()
|
||||
- self.port_in.enthalpy().to_joules_per_kg();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_s: &StateSlice,
|
||||
_j: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
fn get_ports(&self) -> &[ConnectedPort] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
|
||||
Ok(vec![
|
||||
MassFlow::from_kg_per_s(0.05),
|
||||
MassFlow::from_kg_per_s(-0.05),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
struct MockEvaporator {
|
||||
port_in: CP,
|
||||
port_out: CP,
|
||||
}
|
||||
|
||||
impl Component for MockEvaporator {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
_s: &StateSlice,
|
||||
r: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
r[0] = self.port_out.pressure().to_pascals() - self.port_in.pressure().to_pascals();
|
||||
r[1] = self.port_out.enthalpy().to_joules_per_kg()
|
||||
- (self.port_in.enthalpy().to_joules_per_kg() + 150_000.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_s: &StateSlice,
|
||||
_j: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
fn get_ports(&self) -> &[ConnectedPort] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
|
||||
Ok(vec![
|
||||
MassFlow::from_kg_per_s(0.05),
|
||||
MassFlow::from_kg_per_s(-0.05),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fn port(p_pa: f64, h_j_kg: f64) -> CP {
|
||||
let (connected, _) = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_pascals(p_pa),
|
||||
Enthalpy::from_joules_per_kg(h_j_kg),
|
||||
)
|
||||
.connect(Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_pascals(p_pa),
|
||||
Enthalpy::from_joules_per_kg(h_j_kg),
|
||||
))
|
||||
.unwrap();
|
||||
connected
|
||||
}
|
||||
|
||||
/// Builds the deterministic 4-component mock cycle used for Jacobian/assembly
|
||||
/// micro-benchmarks. The system is analytically closed and converges in one
|
||||
/// iteration from the exact initial state.
|
||||
pub fn build_mock_system() -> (System, Vec<f64>) {
|
||||
let p_lp = 350_000.0_f64;
|
||||
let p_hp = 1_350_000.0_f64;
|
||||
|
||||
let comp = Box::new(MockCompressor {
|
||||
port_suc: port(p_lp, 410_000.0),
|
||||
port_disc: port(p_hp, 485_000.0),
|
||||
});
|
||||
let cond = Box::new(MockCondenser {
|
||||
port_in: port(p_hp, 485_000.0),
|
||||
port_out: port(p_hp, 260_000.0),
|
||||
});
|
||||
let valv = Box::new(MockValve {
|
||||
port_in: port(p_hp, 260_000.0),
|
||||
port_out: port(p_lp, 260_000.0),
|
||||
});
|
||||
let evap = Box::new(MockEvaporator {
|
||||
port_in: port(p_lp, 260_000.0),
|
||||
port_out: port(p_lp, 410_000.0),
|
||||
});
|
||||
|
||||
let mut system = System::new();
|
||||
let n_comp = system.add_component(comp);
|
||||
let n_cond = system.add_component(cond);
|
||||
let n_valv = system.add_component(valv);
|
||||
let n_evap = system.add_component(evap);
|
||||
|
||||
system.add_edge(n_comp, n_cond).unwrap();
|
||||
system.add_edge(n_cond, n_valv).unwrap();
|
||||
system.add_edge(n_valv, n_evap).unwrap();
|
||||
system.add_edge(n_evap, n_comp).unwrap();
|
||||
|
||||
system.finalize().unwrap();
|
||||
|
||||
let m = DEFAULT_MASS_FLOW_SEED_KG_S;
|
||||
let initial_state = vec![
|
||||
m, p_hp, 485_000.0, p_hp, 260_000.0, p_lp, 260_000.0, p_lp, 410_000.0,
|
||||
];
|
||||
|
||||
(system, initial_state)
|
||||
}
|
||||
|
||||
/// Assembles a dense `JacobianMatrix` from the mock system at the given state.
|
||||
pub fn assemble_jacobian(system: &System, state: &[f64]) -> JacobianMatrix {
|
||||
let mut builder = JacobianBuilder::new();
|
||||
system.assemble_jacobian(state, &mut builder).unwrap();
|
||||
|
||||
let n = system.full_state_vector_len();
|
||||
let mut entries = Vec::new();
|
||||
for (r, c, v) in builder.entries() {
|
||||
entries.push((*r, *c, *v));
|
||||
}
|
||||
|
||||
JacobianMatrix::from_builder(&entries, n, n)
|
||||
}
|
||||
|
||||
// ── Reference-cycle construction (no CLI spawn) ──────────────────────────────
|
||||
|
||||
fn make_connected_port(fluid: &str, p_pa: f64, h_j_kg: f64) -> ConnectedPort {
|
||||
let a = Port::new(
|
||||
FluidId::new(fluid),
|
||||
Pressure::from_pascals(p_pa),
|
||||
Enthalpy::from_joules_per_kg(h_j_kg),
|
||||
);
|
||||
let b = Port::new(
|
||||
FluidId::new(fluid),
|
||||
Pressure::from_pascals(p_pa),
|
||||
Enthalpy::from_joules_per_kg(h_j_kg),
|
||||
);
|
||||
a.connect(b).unwrap().0
|
||||
}
|
||||
|
||||
fn coolprop_backend() -> Arc<dyn FluidBackend> {
|
||||
Arc::new(CoolPropBackend::new())
|
||||
}
|
||||
|
||||
/// Builds a real emergent-pressure R134a cycle. Three parameter sets give three
|
||||
/// distinct reference cycles that all converge reliably without secondary-side
|
||||
/// boundary components, keeping the benchmark focused on the Newton solver.
|
||||
fn build_emergent_cycle(
|
||||
cond_sec_temp_k: f64,
|
||||
evap_sec_temp_k: f64,
|
||||
ua_cond: f64,
|
||||
ua_evap: f64,
|
||||
) -> System {
|
||||
use entropyk_components::isentropic_compressor::VolumetricEfficiency;
|
||||
|
||||
let backend = coolprop_backend();
|
||||
let fluid = "R134a";
|
||||
|
||||
let comp = Box::new(
|
||||
IsentropicCompressor::new(0.70, 318.15, 278.15, 5.0)
|
||||
.with_refrigerant(fluid)
|
||||
.with_fluid_backend(backend.clone())
|
||||
.with_displacement(6.5e-5, 50.0, VolumetricEfficiency::Constant(0.92)),
|
||||
);
|
||||
|
||||
let cond = Box::new(
|
||||
Condenser::new(ua_cond)
|
||||
.with_refrigerant(fluid)
|
||||
.with_fluid_backend(backend.clone())
|
||||
.with_secondary_stream(cond_sec_temp_k, 1500.0)
|
||||
.with_emergent_pressure(5.0),
|
||||
);
|
||||
|
||||
let exv = Box::new(
|
||||
IsenthalpicExpansionValve::new(278.15)
|
||||
.with_refrigerant(fluid)
|
||||
.with_fluid_backend(backend.clone())
|
||||
.with_emergent_pressure(),
|
||||
);
|
||||
|
||||
let evap = Box::new(
|
||||
Evaporator::new(ua_evap)
|
||||
.with_refrigerant(fluid)
|
||||
.with_fluid_backend(backend.clone())
|
||||
.with_secondary_stream(evap_sec_temp_k, 2000.0)
|
||||
.with_emergent_pressure(),
|
||||
);
|
||||
|
||||
let mut system = System::new();
|
||||
let n_comp = system.add_component(comp);
|
||||
let n_cond = system.add_component(cond);
|
||||
let n_exv = system.add_component(exv);
|
||||
let n_evap = system.add_component(evap);
|
||||
|
||||
system.add_edge(n_comp, n_cond).unwrap();
|
||||
system.add_edge(n_cond, n_exv).unwrap();
|
||||
system.add_edge(n_exv, n_evap).unwrap();
|
||||
system.add_edge(n_evap, n_comp).unwrap();
|
||||
|
||||
system.finalize().unwrap();
|
||||
system
|
||||
}
|
||||
|
||||
pub fn build_reference_cycle_a() -> System {
|
||||
build_emergent_cycle(303.15, 285.15, 766.0, 1468.0)
|
||||
}
|
||||
|
||||
pub fn build_reference_cycle_b() -> System {
|
||||
build_emergent_cycle(313.15, 283.15, 900.0, 1600.0)
|
||||
}
|
||||
|
||||
pub fn build_reference_cycle_c() -> System {
|
||||
build_emergent_cycle(308.15, 291.15, 850.0, 1800.0)
|
||||
}
|
||||
|
||||
/// Solves a reference cycle with the default fallback solver and a good initial seed.
|
||||
pub fn solve_reference_system(system: &mut System) {
|
||||
let initial_state = vec![
|
||||
0.05, // shared mass flow [kg/s]
|
||||
11.6e5, // comp->cond pressure [Pa]
|
||||
445e3, // comp->cond enthalpy [J/kg]
|
||||
11.6e5, // cond->exv pressure [Pa]
|
||||
262e3, // cond->exv enthalpy [J/kg]
|
||||
3.5e5, // exv->evap pressure [Pa]
|
||||
262e3, // exv->evap enthalpy [J/kg]
|
||||
3.5e5, // evap->comp pressure [Pa]
|
||||
405e3, // evap->comp enthalpy [J/kg]
|
||||
];
|
||||
let newton = NewtonConfig {
|
||||
max_iterations: 200,
|
||||
tolerance: 1e-6,
|
||||
initial_state: Some(initial_state),
|
||||
..NewtonConfig::default()
|
||||
};
|
||||
let mut solver = FallbackSolver::new(FallbackConfig::default()).with_newton_config(newton);
|
||||
solver.solve(system).expect("reference cycle must converge");
|
||||
}
|
||||
|
||||
// ── Criterion configuration ─────────────────────────────────────────────────
|
||||
|
||||
/// Returns a Criterion configuration suitable for expensive end-to-end solves.
|
||||
///
|
||||
/// Uses a small sample count and a bounded measurement window so that the
|
||||
/// reference-cycle benchmarks complete in a reasonable time while still
|
||||
/// producing stable Phase-0 baseline numbers.
|
||||
pub fn end_to_end_criterion() -> Criterion {
|
||||
Criterion::default()
|
||||
.sample_size(10)
|
||||
.measurement_time(Duration::from_secs(3))
|
||||
.warm_up_time(Duration::from_secs(1))
|
||||
}
|
||||
|
||||
// ── CLI helpers (kept for optional manual verification) ──────────────────────
|
||||
|
||||
/// Returns the path to the release `entropyk-cli` binary relative to the
|
||||
/// workspace root. Benchmarks are executed from `crates/solver`, so the
|
||||
/// workspace root is two directories up.
|
||||
pub fn cli_binary_path() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../..")
|
||||
.join("target/release/entropyk-cli.exe")
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/release/entropyk-cli")
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolves a reference-cycle config path relative to the workspace root.
|
||||
pub fn reference_config_path(name: &str) -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../..")
|
||||
.join("crates/cli/examples")
|
||||
.join(name)
|
||||
}
|
||||
|
||||
/// Runs one reference cycle through the release CLI and asserts convergence.
|
||||
pub fn run_cli_cycle(config_name: &str) {
|
||||
let binary = cli_binary_path();
|
||||
let config = reference_config_path(config_name);
|
||||
|
||||
assert!(
|
||||
binary.exists(),
|
||||
"release CLI binary not found at {}. Build it with: cargo build --release --bin entropyk-cli",
|
||||
binary.display()
|
||||
);
|
||||
assert!(config.exists(), "config not found: {}", config.display());
|
||||
|
||||
let output = Command::new(&binary)
|
||||
.arg("run")
|
||||
.arg("--config")
|
||||
.arg(&config)
|
||||
.output()
|
||||
.expect("failed to execute CLI binary");
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"CLI solve failed for {}\nstdout:\n{}\nstderr:\n{}",
|
||||
config_name,
|
||||
stdout,
|
||||
stderr
|
||||
);
|
||||
assert!(
|
||||
stdout.contains("Status: CONVERGED"),
|
||||
"CLI did not report CONVERGED for {}\nstdout:\n{}",
|
||||
config_name,
|
||||
stdout
|
||||
);
|
||||
}
|
||||
45
crates/solver/benches/full_solve.rs
Normal file
45
crates/solver/benches/full_solve.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
//! Phase-0 benchmark: end-to-end solve on three reference cycles.
|
||||
//!
|
||||
//! Each benchmark builds a real CoolProp-backed emergent-pressure R134a cycle
|
||||
//! and solves it with the fallback Newton solver.
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
mod common;
|
||||
|
||||
fn bench_reference_cycle_a(c: &mut Criterion) {
|
||||
c.bench_function("full_solve_reference_cycle_a", |b| {
|
||||
b.iter(|| {
|
||||
let mut system = common::build_reference_cycle_a();
|
||||
common::solve_reference_system(&mut system);
|
||||
black_box(());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_reference_cycle_b(c: &mut Criterion) {
|
||||
c.bench_function("full_solve_reference_cycle_b", |b| {
|
||||
b.iter(|| {
|
||||
let mut system = common::build_reference_cycle_b();
|
||||
common::solve_reference_system(&mut system);
|
||||
black_box(());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_reference_cycle_c(c: &mut Criterion) {
|
||||
c.bench_function("full_solve_reference_cycle_c", |b| {
|
||||
b.iter(|| {
|
||||
let mut system = common::build_reference_cycle_c();
|
||||
common::solve_reference_system(&mut system);
|
||||
black_box(());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
name = benches;
|
||||
config = common::end_to_end_criterion();
|
||||
targets = bench_reference_cycle_a, bench_reference_cycle_b, bench_reference_cycle_c
|
||||
}
|
||||
criterion_main!(benches);
|
||||
30
crates/solver/benches/lu_solve.rs
Normal file
30
crates/solver/benches/lu_solve.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
//! Phase-0 benchmark: dense LU solve on an assembled Jacobian.
|
||||
//!
|
||||
//! The Jacobian is taken from the deterministic mock refrigeration cycle so the
|
||||
//! benchmark is independent of the heavy CoolProp backend and measures only the
|
||||
//! linear algebra path used by the Newton solver (Ruiz equilibration + LU).
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
mod common;
|
||||
|
||||
fn bench_lu_solve(c: &mut Criterion) {
|
||||
let (system, state) = common::build_mock_system();
|
||||
let jacobian = common::assemble_jacobian(&system, &state);
|
||||
|
||||
// Residual vector at the analytical solution is non-zero for the mock
|
||||
// components because they read from fixed ports, not from the state slice.
|
||||
// We therefore benchmark the LU solve with a representative RHS.
|
||||
let residuals: Vec<f64> = (0..jacobian.nrows())
|
||||
.map(|i| state[i].sin() * 1e-3)
|
||||
.collect();
|
||||
|
||||
c.bench_function("lu_solve_mock_9x9", |b| {
|
||||
b.iter(|| {
|
||||
black_box(jacobian.solve(&residuals));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_lu_solve);
|
||||
criterion_main!(benches);
|
||||
40
crates/solver/benches/residual_jacobian_assembly.rs
Normal file
40
crates/solver/benches/residual_jacobian_assembly.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
//! Phase-0 benchmark: residual + Jacobian assembly pass over the full system.
|
||||
//!
|
||||
//! Measures the cost of evaluating all component residuals and analytical
|
||||
//! Jacobian entries once — the per-Newton-iteration work that dominates solver
|
||||
//! runtime for real cycles.
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use entropyk_components::JacobianBuilder;
|
||||
use entropyk_components::ResidualVector;
|
||||
|
||||
mod common;
|
||||
|
||||
fn bench_residual_assembly(c: &mut Criterion) {
|
||||
let (system, state) = common::build_mock_system();
|
||||
let mut residuals = ResidualVector::with_capacity(system.full_state_vector_len());
|
||||
|
||||
c.bench_function("residual_assembly_mock", |b| {
|
||||
b.iter(|| {
|
||||
residuals.clear();
|
||||
residuals.resize(system.full_state_vector_len(), 0.0);
|
||||
let _: () = system.compute_residuals(&state, &mut residuals).unwrap();
|
||||
black_box(());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_jacobian_assembly(c: &mut Criterion) {
|
||||
let (system, state) = common::build_mock_system();
|
||||
|
||||
c.bench_function("jacobian_assembly_mock", |b| {
|
||||
b.iter(|| {
|
||||
let mut builder = JacobianBuilder::new();
|
||||
let _: () = system.assemble_jacobian(&state, &mut builder).unwrap();
|
||||
black_box(());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_residual_assembly, bench_jacobian_assembly);
|
||||
criterion_main!(benches);
|
||||
Reference in New Issue
Block a user