Replace Python numerical kernels with native C execution
This commit is contained in:
1 parent
48da6be21c
commit
3b38f73fe0
227 files changed
+16801
-75499
No files matched your search
@@ -1,31 +1,10 @@
|
||||
"""Component parameters, ports and output definitions; numerical equations execute in C."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
|
||||
from app.simulation.core.base import AlgebraicComponent
|
||||
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
|
||||
from app.simulation.core.equations import EquationResidual
|
||||
from app.simulation.core.medium import IdealGasMedium
|
||||
from app.simulation.core.ports import PortDefinition, PortState
|
||||
|
||||
|
||||
_REFERENCE_OUTFLOW_REGULARIZATION_RATIO = 0.05
|
||||
|
||||
|
||||
def _regularized_inverse_outflow(flow: float, transition_flow: float) -> float:
|
||||
"""Return a C1 inverse that tends to zero as a negative flow vanishes."""
|
||||
|
||||
if flow >= 0.0:
|
||||
return 0.0
|
||||
transition_flow = max(float(transition_flow), 1.0e-12)
|
||||
if -flow >= transition_flow:
|
||||
return 1.0 / flow
|
||||
return (
|
||||
flow
|
||||
* (2.0 * transition_flow * transition_flow - flow * flow)
|
||||
/ transition_flow**4
|
||||
)
|
||||
|
||||
from app.simulation.core.ports import PortDefinition
|
||||
|
||||
class _AmesimPneumaticNode(AlgebraicComponent):
|
||||
"""Shared implementation for AMESim pneumatic junction submodels.
|
||||
@@ -35,9 +14,7 @@ class _AmesimPneumaticNode(AlgebraicComponent):
|
||||
an outlet, its enthalpy is the residual that closes the junction energy
|
||||
balance, matching the AMESim dh2 causality.
|
||||
"""
|
||||
|
||||
PRESSURE_FLOW_DEPENDS_ON_STREAM = False
|
||||
REFERENCE_PORT = "port_2"
|
||||
REFERENCE_PORT = 'port_2'
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
super().__init__(name=name)
|
||||
@@ -46,194 +23,30 @@ class _AmesimPneumaticNode(AlgebraicComponent):
|
||||
for definition in self.PORTS:
|
||||
setattr(self, definition.name, self.register_declared_port(definition.name))
|
||||
|
||||
def pressure_flow_equation_values(self) -> tuple[float, ...]:
|
||||
reference = self.get_port(self.REFERENCE_PORT)
|
||||
return tuple(
|
||||
self.get_port(definition.name).p - reference.p
|
||||
for definition in self.PORTS
|
||||
if definition.name != self.REFERENCE_PORT
|
||||
) + (
|
||||
sum(
|
||||
self.get_port(definition.name).m_flow
|
||||
for definition in self.PORTS
|
||||
),
|
||||
)
|
||||
|
||||
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
|
||||
reference = self.get_port(self.REFERENCE_PORT)
|
||||
residuals: list[EquationResidual] = []
|
||||
for definition in self.PORTS:
|
||||
if definition.name == self.REFERENCE_PORT:
|
||||
continue
|
||||
port = self.get_port(definition.name)
|
||||
residuals.append(
|
||||
EquationResidual(
|
||||
id=f"{self.name}:{definition.name}_pressure_reference",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="equal",
|
||||
variables=(
|
||||
f"{self.name}.{definition.name}.p",
|
||||
f"{self.name}.{self.REFERENCE_PORT}.p",
|
||||
),
|
||||
role="effort",
|
||||
value=port.p - reference.p,
|
||||
)
|
||||
)
|
||||
residuals.append(
|
||||
EquationResidual(
|
||||
id=f"{self.name}:mass_flow_balance",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="sumToZero",
|
||||
variables=tuple(
|
||||
f"{self.name}.{definition.name}.m_flow"
|
||||
for definition in self.PORTS
|
||||
),
|
||||
role="flow",
|
||||
value=sum(self.get_port(definition.name).m_flow for definition in self.PORTS),
|
||||
)
|
||||
)
|
||||
return tuple(residuals)
|
||||
|
||||
def update_stream_outflows(self, connected_h: Mapping[str, float]) -> None:
|
||||
self.temperature_reference_h = connected_h.get(
|
||||
self.REFERENCE_PORT,
|
||||
sum(connected_h.values()) / len(connected_h) if connected_h else 0.0,
|
||||
)
|
||||
incoming = [
|
||||
(port.m_flow, connected_h[name])
|
||||
for name, port in self.ports.items()
|
||||
if port.m_flow > 1e-12
|
||||
]
|
||||
total_flow = sum(m_flow for m_flow, _ in incoming)
|
||||
if total_flow > 1e-12:
|
||||
mixed_h = sum(m_flow * h for m_flow, h in incoming) / total_flow
|
||||
else:
|
||||
mixed_h = self.temperature_reference_h
|
||||
|
||||
reference_port = self.get_port(self.REFERENCE_PORT)
|
||||
for name, port in self.ports.items():
|
||||
port.h_outflow = (
|
||||
mixed_h
|
||||
if name == self.REFERENCE_PORT
|
||||
else self.temperature_reference_h
|
||||
)
|
||||
|
||||
if reference_port.m_flow < 0.0:
|
||||
energy_without_reference = sum(
|
||||
port.m_flow
|
||||
* (
|
||||
connected_h[name]
|
||||
if port.m_flow > 1e-12
|
||||
else self.temperature_reference_h
|
||||
)
|
||||
for name, port in self.ports.items()
|
||||
if name != self.REFERENCE_PORT
|
||||
)
|
||||
non_reference_flow_scale = sum(
|
||||
abs(port.m_flow)
|
||||
for name, port in self.ports.items()
|
||||
if name != self.REFERENCE_PORT
|
||||
)
|
||||
transition_flow = (
|
||||
_REFERENCE_OUTFLOW_REGULARIZATION_RATIO
|
||||
* non_reference_flow_scale
|
||||
)
|
||||
# Port 2 carries AMESim's residual-energy causality. Exact
|
||||
# division is singular when its outflow reverses through zero, so
|
||||
# use a C1 band that matches the exact balance at its boundary and
|
||||
# tends to the mixed enthalpy at zero flow.
|
||||
inverse_flow = _regularized_inverse_outflow(
|
||||
reference_port.m_flow,
|
||||
transition_flow,
|
||||
)
|
||||
energy_residual_at_mixed_h = (
|
||||
energy_without_reference
|
||||
+ reference_port.m_flow * mixed_h
|
||||
)
|
||||
reference_port.h_outflow = (
|
||||
mixed_h - energy_residual_at_mixed_h * inverse_flow
|
||||
)
|
||||
|
||||
|
||||
class AmesimPn3Node2(_AmesimPneumaticNode):
|
||||
"""AMESim PN3NODE2 pneumatic three-port junction."""
|
||||
|
||||
MODEL_TYPE = "amesim_pn3node2"
|
||||
MODEL_VERSION = "0.3.0"
|
||||
PRESSURE_FLOW_DEPENDS_ON_STREAM = False
|
||||
PRESSURE_FLOW_EXACT_SUM_TO_ZERO_EQUATION_SUFFIXES = frozenset(
|
||||
("mass_flow_balance",)
|
||||
)
|
||||
PORTS = (
|
||||
PortDefinition.pneumatic("port_1", nominal_role="bidirectional"),
|
||||
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
|
||||
PortDefinition.pneumatic("port_3", nominal_role="bidirectional"),
|
||||
)
|
||||
MODEL_TYPE = 'amesim_pn3node2'
|
||||
MODEL_VERSION = '0.3.0'
|
||||
PORTS = (PortDefinition.pneumatic('port_1', nominal_role='bidirectional'), PortDefinition.pneumatic('port_2', nominal_role='bidirectional'), PortDefinition.pneumatic('port_3', nominal_role='bidirectional'))
|
||||
PARAMETERS = ()
|
||||
RESULT_VARIABLES = ()
|
||||
DISPLAY = ComponentDisplaySpec(
|
||||
label="PN3NODE2 三端气动节点",
|
||||
library_id="amesim",
|
||||
category_id="junctions",
|
||||
symbol="amesim_pn3node2",
|
||||
ports=(
|
||||
PortDisplaySpec("port_1", "left", order=10),
|
||||
PortDisplaySpec("port_2", "right", order=20),
|
||||
PortDisplaySpec("port_3", "right", order=30),
|
||||
),
|
||||
order=10,
|
||||
)
|
||||
DISPLAY = ComponentDisplaySpec(label='PN3NODE2 三端气动节点', library_id='amesim', category_id='junctions', symbol='amesim_pn3node2', ports=(PortDisplaySpec('port_1', 'left', order=10), PortDisplaySpec('port_2', 'right', order=20), PortDisplaySpec('port_3', 'right', order=30)), order=10)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
*,
|
||||
name: str,
|
||||
medium: IdealGasMedium,
|
||||
parameters: Mapping[str, float],
|
||||
) -> AmesimPn3Node2:
|
||||
def create(cls, *, name: str, medium: IdealGasMedium, parameters: Mapping[str, float]) -> AmesimPn3Node2:
|
||||
return cls(name=name)
|
||||
|
||||
EQUATIONS = ({'id': '__MODEL__:port_1_pressure_reference', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'equal', 'variables': ['__MODEL__.port_1.p', '__MODEL__.port_2.p'], 'role': 'effort'}, {'id': '__MODEL__:port_3_pressure_reference', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'equal', 'variables': ['__MODEL__.port_3.p', '__MODEL__.port_2.p'], 'role': 'effort'}, {'id': '__MODEL__:mass_flow_balance', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'sumToZero', 'variables': ['__MODEL__.port_1.m_flow', '__MODEL__.port_2.m_flow', '__MODEL__.port_3.m_flow'], 'role': 'flow'})
|
||||
|
||||
class AmesimP4Node2(_AmesimPneumaticNode):
|
||||
"""AMESim P4NODE2 pneumatic four-port junction."""
|
||||
|
||||
MODEL_TYPE = "amesim_p4node2"
|
||||
MODEL_VERSION = "0.3.0"
|
||||
PRESSURE_FLOW_DEPENDS_ON_STREAM = False
|
||||
PRESSURE_FLOW_EXACT_SUM_TO_ZERO_EQUATION_SUFFIXES = frozenset(
|
||||
("mass_flow_balance",)
|
||||
)
|
||||
PORTS = (
|
||||
PortDefinition.pneumatic("port_1", nominal_role="bidirectional"),
|
||||
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
|
||||
PortDefinition.pneumatic("port_3", nominal_role="bidirectional"),
|
||||
PortDefinition.pneumatic("port_4", nominal_role="bidirectional"),
|
||||
)
|
||||
MODEL_TYPE = 'amesim_p4node2'
|
||||
MODEL_VERSION = '0.3.0'
|
||||
PORTS = (PortDefinition.pneumatic('port_1', nominal_role='bidirectional'), PortDefinition.pneumatic('port_2', nominal_role='bidirectional'), PortDefinition.pneumatic('port_3', nominal_role='bidirectional'), PortDefinition.pneumatic('port_4', nominal_role='bidirectional'))
|
||||
PARAMETERS = ()
|
||||
RESULT_VARIABLES = ()
|
||||
DISPLAY = ComponentDisplaySpec(
|
||||
label="P4NODE2 四端气动节点",
|
||||
library_id="amesim",
|
||||
category_id="junctions",
|
||||
symbol="amesim_p4node2",
|
||||
ports=(
|
||||
PortDisplaySpec("port_1", "left", order=10),
|
||||
PortDisplaySpec("port_2", "right", order=20),
|
||||
PortDisplaySpec("port_3", "right", order=30),
|
||||
PortDisplaySpec("port_4", "right", order=40),
|
||||
),
|
||||
order=20,
|
||||
)
|
||||
DISPLAY = ComponentDisplaySpec(label='P4NODE2 四端气动节点', library_id='amesim', category_id='junctions', symbol='amesim_p4node2', ports=(PortDisplaySpec('port_1', 'left', order=10), PortDisplaySpec('port_2', 'right', order=20), PortDisplaySpec('port_3', 'right', order=30), PortDisplaySpec('port_4', 'right', order=40)), order=20)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
*,
|
||||
name: str,
|
||||
medium: IdealGasMedium,
|
||||
parameters: Mapping[str, float],
|
||||
) -> AmesimP4Node2:
|
||||
def create(cls, *, name: str, medium: IdealGasMedium, parameters: Mapping[str, float]) -> AmesimP4Node2:
|
||||
return cls(name=name)
|
||||
EQUATIONS = ({'id': '__MODEL__:port_1_pressure_reference', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'equal', 'variables': ['__MODEL__.port_1.p', '__MODEL__.port_2.p'], 'role': 'effort'}, {'id': '__MODEL__:port_3_pressure_reference', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'equal', 'variables': ['__MODEL__.port_3.p', '__MODEL__.port_2.p'], 'role': 'effort'}, {'id': '__MODEL__:port_4_pressure_reference', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'equal', 'variables': ['__MODEL__.port_4.p', '__MODEL__.port_2.p'], 'role': 'effort'}, {'id': '__MODEL__:mass_flow_balance', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'sumToZero', 'variables': ['__MODEL__.port_1.m_flow', '__MODEL__.port_2.m_flow', '__MODEL__.port_3.m_flow', '__MODEL__.port_4.m_flow'], 'role': 'flow'})
|
||||
Reference in new issue
Block a user