99 lines
3.9 KiB
Python

import re
with open("src/heat_exchanger/moving_boundary_hx.rs", "r") as f:
content = f.read()
content = content.replace("use std::cell::Cell;", "use std::cell::{Cell, RefCell};")
content = content.replace("cache: Cell<MovingBoundaryCache>,", "cache: RefCell<MovingBoundaryCache>,")
content = content.replace("cache: Cell::new(MovingBoundaryCache::default()),", "cache: RefCell::new(MovingBoundaryCache::default()),")
# Patch compute_residuals
old_compute_residuals = """ fn compute_residuals(
&self,
state: &StateSlice,
residuals: &mut ResidualVector,
) -> Result<(), ComponentError> {
// For a moving boundary HX, we need to:
// 1. Identify zones based on current inlet/outlet enthalpies
// 2. Calculate UA for each zone
// 3. Update nominal UA in the inner model
// 4. Compute residuals using the standard model (e.g. EpsNtu)
// HACK: For now, we use placeholder enthalpies to test the identification logic.
// Proper port extraction will be added in Story 4.1.
let h_in = 400_000.0;
let h_out = 200_000.0;
let p = 500_000.0;
let m_refrig = 0.1; // Placeholder mass flow
let t_sec_in = 300.0;
let t_sec_out = 320.0;
let mut cache = self.cache.take();
let use_cache = cache.is_valid_for(p, m_refrig);
let _discretization = if use_cache {
cache.discretization.clone()
} else {
let (disc, h_sat_l, h_sat_v) = self.identify_zones(h_in, h_out, p, t_sec_in, t_sec_out)?;
cache.valid = true;
cache.p_ref = p;
cache.m_ref = m_refrig;
cache.h_sat_l = h_sat_l;
cache.h_sat_v = h_sat_v;
cache.discretization = disc.clone();
disc
};
self.cache.set(cache);
// Update total UA in the inner model (EpsNtuModel)
// Note: HeatExchanger/Model are often immutable, but calibration indices can be used.
// For now, we use Cell or similar if we need to store internal state,
// but typically the Model handles the UA.
// self.inner.model.set_ua(discretization.total_ua);
// Wait, EpsNtuModel's UA is fixed. We might need a custom model or use ua_scale.
self.inner.compute_residuals(state, residuals)
}"""
new_compute_residuals = """ fn compute_residuals(
&self,
state: &StateSlice,
residuals: &mut ResidualVector,
) -> Result<(), ComponentError> {
let (p, m_refrig, t_sec_in, t_sec_out) = if let (Some(hot), Some(cold)) = (self.inner.hot_conditions(), self.inner.cold_conditions()) {
(hot.pressure_pa(), hot.mass_flow_kg_s(), cold.temperature_k(), cold.temperature_k() + 5.0)
} else {
(500_000.0, 0.1, 300.0, 320.0)
};
// Extract enthalpies exactly as HeatExchanger does:
let enthalpies = self.port_enthalpies(state)?;
let h_in = enthalpies[0].to_joules_per_kg();
let h_out = enthalpies[1].to_joules_per_kg();
let mut cache = self.cache.borrow_mut();
let use_cache = cache.is_valid_for(p, m_refrig);
if !use_cache {
let (disc, h_sat_l, h_sat_v) = self.identify_zones(h_in, h_out, p, t_sec_in, t_sec_out)?;
cache.valid = true;
cache.p_ref = p;
cache.m_ref = m_refrig;
cache.h_sat_l = h_sat_l;
cache.h_sat_v = h_sat_v;
cache.discretization = disc;
}
let total_ua = cache.discretization.total_ua;
let base_ua = self.inner.ua_nominal();
let custom_ua_scale = if base_ua > 0.0 { total_ua / base_ua } else { 1.0 };
self.inner.compute_residuals_with_ua_scale(state, residuals, custom_ua_scale)
}"""
content = content.replace(old_compute_residuals, new_compute_residuals)
with open("src/heat_exchanger/moving_boundary_hx.rs", "w") as f:
f.write(content)