修复机械事件力曲线并对齐气动孔口端口显示
This commit is contained in:
1 parent
e18399c022
commit
408b4ecb22
11 files changed
+697
-43
No files matched your search
@@ -574,8 +574,9 @@ class AmesimPnvo001FixedOpening(AlgebraicComponent):
|
|||||||
category_id="flow",
|
category_id="flow",
|
||||||
symbol="amesim_pnvo001_fixed",
|
symbol="amesim_pnvo001_fixed",
|
||||||
ports=(
|
ports=(
|
||||||
PortDisplaySpec("port_2", "left", order=10),
|
# AMESim default geometry places port 2 right and port 3 left.
|
||||||
PortDisplaySpec("port_3", "right", order=20),
|
PortDisplaySpec("port_2", "right", order=10),
|
||||||
|
PortDisplaySpec("port_3", "left", order=20),
|
||||||
),
|
),
|
||||||
order=30,
|
order=30,
|
||||||
parameter_groups=(_PNVO001_FLOW_COEFFICIENT_GROUP,),
|
parameter_groups=(_PNVO001_FLOW_COEFFICIENT_GROUP,),
|
||||||
@@ -990,8 +991,9 @@ class AmesimPnvo001SignalOpening(AmesimPnvo001FixedOpening):
|
|||||||
symbol="amesim_pnvo001",
|
symbol="amesim_pnvo001",
|
||||||
ports=(
|
ports=(
|
||||||
PortDisplaySpec("res", "left", order=5),
|
PortDisplaySpec("res", "left", order=5),
|
||||||
PortDisplaySpec("port_2", "left", order=10),
|
# AMESim default geometry places port 2 right and port 3 left.
|
||||||
PortDisplaySpec("port_3", "right", order=20),
|
PortDisplaySpec("port_2", "right", order=10),
|
||||||
|
PortDisplaySpec("port_3", "left", order=20),
|
||||||
),
|
),
|
||||||
order=35,
|
order=35,
|
||||||
parameter_groups=(_PNVO001_FLOW_COEFFICIENT_GROUP,),
|
parameter_groups=(_PNVO001_FLOW_COEFFICIENT_GROUP,),
|
||||||
|
|||||||
@@ -8,3 +8,9 @@
|
|||||||
- 纳管慢区归因、真实 worker/API/浏览器复验及未启用候选的历史报告;最终默认方案保持 `legacy` 机械容差和 SciPy Jacobian。本地证据中 production worker `0.2 s / 0.001 s` 相对旧批准基线缩短约 `7.50%`,AMESim 物理门保持通过,改变轨迹或收益不足的接触感知容差与半解析 Jacobian 候选未启用。
|
- 纳管慢区归因、真实 worker/API/浏览器复验及未启用候选的历史报告;最终默认方案保持 `legacy` 机械容差和 SciPy Jacobian。本地证据中 production worker `0.2 s / 0.001 s` 相对旧批准基线缩短约 `7.50%`,AMESim 物理门保持通过,改变轨迹或收益不足的接触感知容差与半解析 Jacobian 候选未启用。
|
||||||
- 合并后的完整后端 900 项测试通过,其中 3 项按条件跳过;前端两套 TypeScript 检查、Vite 生产构建及 12 项活动看门狗/超时回归通过,差异格式检查无异常。
|
- 合并后的完整后端 900 项测试通过,其中 3 项按条件跳过;前端两套 TypeScript 检查、Vite 生产构建及 12 项活动看门狗/超时回归通过,差异格式检查无异常。
|
||||||
- 仍存在的问题:跨 `maxStep` 的自动分层比较器尚未完成;当前权威 `5 s` 聚合报告和 `10 s / 0.001 s` 基线尚未生成;真正无活动调用的硬终止、客户端断连恢复、并发资源门控及 SciPy 内部有限差分 Jacobian 的实时分类仍待实现。
|
- 仍存在的问题:跨 `maxStep` 的自动分层比较器尚未完成;当前权威 `5 s` 聚合报告和 `10 s / 0.001 s` 基线尚未生成;真正无活动调用的硬终止、客户端断连恢复、并发资源门控及 SciPy 内部有限差分 Jacobian 的实时分类仍待实现。
|
||||||
|
|
||||||
|
## 13:55
|
||||||
|
|
||||||
|
- 对齐固定开度与信号控制 PNVO001 的 AMESim 默认端口显示:`port_2` 位于右侧、`port_3` 位于左侧,同时保留目录中的端口编号顺序;旧工程仅在检测到历史端口方位快照时幂等迁移一次镜像状态,不修改连接端口名、压力流量方程或结果键。
|
||||||
|
- 修复机械状态事件对结果曲线的展示干扰:结果页仅隐藏事件时刻相对两侧连续样本异常放大的孤立力尖峰,并连续绘制其两侧正常样本;规则同时覆盖机械端口力和 LSTP00A、LMECHN1 等机械组件的派生力,不处理外部力信号、持续接触力及非力状态。原始 `series`、`.simresult` 与 CSV 导出保持不变。
|
||||||
|
- PNVO 目录/组件定向后端测试 20 项、端口符号/旧工程迁移/事件力纯函数与真实浏览器回归 4 项均通过;前端 TypeScript 检查与 Vite 生产构建通过,差异格式检查无异常。
|
||||||
@@ -142,6 +142,7 @@ import {
|
|||||||
isParameterExpressionValue,
|
isParameterExpressionValue,
|
||||||
isScientificNotationValue,
|
isScientificNotationValue,
|
||||||
} from "./parameterExpression";
|
} from "./parameterExpression";
|
||||||
|
import { migratedPnvo001Mirrored } from "./pnvoPortDisplayMigration";
|
||||||
import {
|
import {
|
||||||
WorkspaceViewTabs,
|
WorkspaceViewTabs,
|
||||||
type WorkspaceView,
|
type WorkspaceView,
|
||||||
@@ -9473,7 +9474,12 @@ function normalizeLoadedNode(
|
|||||||
parameterUnits,
|
parameterUnits,
|
||||||
parameterScientificNotation,
|
parameterScientificNotation,
|
||||||
rotation: normalizeNodeRotation(node.data.rotation),
|
rotation: normalizeNodeRotation(node.data.rotation),
|
||||||
mirrored: Boolean(node.data.mirrored),
|
mirrored: migratedPnvo001Mirrored(
|
||||||
|
definition?.modelType ?? node.data.modelType,
|
||||||
|
node.data.ports,
|
||||||
|
definition?.ports,
|
||||||
|
Boolean(node.data.mirrored),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ import {
|
|||||||
orthogonalEdgePoints,
|
orthogonalEdgePoints,
|
||||||
type EdgeRouteData,
|
type EdgeRouteData,
|
||||||
} from "./edgeRouting";
|
} from "./edgeRouting";
|
||||||
|
import {
|
||||||
|
continuousMechanicalForceChartValues,
|
||||||
|
resultStateTransitionSampleIndices,
|
||||||
|
segmentedChartPath,
|
||||||
|
} from "./resultEventSeries";
|
||||||
|
|
||||||
type ResultPortDefinition = {
|
type ResultPortDefinition = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -136,6 +141,11 @@ type ResultPayload = {
|
|||||||
final: Record<string, number>;
|
final: Record<string, number>;
|
||||||
series: Record<string, number[]>;
|
series: Record<string, number[]>;
|
||||||
diagnostics: {
|
diagnostics: {
|
||||||
|
integration?: {
|
||||||
|
segments?: Array<{
|
||||||
|
stateTransitionTimes?: number[];
|
||||||
|
}>;
|
||||||
|
};
|
||||||
pressureFlow: {
|
pressureFlow: {
|
||||||
solveCount: number;
|
solveCount: number;
|
||||||
maxScaledResidual: number;
|
maxScaledResidual: number;
|
||||||
@@ -383,6 +393,14 @@ export function SimulationResultsView({
|
|||||||
}: SimulationResultsViewProps) {
|
}: SimulationResultsViewProps) {
|
||||||
const reactFlow = useReactFlow();
|
const reactFlow = useReactFlow();
|
||||||
const resultStatus = simulationResultStatus(snapshot.result);
|
const resultStatus = simulationResultStatus(snapshot.result);
|
||||||
|
const stateTransitionSampleIndices = useMemo(
|
||||||
|
() =>
|
||||||
|
resultStateTransitionSampleIndices(
|
||||||
|
snapshot.result.series.time ?? [],
|
||||||
|
snapshot.result.diagnostics,
|
||||||
|
),
|
||||||
|
[snapshot.result.diagnostics, snapshot.result.series.time],
|
||||||
|
);
|
||||||
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(
|
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(
|
||||||
snapshot.project.nodes[0]?.id ?? null,
|
snapshot.project.nodes[0]?.id ?? null,
|
||||||
);
|
);
|
||||||
@@ -1613,9 +1631,16 @@ export function SimulationResultsView({
|
|||||||
variable,
|
variable,
|
||||||
chartWindow.unit,
|
chartWindow.unit,
|
||||||
);
|
);
|
||||||
const displayValues = (
|
const chartTime = snapshot.result.series.time ?? [];
|
||||||
snapshot.result.series[chartWindow.variableKey] ?? []
|
const primaryChartValues = continuousMechanicalForceChartValues(
|
||||||
).map(selectedUnit.fromBase);
|
variable,
|
||||||
|
component,
|
||||||
|
snapshot.result.series[chartWindow.variableKey] ?? [],
|
||||||
|
stateTransitionSampleIndices,
|
||||||
|
);
|
||||||
|
const displayValues = primaryChartValues.values.map(
|
||||||
|
selectedUnit.fromBase,
|
||||||
|
);
|
||||||
const combinedVariables = windowVariables.filter(
|
const combinedVariables = windowVariables.filter(
|
||||||
(item) => isMixed || item.unit === variable.unit,
|
(item) => isMixed || item.unit === variable.unit,
|
||||||
);
|
);
|
||||||
@@ -1632,6 +1657,13 @@ export function SimulationResultsView({
|
|||||||
chartWindow.variableUnits[item.key] ?? item.unit,
|
chartWindow.variableUnits[item.key] ?? item.unit,
|
||||||
)
|
)
|
||||||
: selectedUnit;
|
: selectedUnit;
|
||||||
|
const itemChartValues =
|
||||||
|
continuousMechanicalForceChartValues(
|
||||||
|
item,
|
||||||
|
itemComponent,
|
||||||
|
snapshot.result.series[item.key] ?? [],
|
||||||
|
stateTransitionSampleIndices,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
key: item.key,
|
key: item.key,
|
||||||
label: resultVariableTitle(
|
label: resultVariableTitle(
|
||||||
@@ -1640,12 +1672,18 @@ export function SimulationResultsView({
|
|||||||
),
|
),
|
||||||
color: colorForVariable(item.key),
|
color: colorForVariable(item.key),
|
||||||
unit: itemUnit.label,
|
unit: itemUnit.label,
|
||||||
values: (snapshot.result.series[item.key] ?? []).map(
|
values: itemChartValues.values.map(itemUnit.fromBase),
|
||||||
itemUnit.fromBase,
|
separatedEventSampleCount:
|
||||||
),
|
itemChartValues.separatedEventSampleCount,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const chartTime = snapshot.result.series.time ?? [];
|
const separatedEventSampleCount = isCombined
|
||||||
|
? multiSeries.reduce(
|
||||||
|
(count, item) =>
|
||||||
|
count + item.separatedEventSampleCount,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
: primaryChartValues.separatedEventSampleCount;
|
||||||
const visibilitySeries = isCombined
|
const visibilitySeries = isCombined
|
||||||
? multiSeries
|
? multiSeries
|
||||||
: [
|
: [
|
||||||
@@ -1671,6 +1709,7 @@ export function SimulationResultsView({
|
|||||||
}`}
|
}`}
|
||||||
data-chart-kind={chartWindow.kind}
|
data-chart-kind={chartWindow.kind}
|
||||||
data-pending="false"
|
data-pending="false"
|
||||||
|
data-separated-event-samples={separatedEventSampleCount}
|
||||||
key={chartWindow.id}
|
key={chartWindow.id}
|
||||||
onDragLeave={(event) => {
|
onDragLeave={(event) => {
|
||||||
if (
|
if (
|
||||||
@@ -1727,6 +1766,9 @@ export function SimulationResultsView({
|
|||||||
{isCombined
|
{isCombined
|
||||||
? `${visibleCombinedVariables.length}/${combinedVariables.length} 条显示 · ${isMixed ? "上下同步图" : variable.unit || "无量纲"}`
|
? `${visibleCombinedVariables.length}/${combinedVariables.length} 条显示 · ${isMixed ? "上下同步图" : variable.unit || "无量纲"}`
|
||||||
: componentName}
|
: componentName}
|
||||||
|
{separatedEventSampleCount > 0
|
||||||
|
? ` · 已隐藏 ${separatedEventSampleCount} 个孤立事件力尖峰`
|
||||||
|
: ""}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="result-chart-window-actions">
|
<div className="result-chart-window-actions">
|
||||||
@@ -2026,7 +2068,7 @@ export function SimulationResultsView({
|
|||||||
}
|
}
|
||||||
onZoomPrevious={() => undoChartWindowZoom(chartWindow.id)}
|
onZoomPrevious={() => undoChartWindowZoom(chartWindow.id)}
|
||||||
series={multiSeries}
|
series={multiSeries}
|
||||||
time={snapshot.result.series.time ?? []}
|
time={chartTime}
|
||||||
viewport={chartWindow.viewport}
|
viewport={chartWindow.viewport}
|
||||||
zoomEnabled={chartWindow.zoomEnabled}
|
zoomEnabled={chartWindow.zoomEnabled}
|
||||||
/>
|
/>
|
||||||
@@ -2038,7 +2080,7 @@ export function SimulationResultsView({
|
|||||||
}
|
}
|
||||||
onZoomPrevious={() => undoChartWindowZoom(chartWindow.id)}
|
onZoomPrevious={() => undoChartWindowZoom(chartWindow.id)}
|
||||||
series={multiSeries}
|
series={multiSeries}
|
||||||
time={snapshot.result.series.time ?? []}
|
time={chartTime}
|
||||||
title="多曲线对比"
|
title="多曲线对比"
|
||||||
unit={selectedUnit.label}
|
unit={selectedUnit.label}
|
||||||
viewport={chartWindow.viewport}
|
viewport={chartWindow.viewport}
|
||||||
@@ -2052,7 +2094,7 @@ export function SimulationResultsView({
|
|||||||
updateChartWindowViewport(chartWindow.id, viewport)
|
updateChartWindowViewport(chartWindow.id, viewport)
|
||||||
}
|
}
|
||||||
onZoomPrevious={() => undoChartWindowZoom(chartWindow.id)}
|
onZoomPrevious={() => undoChartWindowZoom(chartWindow.id)}
|
||||||
time={snapshot.result.series.time ?? []}
|
time={chartTime}
|
||||||
title={chartTitle}
|
title={chartTitle}
|
||||||
unit={selectedUnit.label}
|
unit={selectedUnit.label}
|
||||||
values={displayValues}
|
values={displayValues}
|
||||||
@@ -2268,6 +2310,7 @@ type MultiCurveSeries = {
|
|||||||
color: string;
|
color: string;
|
||||||
unit: string;
|
unit: string;
|
||||||
values: number[];
|
values: number[];
|
||||||
|
separatedEventSampleCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ChartVisibilitySeries = {
|
type ChartVisibilitySeries = {
|
||||||
@@ -3598,12 +3641,12 @@ function MultiCurvePlot({
|
|||||||
/>
|
/>
|
||||||
<g clipPath={`url(#${clipPathId})`} data-chart-clipped-series="true">
|
<g clipPath={`url(#${clipPathId})`} data-chart-clipped-series="true">
|
||||||
{plottedSeries.map((curve) => {
|
{plottedSeries.map((curve) => {
|
||||||
const path = curve.samples
|
const path = segmentedChartPath(
|
||||||
.map(
|
time,
|
||||||
(sample, index) =>
|
curve.values,
|
||||||
`${index === 0 ? "M" : "L"} ${xPosition(sample.x).toFixed(2)} ${yPosition(sample.y).toFixed(2)}`,
|
xPosition,
|
||||||
)
|
yPosition,
|
||||||
.join(" ");
|
);
|
||||||
return (
|
return (
|
||||||
<path
|
<path
|
||||||
d={path}
|
d={path}
|
||||||
@@ -4157,12 +4200,12 @@ function StackedCurvePlot({
|
|||||||
</defs>
|
</defs>
|
||||||
{plotBands.map((band) => {
|
{plotBands.map((band) => {
|
||||||
const isLast = band.index === plotBands.length - 1;
|
const isLast = band.index === plotBands.length - 1;
|
||||||
const path = band.curve.samples
|
const path = segmentedChartPath(
|
||||||
.map(
|
time,
|
||||||
(sample, sampleIndex) =>
|
band.curve.values,
|
||||||
`${sampleIndex === 0 ? "M" : "L"} ${xPosition(sample.x).toFixed(2)} ${band.yPosition(sample.y).toFixed(2)}`,
|
xPosition,
|
||||||
)
|
band.yPosition,
|
||||||
.join(" ");
|
);
|
||||||
return (
|
return (
|
||||||
<g className="result-chart-stack-band" key={band.curve.key}>
|
<g className="result-chart-stack-band" key={band.curve.key}>
|
||||||
{band.index > 0 ? (
|
{band.index > 0 ? (
|
||||||
@@ -4614,14 +4657,12 @@ function CurvePlot({
|
|||||||
margin.top + (1 - (value - yMin) / yRange) * plotHeight;
|
margin.top + (1 - (value - yMin) / yRange) * plotHeight;
|
||||||
const xTicks = chartTicks(xMin, xMax, 5);
|
const xTicks = chartTicks(xMin, xMax, 5);
|
||||||
const yTicks = chartTicks(yMin, yMax, 5);
|
const yTicks = chartTicks(yMin, yMax, 5);
|
||||||
const path = samples
|
const path = segmentedChartPath(
|
||||||
.map(
|
time,
|
||||||
(sample, index) =>
|
values,
|
||||||
`${index === 0 ? "M" : "L"} ${xPosition(sample.x).toFixed(2)} ${yPosition(
|
xPosition,
|
||||||
sample.y,
|
yPosition,
|
||||||
).toFixed(2)}`,
|
);
|
||||||
)
|
|
||||||
.join(" ");
|
|
||||||
const requestedCursorIndex =
|
const requestedCursorIndex =
|
||||||
cursorIndex === null
|
cursorIndex === null
|
||||||
? Math.floor(samples.length / 2)
|
? Math.floor(samples.length / 2)
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
type PortSideSnapshot = {
|
||||||
|
name: string;
|
||||||
|
side: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PNVO001_MODEL_TYPES = new Set([
|
||||||
|
"amesim_pnvo001_fixed",
|
||||||
|
"amesim_pnvo001",
|
||||||
|
]);
|
||||||
|
|
||||||
|
function portUsesSide(
|
||||||
|
ports: readonly PortSideSnapshot[],
|
||||||
|
portName: string,
|
||||||
|
side: string,
|
||||||
|
) {
|
||||||
|
return ports.some((port) => port.name === portName && port.side === side);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function migratedPnvo001Mirrored(
|
||||||
|
modelType: string,
|
||||||
|
savedPorts: readonly PortSideSnapshot[],
|
||||||
|
currentPorts: readonly PortSideSnapshot[] | undefined,
|
||||||
|
mirrored: boolean,
|
||||||
|
) {
|
||||||
|
if (!PNVO001_MODEL_TYPES.has(modelType) || !currentPorts) {
|
||||||
|
return mirrored;
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedUsesLegacySides =
|
||||||
|
portUsesSide(savedPorts, "port_2", "left") &&
|
||||||
|
portUsesSide(savedPorts, "port_3", "right");
|
||||||
|
const catalogUsesAmesimSides =
|
||||||
|
portUsesSide(currentPorts, "port_2", "right") &&
|
||||||
|
portUsesSide(currentPorts, "port_3", "left");
|
||||||
|
|
||||||
|
return savedUsesLegacySides && catalogUsesAmesimSides
|
||||||
|
? !mirrored
|
||||||
|
: mirrored;
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
export type StateTransitionDiagnostics = {
|
||||||
|
integration?: {
|
||||||
|
segments?: Array<{
|
||||||
|
stateTransitionTimes?: number[];
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type EventSeriesVariable = {
|
||||||
|
scope: "component" | "port";
|
||||||
|
portName: string | null;
|
||||||
|
quantity: string;
|
||||||
|
category: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EventSeriesComponent = {
|
||||||
|
data: {
|
||||||
|
ports: Array<{
|
||||||
|
name: string;
|
||||||
|
domain: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const ISOLATED_EVENT_FORCE_RATIO = 100;
|
||||||
|
|
||||||
|
export function resultStateTransitionSampleIndices(
|
||||||
|
time: number[],
|
||||||
|
diagnostics: StateTransitionDiagnostics,
|
||||||
|
) {
|
||||||
|
const indices = new Set<number>();
|
||||||
|
for (const segment of diagnostics.integration?.segments ?? []) {
|
||||||
|
for (const eventTime of segment.stateTransitionTimes ?? []) {
|
||||||
|
if (!Number.isFinite(eventTime)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let index = time.indexOf(eventTime);
|
||||||
|
if (index < 0) {
|
||||||
|
const tolerance = 1e-12 * Math.max(Math.abs(eventTime), 1);
|
||||||
|
index = time.findIndex(
|
||||||
|
(sampleTime) => Math.abs(sampleTime - eventTime) <= tolerance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (index >= 0) {
|
||||||
|
indices.add(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function continuousMechanicalForceChartValues(
|
||||||
|
variable: EventSeriesVariable,
|
||||||
|
component: EventSeriesComponent | undefined,
|
||||||
|
values: number[],
|
||||||
|
stateTransitionSampleIndices: ReadonlySet<number>,
|
||||||
|
) {
|
||||||
|
const componentHasMechanicalPort = Boolean(
|
||||||
|
component?.data.ports.some((port) => port.domain === "mechanical"),
|
||||||
|
);
|
||||||
|
const forceBelongsToMechanicalDomain =
|
||||||
|
variable.scope === "component"
|
||||||
|
? variable.category === "derived" && componentHasMechanicalPort
|
||||||
|
: variable.portName !== null &&
|
||||||
|
Boolean(
|
||||||
|
component?.data.ports.some(
|
||||||
|
(port) =>
|
||||||
|
port.name === variable.portName &&
|
||||||
|
port.domain === "mechanical",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (variable.quantity !== "force" || !forceBelongsToMechanicalDomain) {
|
||||||
|
return { values, separatedEventSampleCount: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const isolatedEventIndices: number[] = [];
|
||||||
|
for (const index of stateTransitionSampleIndices) {
|
||||||
|
if (
|
||||||
|
index < 0 ||
|
||||||
|
index >= values.length ||
|
||||||
|
!isolatedEventForceSample(
|
||||||
|
values,
|
||||||
|
index,
|
||||||
|
stateTransitionSampleIndices,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
isolatedEventIndices.push(index);
|
||||||
|
}
|
||||||
|
if (isolatedEventIndices.length === 0) {
|
||||||
|
return { values, separatedEventSampleCount: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayValues = values.slice();
|
||||||
|
for (const index of isolatedEventIndices) {
|
||||||
|
displayValues[index] = Number.NaN;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
values: displayValues,
|
||||||
|
separatedEventSampleCount: isolatedEventIndices.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isolatedEventForceSample(
|
||||||
|
values: number[],
|
||||||
|
index: number,
|
||||||
|
stateTransitionSampleIndices: ReadonlySet<number>,
|
||||||
|
) {
|
||||||
|
const value = Number(values[index]);
|
||||||
|
if (!Number.isFinite(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const previous = finiteNonEventNeighbor(
|
||||||
|
values,
|
||||||
|
index - 1,
|
||||||
|
-1,
|
||||||
|
stateTransitionSampleIndices,
|
||||||
|
);
|
||||||
|
const next = finiteNonEventNeighbor(
|
||||||
|
values,
|
||||||
|
index + 1,
|
||||||
|
1,
|
||||||
|
stateTransitionSampleIndices,
|
||||||
|
);
|
||||||
|
if (previous === null || next === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localScale = Math.max(
|
||||||
|
Math.abs(previous),
|
||||||
|
Math.abs(next),
|
||||||
|
Math.abs(next - previous),
|
||||||
|
1e-9,
|
||||||
|
);
|
||||||
|
const localEstimate = (previous + next) / 2;
|
||||||
|
return (
|
||||||
|
Math.abs(value) > ISOLATED_EVENT_FORCE_RATIO * localScale &&
|
||||||
|
Math.abs(value - localEstimate) >
|
||||||
|
ISOLATED_EVENT_FORCE_RATIO * localScale
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finiteNonEventNeighbor(
|
||||||
|
values: number[],
|
||||||
|
startIndex: number,
|
||||||
|
direction: -1 | 1,
|
||||||
|
stateTransitionSampleIndices: ReadonlySet<number>,
|
||||||
|
) {
|
||||||
|
for (
|
||||||
|
let index = startIndex;
|
||||||
|
index >= 0 && index < values.length;
|
||||||
|
index += direction
|
||||||
|
) {
|
||||||
|
if (stateTransitionSampleIndices.has(index)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const value = Number(values[index]);
|
||||||
|
if (Number.isFinite(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function segmentedChartPath(
|
||||||
|
time: number[],
|
||||||
|
values: number[],
|
||||||
|
xPosition: (value: number) => number,
|
||||||
|
yPosition: (value: number) => number,
|
||||||
|
) {
|
||||||
|
const commands: string[] = [];
|
||||||
|
let continuesSegment = false;
|
||||||
|
for (let index = 0; index < time.length; index += 1) {
|
||||||
|
const x = Number(time[index]);
|
||||||
|
const y = Number(values[index]);
|
||||||
|
if (!Number.isFinite(x)) {
|
||||||
|
continuesSegment = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!Number.isFinite(y)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
commands.push(
|
||||||
|
`${continuesSegment ? "L" : "M"} ${xPosition(x).toFixed(2)} ${yPosition(y).toFixed(2)}`,
|
||||||
|
);
|
||||||
|
continuesSegment = true;
|
||||||
|
}
|
||||||
|
return commands.join(" ");
|
||||||
|
}
|
||||||
@@ -107,15 +107,15 @@ function portsForSymbol(modelType: string) {
|
|||||||
}
|
}
|
||||||
if (modelType === "amesim_pnvo001_fixed") {
|
if (modelType === "amesim_pnvo001_fixed") {
|
||||||
return [
|
return [
|
||||||
physicalPort("port_2", "left", 10),
|
physicalPort("port_2", "right", 10),
|
||||||
physicalPort("port_3", "right", 20),
|
physicalPort("port_3", "left", 20),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (modelType === "amesim_pnvo001") {
|
if (modelType === "amesim_pnvo001") {
|
||||||
return [
|
return [
|
||||||
signalPort("res", "left", "input", 5),
|
signalPort("res", "left", "input", 5),
|
||||||
physicalPort("port_2", "left", 10),
|
physicalPort("port_2", "right", 10),
|
||||||
physicalPort("port_3", "right", 20),
|
physicalPort("port_3", "left", 20),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -784,8 +784,8 @@ test("PNOR001 与两种 PNVO001 使用统一孔板风格和 small 画布", async
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
anchors: [
|
anchors: [
|
||||||
{ portName: "port_2", x: 12.8, y: 24 },
|
{ portName: "port_3", x: 12.8, y: 24 },
|
||||||
{ portName: "port_3", x: 51.2, y: 24 },
|
{ portName: "port_2", x: 51.2, y: 24 },
|
||||||
],
|
],
|
||||||
control: "fixed",
|
control: "fixed",
|
||||||
modelType: "amesim_pnvo001_fixed",
|
modelType: "amesim_pnvo001_fixed",
|
||||||
@@ -794,8 +794,8 @@ test("PNOR001 与两种 PNVO001 使用统一孔板风格和 small 画布", async
|
|||||||
{
|
{
|
||||||
anchors: [
|
anchors: [
|
||||||
{ portName: "res", x: 27.35, y: 43.2 },
|
{ portName: "res", x: 27.35, y: 43.2 },
|
||||||
{ portName: "port_2", x: 12.8, y: 24 },
|
{ portName: "port_3", x: 12.8, y: 24 },
|
||||||
{ portName: "port_3", x: 51.2, y: 24 },
|
{ portName: "port_2", x: 51.2, y: 24 },
|
||||||
],
|
],
|
||||||
control: "signal",
|
control: "signal",
|
||||||
modelType: "amesim_pnvo001",
|
modelType: "amesim_pnvo001",
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
import { migratedPnvo001Mirrored } from "../../src/pnvoPortDisplayMigration";
|
||||||
|
|
||||||
|
const legacyPorts = [
|
||||||
|
{ name: "res", side: "left" },
|
||||||
|
{ name: "port_2", side: "left" },
|
||||||
|
{ name: "port_3", side: "right" },
|
||||||
|
];
|
||||||
|
const amesimPorts = [
|
||||||
|
{ name: "res", side: "left" },
|
||||||
|
{ name: "port_2", side: "right" },
|
||||||
|
{ name: "port_3", side: "left" },
|
||||||
|
];
|
||||||
|
|
||||||
|
test("旧 PNVO001 端口快照只迁移一次镜像方向", () => {
|
||||||
|
for (const modelType of [
|
||||||
|
"amesim_pnvo001_fixed",
|
||||||
|
"amesim_pnvo001",
|
||||||
|
]) {
|
||||||
|
const migrated = migratedPnvo001Mirrored(
|
||||||
|
modelType,
|
||||||
|
legacyPorts,
|
||||||
|
amesimPorts,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(migrated).toBe(false);
|
||||||
|
expect(
|
||||||
|
migratedPnvo001Mirrored(
|
||||||
|
modelType,
|
||||||
|
amesimPorts,
|
||||||
|
amesimPorts,
|
||||||
|
migrated,
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
migratedPnvo001Mirrored(
|
||||||
|
modelType,
|
||||||
|
legacyPorts,
|
||||||
|
amesimPorts,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(
|
||||||
|
migratedPnvo001Mirrored(
|
||||||
|
"amesim_pnor001",
|
||||||
|
legacyPorts,
|
||||||
|
amesimPorts,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
import {
|
||||||
|
continuousMechanicalForceChartValues,
|
||||||
|
resultStateTransitionSampleIndices,
|
||||||
|
segmentedChartPath,
|
||||||
|
} from "../../src/resultEventSeries";
|
||||||
|
|
||||||
|
const mechanicalComponent = {
|
||||||
|
data: {
|
||||||
|
ports: [{ name: "port_2", domain: "mechanical" }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mechanicalPortForce = {
|
||||||
|
scope: "port" as const,
|
||||||
|
portName: "port_2",
|
||||||
|
quantity: "force",
|
||||||
|
category: "flow",
|
||||||
|
};
|
||||||
|
|
||||||
|
test("只分离机械事件中的孤立力尖峰,同时保留连续曲线和原始值", () => {
|
||||||
|
const time = [0, 0.5, 0.75, 1, 1.5];
|
||||||
|
const eventIndices = resultStateTransitionSampleIndices(time, {
|
||||||
|
integration: {
|
||||||
|
segments: [
|
||||||
|
{ stateTransitionTimes: [0.75] },
|
||||||
|
{ stateTransitionTimes: [0.7500000000000001] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect([...eventIndices]).toEqual([2]);
|
||||||
|
|
||||||
|
const rawForce = [1, 2, 1e12, 3, 4];
|
||||||
|
const displayedForce = continuousMechanicalForceChartValues(
|
||||||
|
mechanicalPortForce,
|
||||||
|
mechanicalComponent,
|
||||||
|
rawForce,
|
||||||
|
eventIndices,
|
||||||
|
);
|
||||||
|
expect(displayedForce.separatedEventSampleCount).toBe(1);
|
||||||
|
expect(Number.isNaN(displayedForce.values[2])).toBe(true);
|
||||||
|
expect(Math.max(...displayedForce.values.filter(Number.isFinite))).toBe(4);
|
||||||
|
expect(rawForce[2]).toBe(1e12);
|
||||||
|
|
||||||
|
const path = segmentedChartPath(
|
||||||
|
time,
|
||||||
|
displayedForce.values,
|
||||||
|
(value) => value,
|
||||||
|
(value) => value,
|
||||||
|
);
|
||||||
|
expect(path.match(/\bM /g)).toHaveLength(1);
|
||||||
|
|
||||||
|
const smoothForce = [1, 2, 2.5, 3, 4];
|
||||||
|
const displayedSmoothForce = continuousMechanicalForceChartValues(
|
||||||
|
mechanicalPortForce,
|
||||||
|
mechanicalComponent,
|
||||||
|
smoothForce,
|
||||||
|
eventIndices,
|
||||||
|
);
|
||||||
|
expect(displayedSmoothForce.values).toBe(smoothForce);
|
||||||
|
expect(displayedSmoothForce.separatedEventSampleCount).toBe(0);
|
||||||
|
|
||||||
|
const componentForce = continuousMechanicalForceChartValues(
|
||||||
|
{
|
||||||
|
scope: "component",
|
||||||
|
portName: null,
|
||||||
|
quantity: "force",
|
||||||
|
category: "derived",
|
||||||
|
},
|
||||||
|
mechanicalComponent,
|
||||||
|
[1, 2, -1e12, 3, 4],
|
||||||
|
eventIndices,
|
||||||
|
);
|
||||||
|
expect(componentForce.separatedEventSampleCount).toBe(1);
|
||||||
|
expect(Number.isNaN(componentForce.values[2])).toBe(true);
|
||||||
|
|
||||||
|
const signalForce = continuousMechanicalForceChartValues(
|
||||||
|
{
|
||||||
|
scope: "component",
|
||||||
|
portName: null,
|
||||||
|
quantity: "force",
|
||||||
|
category: "signal",
|
||||||
|
},
|
||||||
|
mechanicalComponent,
|
||||||
|
[1, 2, -1e12, 3, 4],
|
||||||
|
eventIndices,
|
||||||
|
);
|
||||||
|
expect(signalForce.values[2]).toBe(-1e12);
|
||||||
|
expect(signalForce.separatedEventSampleCount).toBe(0);
|
||||||
|
|
||||||
|
const stateValues = [0, 0.5, 1e9, 1, 1.5];
|
||||||
|
const displayedState = continuousMechanicalForceChartValues(
|
||||||
|
{ ...mechanicalPortForce, quantity: "velocity" },
|
||||||
|
mechanicalComponent,
|
||||||
|
stateValues,
|
||||||
|
eventIndices,
|
||||||
|
);
|
||||||
|
expect(displayedState.values).toBe(stateValues);
|
||||||
|
expect(displayedState.values[2]).toBe(1e9);
|
||||||
|
|
||||||
|
const pneumaticForce = continuousMechanicalForceChartValues(
|
||||||
|
mechanicalPortForce,
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
ports: [{ name: "port_2", domain: "pneumatic" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rawForce,
|
||||||
|
eventIndices,
|
||||||
|
);
|
||||||
|
expect(pneumaticForce.values).toBe(rawForce);
|
||||||
|
|
||||||
|
const persistentContactForce = [0, 0, 1e6, 1e6, 1e6];
|
||||||
|
const displayedPersistentContactForce =
|
||||||
|
continuousMechanicalForceChartValues(
|
||||||
|
mechanicalPortForce,
|
||||||
|
mechanicalComponent,
|
||||||
|
persistentContactForce,
|
||||||
|
eventIndices,
|
||||||
|
);
|
||||||
|
expect(displayedPersistentContactForce.values).toBe(
|
||||||
|
persistentContactForce,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
displayedPersistentContactForce.separatedEventSampleCount,
|
||||||
|
).toBe(0);
|
||||||
|
expect(
|
||||||
|
resultStateTransitionSampleIndices(time, {}),
|
||||||
|
).toEqual(new Set<number>());
|
||||||
|
});
|
||||||
@@ -34,6 +34,88 @@ const chartWindowSnapshot = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const eventForceSnapshot = {
|
||||||
|
...chartWindowSnapshot,
|
||||||
|
id: "e2e-mechanical-event-force-chart",
|
||||||
|
project: {
|
||||||
|
...chartWindowSnapshot.project,
|
||||||
|
nodes: chartWindowSnapshot.project.nodes.map((node, nodeIndex) =>
|
||||||
|
nodeIndex === 0
|
||||||
|
? {
|
||||||
|
...node,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
ports: node.data.ports.map((port, portIndex) =>
|
||||||
|
portIndex === 0
|
||||||
|
? { ...port, domain: "mechanical" }
|
||||||
|
: port,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: node,
|
||||||
|
),
|
||||||
|
simulation: {
|
||||||
|
...chartWindowSnapshot.project.simulation,
|
||||||
|
t_stop: 2,
|
||||||
|
step: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: {
|
||||||
|
...chartWindowSnapshot.result,
|
||||||
|
simulatedUntil: 2,
|
||||||
|
requestedStopTime: 2,
|
||||||
|
variables: [
|
||||||
|
...chartWindowSnapshot.result.variables,
|
||||||
|
{
|
||||||
|
key: "generic_sensor_1.port_a.f",
|
||||||
|
componentId: "generic_sensor_1",
|
||||||
|
componentType: "generic_sensor",
|
||||||
|
scope: "port",
|
||||||
|
portName: "port_a",
|
||||||
|
name: "f",
|
||||||
|
label: "力",
|
||||||
|
quantity: "force",
|
||||||
|
unit: "N",
|
||||||
|
category: "flow",
|
||||||
|
order: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "generic_sensor_1.force",
|
||||||
|
componentId: "generic_sensor_1",
|
||||||
|
componentType: "generic_sensor",
|
||||||
|
scope: "component",
|
||||||
|
portName: null,
|
||||||
|
name: "force",
|
||||||
|
label: "接触力",
|
||||||
|
quantity: "force",
|
||||||
|
unit: "N",
|
||||||
|
category: "derived",
|
||||||
|
order: 31,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
final: {
|
||||||
|
...chartWindowSnapshot.result.final,
|
||||||
|
"generic_sensor_1.value": 2,
|
||||||
|
"generic_sensor_1.port_a.f": 5,
|
||||||
|
"generic_sensor_1.force": 5,
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
...chartWindowSnapshot.result.series,
|
||||||
|
time: [0, 0.5, 0.75, 1, 1.5, 2],
|
||||||
|
"generic_sensor_1.value": [0, 0.5, 1e9, 1, 1.5, 2],
|
||||||
|
"generic_sensor_1.port_a.f": [1, 2, 1e12, 3, 4, 5],
|
||||||
|
"generic_sensor_1.force": [1, 2, -1e12, 3, 4, 5],
|
||||||
|
},
|
||||||
|
diagnostics: {
|
||||||
|
...chartWindowSnapshot.result.diagnostics,
|
||||||
|
integration: {
|
||||||
|
segments: [{ stateTransitionTimes: [0.75] }],
|
||||||
|
},
|
||||||
|
sampleCount: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
async function openResults(page: Page) {
|
async function openResults(page: Page) {
|
||||||
await page.getByRole("tab", { name: "结果", exact: true }).click();
|
await page.getByRole("tab", { name: "结果", exact: true }).click();
|
||||||
await expect(page.locator(".results-chart-workspace")).toBeVisible();
|
await expect(page.locator(".results-chart-workspace")).toBeVisible();
|
||||||
@@ -129,6 +211,95 @@ test.beforeEach(async ({ page }) => {
|
|||||||
}, chartWindowSnapshot);
|
}, chartWindowSnapshot);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("机械状态事件力点不参与连续曲线纵轴,但原始数据与非力状态保持不变", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.addInitScript((snapshot) => {
|
||||||
|
window.sessionStorage.setItem(
|
||||||
|
"system-simulation-flow:latest-result",
|
||||||
|
JSON.stringify(snapshot),
|
||||||
|
);
|
||||||
|
}, eventForceSnapshot);
|
||||||
|
await page.goto("/");
|
||||||
|
await openResults(page);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator(".results-variable-list button")
|
||||||
|
.filter({ hasText: "port_a · 力" })
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
const forceWindow = page
|
||||||
|
.locator('.result-chart-window[data-chart-kind="single"]')
|
||||||
|
.filter({ hasText: "port_a.f" });
|
||||||
|
await expect(forceWindow).toHaveAttribute(
|
||||||
|
"data-separated-event-samples",
|
||||||
|
"1",
|
||||||
|
);
|
||||||
|
await expect(forceWindow).toContainText("已隐藏 1 个孤立事件力尖峰");
|
||||||
|
|
||||||
|
const forceChart = forceWindow.locator('svg[data-result-chart="true"]');
|
||||||
|
const forceDomain = await readChartDomain(forceChart);
|
||||||
|
expect(forceDomain.yMax).toBeLessThan(10);
|
||||||
|
const forcePath = await forceChart
|
||||||
|
.locator('path[data-chart-clipped-series="true"]')
|
||||||
|
.getAttribute("d");
|
||||||
|
expect(forcePath?.match(/\bM /g)).toHaveLength(1);
|
||||||
|
|
||||||
|
const storedEventForce = await page.evaluate(() => {
|
||||||
|
const raw = sessionStorage.getItem(
|
||||||
|
"system-simulation-flow:latest-result",
|
||||||
|
);
|
||||||
|
return raw
|
||||||
|
? JSON.parse(raw).result.series["generic_sensor_1.port_a.f"][2]
|
||||||
|
: null;
|
||||||
|
});
|
||||||
|
expect(storedEventForce).toBe(1e12);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator(".results-variable-list button")
|
||||||
|
.filter({ hasText: "接触力" })
|
||||||
|
.click();
|
||||||
|
const componentForceWindow = page
|
||||||
|
.locator('.result-chart-window[data-chart-kind="single"]')
|
||||||
|
.filter({ hasText: "generic_sensor_1.force" });
|
||||||
|
await expect(componentForceWindow).toHaveAttribute(
|
||||||
|
"data-separated-event-samples",
|
||||||
|
"1",
|
||||||
|
);
|
||||||
|
const componentForceDomain = await readChartDomain(
|
||||||
|
componentForceWindow.locator('svg[data-result-chart="true"]'),
|
||||||
|
);
|
||||||
|
expect(componentForceDomain.yMin).toBeGreaterThan(-10);
|
||||||
|
|
||||||
|
const storedComponentForce = await page.evaluate(() => {
|
||||||
|
const raw = sessionStorage.getItem(
|
||||||
|
"system-simulation-flow:latest-result",
|
||||||
|
);
|
||||||
|
return raw
|
||||||
|
? JSON.parse(raw).result.series["generic_sensor_1.force"][2]
|
||||||
|
: null;
|
||||||
|
});
|
||||||
|
expect(storedComponentForce).toBe(-1e12);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator(".results-variable-list button")
|
||||||
|
.filter({ hasText: "数值" })
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
const stateWindow = page
|
||||||
|
.locator('.result-chart-window[data-chart-kind="single"]')
|
||||||
|
.filter({ hasText: "数值" });
|
||||||
|
await expect(stateWindow).toHaveAttribute(
|
||||||
|
"data-separated-event-samples",
|
||||||
|
"0",
|
||||||
|
);
|
||||||
|
const stateDomain = await readChartDomain(
|
||||||
|
stateWindow.locator('svg[data-result-chart="true"]'),
|
||||||
|
);
|
||||||
|
expect(stateDomain.yMax).toBeGreaterThan(1e8);
|
||||||
|
});
|
||||||
|
|
||||||
test("组合曲线窗口先创建为空窗,并在删除最后一条曲线后恢复待定", async ({
|
test("组合曲线窗口先创建为空窗,并在删除最后一条曲线后恢复待定", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -141,6 +141,13 @@ class ComponentCatalogTests(unittest.TestCase):
|
|||||||
self.assertEqual(components["amesim_pnvo001_fixed"]["category"]["id"], "flow")
|
self.assertEqual(components["amesim_pnvo001_fixed"]["category"]["id"], "flow")
|
||||||
self.assertEqual(pnvo_parameters["opening"]["maximum"], 1.0)
|
self.assertEqual(pnvo_parameters["opening"]["maximum"], 1.0)
|
||||||
self.assertEqual([port["name"] for port in components["amesim_pnvo001_fixed"]["ports"]], ["port_2", "port_3"])
|
self.assertEqual([port["name"] for port in components["amesim_pnvo001_fixed"]["ports"]], ["port_2", "port_3"])
|
||||||
|
self.assertEqual(
|
||||||
|
{
|
||||||
|
port["name"]: port["side"]
|
||||||
|
for port in components["amesim_pnvo001_fixed"]["ports"]
|
||||||
|
},
|
||||||
|
{"port_2": "right", "port_3": "left"},
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(components["amesim_step0"]["category"]["id"], "signals")
|
self.assertEqual(components["amesim_step0"]["category"]["id"], "signals")
|
||||||
self.assertEqual([port["name"] for port in components["amesim_step0"]["ports"]], ["out"])
|
self.assertEqual([port["name"] for port in components["amesim_step0"]["ports"]], ["out"])
|
||||||
@@ -383,6 +390,13 @@ class ComponentCatalogTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(components["amesim_pnvo001"]["category"]["id"], "flow")
|
self.assertEqual(components["amesim_pnvo001"]["category"]["id"], "flow")
|
||||||
self.assertEqual([port["name"] for port in components["amesim_pnvo001"]["ports"]], ["res", "port_2", "port_3"])
|
self.assertEqual([port["name"] for port in components["amesim_pnvo001"]["ports"]], ["res", "port_2", "port_3"])
|
||||||
|
self.assertEqual(
|
||||||
|
{
|
||||||
|
port["name"]: port["side"]
|
||||||
|
for port in components["amesim_pnvo001"]["ports"]
|
||||||
|
},
|
||||||
|
{"res": "left", "port_2": "right", "port_3": "left"},
|
||||||
|
)
|
||||||
|
|
||||||
pnl_parameters = {
|
pnl_parameters = {
|
||||||
parameter["name"]: parameter
|
parameter["name"]: parameter
|
||||||
|
|||||||
Reference in new issue
Block a user