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,", "cache: RefCell,") 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)