Stabilize Modelica Fixed/Free calibration and stop flaky Newton embeds.
Some checks failed
CI / check (push) Has been cancelled
Some checks failed
CI / check (push) Has been cancelled
Sort constraint/control assembly for deterministic Jacobians, keep live z_ua on legacy HX, and add generic Fixed/Free UI assist without removing Z factors. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -617,7 +617,19 @@ impl Condenser {
|
||||
FLOOD_LAMBDA_HI,
|
||||
FLOOD_LAMBDA_WIDTH,
|
||||
);
|
||||
jacobian.add_entry(row, act_idx, self.ua() * c.delta_t * c.e_exp * dlam);
|
||||
let ua_nom = self.inner.ua_nominal() * self.live_z_ua(state);
|
||||
jacobian.add_entry(row, act_idx, ua_nom * c.delta_t * c.e_exp * dlam);
|
||||
}
|
||||
// Live z_ua on secondary energy: r = ṁ·Δh − Q ⇒ ∂r/∂z = −∂Q/∂z
|
||||
if let Some(z_ua_idx) = self.inner.calib_indices_ref().z_ua {
|
||||
let flood_scale = if self.flood_ready() {
|
||||
1.0 - self.flooded_level(state)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let d_q_dz =
|
||||
self.inner.ua_nominal() * flood_scale * c.e_exp * c.delta_t;
|
||||
jacobian.add_entry(row, z_ua_idx, -d_q_dz);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@@ -782,14 +794,27 @@ impl Condenser {
|
||||
}
|
||||
}
|
||||
|
||||
/// Effective conductance `UA_eff` [W/K] used by the coupled duty. With an
|
||||
/// active flooding actuator this is `(1 − λ)·UA_nominal`; otherwise the
|
||||
/// nominal `UA`.
|
||||
/// Live calibration factor `z_ua` from Newton state when free, else calib param.
|
||||
fn live_z_ua(&self, state: &StateSlice) -> f64 {
|
||||
self.inner
|
||||
.calib_indices_ref()
|
||||
.z_ua
|
||||
.and_then(|idx| state.get(idx).copied())
|
||||
.unwrap_or_else(|| self.calib().z_ua)
|
||||
.max(0.0)
|
||||
}
|
||||
|
||||
/// Effective conductance `UA_eff` [W/K] used by the coupled duty.
|
||||
///
|
||||
/// `UA_eff = UA_nominal · z_ua · (1 − λ)` when flooding is active, else
|
||||
/// `UA_nominal · z_ua`. Reads live `state[z_ua]` when an embedding frees it
|
||||
/// (otherwise ∂Q/∂z_ua = 0 → singular Jacobian).
|
||||
fn effective_ua(&self, state: &StateSlice) -> f64 {
|
||||
let ua = self.inner.ua_nominal() * self.live_z_ua(state);
|
||||
if self.flood_ready() {
|
||||
self.ua() * (1.0 - self.flooded_level(state))
|
||||
ua * (1.0 - self.flooded_level(state))
|
||||
} else {
|
||||
self.ua()
|
||||
ua
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1592,7 +1617,7 @@ impl Component for Condenser {
|
||||
// ∂r1/∂λ = +UA_nom·(T_cond − T_sec,in)·e · dλ_eff/dλ.
|
||||
if self.flood_ready() {
|
||||
if let Some(lvl_idx) = self.fan_actuator_idx {
|
||||
let ua_nom = self.ua();
|
||||
let ua_nom = self.inner.ua_nominal() * self.live_z_ua(state);
|
||||
let e = if c_sec > 1e-10 {
|
||||
(-ua_eff / c_sec).exp()
|
||||
} else {
|
||||
@@ -1608,6 +1633,24 @@ impl Component for Condenser {
|
||||
}
|
||||
}
|
||||
|
||||
// Live z_ua: UA = UA_nom·z_ua·(1−λ), ε = 1−e^(−UA/C), Q = ε·C·ΔT
|
||||
// ⇒ ∂Q/∂z_ua = UA_nom·(1−λ)·e·ΔT ⇒ ∂r_energy/∂z_ua = −∂Q/∂z_ua
|
||||
if let Some(z_ua_idx) = self.inner.calib_indices_ref().z_ua {
|
||||
let e = if c_sec > 1e-10 {
|
||||
(-ua_eff / c_sec).exp()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let flood_scale = if self.flood_ready() {
|
||||
1.0 - self.flooded_level(state)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let d_q_dz =
|
||||
self.inner.ua_nominal() * flood_scale * e * (t_cond - t_sec_in);
|
||||
jacobian.add_entry(row, z_ua_idx, -d_q_dz);
|
||||
}
|
||||
|
||||
// r2 (emergent) = H_out − h_target(P_in): ∂/∂H_out = 1,
|
||||
// ∂/∂P_in = −dh_target/dP via central finite difference.
|
||||
if self.emergent_pressure {
|
||||
|
||||
@@ -551,6 +551,18 @@ impl Evaporator {
|
||||
);
|
||||
// ∂r/∂P_ref_in = ∂Q/∂P = −g·dT_evap/dP.
|
||||
jacobian.add_entry(row, c.ref_p_in_idx, -c.g * c.dtevap_dp);
|
||||
// Live z_ua: secondary r = ṁ·Δh + Q ⇒ ∂r/∂z = +∂Q/∂z
|
||||
if let Some(z_ua_idx) = self.inner.calib_indices_ref().z_ua {
|
||||
let ua = self.live_ua(Some(state));
|
||||
let c_sec = state[m_in].abs() * c.cp_sec;
|
||||
let e = if c_sec > 1e-10 && ua > 0.0 {
|
||||
(-ua / c_sec).exp()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let d_q_dz = self.inner.ua_nominal() * e * c.delta_t;
|
||||
jacobian.add_entry(row, z_ua_idx, d_q_dz);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
jacobian.add_entry(row, h_out, 1.0);
|
||||
@@ -797,10 +809,22 @@ impl Evaporator {
|
||||
.map_err(|e| ComponentError::CalculationFailed(e.to_string()))
|
||||
}
|
||||
|
||||
/// Live UA = UA_nominal × z_ua (reads `state[calib_indices.z_ua]` when free).
|
||||
fn live_ua(&self, state: Option<&StateSlice>) -> f64 {
|
||||
let z = state
|
||||
.and_then(|st| {
|
||||
self.inner
|
||||
.calib_indices_ref()
|
||||
.z_ua
|
||||
.and_then(|idx| st.get(idx).copied())
|
||||
})
|
||||
.unwrap_or_else(|| self.calib().z_ua);
|
||||
self.inner.ua_nominal() * z.max(0.0)
|
||||
}
|
||||
|
||||
/// Effectiveness for a phase-changing refrigerant (`C_min = C_sec`, `C_r → 0`):
|
||||
/// `ε = 1 − exp(−UA / C_sec)`.
|
||||
fn effectiveness(&self, c_sec: f64) -> f64 {
|
||||
let ua = self.ua();
|
||||
fn effectiveness(&self, c_sec: f64, ua: f64) -> f64 {
|
||||
if c_sec <= 1e-10 || ua <= 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
@@ -815,7 +839,8 @@ impl Evaporator {
|
||||
let c_sec = self.secondary_capacity_rate.unwrap_or(0.0);
|
||||
let t_sec_in = self.secondary_inlet_temp_k.unwrap_or(0.0);
|
||||
let t_evap = self.evap_temperature(p_in_pa)?;
|
||||
let eps = self.effectiveness(c_sec);
|
||||
let ua = self.live_ua(None);
|
||||
let eps = self.effectiveness(c_sec, ua);
|
||||
Ok(eps * c_sec * (t_sec_in - t_evap))
|
||||
}
|
||||
|
||||
@@ -1053,7 +1078,8 @@ impl Component for Evaporator {
|
||||
// Live secondary stream: edge-driven in 4-port mode (Modelica).
|
||||
let (t_sec_in, c_sec) = self.live_secondary_stream(state)?;
|
||||
let t_evap = self.evap_temperature(p_in)?;
|
||||
let eps = self.effectiveness(c_sec);
|
||||
let ua = self.live_ua(Some(state));
|
||||
let eps = self.effectiveness(c_sec, ua);
|
||||
let q = eps * c_sec * (t_sec_in - t_evap);
|
||||
|
||||
// r0: refrigerant pressure drop (tube MSH/Friedel + accel, or
|
||||
@@ -1266,7 +1292,8 @@ impl Component for Evaporator {
|
||||
// ∂r1/∂P_in = −∂Q/∂P_in = ε·C_sec·dT_evap/dP_in (T_sec,in constant),
|
||||
// dT_evap/dP via central finite difference.
|
||||
let (t_sec_in, c_sec) = self.live_secondary_stream(state)?;
|
||||
let eps = self.effectiveness(c_sec);
|
||||
let ua = self.live_ua(Some(state));
|
||||
let eps = self.effectiveness(c_sec, ua);
|
||||
let g = eps * c_sec;
|
||||
let t_evap = self.evap_temperature(p_in)?;
|
||||
let dp = p_in * 1e-4 + 100.0;
|
||||
@@ -1275,6 +1302,17 @@ impl Component for Evaporator {
|
||||
let dt_dp = (t_plus - t_minus) / (2.0 * dp);
|
||||
jacobian.add_entry(row, inlet_p_idx, g * dt_dp);
|
||||
|
||||
// Live z_ua: UA = UA_nom·z_ua, Q = ε·C·ΔT ⇒ ∂r_energy/∂z = −∂Q/∂z
|
||||
if let Some(z_ua_idx) = self.inner.calib_indices_ref().z_ua {
|
||||
let e = if c_sec > 1e-10 {
|
||||
(-ua / c_sec).exp()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let d_q_dz = self.inner.ua_nominal() * e * (t_sec_in - t_evap);
|
||||
jacobian.add_entry(row, z_ua_idx, -d_q_dz);
|
||||
}
|
||||
|
||||
// 4-port cross-derivatives of r1 to the secondary edge state:
|
||||
// ∂r1/∂h_sec,in = −∂Q/∂h_sec,in = −g·dT_sec/dh (exact 1/cp),
|
||||
// ∂r1/∂ṁ_sec = −∂Q/∂ṁ_sec = −g'(C_sec)·cp·(T_sec,in − T_evap).
|
||||
@@ -1282,7 +1320,6 @@ impl Component for Evaporator {
|
||||
let (m_s, p_s, h_s) = self.sec_in_idx.unwrap();
|
||||
let cp_sec = self.sec_cp(state[p_s], state[h_s])?;
|
||||
let dt_dh = 1.0 / cp_sec;
|
||||
let ua = self.ua();
|
||||
let g_prime = if c_sec <= 1e-10 || ua <= 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
@@ -2084,7 +2121,7 @@ mod tests {
|
||||
let c_sec = state[6] * cp_air;
|
||||
let t_air_in = (state[8] - 2_501_000.0 * w) / cp_air + 273.15;
|
||||
let t_evap = evap.evap_temperature(state[1]).unwrap();
|
||||
let eps = evap.effectiveness(c_sec);
|
||||
let eps = evap.effectiveness(c_sec, evap.ua());
|
||||
let q = eps * c_sec * (t_air_in - t_evap);
|
||||
assert!(q > 0.0, "evaporator must absorb heat: q={q}");
|
||||
let expected = state[6] * (state[11] - state[8]) + q;
|
||||
|
||||
Reference in New Issue
Block a user