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,36 +1,15 @@
|
||||
"""Component parameters, ports and output definitions; numerical equations execute in C."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from math import isfinite, pi
|
||||
|
||||
from app.simulation.components.amesim.gases import (
|
||||
AMESIM_GAS_INDEX_PARAMETER,
|
||||
normalize_amesim_gas_index,
|
||||
)
|
||||
from collections.abc import Mapping
|
||||
from math import pi
|
||||
from app.simulation.components.amesim.gases import AMESIM_GAS_INDEX_PARAMETER, normalize_amesim_gas_index
|
||||
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.metadata import ParameterDefinition, ResultVariableDefinition
|
||||
from app.simulation.core.medium import GasMedium
|
||||
from app.simulation.core.ports import PortDefinition
|
||||
|
||||
|
||||
AMESIM_REFERENCE_PRESSURE_PA = 101300.0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Pnrp17Linearization:
|
||||
volume: float
|
||||
volume_flow: float
|
||||
pressure_force: float
|
||||
volume_tangent: tuple[float, ...]
|
||||
volume_flow_tangent: tuple[float, ...]
|
||||
pressure_force_tangent: tuple[float, ...]
|
||||
valid: bool = True
|
||||
reason: str | None = None
|
||||
|
||||
|
||||
class AmesimPnrp17(AlgebraicComponent):
|
||||
"""AMESim PNRP17 pneumatic piston with two mechanical faces.
|
||||
|
||||
@@ -38,268 +17,32 @@ class AmesimPnrp17(AlgebraicComponent):
|
||||
cylinder-side motion. The pneumatic port contributes its swept volume and
|
||||
volume rate to the connected variable-volume chamber.
|
||||
"""
|
||||
MODEL_TYPE = 'amesim_pnrp17'
|
||||
MODEL_VERSION = '0.1.0'
|
||||
PORTS = (PortDefinition.pneumatic('port_1', nominal_role='bidirectional'), PortDefinition.mechanical_translational('port_2'), PortDefinition.mechanical_translational('port_3'), PortDefinition.mechanical_translational('port_4'), PortDefinition.mechanical_translational('port_5'))
|
||||
PARAMETERS = (AMESIM_GAS_INDEX_PARAMETER, ParameterDefinition('dp', 0.2, label='活塞直径', quantity='length', unit='m', minimum=0.0, minimum_exclusive=True, description='活塞外径;与活塞杆直径共同确定有效受压面积。'), ParameterDefinition('dr', 0.001, label='活塞杆直径', quantity='length', unit='m', minimum=0.0, description='穿过气室一侧的活塞杆直径,必须不大于活塞直径。'), ParameterDefinition('x0', 0.0, label='初始腔长', quantity='length', unit='m', description='机械端位移均为零时的气动腔长度。'))
|
||||
RESULT_VARIABLES = (ResultVariableDefinition('volume', '扫掠容积', 'volume', 'm3', 'derived', 10), ResultVariableDefinition('volume_flow', '扫掠容积变化率', 'volume_flow', 'm3/s', 'derived', 20), ResultVariableDefinition('length', '气动腔长度', 'length', 'm', 'derived', 30), ResultVariableDefinition('pressure_force', '气压力', 'force', 'N', 'derived', 40))
|
||||
DISPLAY = ComponentDisplaySpec(label='PNRP17 气动活塞', library_id='amesim', category_id='mechanical', symbol='amesim_pnrp17', ports=(PortDisplaySpec('port_1', 'left', order=10), PortDisplaySpec('port_3', 'left', order=20), PortDisplaySpec('port_2', 'left', order=30), PortDisplaySpec('port_4', 'right', order=40), PortDisplaySpec('port_5', 'right', order=50)), order=60)
|
||||
|
||||
MODEL_TYPE = "amesim_pnrp17"
|
||||
MODEL_VERSION = "0.1.0"
|
||||
PRESSURE_FLOW_DEPENDS_ON_STREAM = False
|
||||
PORTS = (
|
||||
PortDefinition.pneumatic("port_1", nominal_role="bidirectional"),
|
||||
PortDefinition.mechanical_translational("port_2"),
|
||||
PortDefinition.mechanical_translational("port_3"),
|
||||
PortDefinition.mechanical_translational("port_4"),
|
||||
PortDefinition.mechanical_translational("port_5"),
|
||||
)
|
||||
PARAMETERS = (
|
||||
AMESIM_GAS_INDEX_PARAMETER,
|
||||
ParameterDefinition(
|
||||
"dp",
|
||||
0.2,
|
||||
label="活塞直径",
|
||||
quantity="length",
|
||||
unit="m",
|
||||
minimum=0.0,
|
||||
minimum_exclusive=True,
|
||||
description="活塞外径;与活塞杆直径共同确定有效受压面积。",
|
||||
),
|
||||
ParameterDefinition(
|
||||
"dr",
|
||||
0.001,
|
||||
label="活塞杆直径",
|
||||
quantity="length",
|
||||
unit="m",
|
||||
minimum=0.0,
|
||||
description="穿过气室一侧的活塞杆直径,必须不大于活塞直径。",
|
||||
),
|
||||
ParameterDefinition(
|
||||
"x0",
|
||||
0.0,
|
||||
label="初始腔长",
|
||||
quantity="length",
|
||||
unit="m",
|
||||
description="机械端位移均为零时的气动腔长度。",
|
||||
),
|
||||
)
|
||||
RESULT_VARIABLES = (
|
||||
ResultVariableDefinition("volume", "扫掠容积", "volume", "m3", "derived", 10),
|
||||
ResultVariableDefinition(
|
||||
"volume_flow",
|
||||
"扫掠容积变化率",
|
||||
"volume_flow",
|
||||
"m3/s",
|
||||
"derived",
|
||||
20,
|
||||
),
|
||||
ResultVariableDefinition("length", "气动腔长度", "length", "m", "derived", 30),
|
||||
ResultVariableDefinition(
|
||||
"pressure_force",
|
||||
"气压力",
|
||||
"force",
|
||||
"N",
|
||||
"derived",
|
||||
40,
|
||||
),
|
||||
)
|
||||
DISPLAY = ComponentDisplaySpec(
|
||||
label="PNRP17 气动活塞",
|
||||
library_id="amesim",
|
||||
category_id="mechanical",
|
||||
symbol="amesim_pnrp17",
|
||||
ports=(
|
||||
PortDisplaySpec("port_1", "left", order=10),
|
||||
PortDisplaySpec("port_3", "left", order=20),
|
||||
PortDisplaySpec("port_2", "left", order=30),
|
||||
PortDisplaySpec("port_4", "right", order=40),
|
||||
PortDisplaySpec("port_5", "right", order=50),
|
||||
),
|
||||
order=60,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
medium: GasMedium,
|
||||
*,
|
||||
gi: float = 0.0,
|
||||
dp: float = 0.2,
|
||||
dr: float = 0.001,
|
||||
x0: float = 0.0,
|
||||
) -> None:
|
||||
def __init__(self, name: str, medium: GasMedium, *, gi: float=0.0, dp: float=0.2, dr: float=0.001, x0: float=0.0) -> None:
|
||||
super().__init__(name=name)
|
||||
self.set_parameter_values({"gi": gi, "dp": dp, "dr": dr, "x0": x0})
|
||||
self.set_parameter_values({'gi': gi, 'dp': dp, 'dr': dr, 'x0': x0})
|
||||
self.medium = medium
|
||||
self.gi = normalize_amesim_gas_index(gi)
|
||||
self.dp = float(dp)
|
||||
self.dr = float(dr)
|
||||
self.x0 = float(x0)
|
||||
if self.dr > self.dp:
|
||||
raise ValueError("PNRP17 rod diameter dr must not exceed piston diameter dp.")
|
||||
raise ValueError('PNRP17 rod diameter dr must not exceed piston diameter dp.')
|
||||
for definition in self.PORTS:
|
||||
port = self.register_declared_port(definition.name)
|
||||
setattr(self, definition.name, port)
|
||||
self.port_1.h_outflow = medium.specific_enthalpy(medium.T_ref)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
*,
|
||||
name: str,
|
||||
medium: GasMedium,
|
||||
parameters: Mapping[str, float],
|
||||
) -> "AmesimPnrp17":
|
||||
def create(cls, *, name: str, medium: GasMedium, parameters: Mapping[str, float]) -> 'AmesimPnrp17':
|
||||
return cls(name=name, medium=medium, **dict(parameters))
|
||||
|
||||
@property
|
||||
def effective_area(self) -> float:
|
||||
return pi * (self.dp * self.dp - self.dr * self.dr) / 4.0
|
||||
|
||||
@property
|
||||
def chamber_length(self) -> float:
|
||||
return self.x0 + self.port_5.x - self.port_4.x
|
||||
|
||||
@property
|
||||
def chamber_volume(self) -> float:
|
||||
return self.effective_area * self.chamber_length
|
||||
|
||||
@property
|
||||
def chamber_volume_flow(self) -> float:
|
||||
return self.effective_area * (self.port_5.v - self.port_4.v)
|
||||
|
||||
@property
|
||||
def pressure_force(self) -> float:
|
||||
return (self.port_1.p - AMESIM_REFERENCE_PRESSURE_PA) * self.effective_area
|
||||
|
||||
def pressure_flow_equation_values(self) -> tuple[float, ...]:
|
||||
values = [self.port_1.m_flow]
|
||||
effort_pairs = (("port_2", "port_5"), ("port_3", "port_4"))
|
||||
for first_name, second_name in effort_pairs:
|
||||
first = self.get_port(first_name)
|
||||
second = self.get_port(second_name)
|
||||
values.extend((first.x - second.x, first.v - second.v))
|
||||
force = self.pressure_force
|
||||
values.extend(
|
||||
(
|
||||
self.port_2.f + self.port_5.f + force,
|
||||
self.port_3.f + self.port_4.f - force,
|
||||
)
|
||||
)
|
||||
return tuple(values)
|
||||
|
||||
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
|
||||
effort_pairs = (("port_2", "port_5"), ("port_3", "port_4"))
|
||||
residuals: list[EquationResidual] = [
|
||||
EquationResidual(
|
||||
id=f"{self.name}:pneumatic_zero_mass_flow",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="constitutive",
|
||||
variables=(f"{self.name}.port_1.m_flow",),
|
||||
role="flow",
|
||||
value=self.port_1.m_flow,
|
||||
)
|
||||
]
|
||||
for first_name, second_name in effort_pairs:
|
||||
first = self.get_port(first_name)
|
||||
second = self.get_port(second_name)
|
||||
for variable in ("x", "v"):
|
||||
residuals.append(
|
||||
EquationResidual(
|
||||
id=f"{self.name}:{first_name}_{second_name}_{variable}_equal",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="equal",
|
||||
variables=(
|
||||
f"{self.name}.{first_name}.{variable}",
|
||||
f"{self.name}.{second_name}.{variable}",
|
||||
),
|
||||
role="effort",
|
||||
value=getattr(first, variable) - getattr(second, variable),
|
||||
)
|
||||
)
|
||||
force = self.pressure_force
|
||||
residuals.extend(
|
||||
(
|
||||
EquationResidual(
|
||||
id=f"{self.name}:piston_side_force_balance",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="constitutive",
|
||||
variables=(f"{self.name}.port_2.f", f"{self.name}.port_5.f", f"{self.name}.port_1.p"),
|
||||
role="flow",
|
||||
value=self.port_2.f + self.port_5.f + force,
|
||||
),
|
||||
EquationResidual(
|
||||
id=f"{self.name}:cylinder_side_force_balance",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="constitutive",
|
||||
variables=(f"{self.name}.port_3.f", f"{self.name}.port_4.f", f"{self.name}.port_1.p"),
|
||||
role="flow",
|
||||
value=self.port_3.f + self.port_4.f - force,
|
||||
),
|
||||
)
|
||||
)
|
||||
return tuple(residuals)
|
||||
|
||||
def pneumatic_volume_outputs(self) -> Mapping[str, tuple[float, float]]:
|
||||
return {"port_1": (self.chamber_volume, self.chamber_volume_flow)}
|
||||
|
||||
def linearize_geometry_and_force(
|
||||
self,
|
||||
port_4_x_tangent: Sequence[float],
|
||||
port_5_x_tangent: Sequence[float],
|
||||
port_4_v_tangent: Sequence[float],
|
||||
port_5_v_tangent: Sequence[float],
|
||||
port_1_pressure_tangent: Sequence[float],
|
||||
) -> Pnrp17Linearization:
|
||||
"""Return exact piston geometry and pressure-force tangents."""
|
||||
|
||||
vectors = tuple(
|
||||
tuple(float(value) for value in values)
|
||||
for values in (
|
||||
port_4_x_tangent,
|
||||
port_5_x_tangent,
|
||||
port_4_v_tangent,
|
||||
port_5_v_tangent,
|
||||
port_1_pressure_tangent,
|
||||
)
|
||||
)
|
||||
widths = {len(values) for values in vectors}
|
||||
if len(widths) != 1:
|
||||
raise ValueError("PNRP17 tangent vectors must have equal lengths.")
|
||||
valid = all(isfinite(value) for values in vectors for value in values)
|
||||
area = self.effective_area
|
||||
volume_tangent = tuple(
|
||||
area * (right - left)
|
||||
for left, right in zip(vectors[0], vectors[1], strict=True)
|
||||
)
|
||||
volume_flow_tangent = tuple(
|
||||
area * (right - left)
|
||||
for left, right in zip(vectors[2], vectors[3], strict=True)
|
||||
)
|
||||
pressure_force_tangent = tuple(
|
||||
area * value for value in vectors[4]
|
||||
)
|
||||
return Pnrp17Linearization(
|
||||
volume=self.chamber_volume,
|
||||
volume_flow=self.chamber_volume_flow,
|
||||
pressure_force=self.pressure_force,
|
||||
volume_tangent=volume_tangent,
|
||||
volume_flow_tangent=volume_flow_tangent,
|
||||
pressure_force_tangent=pressure_force_tangent,
|
||||
valid=valid,
|
||||
reason=None if valid else "non_finite_tangent_input",
|
||||
)
|
||||
|
||||
def update_stream_outflows(self, connected_h: Mapping[str, float]) -> None:
|
||||
self.port_1.h_outflow = connected_h.get(
|
||||
"port_1",
|
||||
self.medium.specific_enthalpy(self.medium.T_ref),
|
||||
)
|
||||
|
||||
def component_result_values(self) -> Mapping[str, float]:
|
||||
return {
|
||||
"volume": self.chamber_volume,
|
||||
"volume_flow": self.chamber_volume_flow,
|
||||
"length": self.chamber_length,
|
||||
"pressure_force": self.pressure_force,
|
||||
}
|
||||
EQUATIONS = ({'id': '__MODEL__:pneumatic_zero_mass_flow', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'constitutive', 'variables': ['__MODEL__.port_1.m_flow'], 'role': 'flow'}, {'id': '__MODEL__:port_2_port_5_x_equal', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'equal', 'variables': ['__MODEL__.port_2.x', '__MODEL__.port_5.x'], 'role': 'effort'}, {'id': '__MODEL__:port_2_port_5_v_equal', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'equal', 'variables': ['__MODEL__.port_2.v', '__MODEL__.port_5.v'], 'role': 'effort'}, {'id': '__MODEL__:port_3_port_4_x_equal', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'equal', 'variables': ['__MODEL__.port_3.x', '__MODEL__.port_4.x'], 'role': 'effort'}, {'id': '__MODEL__:port_3_port_4_v_equal', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'equal', 'variables': ['__MODEL__.port_3.v', '__MODEL__.port_4.v'], 'role': 'effort'}, {'id': '__MODEL__:piston_side_force_balance', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'constitutive', 'variables': ['__MODEL__.port_2.f', '__MODEL__.port_5.f', '__MODEL__.port_1.p'], 'role': 'flow'}, {'id': '__MODEL__:cylinder_side_force_balance', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'constitutive', 'variables': ['__MODEL__.port_3.f', '__MODEL__.port_4.f', '__MODEL__.port_1.p'], 'role': 'flow'})
|
||||
Reference in new issue
Block a user