182 lines
22 KiB
Python
182 lines
22 KiB
Python
"""Component parameters, ports and output definitions; numerical equations execute in C."""
|
|
from __future__ import annotations
|
|
from collections.abc import Mapping
|
|
from app.simulation.core.base import AlgebraicComponent, DynamicComponent
|
|
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 IdealGasMedium
|
|
from app.simulation.core.ports import PortDefinition
|
|
_MECMAS21_FRICTION_ENABLED = ParameterCondition('useFriction', (2.0,))
|
|
_MECMAS21_NON_RESTITUTION = ParameterCondition('stoptype', (1.0, 2.0, 4.0))
|
|
_MECMAS21_LIMITS_ENABLED = ParameterCondition('stoptype', (1.0, 2.0, 3.0))
|
|
_MECMAS21_ELASTIC_STOP = ParameterCondition('stoptype', (2.0,))
|
|
_MECMAS21_RESTITUTION_STOP = ParameterCondition('stoptype', (3.0,))
|
|
_MECMAS21_ADVANCED_FRICTION = ParameterCondition('frictionType', (2.0,))
|
|
_MECMAS21_STRIBECK_ENABLED = ParameterCondition('strib', (2.0,))
|
|
_LSTP00A_NUMERICAL_STIFFNESS = ParameterCondition('stiffmode', (1.0,))
|
|
_LSTP00A_GEOMETRICAL_STIFFNESS = ParameterCondition('stiffmode', (2.0,))
|
|
|
|
class AmesimF000(AlgebraicComponent):
|
|
"""AMESim F000 zero force source."""
|
|
MODEL_TYPE = 'amesim_f000'
|
|
MODEL_VERSION = '0.1.0'
|
|
PORTS = (PortDefinition.mechanical_translational('port_1'),)
|
|
PARAMETERS = ()
|
|
RESULT_VARIABLES = ()
|
|
DISPLAY = ComponentDisplaySpec(label='F000 零力源', library_id='amesim', category_id='mechanical', symbol='amesim_f000', ports=(PortDisplaySpec('port_1', 'right', order=10),), order=10)
|
|
|
|
def __init__(self, name: str) -> None:
|
|
super().__init__(name=name)
|
|
self.set_parameter_values({})
|
|
self.port_1 = self.register_declared_port('port_1')
|
|
|
|
@classmethod
|
|
def create(cls, *, name: str, medium: IdealGasMedium, parameters: Mapping[str, float]) -> 'AmesimF000':
|
|
return cls(name=name)
|
|
EQUATIONS = ({'id': '__MODEL__:zero_force', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'constitutive', 'variables': ['__MODEL__.port_1.f'], 'role': 'flow'},)
|
|
|
|
class AmesimForc(AlgebraicComponent):
|
|
"""AMESim FORC signal-to-force converter."""
|
|
MODEL_TYPE = 'amesim_forc'
|
|
MODEL_VERSION = '0.2.0'
|
|
PORTS = (PortDefinition.signal('res', nominal_role='input'), PortDefinition.mechanical_translational('port_2'))
|
|
PARAMETERS = (ParameterDefinition('direction', 1.0, label='力方向', quantity='dimensionless', unit='', editor='choice', options=(ParameterOption(1.0, '正向'), ParameterOption(-1.0, '反向')), description='显式控制输入信号相对于机械端口正方向的力符号;图标旋转和镜像不会改变该参数。'),)
|
|
RESULT_VARIABLES = (ResultVariableDefinition('force', '输出力', 'force', 'N', 'signal', 10),)
|
|
DISPLAY = ComponentDisplaySpec(label='FORC 信号转力', library_id='amesim', category_id='mechanical', symbol='amesim_forc', ports=(PortDisplaySpec('res', 'left', order=10), PortDisplaySpec('port_2', 'right', order=20)), order=20)
|
|
|
|
def __init__(self, name: str, *, direction: float=1.0) -> None:
|
|
super().__init__(name=name)
|
|
self.set_parameter_values({'direction': direction})
|
|
self.direction = float(direction)
|
|
self.res = self.register_declared_port('res')
|
|
self.port_2 = self.register_declared_port('port_2')
|
|
|
|
@classmethod
|
|
def create(cls, *, name: str, medium: IdealGasMedium, parameters: Mapping[str, float]) -> 'AmesimForc':
|
|
return cls(name=name, direction=parameters['direction'])
|
|
EQUATIONS = ({'id': '__MODEL__:signal_force', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'constitutive', 'variables': ['__MODEL__.port_2.f', '__MODEL__.res.signal'], 'role': 'flow'},)
|
|
|
|
class AmesimMecmas21(DynamicComponent):
|
|
"""AMESim MECMAS21 first public one-dimensional translational mass."""
|
|
MODEL_TYPE = 'amesim_mecmas21'
|
|
MODEL_VERSION = '0.2.0'
|
|
PORTS = (PortDefinition.mechanical_translational('port_1'), PortDefinition.mechanical_translational('port_2'))
|
|
PARAMETERS = (ParameterDefinition('mass', 1.0, label='质量', quantity='mass', unit='kg', minimum=0.0, minimum_exclusive=True, description='平动质量,必须大于零。'), ParameterDefinition('fstick', 0.0, label='静摩擦力', quantity='force', unit='N', minimum=0.0, description='AMESim 静摩擦阈值。当前公开求解器尚未实现静摩擦保持与脱离逻辑,因此该参数暂不参与力计算。', visible_when=(_MECMAS21_NON_RESTITUTION, _MECMAS21_FRICTION_ENABLED)), ParameterDefinition('fcoul', 0.0, label='库仑摩擦力', quantity='force', unit='N', minimum=0.0, description='滑动时的库仑摩擦力绝对值,方向与速度相反。', visible_when=(_MECMAS21_NON_RESTITUTION, _MECMAS21_FRICTION_ENABLED)), ParameterDefinition('rvisc', 0.0, label='黏性摩擦系数', quantity='translational_damping', unit='N/(m/s)', minimum=0.0, description='与速度成正比的黏性摩擦系数。', visible_when=(_MECMAS21_FRICTION_ENABLED,)), ParameterDefinition('wind', 0.0, label='风阻系数', quantity='windage', unit='N/(m/s)^2', minimum=0.0, description='与速度平方成正比、方向与速度相反的风阻系数。', visible_when=(_MECMAS21_FRICTION_ENABLED,)), ParameterDefinition('dvel', 1e-06, label='粘滞速度阈值', quantity='velocity', unit='m/s', minimum=0.0, description='高级摩擦模型的低速切换阈值;当前公开求解器尚未实现对应的高级静摩擦公式。', visible_when=(_MECMAS21_NON_RESTITUTION, _MECMAS21_FRICTION_ENABLED, _MECMAS21_ADVANCED_FRICTION)), ParameterDefinition('restdvel', 1e-06, label='恢复速度阈值', quantity='velocity', unit='m/s', minimum=0.0, description='恢复碰撞低于该入射速度时按无回弹处理。', visible_when=(_MECMAS21_RESTITUTION_STOP,)), ParameterDefinition('restcoeff', 0.65, label='恢复系数', quantity='dimensionless', unit='', minimum=0.0, maximum=1.0, description='恢复碰撞后的速度与碰撞前速度绝对值之比。', visible_when=(_MECMAS21_RESTITUTION_STOP,)), ParameterDefinition('astrib', 0.001, label='Stribeck 常数', quantity='velocity', unit='m/s', minimum=0.0, description='高级 Stribeck 摩擦模型的速度常数;当前公开求解器尚未实现对应的 Stribeck 公式。', visible_when=(_MECMAS21_NON_RESTITUTION, _MECMAS21_FRICTION_ENABLED, _MECMAS21_ADVANCED_FRICTION, _MECMAS21_STRIBECK_ENABLED)), ParameterDefinition('xmin', -1.0, label='下位移限位', quantity='length', unit='m', description='理想、弹性或恢复碰撞限位的下边界位置。', visible_when=(_MECMAS21_LIMITS_ENABLED,)), ParameterDefinition('Kbmin', 1000000000.0, label='下限位刚度', quantity='translational_stiffness', unit='N/m', minimum=0.0, description='弹性下限位的接触刚度。', visible_when=(_MECMAS21_ELASTIC_STOP,)), ParameterDefinition('Dbmin', 10000.0, label='下限位阻尼', quantity='translational_damping', unit='N/(m/s)', minimum=0.0, description='弹性下限位的最大接触阻尼。', visible_when=(_MECMAS21_ELASTIC_STOP,)), ParameterDefinition('Pdmin', 0.0001, label='下限位满阻尼穿透', quantity='length', unit='m', minimum=0.0, description='弹性下限位阻尼由零增至全值所需的穿透量。', visible_when=(_MECMAS21_ELASTIC_STOP,)), ParameterDefinition('xmax', 0.8, label='上位移限位', quantity='length', unit='m', description='理想、弹性或恢复碰撞限位的上边界位置。', visible_when=(_MECMAS21_LIMITS_ENABLED,)), ParameterDefinition('Kbmax', 1000000000.0, label='上限位刚度', quantity='translational_stiffness', unit='N/m', minimum=0.0, description='弹性上限位的接触刚度。', visible_when=(_MECMAS21_ELASTIC_STOP,)), ParameterDefinition('Dbmax', 10000.0, label='上限位阻尼', quantity='translational_damping', unit='N/(m/s)', minimum=0.0, description='弹性上限位的最大接触阻尼。', visible_when=(_MECMAS21_ELASTIC_STOP,)), ParameterDefinition('Pdmax', 0.0001, label='上限位满阻尼穿透', quantity='length', unit='m', minimum=0.0, description='弹性上限位阻尼由零增至全值所需的穿透量。', visible_when=(_MECMAS21_ELASTIC_STOP,)), ParameterDefinition('theta', 0.0, label='倾角(度)', quantity='dimensionless', unit='', description='源 AMESim 参数以度为单位:+90° 表示端口 1 位于最低端,-90° 表示端口 1 位于最高端。当前一维公开求解器尚未加入由该倾角产生的重力分量。'), ParameterDefinition('useFriction', 2.0, label='启用摩擦', quantity='dimensionless', unit='', editor='choice', options=(ParameterOption(1.0, '否'), ParameterOption(2.0, '是')), description='AMESim 原始编码:1 表示不启用摩擦,2 表示启用摩擦。当前公开模型在启用时实现黏性摩擦、风阻和基础库仑摩擦;高级静摩擦与 Stribeck 公式尚未实现。'), ParameterDefinition('stoptype', 1.0, label='限位类型', quantity='dimensionless', unit='', editor='choice', options=(ParameterOption(1.0, '理想限位'), ParameterOption(2.0, '弹性限位'), ParameterOption(3.0, '恢复碰撞'), ParameterOption(4.0, '无限位')), description='AMESim 原始编码:1 为理想限位,2 为弹性限位,3 为带恢复系数的碰撞限位,4 为无限位。'), ParameterDefinition('discContactOption', 1.0, label='允许负接触力', quantity='dimensionless', unit='', editor='choice', options=(ParameterOption(1.0, '允许负接触力'), ParameterOption(2.0, '不允许负接触力')), description='仅在弹性限位中生效。AMESim 原始编码:1 保留阻尼项可能产生的负接触力,2 将接触力限制为非负。', visible_when=(_MECMAS21_ELASTIC_STOP,)), ParameterDefinition('strib', 1.0, label='Stribeck 效应', quantity='dimensionless', unit='', editor='choice', options=(ParameterOption(1.0, '否'), ParameterOption(2.0, '是')), description='AMESim 原始编码:1 表示不使用 Stribeck 效应,2 表示使用。该选项目前用于保留模型配置和控制参数显示;高级 Stribeck 摩擦公式尚未实现。', visible_when=(_MECMAS21_NON_RESTITUTION, _MECMAS21_FRICTION_ENABLED, _MECMAS21_ADVANCED_FRICTION)), ParameterDefinition('frictionType', 1.0, label='摩擦类型', quantity='dimensionless', unit='', editor='choice', options=(ParameterOption(1.0, '简单'), ParameterOption(2.0, '高级')), description='AMESim 原始编码:1 为简单摩擦,2 为高级摩擦。当前公开模型只实现黏性摩擦、风阻和基础库仑摩擦,高级静摩擦与 Stribeck 行为尚未实现。', visible_when=(_MECMAS21_NON_RESTITUTION, _MECMAS21_FRICTION_ENABLED)), ParameterDefinition('v0', 0.0, label='初始速度', quantity='velocity', unit='m/s', description='仿真开始时质量的平动速度。'), ParameterDefinition('x0', 0.0, label='初始位移', quantity='length', unit='m', description='仿真开始时质量的平动位置。'))
|
|
RESULT_VARIABLES = (ResultVariableDefinition('a', '加速度', 'acceleration', 'm/s2', 'state', 10), ResultVariableDefinition('v', '速度', 'velocity', 'm/s', 'state', 20), ResultVariableDefinition('x', '位移', 'length', 'm', 'state', 30), ResultVariableDefinition('Fvisc', '黏性摩擦力', 'force', 'N', 'derived', 40), ResultVariableDefinition('Ffric', '干摩擦力', 'force', 'N', 'derived', 50), ResultVariableDefinition('Fmin', '下限位力', 'force', 'N', 'derived', 60), ResultVariableDefinition('Fmax', '上限位力', 'force', 'N', 'derived', 70))
|
|
DISPLAY = ComponentDisplaySpec(label='MECMAS21 一维质量', library_id='amesim', category_id='mechanical', symbol='amesim_mecmas21', ports=(PortDisplaySpec('port_2', 'left', order=10), PortDisplaySpec('port_1', 'right', order=20)), order=30, parameter_groups=(ParameterGroupDisplaySpec(id='friction', label='摩擦', parameters=('frictionType', 'strib', 'astrib', 'fstick', 'fcoul', 'rvisc', 'wind', 'dvel'), order=10), ParameterGroupDisplaySpec(id='endstops', label='限位', parameters=('discContactOption', 'xmax', 'Kbmax', 'Dbmax', 'Pdmax', 'xmin', 'Kbmin', 'Dbmin', 'Pdmin', 'restcoeff', 'restdvel'), order=20)))
|
|
state_size = 2
|
|
|
|
def __init__(self, name: str, medium: IdealGasMedium, **parameters: float) -> None:
|
|
super().__init__(name=name)
|
|
resolved = {definition.name: parameters.get(definition.name, definition.default) for definition in self.PARAMETERS}
|
|
self.set_parameter_values(resolved)
|
|
for name, value in resolved.items():
|
|
setattr(self, name, float(value))
|
|
self.use_friction = int(self.useFriction) == 2
|
|
self.port_1 = self.register_declared_port('port_1')
|
|
self.port_2 = self.register_declared_port('port_2')
|
|
|
|
@classmethod
|
|
def create(cls, *, name: str, medium: IdealGasMedium, parameters: Mapping[str, float]) -> 'AmesimMecmas21':
|
|
definitions = {definition.name: definition for definition in cls.PARAMETERS}
|
|
for integer_name in ('useFriction', 'stoptype', 'discContactOption', 'strib', 'frictionType'):
|
|
value = float(parameters[integer_name])
|
|
if not value.is_integer():
|
|
raise ValueError(f'MECMAS21 {integer_name} must be an integer.')
|
|
message = definitions[integer_name].validation_message(value)
|
|
if message is not None:
|
|
raise ValueError(f'MECMAS21 {integer_name} {message}.')
|
|
return cls(name=name, medium=medium, **dict(parameters))
|
|
EQUATIONS = ({'id': '__MODEL__:port_1_x_state', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'state', 'variables': ['__MODEL__.port_1.x'], 'role': 'effort'}, {'id': '__MODEL__:port_1_v_state', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'state', 'variables': ['__MODEL__.port_1.v'], 'role': 'effort'}, {'id': '__MODEL__:port_2_x_state', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'state', 'variables': ['__MODEL__.port_2.x'], 'role': 'effort'}, {'id': '__MODEL__:port_2_v_state', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'state', 'variables': ['__MODEL__.port_2.v'], 'role': 'effort'})
|
|
|
|
class AmesimLstp00a(AlgebraicComponent):
|
|
"""AMESim LSTP00A first public elastic contact component."""
|
|
MODEL_TYPE = 'amesim_lstp00a'
|
|
MODEL_VERSION = '0.2.0'
|
|
PORTS = (PortDefinition.mechanical_translational('port_1'), PortDefinition.mechanical_translational('port_2'))
|
|
PARAMETERS = (ParameterDefinition('na', 10.0, label='有效圈数', quantity='dimensionless', unit='', minimum=0.0, minimum_exclusive=True, description='几何刚度模式下使用的弹簧有效圈数。', visible_when=(_LSTP00A_GEOMETRICAL_STIFFNESS,)), ParameterDefinition('gap0', 0.0, label='初始间隙', quantity='length', unit='m', description='两个机械端口开始产生接触力前的初始间隙。'), ParameterDefinition('kcont', 1000000.0, label='接触刚度', quantity='translational_stiffness', unit='N/m', minimum=0.0, description='数值刚度模式下直接指定的接触刚度。', visible_when=(_LSTP00A_NUMERICAL_STIFFNESS,)), ParameterDefinition('G', 85700000000.0, label='剪切模量', quantity='pressure', unit='Pa', minimum=0.0, description='几何刚度模式下的材料剪切模量。', visible_when=(_LSTP00A_GEOMETRICAL_STIFFNESS,)), ParameterDefinition('sdiam', 0.02, label='弹簧直径', quantity='length', unit='m', minimum=0.0, description='几何刚度模式下的弹簧平均直径。', visible_when=(_LSTP00A_GEOMETRICAL_STIFFNESS,)), ParameterDefinition('wdiam', 0.002, label='线径', quantity='length', unit='m', minimum=0.0, description='几何刚度模式下的弹簧线径。', visible_when=(_LSTP00A_GEOMETRICAL_STIFFNESS,)), ParameterDefinition('rcont', 0.0, label='接触阻尼', quantity='translational_damping', unit='N/(m/s)', minimum=0.0, description='接触穿透过程中使用的最大阻尼系数。'), ParameterDefinition('Pdis', 1e-07, label='满阻尼穿透', quantity='length', unit='m', minimum=0.0, description='接触阻尼由零增至全值所需的穿透量。'), ParameterDefinition('stiffmode', 1.0, label='刚度模式', quantity='dimensionless', unit='', minimum=1.0, maximum=2.0, editor='choice', options=(ParameterOption(1.0, '数值刚度'), ParameterOption(2.0, '几何参数')), description='AMESim 原始编码:1 直接使用接触刚度,2 使用弹簧几何参数。当前公开求解器尚未实现由几何参数换算刚度,几何模式仍沿用已保存的接触刚度值。'), ParameterDefinition('discContactOption', 1.0, label='允许负接触力', quantity='dimensionless', unit='', minimum=1.0, maximum=2.0, editor='choice', options=(ParameterOption(1.0, '允许负接触力'), ParameterOption(2.0, '不允许负接触力')), description='AMESim 原始编码:1 保留阻尼项可能产生的负接触力,2 将接触力限制为非负。'))
|
|
RESULT_VARIABLES = (ResultVariableDefinition('gap', '间隙', 'length', 'm', 'derived', 10), ResultVariableDefinition('penetration', '穿透', 'length', 'm', 'derived', 20), ResultVariableDefinition('force', '接触力', 'force', 'N', 'derived', 30))
|
|
DISPLAY = ComponentDisplaySpec(label='LSTP00A 弹性接触', library_id='amesim', category_id='mechanical', symbol='amesim_lstp00a', ports=(PortDisplaySpec('port_1', 'left', order=10), PortDisplaySpec('port_2', 'right', order=20)), order=40, parameter_groups=(ParameterGroupDisplaySpec(id='stiffness', label='刚度', parameters=('kcont', 'G', 'sdiam', 'wdiam', 'na'), order=10), ParameterGroupDisplaySpec(id='contact', label='接触', parameters=('gap0', 'rcont', 'Pdis'), order=20)))
|
|
|
|
def __init__(self, name: str, medium: IdealGasMedium, **parameters: float) -> None:
|
|
super().__init__(name=name)
|
|
resolved = {definition.name: parameters.get(definition.name, definition.default) for definition in self.PARAMETERS}
|
|
self.set_parameter_values(resolved)
|
|
for name, value in resolved.items():
|
|
setattr(self, name, float(value))
|
|
self.port_1 = self.register_declared_port('port_1')
|
|
self.port_2 = self.register_declared_port('port_2')
|
|
|
|
@classmethod
|
|
def create(cls, *, name: str, medium: IdealGasMedium, parameters: Mapping[str, float]) -> 'AmesimLstp00a':
|
|
definitions = {definition.name: definition for definition in cls.PARAMETERS}
|
|
for integer_name in ('stiffmode', 'discContactOption'):
|
|
value = float(parameters[integer_name])
|
|
if not value.is_integer():
|
|
raise ValueError(f'LSTP00A {integer_name} must be an integer.')
|
|
message = definitions[integer_name].validation_message(value)
|
|
if message is not None:
|
|
raise ValueError(f'LSTP00A {integer_name} {message}.')
|
|
return cls(name=name, medium=medium, **dict(parameters))
|
|
EQUATIONS = ({'id': '__MODEL__:port_1_contact_force', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'constitutive', 'variables': ['__MODEL__.port_1.f', '__MODEL__.port_1.x', '__MODEL__.port_1.v', '__MODEL__.port_2.x', '__MODEL__.port_2.v'], 'role': 'flow'}, {'id': '__MODEL__:port_2_contact_force', 'owner': 'component', 'ownerId': '__MODEL__', 'relation': 'constitutive', 'variables': ['__MODEL__.port_2.f', '__MODEL__.port_1.x', '__MODEL__.port_1.v', '__MODEL__.port_2.x', '__MODEL__.port_2.v'], 'role': 'flow'})
|
|
|
|
class AmesimLmechn1(AlgebraicComponent):
|
|
"""AMESim LMECHN1 first public dynamic linear mechanical node."""
|
|
MODEL_TYPE = 'amesim_lmechn1'
|
|
MODEL_VERSION = '0.2.0'
|
|
PORTS = tuple((PortDefinition.mechanical_translational(f'port_{index}') for index in range(1, 22)))
|
|
PARAMETERS = (ParameterDefinition('v1', 2.0, label='右侧端口数', quantity='dimensionless', unit='', minimum=1.0, maximum=20.0, description='设置工作区中显示的右侧机械端口数量,最多 20 个。'), ParameterDefinition('sum', 1.0, label='节点求和模式', quantity='dimensionless', unit='', editor='choice', options=(ParameterOption(1.0, '各端口力代数和为零(标准节点)'),)))
|
|
RESULT_VARIABLES = (ResultVariableDefinition('tforce', '节点合力', 'force', 'N', 'derived', 10),)
|
|
DISPLAY = ComponentDisplaySpec(label='LMECHN1 线性机械节点', library_id='amesim', category_id='mechanical', symbol='amesim_lmechn1', ports=tuple([PortDisplaySpec(f'port_{index}', 'right', order=index * 10) for index in range(1, 21)] + [PortDisplaySpec('port_21', 'left', order=210)]), order=50)
|
|
|
|
def __init__(self, name: str, medium: IdealGasMedium, *, v1: float=2.0, sum: float=1.0) -> None:
|
|
super().__init__(name=name)
|
|
self.set_parameter_values({'v1': v1, 'sum': sum})
|
|
self.v1 = int(v1)
|
|
self.sum = int(sum)
|
|
for definition in self.PORTS:
|
|
setattr(self, definition.name, self.register_declared_port(definition.name))
|
|
|
|
@classmethod
|
|
def create(cls, *, name: str, medium: IdealGasMedium, parameters: Mapping[str, float]) -> 'AmesimLmechn1':
|
|
for integer_name in ('v1', 'sum'):
|
|
if not float(parameters[integer_name]).is_integer():
|
|
raise ValueError(f'LMECHN1 {integer_name} must be an integer.')
|
|
return cls(name=name, medium=medium, v1=parameters['v1'], sum=parameters['sum'])
|
|
|
|
@classmethod
|
|
def active_port_definitions_for_parameters(cls, parameters: Mapping[str, float]) -> tuple[PortDefinition, ...]:
|
|
right_port_count = float(parameters['v1'])
|
|
if not right_port_count.is_integer():
|
|
raise ValueError('LMECHN1 v1 must be an integer.')
|
|
count = int(right_port_count)
|
|
if count < 1 or count > 20:
|
|
raise ValueError('LMECHN1 v1 must be between 1 and 20.')
|
|
return cls.PORTS[:count + 1]
|
|
|
|
@property
|
|
def active_port_definitions(self) -> tuple[PortDefinition, ...]:
|
|
return self.PORTS[:self.v1 + 1]
|
|
|
|
@property
|
|
def active_ports(self) -> tuple[str, ...]:
|
|
return tuple((definition.name for definition in self.active_port_definitions))
|
|
|
|
@property
|
|
def reference_port_name(self) -> str:
|
|
return f'port_{self.v1 + 1}'
|
|
|
|
@property
|
|
def required_connection_ports(self) -> tuple[str, ...]:
|
|
return self.active_ports
|
|
|
|
def equation_definitions(self):
|
|
from app.simulation.core.equations import EquationDefinition
|
|
result = []
|
|
reference = self.reference_port_name
|
|
for name in self.active_ports[:-1]:
|
|
for field in ('x', 'v'):
|
|
result.append(EquationDefinition(id=f'{self.name}:{name}_{field}_equal', owner='component', owner_id=self.name, relation='equal', variables=(f'{self.name}.{name}.{field}', f'{self.name}.{reference}.{field}'), role='effort'))
|
|
result.append(EquationDefinition(id=f'{self.name}:force_balance', owner='component', owner_id=self.name, relation='sumToZero', variables=tuple((f'{self.name}.{name}.f' for name in self.active_ports)), role='flow'))
|
|
return tuple(result)
|