Update project structure and configurations

This commit is contained in:
2026-05-23 10:19:55 +02:00
parent ab5dc7e568
commit 62efea0646
1832 changed files with 83568 additions and 51829 deletions

View File

@@ -687,9 +687,12 @@ fn test_three_constraints_and_three_controls() {
///
/// Note: This test uses mock components with synthetic physics. The mock MIMO
/// coefficients (10.0 primary, 2.0 secondary) simulate thermal coupling for
/// Jacobian verification. Real thermodynamic convergence is tested in AC #4.
/// Tests that the MIMO Jacobian has correct structure and bounds are respected
/// during a Newton-like step. This verifies structural correctness (dense block,
/// proper cross-derivatives, bounded step) rather than actual Newton-Raphson
/// convergence, which requires real thermodynamic components (AC #4).
#[test]
fn test_newton_raphson_reduces_residuals_for_mimo() {
fn test_mimo_jacobian_structure_and_bounds() {
let mut sys = build_two_component_cycle();
// Define two constraints
@@ -744,7 +747,13 @@ fn test_newton_raphson_reduces_residuals_for_mimo() {
// Compute initial residuals
let state_len = sys.state_vector_len();
let initial_state = vec![300000.0f64, 400000.0, 300000.0, 400000.0]; // Non-zero P, h values
let mut initial_state = vec![300000.0f64; state_len]; // Non-zero P, h values sized to full state vector
if state_len > 1 {
initial_state[1] = 400000.0;
}
if state_len > 3 {
initial_state[3] = 400000.0;
}
let mut control_values = vec![0.7_f64, 0.5_f64];
// Extract initial constraint values and compute residuals
@@ -828,3 +837,297 @@ fn test_newton_raphson_reduces_residuals_for_mimo() {
"Newton step applied for MIMO control"
);
}
/// Verifies that the 2x2 MIMO Jacobian block is fully dense — every (i,j) entry
/// is non-zero, confirming cross-coupling between all constraint/control pairs.
#[test]
fn test_2x2_jacobian_block_is_fully_dense() {
let mut sys = build_two_component_cycle();
sys.add_constraint(Constraint::new(
ConstraintId::new("capacity"),
ComponentOutput::Capacity {
component_id: "evaporator".to_string(),
},
5000.0,
))
.unwrap();
sys.add_constraint(Constraint::new(
ConstraintId::new("superheat"),
ComponentOutput::Superheat {
component_id: "evaporator".to_string(),
},
5.0,
))
.unwrap();
let bv1 = BoundedVariable::new(
BoundedVariableId::new("compressor_speed"),
50.0,
20.0,
80.0,
)
.unwrap();
let bv2 = BoundedVariable::new(
BoundedVariableId::new("valve_opening"),
0.5,
0.1,
1.0,
)
.unwrap();
sys.add_bounded_variable(bv1).unwrap();
sys.add_bounded_variable(bv2).unwrap();
sys.link_constraint_to_control(
&ConstraintId::new("capacity"),
&BoundedVariableId::new("compressor_speed"),
)
.unwrap();
sys.link_constraint_to_control(
&ConstraintId::new("superheat"),
&BoundedVariableId::new("valve_opening"),
)
.unwrap();
let state_len = sys.state_vector_len();
let state = vec![300000.0f64; state_len];
let control_values = vec![0.7_f64, 0.5_f64];
let row_offset = 0;
let jac = sys.compute_inverse_control_jacobian(&state, row_offset, &control_values);
// For a 2x2 MIMO system, we expect entries for all (i,j) pairs in the control block
let control_offset = sys.state_vector_len();
let mut found = [[false; 2]; 2];
for &(row, col, val) in &jac {
if col >= control_offset {
let i = row - row_offset;
let j = col - control_offset;
if i < 2 && j < 2 && val.abs() > 1e-10 {
found[i][j] = true;
}
}
}
for i in 0..2 {
for j in 0..2 {
assert!(
found[i][j],
"Jacobian entry ({},{}) is missing or zero — expected dense block",
i,
j
);
}
}
}
/// Verifies that the 3x3 MIMO Jacobian block is fully dense for all 9 entries.
#[test]
fn test_3x3_jacobian_block_is_fully_dense() {
let mut sys = build_three_component_system();
sys.add_constraint(Constraint::new(
ConstraintId::new("capacity"),
ComponentOutput::Capacity {
component_id: "evaporator".to_string(),
},
5000.0,
))
.unwrap();
sys.add_constraint(Constraint::new(
ConstraintId::new("superheat"),
ComponentOutput::Superheat {
component_id: "evaporator".to_string(),
},
5.0,
))
.unwrap();
sys.add_constraint(Constraint::new(
ConstraintId::new("pressure"),
ComponentOutput::Pressure {
component_id: "condenser".to_string(),
},
2000000.0,
))
.unwrap();
let bv1 = BoundedVariable::new(
BoundedVariableId::new("compressor_speed"),
50.0,
20.0,
80.0,
)
.unwrap();
let bv2 = BoundedVariable::new(
BoundedVariableId::new("valve_opening"),
0.5,
0.1,
1.0,
)
.unwrap();
let bv3 = BoundedVariable::new(
BoundedVariableId::new("fan_speed"),
0.8,
0.2,
1.0,
)
.unwrap();
sys.add_bounded_variable(bv1).unwrap();
sys.add_bounded_variable(bv2).unwrap();
sys.add_bounded_variable(bv3).unwrap();
sys.link_constraint_to_control(
&ConstraintId::new("capacity"),
&BoundedVariableId::new("compressor_speed"),
)
.unwrap();
sys.link_constraint_to_control(
&ConstraintId::new("superheat"),
&BoundedVariableId::new("valve_opening"),
)
.unwrap();
sys.link_constraint_to_control(
&ConstraintId::new("pressure"),
&BoundedVariableId::new("fan_speed"),
)
.unwrap();
let state_len = sys.state_vector_len();
let state = vec![300000.0f64; state_len];
let control_values = vec![0.7_f64, 0.5_f64, 0.8_f64];
let row_offset = 0;
let jac = sys.compute_inverse_control_jacobian(&state, row_offset, &control_values);
let control_offset = sys.state_vector_len();
let mut found = [[false; 3]; 3];
for &(row, col, val) in &jac {
if col >= control_offset {
let i = row - row_offset;
let j = col - control_offset;
if i < 3 && j < 3 && val.abs() > 1e-10 {
found[i][j] = true;
}
}
}
for i in 0..3 {
for j in 0..3 {
assert!(
found[i][j],
"3x3 Jacobian entry ({},{}) is missing or zero — expected dense block",
i,
j
);
}
}
}
/// Verifies that the MIMO Jacobian cross-derivatives are consistent:
/// perturbing control j affects constraint i in a predictable direction.
#[test]
fn test_mimo_cross_derivatives_have_consistent_signs() {
let mut sys = build_two_component_cycle();
sys.add_constraint(Constraint::new(
ConstraintId::new("capacity"),
ComponentOutput::Capacity {
component_id: "evaporator".to_string(),
},
5000.0,
))
.unwrap();
sys.add_constraint(Constraint::new(
ConstraintId::new("superheat"),
ComponentOutput::Superheat {
component_id: "evaporator".to_string(),
},
5.0,
))
.unwrap();
let bv1 = BoundedVariable::new(
BoundedVariableId::new("compressor_speed"),
50.0,
20.0,
80.0,
)
.unwrap();
let bv2 = BoundedVariable::new(
BoundedVariableId::new("valve_opening"),
0.5,
0.1,
1.0,
)
.unwrap();
sys.add_bounded_variable(bv1).unwrap();
sys.add_bounded_variable(bv2).unwrap();
sys.link_constraint_to_control(
&ConstraintId::new("capacity"),
&BoundedVariableId::new("compressor_speed"),
)
.unwrap();
sys.link_constraint_to_control(
&ConstraintId::new("superheat"),
&BoundedVariableId::new("valve_opening"),
)
.unwrap();
let state_len = sys.state_vector_len();
let state = vec![300000.0f64; state_len];
let control_values = vec![0.7_f64, 0.5_f64];
let jac = sys.compute_inverse_control_jacobian(&state, 0, &control_values);
// Collect all derivatives as (row, col, value)
let control_offset = sys.state_vector_len();
let entries: Vec<(usize, usize, f64)> = jac
.into_iter()
.filter(|&(_, col, _)| col >= control_offset)
.map(|(r, c, v)| (r, c - control_offset, v))
.collect();
// All derivatives should be finite
for &(i, j, v) in &entries {
assert!(
v.is_finite(),
"Jacobian entry (constraint={}, control={}) is not finite: {}",
i,
j,
v
);
}
// Diagonal entries should exist and be non-zero (structural check for mock components)
let diagonal: Vec<f64> = entries
.iter()
.filter(|&&(r, c, _)| r == c)
.map(|&(_, _, v)| v.abs())
.collect();
let off_diagonal: Vec<f64> = entries
.iter()
.filter(|&&(r, c, _)| r != c)
.map(|&(_, _, v)| v.abs())
.collect();
assert!(
!diagonal.is_empty(),
"Should have diagonal Jacobian entries"
);
assert!(
!off_diagonal.is_empty(),
"Should have off-diagonal (cross-coupling) Jacobian entries"
);
// Note: diagonal dominance is a physical property not guaranteed by mock components.
}
/// Helper: builds a three-component system for 3x3 MIMO testing.
fn build_three_component_system() -> System {
let mut sys = System::new();
let comp = sys.add_component(mock(2)); // compressor
let evap = sys.add_component(mock(2)); // evaporator
let cond = sys.add_component(mock(2)); // condenser
sys.add_edge(comp, evap).unwrap();
sys.add_edge(evap, cond).unwrap();
sys.add_edge(cond, comp).unwrap();
sys.register_component_name("compressor", comp);
sys.register_component_name("evaporator", evap);
sys.register_component_name("condenser", cond);
sys.finalize().unwrap();
sys
}