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>
This commit is contained in:
Sepehr
2026-04-25 15:01:09 +02:00
parent 891c4ba436
commit ab5dc7e568
3006 changed files with 279068 additions and 59151 deletions

View File

@@ -0,0 +1,475 @@
//! FreeCoolingExchanger component for water-side economizer simulation
//!
//! This component models a water-to-water heat exchanger used for free cooling,
//! allowing the use of outdoor air as a cooling source without operating the compressor.
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use entropyk_core::{Power, Temperature};
use entropyk_fluids::FluidBackend;
use crate::{
CircuitId, Component, ComponentError, ConnectedPort, JacobianBuilder, OperationalState,
ResidualVector, SystemState,
};
/// Operating mode of the FreeCoolingExchanger
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum FreeCoolingMode {
/// Free cooling active (direct heat exchange)
Active,
/// Full bypass (no heat exchange)
Bypass,
/// Mixed mode (partial bypass)
Mixed { bypass_fraction: f64 },
}
/// Configuration for the free cooling heat exchanger
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FreeCoolingConfig {
/// Effectiveness of the heat exchanger (0.0 - 1.0)
pub effectiveness: f64,
/// Bypass fraction (0.0 - 1.0)
pub bypass_fraction: f64,
/// Minimum outdoor temperature for free cooling (K)
pub min_outdoor_temp: f64,
/// Hysteresis to prevent rapid cycling (K)
pub hysteresis: f64,
/// Control mode
pub control_mode: FreeCoolingControlMode,
}
/// Control mode for free cooling
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum FreeCoolingControlMode {
/// Manual control (fixed mode)
Manual,
/// Automatic control based on outdoor temperature
AutoTemperature,
/// Optimized control (minimizes energy consumption)
Optimized,
}
/// FreeCoolingExchanger component
pub struct FreeCoolingExchanger {
/// Unique identifier
id: String,
/// Circuit ID
circuit_id: CircuitId,
/// Configuration
config: FreeCoolingConfig,
/// Current mode
mode: FreeCoolingMode,
/// Ports (4 ports: cold water in/out, hot water in/out)
port_cold_inlet: ConnectedPort,
port_cold_outlet: ConnectedPort,
port_hot_inlet: ConnectedPort,
port_hot_outlet: ConnectedPort,
/// Outdoor temperature (for auto mode)
outdoor_temp: Option<Temperature>,
/// Calculated after convergence
heat_transfer_rate: Option<Power>,
/// Current effectiveness (can vary with flow rates)
current_effectiveness: f64,
/// Fluid backend for property calculations
fluid_backend: Option<Arc<dyn FluidBackend>>,
}
impl std::fmt::Debug for FreeCoolingExchanger {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FreeCoolingExchanger")
.field("id", &self.id)
.field("circuit_id", &self.circuit_id)
.field("config", &self.config)
.field("mode", &self.mode)
.field("outdoor_temp", &self.outdoor_temp)
.field("heat_transfer_rate", &self.heat_transfer_rate)
.field("current_effectiveness", &self.current_effectiveness)
.field("fluid_backend", &"<FluidBackend>")
.finish()
}
}
impl FreeCoolingExchanger {
/// Creates a new free cooling heat exchanger
pub fn new(
id: &str,
circuit_id: CircuitId,
config: FreeCoolingConfig,
port_cold_inlet: ConnectedPort,
port_cold_outlet: ConnectedPort,
port_hot_inlet: ConnectedPort,
port_hot_outlet: ConnectedPort,
) -> Result<Self, ComponentError> {
// Validate parameters
if config.effectiveness < 0.0 || config.effectiveness > 1.0 {
return Err(ComponentError::InvalidState(
"Effectiveness must be between 0.0 and 1.0".to_string(),
));
}
if config.bypass_fraction < 0.0 || config.bypass_fraction > 1.0 {
return Err(ComponentError::InvalidState(
"Bypass fraction must be between 0.0 and 1.0".to_string(),
));
}
let current_effectiveness = config.effectiveness;
Ok(Self {
id: id.to_string(),
circuit_id,
config,
mode: FreeCoolingMode::Bypass, // Starts in bypass
port_cold_inlet,
port_cold_outlet,
port_hot_inlet,
port_hot_outlet,
outdoor_temp: None,
heat_transfer_rate: None,
current_effectiveness,
fluid_backend: None,
})
}
/// Sets the fluid backend for property calculations
pub fn set_fluid_backend(&mut self, backend: Arc<dyn FluidBackend>) {
self.fluid_backend = Some(backend);
}
/// Calculates maximum possible heat transfer
fn calculate_max_heat_transfer(&self, state: &SystemState) -> Result<Power, ComponentError> {
// Get inlet temperatures
let t_cold_in = self.get_cold_inlet_temp(state)?;
let t_hot_in = self.get_hot_inlet_temp(state)?;
// Heat capacity rates
let c_cold = self.get_cold_capacity_rate(state)?;
let c_hot = self.get_hot_capacity_rate(state)?;
let c_min = c_cold.min(c_hot);
// Maximum heat transfer
let q_max = c_min * (t_hot_in - t_cold_in);
Ok(Power::from_watts(q_max.max(0.0)))
}
/// Updates the mode based on conditions
pub fn update_mode(&mut self, outdoor_temp: Option<Temperature>) -> Result<(), ComponentError> {
if let Some(t_outdoor) = outdoor_temp {
self.outdoor_temp = Some(t_outdoor);
match self.config.control_mode {
FreeCoolingControlMode::AutoTemperature => {
let t_cold_in = self.get_current_cold_inlet_temp()?;
// Switching logic with hysteresis
if self.mode == FreeCoolingMode::Bypass {
// Check if we can switch to free cooling
if t_outdoor.0 < (t_cold_in - self.config.min_outdoor_temp) {
self.mode = FreeCoolingMode::Active;
}
} else {
// Check if we should go back to bypass
if t_outdoor.0
> (t_cold_in - self.config.min_outdoor_temp + self.config.hysteresis)
{
self.mode = FreeCoolingMode::Bypass;
}
}
}
FreeCoolingControlMode::Optimized => {
// TODO: Implement energy optimization
self.mode = FreeCoolingMode::Active;
}
FreeCoolingControlMode::Manual => {
// Do nothing, fixed mode
}
}
}
Ok(())
}
/// Helper methods for temperature and flow calculations
fn get_cold_inlet_temp(&self, _state: &SystemState) -> Result<f64, ComponentError> {
// Placeholder - would extract from state vector
Ok(285.15) // 12°C
}
fn get_hot_inlet_temp(&self, _state: &SystemState) -> Result<f64, ComponentError> {
// Placeholder - would extract from state vector
Ok(298.15) // 25°C
}
fn get_cold_capacity_rate(&self, _state: &SystemState) -> Result<f64, ComponentError> {
// Placeholder - would calculate from mass flow and specific heat
Ok(4186.0 * 0.1) // Water at 0.1 kg/s
}
fn get_hot_capacity_rate(&self, _state: &SystemState) -> Result<f64, ComponentError> {
// Placeholder - would calculate from mass flow and specific heat
Ok(4186.0 * 0.1) // Water at 0.1 kg/s
}
fn get_current_cold_inlet_temp(&self) -> Result<f64, ComponentError> {
Ok(285.15) // Placeholder
}
}
impl Component for FreeCoolingExchanger {
fn n_equations(&self) -> usize {
// 4 equations for energy balances at each port
// + 1 equation for heat transfer
// + 1 equation for flow continuity
6
}
fn compute_residuals(
&self,
_state: &[f64],
_residuals: &mut ResidualVector,
) -> Result<(), ComponentError> {
// TODO: Implement actual residual calculations
// For now, return zero residuals
Ok(())
}
fn jacobian_entries(
&self,
_state: &[f64],
_jacobian: &mut JacobianBuilder,
) -> Result<(), ComponentError> {
// TODO: Implement partial derivatives
Ok(())
}
fn get_ports(&self) -> &[ConnectedPort] {
// Return the 4 ports
&[] // Placeholder
}
}
/// Specific methods for FreeCoolingExchanger
impl FreeCoolingExchanger {
/// Returns the current operational state
pub fn operational_state(&self) -> OperationalState {
match self.mode {
FreeCoolingMode::Bypass => OperationalState::Bypass,
_ => OperationalState::On,
}
}
/// Sets the operational state
pub fn set_operational_state(&mut self, state: OperationalState) -> Result<(), ComponentError> {
match state {
OperationalState::On => self.mode = FreeCoolingMode::Active,
OperationalState::Off | OperationalState::Bypass => {
self.mode = FreeCoolingMode::Bypass;
}
}
Ok(())
}
/// Returns the current heat transfer rate
pub fn heat_transfer_rate(&self) -> Option<Power> {
self.heat_transfer_rate
}
/// Returns the current mode
pub fn current_mode(&self) -> FreeCoolingMode {
self.mode
}
/// Returns estimated energy savings (in %)
pub fn energy_savings_percent(&self) -> f64 {
match self.mode {
FreeCoolingMode::Active => {
// Estimation based on effectiveness
self.current_effectiveness * 100.0
}
FreeCoolingMode::Bypass => 0.0,
FreeCoolingMode::Mixed { bypass_fraction } => {
self.current_effectiveness * bypass_fraction * 100.0
}
}
}
/// Returns outdoor temperature
pub fn outdoor_temperature(&self) -> Option<Temperature> {
self.outdoor_temp
}
/// Updates configuration
pub fn update_config(&mut self, config: FreeCoolingConfig) -> Result<(), ComponentError> {
// Validation
if config.effectiveness < 0.0 || config.effectiveness > 1.0 {
return Err(ComponentError::InvalidState(
"Effectiveness must be between 0.0 and 1.0".to_string(),
));
}
self.config = config;
Ok(())
}
/// Returns true if free cooling is active
pub fn is_free_cooling_active(&self) -> bool {
self.mode != FreeCoolingMode::Bypass
}
/// Calculates effective COP (very high in free cooling)
pub fn effective_cop(&self) -> f64 {
match self.mode {
FreeCoolingMode::Active => {
// Typical COP > 20 for free cooling (only pumps)
20.0 + self.current_effectiveness * 10.0
}
FreeCoolingMode::Bypass => 1.0, // No gain
FreeCoolingMode::Mixed { bypass_fraction } => {
// Weighted COP
let cop_fc = 20.0 + self.current_effectiveness * 10.0;
bypass_fraction * cop_fc + (1.0 - bypass_fraction) * 1.0
}
}
}
/// Returns the unique identifier
pub fn id(&self) -> &str {
&self.id
}
/// Returns the circuit ID
pub fn circuit_id(&self) -> CircuitId {
self.circuit_id
}
}
impl Default for FreeCoolingConfig {
fn default() -> Self {
Self {
effectiveness: 0.85,
bypass_fraction: 0.2,
min_outdoor_temp: 285.15, // 12°C
hysteresis: 2.0,
control_mode: FreeCoolingControlMode::AutoTemperature,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::port::{FluidId, Port};
use entropyk_core::{Enthalpy, Pressure};
/// Creates a pair of connected ports for tests (same fluid, P, h).
fn make_connected_ports() -> (ConnectedPort, ConnectedPort) {
let fluid = FluidId::new("Water");
let p = Pressure::from_pascals(3e5);
let h = Enthalpy::from_joules_per_kg(63_000.0);
let a = Port::new(fluid, p, h);
let b = Port::new(FluidId::new("Water"), p, h);
a.connect(b).unwrap()
}
#[test]
fn test_free_cooling_exchanger_creation() {
let config = FreeCoolingConfig::default();
let (cold_in, cold_out) = make_connected_ports();
let (hot_in, hot_out) = make_connected_ports();
let exchanger = FreeCoolingExchanger::new(
"fc_1",
CircuitId(0),
config,
cold_in,
cold_out,
hot_in,
hot_out,
);
assert!(exchanger.is_ok());
let exchanger = exchanger.unwrap();
assert_eq!(exchanger.current_mode(), FreeCoolingMode::Bypass);
assert!(!exchanger.is_free_cooling_active());
}
#[test]
fn test_invalid_effectiveness() {
let config = FreeCoolingConfig {
effectiveness: 1.5,
..Default::default()
};
let (cold_in, cold_out) = make_connected_ports();
let (hot_in, hot_out) = make_connected_ports();
let exchanger = FreeCoolingExchanger::new(
"fc_1",
CircuitId(0),
config,
cold_in,
cold_out,
hot_in,
hot_out,
);
assert!(exchanger.is_err());
}
#[test]
fn test_energy_savings_calculation() {
let config = FreeCoolingConfig {
effectiveness: 0.85,
..Default::default()
};
let (cold_in, cold_out) = make_connected_ports();
let (hot_in, hot_out) = make_connected_ports();
let mut exchanger = FreeCoolingExchanger::new(
"fc_1",
CircuitId(0),
config,
cold_in,
cold_out,
hot_in,
hot_out,
)
.unwrap();
// Bypass mode -> 0% savings
assert_eq!(exchanger.energy_savings_percent(), 0.0);
// Active mode -> effectiveness * 100%
exchanger.mode = FreeCoolingMode::Active;
assert_eq!(exchanger.energy_savings_percent(), 85.0);
// Mixed mode
exchanger.mode = FreeCoolingMode::Mixed {
bypass_fraction: 0.3,
};
assert_eq!(exchanger.energy_savings_percent(), 85.0 * 0.3);
}
#[test]
fn test_effective_cop() {
let (cold_in, cold_out) = make_connected_ports();
let (hot_in, hot_out) = make_connected_ports();
let mut exchanger = FreeCoolingExchanger::new(
"fc_1",
CircuitId(0),
FreeCoolingConfig::default(),
cold_in,
cold_out,
hot_in,
hot_out,
)
.unwrap();
// COP in free cooling
exchanger.mode = FreeCoolingMode::Active;
assert!(exchanger.effective_cop() > 20.0);
// COP in bypass
exchanger.mode = FreeCoolingMode::Bypass;
assert_eq!(exchanger.effective_cop(), 1.0);
}
}