公开更多 AMESim 组件并接入信号机械闭环
This commit is contained in:
1 parent
db4bdb4b70
commit
3f51a124d6
28 files changed
+3397
-58
No files matched your search
@@ -608,3 +608,94 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
|
||||
def update_stream_outflows(self, connected_h: Mapping[str, float]) -> None:
|
||||
self.port_2.h_outflow = connected_h["port_3"]
|
||||
self.port_3.h_outflow = connected_h["port_2"]
|
||||
|
||||
|
||||
class AmesimPnvo001SignalOpening(AmesimPnvo001FixedOpening):
|
||||
"""AMESim PNVO001 signal-controlled pneumatic orifice."""
|
||||
|
||||
MODEL_TYPE = "amesim_pnvo001"
|
||||
MODEL_VERSION = "0.1.0"
|
||||
PORTS = (
|
||||
PortDefinition.signal("res", nominal_role="input"),
|
||||
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
|
||||
PortDefinition.pneumatic("port_3", nominal_role="bidirectional"),
|
||||
)
|
||||
PARAMETERS = (
|
||||
ParameterDefinition("cq", 0.72, label="流量系数 Cq", quantity="dimensionless", unit="", minimum=1.0e-10, maximum=1.0),
|
||||
ParameterDefinition("area0", 5.0e-6, label="最大孔口面积", quantity="area", unit="m2", minimum=0.0, maximum=1.0),
|
||||
ParameterDefinition("Cv", 0.5, label="最大流量系数 Cv", quantity="dimensionless", unit="", minimum=0.0),
|
||||
ParameterDefinition("Kv", 0.4, label="最大流量系数 Kv", quantity="dimensionless", unit="", minimum=0.0),
|
||||
ParameterDefinition("gi", 1.0, label="气体类型索引", quantity="dimensionless", unit="", minimum=1.0, maximum=99.0),
|
||||
ParameterDefinition("flowset", 1.0, label="流量系数设置", quantity="dimensionless", unit="", minimum=1.0, maximum=3.0),
|
||||
ParameterDefinition("opening0", 1.0, label="初始开度", quantity="dimensionless", unit="", minimum=0.0, maximum=1.0),
|
||||
)
|
||||
RESULT_VARIABLES = AmesimPnvo001FixedOpening.RESULT_VARIABLES
|
||||
DISPLAY = ComponentDisplaySpec(
|
||||
label="PNVO001 信号开度气动孔口",
|
||||
library_id="amesim",
|
||||
category_id="flow",
|
||||
symbol="orifice",
|
||||
ports=(
|
||||
PortDisplaySpec("res", "left", order=5),
|
||||
PortDisplaySpec("port_2", "left", order=10),
|
||||
PortDisplaySpec("port_3", "right", order=20),
|
||||
),
|
||||
order=35,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
medium: IdealGasMedium,
|
||||
*,
|
||||
cq: float = 0.72,
|
||||
area0: float = 5.0e-6,
|
||||
Cv: float = 0.5,
|
||||
Kv: float = 0.4,
|
||||
gi: float = 1.0,
|
||||
flowset: float = 1.0,
|
||||
opening0: float = 1.0,
|
||||
) -> None:
|
||||
AlgebraicComponent.__init__(self, name=name)
|
||||
self.set_parameter_values(
|
||||
{
|
||||
"cq": cq,
|
||||
"area0": area0,
|
||||
"Cv": Cv,
|
||||
"Kv": Kv,
|
||||
"gi": gi,
|
||||
"flowset": flowset,
|
||||
"opening0": opening0,
|
||||
}
|
||||
)
|
||||
self.medium = medium
|
||||
self.cq = float(cq)
|
||||
self.area0 = float(area0)
|
||||
self.Cv = float(Cv)
|
||||
self.Kv = float(Kv)
|
||||
self.gi = self._integer_parameter("gi", gi)
|
||||
self.flowset = self._integer_parameter("flowset", flowset)
|
||||
if self.flowset not in {1, 2, 3}:
|
||||
raise ValueError("PNVO001 signal-opening flowset must be 1, 2, or 3.")
|
||||
self.opening0 = min(1.0, max(0.0, float(opening0)))
|
||||
self.res = self.register_declared_port("res")
|
||||
self.res.signal = self.opening0
|
||||
initial_h = medium.specific_enthalpy(medium.T_ref)
|
||||
self.port_2 = self.register_declared_port("port_2")
|
||||
self.port_2.h_outflow = initial_h
|
||||
self.port_3 = self.register_declared_port("port_3")
|
||||
self.port_3.h_outflow = initial_h
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
*,
|
||||
name: str,
|
||||
medium: IdealGasMedium,
|
||||
parameters: Mapping[str, float],
|
||||
) -> "AmesimPnvo001SignalOpening":
|
||||
return cls(name=name, medium=medium, **dict(parameters))
|
||||
|
||||
@property
|
||||
def opening(self) -> float:
|
||||
return min(1.0, max(0.0, self.res.signal))
|
||||
@@ -3,15 +3,17 @@ 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.base import AlgebraicComponent, DynamicComponent, ThermodynamicVolumeComponent
|
||||
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
|
||||
from app.simulation.core.equations import EquationResidual
|
||||
from app.simulation.core.metadata import (
|
||||
ParameterDefinition,
|
||||
ResultVariableDefinition,
|
||||
THERMODYNAMIC_VOLUME_RESULT_VARIABLES,
|
||||
)
|
||||
from app.simulation.core.medium import IdealGasMedium
|
||||
from app.simulation.core.medium import IdealGasMedium, ThermodynamicProperties
|
||||
from app.simulation.core.ports import PortDefinition
|
||||
from app.simulation.core.state import VolumeState
|
||||
|
||||
|
||||
class AmesimPnl00r(AlgebraicComponent):
|
||||
@@ -308,3 +310,887 @@ class AmesimPnl00r(AlgebraicComponent):
|
||||
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"]
|
||||
|
||||
|
||||
class AmesimPnl0001(ThermodynamicVolumeComponent):
|
||||
"""AMESim PNL0001 C-R pneumatic pipe with compressibility and friction."""
|
||||
|
||||
MODEL_TYPE = "amesim_pnl0001"
|
||||
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(
|
||||
"k",
|
||||
1.35,
|
||||
label="多方指数",
|
||||
quantity="dimensionless",
|
||||
unit="",
|
||||
minimum=0.0,
|
||||
minimum_exclusive=True,
|
||||
maximum=2.0,
|
||||
),
|
||||
ParameterDefinition(
|
||||
"kth",
|
||||
0.0,
|
||||
label="换热系数",
|
||||
quantity="heat_transfer_coefficient",
|
||||
unit="W/(m2*K)",
|
||||
minimum=0.0,
|
||||
),
|
||||
ParameterDefinition(
|
||||
"extemp",
|
||||
293.15,
|
||||
label="外部温度",
|
||||
quantity="temperature",
|
||||
unit="K",
|
||||
minimum=0.0,
|
||||
minimum_exclusive=True,
|
||||
),
|
||||
ParameterDefinition(
|
||||
"gi",
|
||||
1.0,
|
||||
label="气体类型索引",
|
||||
quantity="dimensionless",
|
||||
unit="",
|
||||
minimum=1.0,
|
||||
maximum=99.0,
|
||||
),
|
||||
ParameterDefinition(
|
||||
"mode",
|
||||
2.0,
|
||||
label="热模型",
|
||||
quantity="dimensionless",
|
||||
unit="",
|
||||
minimum=1.0,
|
||||
maximum=2.0,
|
||||
),
|
||||
ParameterDefinition(
|
||||
"p0",
|
||||
100000.0,
|
||||
label="初始压力",
|
||||
quantity="pressure",
|
||||
unit="Pa",
|
||||
minimum=0.0,
|
||||
minimum_exclusive=True,
|
||||
),
|
||||
ParameterDefinition(
|
||||
"T0",
|
||||
293.15,
|
||||
label="初始温度",
|
||||
quantity="temperature",
|
||||
unit="K",
|
||||
minimum=0.0,
|
||||
minimum_exclusive=True,
|
||||
),
|
||||
)
|
||||
RESULT_VARIABLES = THERMODYNAMIC_VOLUME_RESULT_VARIABLES + (
|
||||
ResultVariableDefinition(
|
||||
"re",
|
||||
label="Reynolds 数",
|
||||
quantity="dimensionless",
|
||||
unit="",
|
||||
category="derived",
|
||||
order=100,
|
||||
),
|
||||
ResultVariableDefinition(
|
||||
"cm",
|
||||
label="质量流量参数",
|
||||
quantity="dimensionless",
|
||||
unit="",
|
||||
category="derived",
|
||||
order=110,
|
||||
),
|
||||
ResultVariableDefinition(
|
||||
"v",
|
||||
label="平均气体速度",
|
||||
quantity="velocity",
|
||||
unit="m/s",
|
||||
category="derived",
|
||||
order=120,
|
||||
),
|
||||
ResultVariableDefinition(
|
||||
"ff",
|
||||
label="摩擦因子",
|
||||
quantity="dimensionless",
|
||||
unit="",
|
||||
category="derived",
|
||||
order=130,
|
||||
),
|
||||
)
|
||||
DISPLAY = ComponentDisplaySpec(
|
||||
label="PNL0001 C-R 动态管路",
|
||||
library_id="amesim",
|
||||
category_id="flow",
|
||||
symbol="pipe",
|
||||
ports=(
|
||||
PortDisplaySpec("port_1", "left", order=10),
|
||||
PortDisplaySpec("port_2", "right", order=20),
|
||||
),
|
||||
order=30,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
medium: IdealGasMedium,
|
||||
*,
|
||||
diam: float = 0.01,
|
||||
le: float = 1.0,
|
||||
rr: float = 1.0e-5,
|
||||
k: float = 1.35,
|
||||
kth: float = 0.0,
|
||||
extemp: float = 293.15,
|
||||
gi: float = 1.0,
|
||||
mode: float = 2.0,
|
||||
p0: float = 100000.0,
|
||||
T0: float = 293.15,
|
||||
) -> None:
|
||||
super().__init__(name=name)
|
||||
self.set_parameter_values(
|
||||
{
|
||||
"diam": diam,
|
||||
"le": le,
|
||||
"rr": rr,
|
||||
"k": k,
|
||||
"kth": kth,
|
||||
"extemp": extemp,
|
||||
"gi": gi,
|
||||
"mode": mode,
|
||||
"p0": p0,
|
||||
"T0": T0,
|
||||
}
|
||||
)
|
||||
self.medium = medium
|
||||
self.diam = float(diam)
|
||||
self.le = float(le)
|
||||
self.rr = float(rr)
|
||||
self.k = float(k)
|
||||
self.kth = float(kth)
|
||||
self.extemp = float(extemp)
|
||||
self.gi = self._integer_parameter("gi", gi)
|
||||
self.mode = self._integer_parameter("mode", mode)
|
||||
self.p0 = float(p0)
|
||||
self.T0 = float(T0)
|
||||
self.area = pi * self.diam * self.diam / 4.0
|
||||
self.volume = self.area * self.le
|
||||
self.exchange_area = pi * self.diam * self.le
|
||||
m0 = self.p0 * self.volume / (medium.R_gas * self.T0)
|
||||
U0 = m0 * medium.specific_internal_energy(self.T0)
|
||||
self.state = VolumeState(m=m0, U=U0)
|
||||
initial_h = medium.specific_enthalpy(self.T0)
|
||||
self.port_1 = self.register_declared_port("port_1")
|
||||
self.port_1.p = self.p0
|
||||
self.port_1.h_outflow = initial_h
|
||||
self.port_2 = self.register_declared_port("port_2")
|
||||
self.port_2.p = self.p0
|
||||
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"PNL0001 parameter {name} must be an integer value.")
|
||||
return int(rounded)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
*,
|
||||
name: str,
|
||||
medium: IdealGasMedium,
|
||||
parameters: Mapping[str, float],
|
||||
) -> "AmesimPnl0001":
|
||||
return cls(
|
||||
name=name,
|
||||
medium=medium,
|
||||
diam=parameters["diam"],
|
||||
le=parameters["le"],
|
||||
rr=parameters["rr"],
|
||||
k=parameters["k"],
|
||||
kth=parameters["kth"],
|
||||
extemp=parameters["extemp"],
|
||||
gi=parameters["gi"],
|
||||
mode=parameters["mode"],
|
||||
p0=parameters["p0"],
|
||||
T0=parameters["T0"],
|
||||
)
|
||||
|
||||
def get_state_vector(self) -> list[float]:
|
||||
return self.state.as_vector()
|
||||
|
||||
def set_state_vector(self, values: list[float]) -> None:
|
||||
self.state = VolumeState.from_vector(values)
|
||||
|
||||
def properties(self) -> ThermodynamicProperties:
|
||||
props = self.medium.properties_from_mU(self.state.m, self.state.U, self.volume)
|
||||
self.port_1.h_outflow = props.h
|
||||
self.port_2.p = props.p
|
||||
self.port_2.h_outflow = props.h
|
||||
return props
|
||||
|
||||
def refresh_thermodynamic_ports(self) -> ThermodynamicProperties:
|
||||
return self.properties()
|
||||
|
||||
def thermal_energy_flow_w(self, temperature: float) -> float:
|
||||
if self.mode == 1:
|
||||
return 0.0
|
||||
return self.kth * self.exchange_area * (self.extemp - temperature)
|
||||
|
||||
@staticmethod
|
||||
def _dynamic_viscosity(temperature_k: float) -> float:
|
||||
return AmesimPnl00r._dynamic_viscosity(temperature_k)
|
||||
|
||||
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:
|
||||
return AmesimPnl00r.friction_factor(self, reynolds_number)
|
||||
|
||||
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 PNL0001 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, temperature: 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)
|
||||
density = max(self.medium.density(upstream_pressure, temperature), 1.0e-12)
|
||||
magnitude = self._mass_flow_for_pressure_drop(
|
||||
abs(pressure_difference),
|
||||
density=density,
|
||||
temperature=temperature,
|
||||
)
|
||||
return magnitude if pressure_difference > 0.0 else -magnitude
|
||||
|
||||
def component_result_values(self) -> Mapping[str, float]:
|
||||
props = self.properties()
|
||||
flow = self.mass_flow(self.port_1.p, props.p, props.T)
|
||||
upstream_pressure = max(self.port_1.p, props.p, 1.0)
|
||||
density = max(self.medium.density(upstream_pressure, props.T), 1.0e-12)
|
||||
reynolds = self.reynolds_number(flow, props.T)
|
||||
return {
|
||||
"m": self.state.m,
|
||||
"U": self.state.U,
|
||||
"p": props.p,
|
||||
"T": props.T,
|
||||
"rho": props.rho,
|
||||
"u": props.u,
|
||||
"h": props.h,
|
||||
"re": reynolds,
|
||||
"cm": abs(flow) / max(self.area * upstream_pressure, 1.0e-18),
|
||||
"v": flow / (density * self.area),
|
||||
"ff": self.friction_factor(reynolds),
|
||||
}
|
||||
|
||||
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
|
||||
props = self.medium.properties_from_mU(self.state.m, self.state.U, self.volume)
|
||||
return (
|
||||
EquationResidual(
|
||||
id=f"{self.name}:port_2_pressure_state",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="state",
|
||||
variables=(f"{self.name}.port_2.p", f"{self.name}.state"),
|
||||
role="effort",
|
||||
value=self.port_2.p - props.p,
|
||||
),
|
||||
EquationResidual(
|
||||
id=f"{self.name}:port_1_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, props.p, props.T),
|
||||
),
|
||||
)
|
||||
|
||||
def state_derivative_from_ports(
|
||||
self,
|
||||
connected_h: Mapping[str, float],
|
||||
) -> list[float]:
|
||||
props = self.properties()
|
||||
inlet_h_1 = self.connection_inlet_enthalpy(
|
||||
port_m_flow=self.port_1.m_flow,
|
||||
connected_h=connected_h["port_1"],
|
||||
internal_h=props.h,
|
||||
)
|
||||
inlet_h_2 = self.connection_inlet_enthalpy(
|
||||
port_m_flow=self.port_2.m_flow,
|
||||
connected_h=connected_h["port_2"],
|
||||
internal_h=props.h,
|
||||
)
|
||||
derivative = VolumeState(
|
||||
m=self.port_1.m_flow + self.port_2.m_flow,
|
||||
U=(
|
||||
self.port_1.m_flow * inlet_h_1
|
||||
+ self.port_2.m_flow * inlet_h_2
|
||||
+ self.thermal_energy_flow_w(props.T)
|
||||
),
|
||||
)
|
||||
return derivative.as_vector()
|
||||
|
||||
|
||||
class AmesimPnl0002(AmesimPnl0001):
|
||||
"""AMESim PNL0002 R-C-R pneumatic pipe with one center compliance."""
|
||||
|
||||
MODEL_TYPE = "amesim_pnl0002"
|
||||
MODEL_VERSION = "0.1.0"
|
||||
PORTS = (
|
||||
PortDefinition.pneumatic("port_1", nominal_role="bidirectional"),
|
||||
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
|
||||
)
|
||||
PARAMETERS = AmesimPnl0001.PARAMETERS
|
||||
RESULT_VARIABLES = AmesimPnl0001.RESULT_VARIABLES
|
||||
DISPLAY = ComponentDisplaySpec(
|
||||
label="PNL0002 R-C-R 动态管路",
|
||||
library_id="amesim",
|
||||
category_id="flow",
|
||||
symbol="pipe",
|
||||
ports=(
|
||||
PortDisplaySpec("port_1", "left", order=10),
|
||||
PortDisplaySpec("port_2", "right", order=20),
|
||||
),
|
||||
order=40,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
*,
|
||||
name: str,
|
||||
medium: IdealGasMedium,
|
||||
parameters: Mapping[str, float],
|
||||
) -> "AmesimPnl0002":
|
||||
return cls(
|
||||
name=name,
|
||||
medium=medium,
|
||||
diam=parameters["diam"],
|
||||
le=parameters["le"],
|
||||
rr=parameters["rr"],
|
||||
k=parameters["k"],
|
||||
kth=parameters["kth"],
|
||||
extemp=parameters["extemp"],
|
||||
gi=parameters["gi"],
|
||||
mode=parameters["mode"],
|
||||
p0=parameters["p0"],
|
||||
T0=parameters["T0"],
|
||||
)
|
||||
|
||||
@property
|
||||
def resistance_length(self) -> float:
|
||||
return self.le / 2.0
|
||||
|
||||
def properties(self) -> ThermodynamicProperties:
|
||||
props = self.medium.properties_from_mU(self.state.m, self.state.U, self.volume)
|
||||
self.port_1.h_outflow = props.h
|
||||
self.port_2.h_outflow = props.h
|
||||
return props
|
||||
|
||||
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.resistance_length / self.diam)
|
||||
* density
|
||||
* velocity
|
||||
* velocity
|
||||
/ 2.0
|
||||
)
|
||||
return magnitude if mass_flow > 0.0 else -magnitude
|
||||
|
||||
def port_mass_flow(
|
||||
self,
|
||||
port_pressure: float,
|
||||
center_pressure: float,
|
||||
center_temperature: float,
|
||||
) -> float:
|
||||
return self.mass_flow(port_pressure, center_pressure, center_temperature)
|
||||
|
||||
def component_result_values(self) -> Mapping[str, float]:
|
||||
props = self.properties()
|
||||
flow_1 = self.port_mass_flow(self.port_1.p, props.p, props.T)
|
||||
flow_2 = self.port_mass_flow(self.port_2.p, props.p, props.T)
|
||||
diagnostic_flow = flow_1 if abs(flow_1) >= abs(flow_2) else flow_2
|
||||
upstream_pressure = max(self.port_1.p, self.port_2.p, props.p, 1.0)
|
||||
density = max(self.medium.density(upstream_pressure, props.T), 1.0e-12)
|
||||
reynolds = self.reynolds_number(diagnostic_flow, props.T)
|
||||
return {
|
||||
"m": self.state.m,
|
||||
"U": self.state.U,
|
||||
"p": props.p,
|
||||
"T": props.T,
|
||||
"rho": props.rho,
|
||||
"u": props.u,
|
||||
"h": props.h,
|
||||
"re": reynolds,
|
||||
"cm": abs(diagnostic_flow) / max(self.area * upstream_pressure, 1.0e-18),
|
||||
"v": diagnostic_flow / (density * self.area),
|
||||
"ff": self.friction_factor(reynolds),
|
||||
}
|
||||
|
||||
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
|
||||
props = self.medium.properties_from_mU(self.state.m, self.state.U, self.volume)
|
||||
return (
|
||||
EquationResidual(
|
||||
id=f"{self.name}:port_1_pressure_flow_relation",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="constitutive",
|
||||
variables=(
|
||||
f"{self.name}.port_1.p",
|
||||
f"{self.name}.state",
|
||||
f"{self.name}.port_1.m_flow",
|
||||
),
|
||||
role="flow",
|
||||
value=self.port_1.m_flow
|
||||
- self.port_mass_flow(self.port_1.p, props.p, props.T),
|
||||
),
|
||||
EquationResidual(
|
||||
id=f"{self.name}:port_2_pressure_flow_relation",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="constitutive",
|
||||
variables=(
|
||||
f"{self.name}.port_2.p",
|
||||
f"{self.name}.state",
|
||||
f"{self.name}.port_2.m_flow",
|
||||
),
|
||||
role="flow",
|
||||
value=self.port_2.m_flow
|
||||
- self.port_mass_flow(self.port_2.p, props.p, props.T),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class AmesimPnl0003(DynamicComponent):
|
||||
"""AMESim PNL0003 C-R-C pneumatic pipe with two end compliances."""
|
||||
|
||||
state_size = 4
|
||||
MODEL_TYPE = "amesim_pnl0003"
|
||||
MODEL_VERSION = "0.1.0"
|
||||
PORTS = (
|
||||
PortDefinition.pneumatic("port_1", nominal_role="bidirectional"),
|
||||
PortDefinition.pneumatic("port_2", nominal_role="bidirectional"),
|
||||
)
|
||||
PARAMETERS = AmesimPnl0001.PARAMETERS[:-2] + (
|
||||
ParameterDefinition(
|
||||
"p1_0",
|
||||
100000.0,
|
||||
label="端口 1 初始压力",
|
||||
quantity="pressure",
|
||||
unit="Pa",
|
||||
minimum=0.0,
|
||||
minimum_exclusive=True,
|
||||
),
|
||||
ParameterDefinition(
|
||||
"T1_0",
|
||||
293.15,
|
||||
label="端口 1 初始温度",
|
||||
quantity="temperature",
|
||||
unit="K",
|
||||
minimum=0.0,
|
||||
minimum_exclusive=True,
|
||||
),
|
||||
ParameterDefinition(
|
||||
"p2_0",
|
||||
100000.0,
|
||||
label="端口 2 初始压力",
|
||||
quantity="pressure",
|
||||
unit="Pa",
|
||||
minimum=0.0,
|
||||
minimum_exclusive=True,
|
||||
),
|
||||
ParameterDefinition(
|
||||
"T2_0",
|
||||
293.15,
|
||||
label="端口 2 初始温度",
|
||||
quantity="temperature",
|
||||
unit="K",
|
||||
minimum=0.0,
|
||||
minimum_exclusive=True,
|
||||
),
|
||||
)
|
||||
RESULT_VARIABLES = (
|
||||
ResultVariableDefinition("m1", "端口 1 侧质量", "mass", "kg", "state", 10),
|
||||
ResultVariableDefinition("U1", "端口 1 侧内能", "internal_energy", "J", "state", 20),
|
||||
ResultVariableDefinition("p1", "端口 1 侧压力", "pressure", "Pa", "thermodynamic", 30),
|
||||
ResultVariableDefinition("T1", "端口 1 侧温度", "temperature", "K", "thermodynamic", 40),
|
||||
ResultVariableDefinition("rho1", "端口 1 侧密度", "density", "kg/m³", "thermodynamic", 50),
|
||||
ResultVariableDefinition("u1", "端口 1 侧比内能", "specific_internal_energy", "J/kg", "thermodynamic", 60),
|
||||
ResultVariableDefinition("h1", "端口 1 侧比焓", "specific_enthalpy", "J/kg", "thermodynamic", 70),
|
||||
ResultVariableDefinition("m2", "端口 2 侧质量", "mass", "kg", "state", 80),
|
||||
ResultVariableDefinition("U2", "端口 2 侧内能", "internal_energy", "J", "state", 90),
|
||||
ResultVariableDefinition("p2", "端口 2 侧压力", "pressure", "Pa", "thermodynamic", 100),
|
||||
ResultVariableDefinition("T2", "端口 2 侧温度", "temperature", "K", "thermodynamic", 110),
|
||||
ResultVariableDefinition("rho2", "端口 2 侧密度", "density", "kg/m³", "thermodynamic", 120),
|
||||
ResultVariableDefinition("u2", "端口 2 侧比内能", "specific_internal_energy", "J/kg", "thermodynamic", 130),
|
||||
ResultVariableDefinition("h2", "端口 2 侧比焓", "specific_enthalpy", "J/kg", "thermodynamic", 140),
|
||||
ResultVariableDefinition("dmctr", "中心质量流量", "mass_flow", "kg/s", "derived", 150),
|
||||
ResultVariableDefinition("re", "Reynolds 数", "dimensionless", "", "derived", 160),
|
||||
ResultVariableDefinition("cm", "质量流量参数", "dimensionless", "", "derived", 170),
|
||||
ResultVariableDefinition("v", "平均气体速度", "velocity", "m/s", "derived", 180),
|
||||
ResultVariableDefinition("ff", "摩擦因子", "dimensionless", "", "derived", 190),
|
||||
)
|
||||
DISPLAY = ComponentDisplaySpec(
|
||||
label="PNL0003 C-R-C 动态管路",
|
||||
library_id="amesim",
|
||||
category_id="flow",
|
||||
symbol="pipe",
|
||||
ports=(
|
||||
PortDisplaySpec("port_1", "left", order=10),
|
||||
PortDisplaySpec("port_2", "right", order=20),
|
||||
),
|
||||
order=50,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
medium: IdealGasMedium,
|
||||
*,
|
||||
diam: float = 0.01,
|
||||
le: float = 1.0,
|
||||
rr: float = 1.0e-5,
|
||||
k: float = 1.35,
|
||||
kth: float = 0.0,
|
||||
extemp: float = 293.15,
|
||||
gi: float = 1.0,
|
||||
mode: float = 2.0,
|
||||
p1_0: float = 100000.0,
|
||||
T1_0: float = 293.15,
|
||||
p2_0: float = 100000.0,
|
||||
T2_0: float = 293.15,
|
||||
) -> None:
|
||||
super().__init__(name=name)
|
||||
self.set_parameter_values(
|
||||
{
|
||||
"diam": diam,
|
||||
"le": le,
|
||||
"rr": rr,
|
||||
"k": k,
|
||||
"kth": kth,
|
||||
"extemp": extemp,
|
||||
"gi": gi,
|
||||
"mode": mode,
|
||||
"p1_0": p1_0,
|
||||
"T1_0": T1_0,
|
||||
"p2_0": p2_0,
|
||||
"T2_0": T2_0,
|
||||
}
|
||||
)
|
||||
self.medium = medium
|
||||
self.diam = float(diam)
|
||||
self.le = float(le)
|
||||
self.rr = float(rr)
|
||||
self.k = float(k)
|
||||
self.kth = float(kth)
|
||||
self.extemp = float(extemp)
|
||||
self.gi = AmesimPnl0001._integer_parameter("gi", gi)
|
||||
self.mode = AmesimPnl0001._integer_parameter("mode", mode)
|
||||
self.area = pi * self.diam * self.diam / 4.0
|
||||
self.volume = self.area * self.le
|
||||
self.compliance_volume = self.volume / 2.0
|
||||
self.exchange_area = pi * self.diam * self.le
|
||||
self.state_1 = self._initial_state(float(p1_0), float(T1_0))
|
||||
self.state_2 = self._initial_state(float(p2_0), float(T2_0))
|
||||
h1 = medium.specific_enthalpy(float(T1_0))
|
||||
h2 = medium.specific_enthalpy(float(T2_0))
|
||||
self.port_1 = self.register_declared_port("port_1")
|
||||
self.port_1.p = float(p1_0)
|
||||
self.port_1.h_outflow = h1
|
||||
self.port_2 = self.register_declared_port("port_2")
|
||||
self.port_2.p = float(p2_0)
|
||||
self.port_2.h_outflow = h2
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
*,
|
||||
name: str,
|
||||
medium: IdealGasMedium,
|
||||
parameters: Mapping[str, float],
|
||||
) -> "AmesimPnl0003":
|
||||
return cls(name=name, medium=medium, **dict(parameters))
|
||||
|
||||
def _initial_state(self, pressure: float, temperature: float) -> VolumeState:
|
||||
mass = pressure * self.compliance_volume / (self.medium.R_gas * temperature)
|
||||
return VolumeState(m=mass, U=mass * self.medium.specific_internal_energy(temperature))
|
||||
|
||||
def get_state_vector(self) -> list[float]:
|
||||
return [*self.state_1.as_vector(), *self.state_2.as_vector()]
|
||||
|
||||
def set_state_vector(self, values: list[float]) -> None:
|
||||
if len(values) != 4:
|
||||
raise ValueError("PNL0003 state vector requires four values")
|
||||
self.state_1 = VolumeState.from_vector(values[:2])
|
||||
self.state_2 = VolumeState.from_vector(values[2:])
|
||||
|
||||
def _properties(self, state: VolumeState) -> ThermodynamicProperties:
|
||||
return self.medium.properties_from_mU(state.m, state.U, self.compliance_volume)
|
||||
|
||||
def properties_1(self) -> ThermodynamicProperties:
|
||||
props = self._properties(self.state_1)
|
||||
self.port_1.p = props.p
|
||||
self.port_1.h_outflow = props.h
|
||||
return props
|
||||
|
||||
def properties_2(self) -> ThermodynamicProperties:
|
||||
props = self._properties(self.state_2)
|
||||
self.port_2.p = props.p
|
||||
self.port_2.h_outflow = props.h
|
||||
return props
|
||||
|
||||
def refresh_thermodynamic_ports(self) -> tuple[ThermodynamicProperties, ThermodynamicProperties]:
|
||||
return self.properties_1(), self.properties_2()
|
||||
|
||||
@staticmethod
|
||||
def _dynamic_viscosity(temperature_k: float) -> float:
|
||||
return AmesimPnl00r._dynamic_viscosity(temperature_k)
|
||||
|
||||
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:
|
||||
return AmesimPnl00r.friction_factor(self, reynolds_number)
|
||||
|
||||
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 PNL0003 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 resistance_mass_flow(self) -> float:
|
||||
port_1 = self._properties(self.state_1)
|
||||
port_2 = self._properties(self.state_2)
|
||||
pressure_difference = port_1.p - port_2.p
|
||||
if pressure_difference == 0.0:
|
||||
return 0.0
|
||||
upstream = port_1 if pressure_difference > 0.0 else port_2
|
||||
magnitude = self._mass_flow_for_pressure_drop(
|
||||
abs(pressure_difference),
|
||||
density=upstream.rho,
|
||||
temperature=upstream.T,
|
||||
)
|
||||
return magnitude if pressure_difference > 0.0 else -magnitude
|
||||
|
||||
def _heat_flow_each(self, temperature_1: float, temperature_2: float) -> float:
|
||||
if self.mode == 1:
|
||||
return 0.0
|
||||
return self.kth * self.exchange_area * (self.extemp - 0.5 * (temperature_1 + temperature_2)) / 2.0
|
||||
|
||||
def component_result_values(self) -> Mapping[str, float]:
|
||||
port_1 = self.properties_1()
|
||||
port_2 = self.properties_2()
|
||||
center_flow = self.resistance_mass_flow()
|
||||
upstream = port_1 if center_flow >= 0.0 else port_2
|
||||
reynolds = self.reynolds_number(center_flow, upstream.T)
|
||||
return {
|
||||
"m1": self.state_1.m,
|
||||
"U1": self.state_1.U,
|
||||
"p1": port_1.p,
|
||||
"T1": port_1.T,
|
||||
"rho1": port_1.rho,
|
||||
"u1": port_1.u,
|
||||
"h1": port_1.h,
|
||||
"m2": self.state_2.m,
|
||||
"U2": self.state_2.U,
|
||||
"p2": port_2.p,
|
||||
"T2": port_2.T,
|
||||
"rho2": port_2.rho,
|
||||
"u2": port_2.u,
|
||||
"h2": port_2.h,
|
||||
"dmctr": center_flow,
|
||||
"re": reynolds,
|
||||
"cm": abs(center_flow) / max(self.area * max(port_1.p, port_2.p, 1.0), 1.0e-18),
|
||||
"v": center_flow / (max(upstream.rho, 1.0e-12) * self.area),
|
||||
"ff": self.friction_factor(reynolds),
|
||||
}
|
||||
|
||||
def pressure_flow_equation_residuals(self) -> tuple[EquationResidual, ...]:
|
||||
port_1 = self._properties(self.state_1)
|
||||
port_2 = self._properties(self.state_2)
|
||||
return (
|
||||
EquationResidual(
|
||||
id=f"{self.name}:port_1_pressure_state",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="state",
|
||||
variables=(f"{self.name}.port_1.p", f"{self.name}.state"),
|
||||
role="effort",
|
||||
value=self.port_1.p - port_1.p,
|
||||
),
|
||||
EquationResidual(
|
||||
id=f"{self.name}:port_2_pressure_state",
|
||||
owner="component",
|
||||
owner_id=self.name,
|
||||
relation="state",
|
||||
variables=(f"{self.name}.port_2.p", f"{self.name}.state"),
|
||||
role="effort",
|
||||
value=self.port_2.p - port_2.p,
|
||||
),
|
||||
)
|
||||
|
||||
def state_derivative_from_ports(self, connected_h: Mapping[str, float]) -> list[float]:
|
||||
port_1 = self.properties_1()
|
||||
port_2 = self.properties_2()
|
||||
center_flow = self.resistance_mass_flow()
|
||||
heat_flow_each = self._heat_flow_each(port_1.T, port_2.T)
|
||||
port_1_external_h = self.connection_inlet_enthalpy(
|
||||
port_m_flow=self.port_1.m_flow,
|
||||
connected_h=connected_h["port_1"],
|
||||
internal_h=port_1.h,
|
||||
)
|
||||
port_2_external_h = self.connection_inlet_enthalpy(
|
||||
port_m_flow=self.port_2.m_flow,
|
||||
connected_h=connected_h["port_2"],
|
||||
internal_h=port_2.h,
|
||||
)
|
||||
port_1_center_h = self.connection_inlet_enthalpy(
|
||||
port_m_flow=-center_flow,
|
||||
connected_h=port_2.h,
|
||||
internal_h=port_1.h,
|
||||
)
|
||||
port_2_center_h = self.connection_inlet_enthalpy(
|
||||
port_m_flow=center_flow,
|
||||
connected_h=port_1.h,
|
||||
internal_h=port_2.h,
|
||||
)
|
||||
d1 = VolumeState(
|
||||
m=self.port_1.m_flow - center_flow,
|
||||
U=self.port_1.m_flow * port_1_external_h - center_flow * port_1_center_h + heat_flow_each,
|
||||
)
|
||||
d2 = VolumeState(
|
||||
m=self.port_2.m_flow + center_flow,
|
||||
U=self.port_2.m_flow * port_2_external_h + center_flow * port_2_center_h + heat_flow_each,
|
||||
)
|
||||
return [*d1.as_vector(), *d2.as_vector()]
|
||||
Reference in new issue
Block a user