Add diagram workbench UI with Modelica DoF coaching and ISO glyphs.
Ship the Next.js cycle editor with CAD chrome, technical HX symbols, Fixed/Free boundary guidance, and secondary water/air pressure drop support in the solver stack. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -25,10 +25,28 @@ use petgraph::graph::{DiGraph, NodeIndex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn default_duty_scale() -> f64 {
|
||||
1.0
|
||||
}
|
||||
|
||||
/// Thermal coupling between two circuits via a heat exchanger.
|
||||
///
|
||||
/// Heat flows from `hot_circuit` to `cold_circuit` proportional to the
|
||||
/// temperature difference and thermal conductance (UA value).
|
||||
///
|
||||
/// ## Physical (duty-transfer) mode
|
||||
///
|
||||
/// When [`hot_component`](Self::hot_component) and
|
||||
/// [`cold_component`](Self::cold_component) are set, the coupling is **physical**:
|
||||
/// the per-coupling state unknown is the transferred heat `Q` [W], closed by the
|
||||
/// residual `r = Q − η·duty(hot_component)` where the duty is *measured* from the
|
||||
/// solved state via the hot component's `measure_output(Capacity)` (e.g. the real
|
||||
/// ε-NTU condenser duty). The cold-side component (a
|
||||
/// [`ThermalLoad`](entropyk_components::ThermalLoad)) consumes `Q` in its energy
|
||||
/// balance `ṁ·(h_out − h_in) = Q`, so the cold circuit genuinely warms up.
|
||||
///
|
||||
/// Without the component references the legacy MVP stub applies (the residual
|
||||
/// simply pins `Q = 0`), preserved for backward compatibility.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ThermalCoupling {
|
||||
@@ -42,6 +60,19 @@ pub struct ThermalCoupling {
|
||||
pub ua: ThermalConductance,
|
||||
/// Efficiency factor (0.0 to 1.0). Default is 1.0 (no losses).
|
||||
pub efficiency: f64,
|
||||
/// Multiplier applied to the measured duty before it is injected into the
|
||||
/// receiver. Use `-1.0` when the receiver must be cooled by the source duty
|
||||
/// (for example, chilled water across an evaporator).
|
||||
#[serde(default = "default_duty_scale", alias = "duty_scale")]
|
||||
pub duty_scale: f64,
|
||||
/// Name of the registered component in the hot circuit whose *measured duty*
|
||||
/// (`measure_output(Capacity)`) is transferred (e.g. an ε-NTU `Condenser`).
|
||||
#[serde(default, alias = "hot_component")]
|
||||
pub hot_component: Option<String>,
|
||||
/// Name of the registered `ThermalLoad` component in the cold circuit that
|
||||
/// receives `Q` in its energy balance.
|
||||
#[serde(default, alias = "cold_component")]
|
||||
pub cold_component: Option<String>,
|
||||
}
|
||||
|
||||
impl ThermalCoupling {
|
||||
@@ -71,6 +102,9 @@ impl ThermalCoupling {
|
||||
cold_circuit,
|
||||
ua,
|
||||
efficiency: 1.0,
|
||||
duty_scale: 1.0,
|
||||
hot_component: None,
|
||||
cold_component: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +116,35 @@ impl ThermalCoupling {
|
||||
self.efficiency = efficiency.clamp(0.0, 1.0);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the signed duty multiplier applied before injecting heat into the
|
||||
/// receiver component.
|
||||
pub fn with_duty_scale(mut self, duty_scale: f64) -> Self {
|
||||
self.duty_scale = if duty_scale.is_finite() {
|
||||
duty_scale
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables the **physical duty-transfer mode**: the measured duty of
|
||||
/// `hot_component` (via `measure_output(Capacity)`) is transferred, scaled
|
||||
/// by `efficiency`, into `cold_component`'s energy balance (a `ThermalLoad`).
|
||||
pub fn with_interface_components(
|
||||
mut self,
|
||||
hot_component: impl Into<String>,
|
||||
cold_component: impl Into<String>,
|
||||
) -> Self {
|
||||
self.hot_component = Some(hot_component.into());
|
||||
self.cold_component = Some(cold_component.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` when the coupling is in physical duty-transfer mode.
|
||||
pub fn is_physical(&self) -> bool {
|
||||
self.hot_component.is_some() && self.cold_component.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes heat transfer for a thermal coupling.
|
||||
|
||||
Reference in New Issue
Block a user