公开PNRP17并接通实时气动机械耦合

This commit is contained in:
huojiarong committed 2026-08-02 14:51:31 +00:00
1 parent 503394f3ed
commit 1f4027573f
18 files changed
+693 -29

No files matched your search

+2 -1
View File
@@ -9,7 +9,7 @@ from app.simulation.core.catalog import (
LIBRARY = ComponentLibrarySpec(
id="amesim",
label="AMESim 组件库",
version="0.2.0",
version="0.3.0",
source_package="app.simulation.components.amesim",
temporary=True,
order=200,
@@ -33,6 +33,7 @@ LIBRARY = ComponentLibrarySpec(
"app.simulation.components.amesim.mechanical.translational:AmesimMecmas21",
"app.simulation.components.amesim.mechanical.translational:AmesimLstp00a",
"app.simulation.components.amesim.mechanical.translational:AmesimLmechn1",
"app.simulation.components.amesim.mechanical.pistons:AmesimPnrp17",
"app.simulation.components.amesim.storage.chambers:AmesimPnch023",
"app.simulation.components.amesim.storage.chambers:AmesimPnch012",
"app.simulation.components.amesim.flow.orifices:AmesimPnor001",
@@ -0,0 +1,228 @@
from __future__ import annotations
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
class AmesimPnrp17(AlgebraicComponent):
"""AMESim PNRP17 pneumatic piston with two mechanical faces.
Mechanical ports 2/5 share the piston-side motion and ports 3/4 share the
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_4", "left", order=30),
PortDisplaySpec("port_2", "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:
super().__init__(name=name)
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.")
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":
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_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 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,
}
@@ -249,10 +249,9 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
"""AMESim PNCH012 variable-volume pneumatic chamber.
AMESim supplies four external volume and volume-rate inputs through the
chamber ports. The current public System XML contract has pneumatic ports
only, so this first public model exposes those external volume inputs as SI
parameters. This represents fixed or prescribed-volume PNCH012 cases and is
not yet the full mechanical-coupled submodel.
chamber ports. Fixed/prescribed contributions remain available as SI
parameters, while connected moving-boundary components can now add live
volume and volume-rate values through the pneumatic connector contract.
"""
MODEL_TYPE = "amesim_pnch012"
@@ -433,14 +432,29 @@ class AmesimPnch012(ThermodynamicVolumeComponent):
) -> "AmesimPnch012":
return cls(name=name, medium=medium, **dict(parameters))
def connected_external_volume(self) -> float:
return sum(
getattr(getattr(self, port_name, None), "volume", 0.0)
for port_name in self.external_volumes
)
def connected_external_volume_rate(self) -> float:
return sum(
getattr(getattr(self, port_name, None), "volume_flow", 0.0)
for port_name in self.external_volume_rates
)
def total_volume(self) -> float:
minimum_volume = self.cvol0 / 100.0
return max(self.cvol0 + sum(self.external_volumes.values()), minimum_volume)
return max(
self.cvol0 + sum(self.external_volumes.values()) + self.connected_external_volume(),
minimum_volume,
)
def total_volume_rate(self) -> float:
if self.total_volume() <= self.cvol0 / 100.0:
return 0.0
return sum(self.external_volume_rates.values())
return sum(self.external_volume_rates.values()) + self.connected_external_volume_rate()
def get_state_vector(self) -> list[float]:
return self.state.as_vector()