Files
Entropyk/crates/entropyk/tests/constraints_api.rs
Sepehr ab5dc7e568 chore: remove BMAD framework files and IDE configuration artifacts
Clean up unused BMAD workflow, agent, and command files across all IDE
configurations (.agent, .clinerules, .cursor, .gemini, .github, .kilocode,
.opencode) and internal module files (_bmad/bmb, _bmad/bmm).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-25 15:01:09 +02:00

143 lines
3.9 KiB
Rust

//! Integration tests for SystemBuilder constraints API (Story 13-3).
//!
//! Verifies that constraints and bounded variables can be added via the builder,
//! linked for inverse control, and that the built system passes DoF validation.
use entropyk::{
BoundedVariable, BoundedVariableId, ComponentOutput, Constraint, ConstraintId, SystemBuilder,
};
use entropyk_components::{
Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector,
};
struct MockComponent {
n_eqs: usize,
}
impl Component for MockComponent {
fn compute_residuals(
&self,
_state: &[f64],
_residuals: &mut ResidualVector,
) -> Result<(), ComponentError> {
Ok(())
}
fn jacobian_entries(
&self,
_state: &[f64],
_jacobian: &mut JacobianBuilder,
) -> Result<(), ComponentError> {
Ok(())
}
fn n_equations(&self) -> usize {
self.n_eqs
}
fn get_ports(&self) -> &[ConnectedPort] {
&[]
}
}
#[test]
fn test_builder_constraints_link_and_validate_dof() {
let constraint = Constraint::new(
ConstraintId::new("superheat"),
ComponentOutput::Superheat {
component_id: "evap".to_string(),
},
5.0,
);
let valve = BoundedVariable::new(
BoundedVariableId::new("valve"),
0.5,
0.0,
1.0,
)
.expect("valid bounds");
// Minimal topology: 2 nodes, 1 edge → 2 edge unknowns (P,h). With 1 constraint and 1 control
// we need 3 equations total: 2 component eqs + 1 constraint = 3 = 2 + 1 unknowns.
let system = SystemBuilder::new()
.component("evap", Box::new(MockComponent { n_eqs: 1 }))
.unwrap()
.component("other", Box::new(MockComponent { n_eqs: 1 }))
.unwrap()
.edge("evap", "other")
.unwrap()
.with_constraint(constraint)
.unwrap()
.with_bounded_variable(valve)
.unwrap()
.link_constraint_to_control(
&ConstraintId::new("superheat"),
&BoundedVariableId::new("valve"),
)
.unwrap()
.build()
.expect("build should succeed");
// DoF validation should pass: 1 constraint, 1 control variable (balanced).
let dof_result = system.validate_inverse_control_dof();
assert!(
dof_result.is_ok(),
"validate_inverse_control_dof should pass when constraint and control are linked: {:?}",
dof_result.err()
);
}
#[test]
fn test_builder_dof_imbalance_two_constraints_one_control() {
let c1 = Constraint::new(
ConstraintId::new("superheat"),
ComponentOutput::Superheat {
component_id: "evap".to_string(),
},
5.0,
);
let c2 = Constraint::new(
ConstraintId::new("subcooling"),
ComponentOutput::Superheat {
component_id: "evap".to_string(),
},
3.0,
);
let valve = BoundedVariable::new(
BoundedVariableId::new("valve"),
0.5,
0.0,
1.0,
)
.expect("valid bounds");
let system = SystemBuilder::new()
.component("evap", Box::new(MockComponent { n_eqs: 1 }))
.unwrap()
.component("other", Box::new(MockComponent { n_eqs: 1 }))
.unwrap()
.edge("evap", "other")
.unwrap()
.with_constraint(c1)
.unwrap()
.with_constraint(c2)
.unwrap()
.with_bounded_variable(valve)
.unwrap()
.link_constraint_to_control(
&ConstraintId::new("superheat"),
&BoundedVariableId::new("valve"),
)
.unwrap()
.build()
.expect("build should succeed");
// DoF validation should fail: 2 constraints but only 1 control (unbalanced).
let dof_result = system.validate_inverse_control_dof();
assert!(
dof_result.is_err(),
"validate_inverse_control_dof should fail with 2 constraints and 1 control, got: {:?}",
dof_result
);
}