Files
SystemSimulationApp/app/simulation/components/amesim/signals/sources.py
T

205 lines
9.2 KiB
Python

from __future__ import annotations
from collections.abc import Mapping
from app.simulation.core.base import AlgebraicComponent
from app.simulation.core.catalog import ComponentDisplaySpec, PortDisplaySpec
from app.simulation.core.metadata import ParameterDefinition, ResultVariableDefinition
from app.simulation.core.medium import IdealGasMedium
from app.simulation.core.ports import PortDefinition
class AmesimStep0(AlgebraicComponent):
"""AMESim STEP0 scalar step signal source."""
MODEL_TYPE = "amesim_step0"
MODEL_VERSION = "0.1.0"
PORTS = (PortDefinition.signal("out", nominal_role="output"),)
PARAMETERS = (
ParameterDefinition("initial", 0.0, label="初始值", quantity="dimensionless", unit=""),
ParameterDefinition("final", 1.0, label="阶跃后值", quantity="dimensionless", unit=""),
ParameterDefinition("time", 0.0, label="阶跃时间", quantity="time", unit="s"),
)
RESULT_VARIABLES = (
ResultVariableDefinition("y", "输出", "dimensionless", "", "signal", 10),
)
DISPLAY = ComponentDisplaySpec(
label="STEP0 阶跃信号",
library_id="amesim",
category_id="signals",
symbol="signal",
ports=(PortDisplaySpec("out", "right", order=10),),
order=10,
)
def __init__(
self,
name: str,
medium: IdealGasMedium,
*,
initial: float = 0.0,
final: float = 1.0,
time: float = 0.0,
) -> None:
super().__init__(name=name)
self.set_parameter_values({"initial": initial, "final": final, "time": time})
self.initial = float(initial)
self.final = float(final)
self.time = float(time)
self.out = self.register_declared_port("out")
self.out.signal = self.output_at(0.0)
@classmethod
def create(
cls,
*,
name: str,
medium: IdealGasMedium,
parameters: Mapping[str, float],
) -> "AmesimStep0":
return cls(
name=name,
medium=medium,
initial=parameters["initial"],
final=parameters["final"],
time=parameters["time"],
)
def output_at(self, time: float) -> float:
return self.final if time >= self.time else self.initial
def signal_output_values(self, time: float) -> dict[str, float]:
return {"out": self.output_at(time)}
def component_result_values(self) -> Mapping[str, float]:
return {"y": self.out.signal}
class AmesimUd00(AlgebraicComponent):
"""AMESim UD00 piecewise-linear scalar signal source."""
MODEL_TYPE = "amesim_ud00"
MODEL_VERSION = "0.1.0"
PORTS = (PortDefinition.signal("out", nominal_role="output"),)
PARAMETERS = (
ParameterDefinition("tstart", 0.0, label="启动时间", quantity="time", unit="s"),
ParameterDefinition("start1", 0.0, label="第 1 段起点", quantity="dimensionless", unit=""),
ParameterDefinition("end1", 1.0, label="第 1 段终点", quantity="dimensionless", unit=""),
ParameterDefinition("t1", 1.0, label="第 1 段时长", quantity="time", unit="s", minimum=0.0),
ParameterDefinition("start2", 1.0, label="第 2 段起点", quantity="dimensionless", unit=""),
ParameterDefinition("end2", 1.0, label="第 2 段终点", quantity="dimensionless", unit=""),
ParameterDefinition("t2", 0.0, label="第 2 段时长", quantity="time", unit="s", minimum=0.0),
ParameterDefinition("start3", 1.0, label="第 3 段起点", quantity="dimensionless", unit=""),
ParameterDefinition("end3", 1.0, label="第 3 段终点", quantity="dimensionless", unit=""),
ParameterDefinition("t3", 0.0, label="第 3 段时长", quantity="time", unit="s", minimum=0.0),
ParameterDefinition("start4", 1.0, label="第 4 段起点", quantity="dimensionless", unit=""),
ParameterDefinition("end4", 1.0, label="第 4 段终点", quantity="dimensionless", unit=""),
ParameterDefinition("t4", 0.0, label="第 4 段时长", quantity="time", unit="s", minimum=0.0),
ParameterDefinition("start5", 1.0, label="第 5 段起点", quantity="dimensionless", unit=""),
ParameterDefinition("end5", 1.0, label="第 5 段终点", quantity="dimensionless", unit=""),
ParameterDefinition("t5", 0.0, label="第 5 段时长", quantity="time", unit="s", minimum=0.0),
ParameterDefinition("start6", 1.0, label="第 6 段起点", quantity="dimensionless", unit=""),
ParameterDefinition("end6", 1.0, label="第 6 段终点", quantity="dimensionless", unit=""),
ParameterDefinition("t6", 0.0, label="第 6 段时长", quantity="time", unit="s", minimum=0.0),
ParameterDefinition("start7", 1.0, label="第 7 段起点", quantity="dimensionless", unit=""),
ParameterDefinition("end7", 1.0, label="第 7 段终点", quantity="dimensionless", unit=""),
ParameterDefinition("t7", 0.0, label="第 7 段时长", quantity="time", unit="s", minimum=0.0),
ParameterDefinition("start8", 1.0, label="第 8 段起点", quantity="dimensionless", unit=""),
ParameterDefinition("end8", 1.0, label="第 8 段终点", quantity="dimensionless", unit=""),
ParameterDefinition("t8", 0.0, label="第 8 段时长", quantity="time", unit="s", minimum=0.0),
ParameterDefinition("nstages", 1.0, label="段数", quantity="dimensionless", unit="", minimum=1.0, maximum=8.0),
ParameterDefinition("iscyclic", 0.0, label="循环", quantity="dimensionless", unit="", minimum=0.0, maximum=1.0),
)
RESULT_VARIABLES = (
ResultVariableDefinition("y", "输出", "dimensionless", "", "signal", 10),
)
DISPLAY = ComponentDisplaySpec(
label="UD00 分段线性信号",
library_id="amesim",
category_id="signals",
symbol="signal",
ports=(PortDisplaySpec("out", "right", order=10),),
order=20,
)
def __init__(
self,
name: str,
medium: IdealGasMedium,
*,
tstart: float = 0.0,
starts: tuple[float, ...] = (0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
ends: tuple[float, ...] = (1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
durations: tuple[float, ...] = (1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
nstages: int = 1,
iscyclic: bool = False,
) -> None:
super().__init__(name=name)
if len(starts) != 8 or len(ends) != 8 or len(durations) != 8:
raise ValueError("UD00 requires exactly eight start, end, and duration values.")
if nstages < 1 or nstages > 8:
raise ValueError("UD00 nstages must be between 1 and 8.")
self.tstart = float(tstart)
self.starts = tuple(float(value) for value in starts)
self.ends = tuple(float(value) for value in ends)
self.durations = tuple(float(value) for value in durations)
self.nstages = int(nstages)
self.iscyclic = bool(iscyclic)
values: dict[str, float] = {"tstart": self.tstart, "nstages": float(self.nstages), "iscyclic": float(int(self.iscyclic))}
for index in range(1, 9):
values[f"start{index}"] = self.starts[index - 1]
values[f"end{index}"] = self.ends[index - 1]
values[f"t{index}"] = self.durations[index - 1]
self.set_parameter_values(values)
self.out = self.register_declared_port("out")
self.out.signal = self.output_at(0.0)
@classmethod
def create(
cls,
*,
name: str,
medium: IdealGasMedium,
parameters: Mapping[str, float],
) -> "AmesimUd00":
nstages = parameters["nstages"]
iscyclic = parameters["iscyclic"]
if not float(nstages).is_integer():
raise ValueError("UD00 nstages must be an integer.")
if not float(iscyclic).is_integer():
raise ValueError("UD00 iscyclic must be 0 or 1.")
return cls(
name=name,
medium=medium,
tstart=parameters["tstart"],
starts=tuple(parameters[f"start{index}"] for index in range(1, 9)),
ends=tuple(parameters[f"end{index}"] for index in range(1, 9)),
durations=tuple(parameters[f"t{index}"] for index in range(1, 9)),
nstages=int(nstages),
iscyclic=bool(int(iscyclic)),
)
def output_at(self, time: float) -> float:
elapsed = max(float(time) - self.tstart, 0.0)
active_durations = self.durations[: self.nstages]
total_duration = sum(active_durations)
if self.iscyclic and total_duration > 0.0:
elapsed = elapsed % total_duration
stage_start_time = 0.0
for index, duration in enumerate(active_durations):
stage_end_time = stage_start_time + duration
if elapsed < stage_end_time or index == self.nstages - 1:
if duration <= 0.0:
return self.ends[index]
fraction = (elapsed - stage_start_time) / duration
return self.starts[index] + fraction * (self.ends[index] - self.starts[index])
stage_start_time = stage_end_time
return self.ends[self.nstages - 1]
def signal_output_values(self, time: float) -> dict[str, float]:
return {"out": self.output_at(time)}
def component_result_values(self) -> Mapping[str, float]:
return {"y": self.out.signal}