修复机械事件力曲线并对齐气动孔口端口显示
This commit is contained in:
1 parent
e18399c022
commit
408b4ecb22
11 files changed
+697
-43
No files matched your search
@@ -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(" ");
|
||||
}
|
||||
Reference in new issue
Block a user