同步仿真框架并接入AMESim气动组件

This commit is contained in:
huojiarong committed 2026-07-30 03:53:10 +00:00
1 parent 420bafeb4e
commit db4bdb4b70
109 files changed
+26920 -420

No files matched your search

@@ -0,0 +1,310 @@
from __future__ import annotations
from collections.abc import Mapping
from math import isclose, log10, pi, sqrt
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 IdealGasMedium
from app.simulation.core.ports import PortDefinition
class AmesimPnl00r(AlgebraicComponent):
"""AMESim PNL00R pneumatic pipe friction resistance.
The public model exposes the AMESim PNL00R catalog/XML contract and uses
an auditable Darcy-Weisbach resistance with Reynolds/roughness-dependent
friction. Exact `pn2pipefr_` parity is left for the later model tuning pass.
"""
MODEL_TYPE = "amesim_pnl00r"
MODEL_VERSION = "0.1.0"
PORTS = (
PortDefinition.pneumatic("port_1", nominal_role="bidirectional"),
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
)
PARAMETERS = (
ParameterDefinition(
"diam",
0.01,
label="管径",
quantity="length",
unit="m",
minimum=0.0,
minimum_exclusive=True,
),
ParameterDefinition(
"le",
1.0,
label="管长",
quantity="length",
unit="m",
minimum=0.0,
minimum_exclusive=True,
),
ParameterDefinition(
"rr",
1.0e-5,
label="相对粗糙度",
quantity="dimensionless",
unit="",
minimum=0.0,
maximum=0.1,
),
ParameterDefinition(
"gi",
1.0,
label="气体类型索引",
quantity="dimensionless",
unit="",
minimum=1.0,
maximum=99.0,
),
)
RESULT_VARIABLES = (
ResultVariableDefinition(
"re",
label="Reynolds 数",
quantity="dimensionless",
unit="",
category="derived",
order=10,
),
ResultVariableDefinition(
"cm",
label="质量流量参数",
quantity="dimensionless",
unit="",
category="derived",
order=20,
),
ResultVariableDefinition(
"v",
label="平均气体速度",
quantity="velocity",
unit="m/s",
category="derived",
order=30,
),
ResultVariableDefinition(
"ff",
label="摩擦因子",
quantity="dimensionless",
unit="",
category="derived",
order=40,
),
)
DISPLAY = ComponentDisplaySpec(
label="PNL00R 气动管路阻力",
library_id="amesim",
category_id="flow",
symbol="pipe",
ports=(
PortDisplaySpec("port_1", "left", order=10),
PortDisplaySpec("port_2", "right", order=20),
),
order=20,
)
def __init__(
self,
name: str,
medium: IdealGasMedium,
*,
diam: float = 0.01,
le: float = 1.0,
rr: float = 1.0e-5,
gi: float = 1.0,
) -> None:
super().__init__(name=name)
self.set_parameter_values({"diam": diam, "le": le, "rr": rr, "gi": gi})
self.medium = medium
self.diam = float(diam)
self.le = float(le)
self.rr = float(rr)
self.gi = self._integer_parameter("gi", gi)
self.area = pi * self.diam * self.diam / 4.0
initial_h = medium.specific_enthalpy(medium.T_ref)
self.port_1 = self.register_declared_port("port_1")
self.port_1.h_outflow = initial_h
self.port_2 = self.register_declared_port("port_2")
self.port_2.h_outflow = initial_h
@staticmethod
def _integer_parameter(name: str, value: float) -> int:
rounded = round(value)
if not isclose(value, rounded, rel_tol=0.0, abs_tol=1.0e-12):
raise ValueError(f"PNL00R parameter {name} must be an integer value.")
return int(rounded)
@classmethod
def create(
cls,
*,
name: str,
medium: IdealGasMedium,
parameters: Mapping[str, float],
) -> AmesimPnl00r:
return cls(
name=name,
medium=medium,
diam=parameters["diam"],
le=parameters["le"],
rr=parameters["rr"],
gi=parameters["gi"],
)
def _port_temperature(self, port_name: str) -> float:
port = self.get_port(port_name)
if port.h_outflow > 0.0:
return max(port.h_outflow / self.medium.cp_ref, 1.0)
return self.medium.T_ref
@staticmethod
def _dynamic_viscosity(temperature_k: float) -> float:
if temperature_k <= 0.0:
raise ValueError("temperature_k must be positive")
reference_temperature = 293.15
reference_viscosity = 1.82e-5
sutherland_constant = 110.4
return (
reference_viscosity
* (temperature_k / reference_temperature) ** 1.5
* (reference_temperature + sutherland_constant)
/ (temperature_k + sutherland_constant)
)
def reynolds_number(self, mass_flow: float, temperature: float) -> float:
viscosity = self._dynamic_viscosity(temperature)
return 4.0 * abs(mass_flow) / (pi * self.diam * viscosity)
def friction_factor(self, reynolds_number: float) -> float:
if reynolds_number <= 0.0:
return 64_000_000.0
laminar = 64.0 / reynolds_number
if reynolds_number <= 2300.0:
return laminar
turbulent = 1.0 / (
-1.8 * log10((self.rr / 3.7) ** 1.11 + 6.9 / reynolds_number)
) ** 2
if reynolds_number >= 4000.0:
return turbulent
fraction = (reynolds_number - 2300.0) / 1700.0
return laminar + fraction * (turbulent - laminar)
def darcy_pressure_drop(
self,
mass_flow: float,
*,
density: float,
temperature: float,
) -> float:
if mass_flow == 0.0:
return 0.0
reynolds = self.reynolds_number(mass_flow, temperature)
friction = self.friction_factor(reynolds)
velocity = mass_flow / (density * self.area)
magnitude = (
friction
* (self.le / self.diam)
* density
* velocity
* velocity
/ 2.0
)
return magnitude if mass_flow > 0.0 else -magnitude
def _mass_flow_for_pressure_drop(
self,
pressure_drop: float,
*,
density: float,
temperature: float,
) -> float:
if pressure_drop <= 0.0:
return 0.0
upper = 1.0e-9
while self.darcy_pressure_drop(upper, density=density, temperature=temperature) < pressure_drop:
upper *= 10.0
if upper > 1.0e3:
raise ValueError("unable to bracket PNL00R resistance flow")
lower = 0.0
for _ in range(48):
middle = 0.5 * (lower + upper)
if self.darcy_pressure_drop(middle, density=density, temperature=temperature) < pressure_drop:
lower = middle
else:
upper = middle
return 0.5 * (lower + upper)
def mass_flow(self, p_1: float, p_2: float) -> float:
if p_1 == p_2:
return 0.0
pressure_difference = p_1 - p_2
upstream_pressure = max(p_1, p_2, 1.0)
upstream_temperature = self._port_temperature("port_1" if pressure_difference > 0.0 else "port_2")
density = max(self.medium.density(upstream_pressure, upstream_temperature), 1.0e-12)
magnitude = self._mass_flow_for_pressure_drop(
abs(pressure_difference),
density=density,
temperature=upstream_temperature,
)
return magnitude if pressure_difference > 0.0 else -magnitude
def component_result_values(self) -> Mapping[str, float]:
m_flow = self.mass_flow(self.port_1.p, self.port_2.p)
upstream_pressure = max(self.port_1.p, self.port_2.p, 1.0)
upstream_temperature = self._port_temperature(
"port_1" if self.port_1.p >= self.port_2.p else "port_2"
)
density = max(self.medium.density(upstream_pressure, upstream_temperature), 1.0e-12)
reynolds = self.reynolds_number(m_flow, upstream_temperature)
velocity = m_flow / (density * self.area)
cm = abs(m_flow) / max(self.area * upstream_pressure, 1.0e-18)
return {
"re": reynolds,
"cm": cm,
"v": velocity,
"ff": self.friction_factor(reynolds),
}
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
return (
EquationResidual(
id=f"{self.name}:mass_flow_balance",
owner="component",
owner_id=self.name,
relation="sumToZero",
variables=(
f"{self.name}.port_1.m_flow",
f"{self.name}.port_2.m_flow",
),
role="flow",
value=self.port_1.m_flow + self.port_2.m_flow,
),
EquationResidual(
id=f"{self.name}:pressure_flow_relation",
owner="component",
owner_id=self.name,
relation="constitutive",
variables=(
f"{self.name}.port_1.p",
f"{self.name}.port_2.p",
f"{self.name}.port_1.m_flow",
),
role="flow",
value=self.port_1.m_flow
- self.mass_flow(self.port_1.p, self.port_2.p),
),
)
def update_stream_outflows(self, connected_h: Mapping[str, float]) -> None:
self.port_1.h_outflow = connected_h["port_2"]
self.port_2.h_outflow = connected_h["port_1"]