Files
SystemSimulationApp/app/simulation/components/amesim/flow/orifices.py
T

172 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Component parameters, ports and output definitions; numerical equations execute in C."""
from __future__ import annotations
from collections.abc import Mapping
from math import isclose, sqrt
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, ParameterGroupDisplaySpec, PortDisplaySpec
from app.simulation.core.metadata import ParameterCondition, ParameterDefinition, ParameterOption, ResultVariableDefinition
from app.simulation.core.medium import GasMedium
from app.simulation.core.ports import PortDefinition
_FLOW_COEFFICIENT_OPTIONS = (ParameterOption(1.0, 'Cq'), ParameterOption(2.0, 'Cv'), ParameterOption(3.0, 'Kv'))
_FLOWSET_USES_CQ = (ParameterCondition('flowset', (1.0,)),)
_FLOWSET_USES_CV = (ParameterCondition('flowset', (2.0,)),)
_FLOWSET_USES_KV = (ParameterCondition('flowset', (3.0,)),)
_PNOR001_FLOW_COEFFICIENT_GROUP = ParameterGroupDisplaySpec(id='flow_coefficient', label='流量系数', parameters=('cq', 'area', 'Cv', 'Kv'), order=10)
_PNVO001_FLOW_COEFFICIENT_GROUP = ParameterGroupDisplaySpec(id='flow_coefficient', label='流量系数', parameters=('cq', 'area0', 'Cv', 'Kv'), order=10)
class AmesimPnor001(AlgebraicComponent):
"""AMESim PNOR001 constant-flow-coefficient pneumatic orifice.
This public component preserves the PNOR001 catalog/XML contract and uses
real-gas pressure-ratio flow with AMESim-style near-equal-pressure smoothing.
"""
MODEL_TYPE = 'amesim_pnor001'
MODEL_VERSION = '0.3.0'
PORTS = (PortDefinition.pneumatic('port_1', nominal_role='bidirectional'), PortDefinition.pneumatic('port_2', nominal_role='bidirectional'))
PARAMETERS = (AMESIM_GAS_INDEX_PARAMETER, ParameterDefinition('cq', 0.72, label='流量系数 Cq', quantity='dimensionless', unit='', minimum=1e-10, maximum=1.0, description='孔口实际质量流量相对于理想可压缩流量的无量纲修正系数。', visible_when=_FLOWSET_USES_CQ), ParameterDefinition('area', 5e-06, label='孔口面积', quantity='area', unit='m2', minimum=0.0, maximum=1.0, description='选择 Cq/面积方式时用于流量计算的有效孔口面积。', visible_when=_FLOWSET_USES_CQ), ParameterDefinition('Cv', 0.5, label='流量系数 Cv', quantity='dimensionless', unit='', minimum=0.0, description='选择 Cv 方式时使用的英制流量系数。', visible_when=_FLOWSET_USES_CV), ParameterDefinition('Kv', 0.4, label='流量系数 Kv', quantity='dimensionless', unit='', minimum=0.0, description='选择 Kv 方式时使用的公制流量系数。', visible_when=_FLOWSET_USES_KV), ParameterDefinition('flowset', 1.0, label='流量系数设置', quantity='dimensionless', unit='', minimum=1.0, maximum=3.0, editor='choice', options=_FLOW_COEFFICIENT_OPTIONS, description='流量参数方式:1 使用 Cq 和面积,2 使用 Cv,3 使用 Kv。'))
RESULT_VARIABLES = (ResultVariableDefinition('cm', label='质量流量参数', quantity='dimensionless', unit='', category='derived', order=10), ResultVariableDefinition('gasvel', label='缩流截面气体速度', quantity='velocity', unit='m/s', category='derived', order=20))
DISPLAY = ComponentDisplaySpec(label='PNOR001 常系数气动孔口', library_id='amesim', category_id='flow', symbol='amesim_pnor001', ports=(PortDisplaySpec('port_1', 'left', order=10), PortDisplaySpec('port_2', 'right', order=20)), order=10, parameter_groups=(_PNOR001_FLOW_COEFFICIENT_GROUP,))
def __init__(self, name: str, medium: GasMedium, *, cq: float=0.72, area: float=5e-06, Cv: float=0.5, Kv: float=0.4, gi: float=1.0, flowset: float=1.0) -> None:
super().__init__(name=name)
self.set_parameter_values({'cq': cq, 'area': area, 'Cv': Cv, 'Kv': Kv, 'gi': gi, 'flowset': flowset})
self.medium = medium
self.cq = float(cq)
self.area = float(area)
self.Cv = float(Cv)
self.Kv = float(Kv)
self.gi = normalize_amesim_gas_index(gi)
self.flowset = self._integer_parameter('flowset', flowset)
if self.flowset not in {1, 2, 3}:
raise ValueError('PNOR001 flowset must be 1, 2, or 3.')
self.port_1 = self.register_declared_port('port_1')
self.port_2 = self.register_declared_port('port_2')
@staticmethod
def _integer_parameter(name: str, value: float) -> int:
rounded = round(value)
if not isclose(value, rounded, rel_tol=0.0, abs_tol=1e-12):
raise ValueError(f'PNOR001 parameter {name} must be an integer value.')
return int(rounded)
@classmethod
def create(cls, *, name: str, medium: GasMedium, parameters: Mapping[str, float]) -> AmesimPnor001:
return cls(name=name, medium=medium, cq=parameters['cq'], area=parameters['area'], Cv=parameters['Cv'], Kv=parameters['Kv'], gi=parameters['gi'], flowset=parameters['flowset'])
@property
def effective_cq(self) -> float:
return self.cq if self.flowset == 1 else 0.72
@property
def effective_area(self) -> float:
if self.flowset == 1:
return self.area
if self.flowset == 2:
return self._area_from_cv(self.Cv, self.effective_cq)
return self._area_from_kv(self.Kv, self.effective_cq)
@staticmethod
def _area_from_cv(Cv: float, cq: float) -> float:
water_density = 999.0
reference_flow_m3_s = Cv * 6.30901964e-05
reference_dp_pa = 6894.75729
return reference_flow_m3_s / (cq * sqrt(2.0 * reference_dp_pa / water_density))
@staticmethod
def _area_from_kv(Kv: float, cq: float) -> float:
water_density = 999.0
reference_flow_m3_s = Kv / 3600.0
reference_dp_pa = 100000.0
return reference_flow_m3_s / (cq * sqrt(2.0 * reference_dp_pa / water_density))
EQUATIONS = ({'id': '__MODEL__:mass_flow_balance', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'sumToZero', 'variables': ['__MODEL__.port_1.m_flow', '__MODEL__.port_2.m_flow'], 'role': 'flow'}, {'id': '__MODEL__:pressure_flow_relation', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'constitutive', 'variables': ['__MODEL__.port_1.p', '__MODEL__.port_2.p', '__MODEL__.port_1.m_flow'], 'role': 'flow'})
class AmesimPnvo001FixedOpening(AlgebraicComponent):
"""Fixed-opening public variant of AMESim PNVO001.
Full PNVO001 has a signal input port. The current public component library
does not support signal simulation, so this model exposes the pneumatic
ports and replaces the signal with a normalized `opening` parameter.
"""
MODEL_TYPE = 'amesim_pnvo001_fixed'
MODEL_VERSION = '0.2.0'
PORTS = (PortDefinition.pneumatic('port_2', nominal_role='bidirectional'), PortDefinition.pneumatic('port_3', nominal_role='bidirectional'))
PARAMETERS = (AMESIM_GAS_INDEX_PARAMETER, ParameterDefinition('cq', 0.72, label='流量系数 Cq', quantity='dimensionless', unit='', minimum=1e-10, maximum=1.0, description='孔口实际质量流量相对于理想可压缩流量的无量纲修正系数。', visible_when=_FLOWSET_USES_CQ), ParameterDefinition('area0', 5e-06, label='最大孔口面积', quantity='area', unit='m2', minimum=0.0, maximum=1.0, description='阀门完全开启时的最大有效孔口面积。', visible_when=_FLOWSET_USES_CQ), ParameterDefinition('Cv', 0.5, label='最大流量系数 Cv', quantity='dimensionless', unit='', minimum=0.0, description='选择 Cv 方式时使用的最大英制流量系数。', visible_when=_FLOWSET_USES_CV), ParameterDefinition('Kv', 0.4, label='最大流量系数 Kv', quantity='dimensionless', unit='', minimum=0.0, description='选择 Kv 方式时使用的最大公制流量系数。', visible_when=_FLOWSET_USES_KV), ParameterDefinition('flowset', 1.0, label='流量系数设置', quantity='dimensionless', unit='', minimum=1.0, maximum=3.0, editor='choice', options=_FLOW_COEFFICIENT_OPTIONS, description='流量参数方式:1 使用 Cq 和面积,2 使用 Cv,3 使用 Kv。'), ParameterDefinition('opening', 1.0, label='固定开度', quantity='dimensionless', unit='', minimum=0.0, maximum=1.0, description='固定的归一化阀门开度;0 表示关闭,1 表示完全开启。'))
RESULT_VARIABLES = (ResultVariableDefinition('xv', label='有效开度', quantity='dimensionless', unit='', category='derived', order=10), ResultVariableDefinition('cm', label='质量流量参数', quantity='dimensionless', unit='', category='derived', order=20), ResultVariableDefinition('gasvel', label='缩流截面气体速度', quantity='velocity', unit='m/s', category='derived', order=30))
DISPLAY = ComponentDisplaySpec(label='PNVO001 固定开度气动孔口', library_id='amesim', category_id='flow', symbol='amesim_pnvo001_fixed', ports=(PortDisplaySpec('port_2', 'right', order=10), PortDisplaySpec('port_3', 'left', order=20)), order=30, parameter_groups=(_PNVO001_FLOW_COEFFICIENT_GROUP,))
def __init__(self, name: str, medium: GasMedium, *, cq: float=0.72, area0: float=5e-06, Cv: float=0.5, Kv: float=0.4, gi: float=1.0, flowset: float=1.0, opening: float=1.0) -> None:
super().__init__(name=name)
self.set_parameter_values({'cq': cq, 'area0': area0, 'Cv': Cv, 'Kv': Kv, 'gi': gi, 'flowset': flowset, 'opening': opening})
self.medium = medium
self.cq = float(cq)
self.area0 = float(area0)
self.Cv = float(Cv)
self.Kv = float(Kv)
self.gi = normalize_amesim_gas_index(gi)
self.flowset = self._integer_parameter('flowset', flowset)
if self.flowset not in {1, 2, 3}:
raise ValueError('PNVO001 fixed-opening flowset must be 1, 2, or 3.')
self.opening = min(1.0, max(0.0, float(opening)))
self.port_2 = self.register_declared_port('port_2')
self.port_3 = self.register_declared_port('port_3')
@staticmethod
def _integer_parameter(name: str, value: float) -> int:
rounded = round(value)
if not isclose(value, rounded, rel_tol=0.0, abs_tol=1e-12):
raise ValueError(f'PNVO001 fixed-opening parameter {name} must be an integer value.')
return int(rounded)
@classmethod
def create(cls, *, name: str, medium: GasMedium, parameters: Mapping[str, float]) -> AmesimPnvo001FixedOpening:
return cls(name=name, medium=medium, cq=parameters['cq'], area0=parameters['area0'], Cv=parameters['Cv'], Kv=parameters['Kv'], gi=parameters['gi'], flowset=parameters['flowset'], opening=parameters['opening'])
@property
def effective_cq(self) -> float:
return self.cq if self.flowset == 1 else 0.72
@property
def maximum_area(self) -> float:
if self.flowset == 1:
return self.area0
if self.flowset == 2:
return AmesimPnor001._area_from_cv(self.Cv, self.effective_cq)
return AmesimPnor001._area_from_kv(self.Kv, self.effective_cq)
@property
def effective_area(self) -> float:
return self.opening * self.maximum_area
EQUATIONS = ({'id': '__MODEL__:mass_flow_balance', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'sumToZero', 'variables': ['__MODEL__.port_2.m_flow', '__MODEL__.port_3.m_flow'], 'role': 'flow'}, {'id': '__MODEL__:pressure_flow_relation', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'constitutive', 'variables': ['__MODEL__.port_2.p', '__MODEL__.port_3.p', '__MODEL__.port_2.m_flow'], 'role': 'flow'})
class AmesimPnvo001SignalOpening(AmesimPnvo001FixedOpening):
"""AMESim PNVO001 signal-controlled pneumatic orifice."""
MODEL_TYPE = 'amesim_pnvo001'
MODEL_VERSION = '0.2.0'
PORTS = (PortDefinition.signal('res', nominal_role='input'), PortDefinition.pneumatic('port_2', nominal_role='bidirectional'), PortDefinition.pneumatic('port_3', nominal_role='bidirectional'))
PARAMETERS = (AMESIM_GAS_INDEX_PARAMETER, ParameterDefinition('cq', 0.72, label='流量系数 Cq', quantity='dimensionless', unit='', minimum=1e-10, maximum=1.0, description='孔口实际质量流量相对于理想可压缩流量的无量纲修正系数。', visible_when=_FLOWSET_USES_CQ), ParameterDefinition('area0', 5e-06, label='最大孔口面积', quantity='area', unit='m2', minimum=0.0, maximum=1.0, description='阀门完全开启时的最大有效孔口面积。', visible_when=_FLOWSET_USES_CQ), ParameterDefinition('Cv', 0.5, label='最大流量系数 Cv', quantity='dimensionless', unit='', minimum=0.0, description='选择 Cv 方式时使用的最大英制流量系数。', visible_when=_FLOWSET_USES_CV), ParameterDefinition('Kv', 0.4, label='最大流量系数 Kv', quantity='dimensionless', unit='', minimum=0.0, description='选择 Kv 方式时使用的最大公制流量系数。', visible_when=_FLOWSET_USES_KV), ParameterDefinition('flowset', 1.0, label='流量系数设置', quantity='dimensionless', unit='', minimum=1.0, maximum=3.0, editor='choice', options=_FLOW_COEFFICIENT_OPTIONS, description='流量参数方式:1 使用 Cq 和面积,2 使用 Cv,3 使用 Kv。'), ParameterDefinition('opening0', 1.0, label='初始开度', quantity='dimensionless', unit='', minimum=0.0, maximum=1.0, description='信号尚未传播时使用的归一化初始开度;0 表示关闭,1 表示完全开启。'))
RESULT_VARIABLES = AmesimPnvo001FixedOpening.RESULT_VARIABLES
DISPLAY = ComponentDisplaySpec(label='PNVO001 信号开度气动孔口', library_id='amesim', category_id='flow', symbol='amesim_pnvo001', ports=(PortDisplaySpec('res', 'left', order=5), PortDisplaySpec('port_2', 'right', order=10), PortDisplaySpec('port_3', 'left', order=20)), order=35, parameter_groups=(_PNVO001_FLOW_COEFFICIENT_GROUP,))
def __init__(self, name: str, medium: GasMedium, *, cq: float=0.72, area0: float=5e-06, 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 = normalize_amesim_gas_index(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.port_2 = self.register_declared_port('port_2')
self.port_3 = self.register_declared_port('port_3')
@classmethod
def create(cls, *, name: str, medium: GasMedium, parameters: Mapping[str, float]) -> 'AmesimPnvo001SignalOpening':
return cls(name=name, medium=medium, **dict(parameters))
EQUATIONS = ({'id': '__MODEL__:mass_flow_balance', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'sumToZero', 'variables': ['__MODEL__.port_2.m_flow', '__MODEL__.port_3.m_flow'], 'role': 'flow'}, {'id': '__MODEL__:pressure_flow_relation', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'constitutive', 'variables': ['__MODEL__.port_2.p', '__MODEL__.port_3.p', '__MODEL__.port_2.m_flow'], 'role': 'flow'})