Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 40 additions & 16 deletions extension/src-language-server/fta/diagram/fta-diagram-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import { AstNode } from "langium";
import { GeneratorContext, IdCache, LangiumDiagramGenerator } from "langium-sprotty";
import { SLabel, SModelElement, SModelRoot, SNode } from "sprotty-protocol";
import { Component, Condition, Gate, ModelFTA, isComponent, isCondition, isKNGate } from "../../generated/ast.js";
import { Component, Condition, Gate, ModelFTA, isComponent, isCondition, isInhibitGate, isKNGate } from "../../generated/ast.js";
import { HEADER_LABEL_TYPE } from "../../stpa/diagram/stpa-model.js";
import { getDescription } from "../../utils.js";
import { topOfAnalysis } from "../analysis/fta-cutSet-calculator.js";
Expand All @@ -37,6 +37,7 @@ import {
} from "./fta-model.js";
import { FtaSynthesisOptions, noCutSet, spofsSet } from "./fta-synthesis-options.js";
import { getFTNodeType, getTargets } from "./utils.js";

export class FtaDiagramGenerator extends LangiumDiagramGenerator {
protected readonly options: FtaSynthesisOptions;

Expand Down Expand Up @@ -71,8 +72,8 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator {
const ftaChildren: SModelElement[] = [
// create nodes for top event, components, conditions, and gates
...model.components.map(component => this.generateFTNode(component, idCache)),
...model.conditions.map(condition => this.generateFTNode(condition, idCache)),
...model.gates.map(gate => this.generateGate(gate, idCache)),
...model.conditions.map(condition => this.generateFTNode(condition, idCache)),
// create edges for the gates and the top event
...model.gates.map(gate => this.generateEdges(gate, idCache)).flat(1),
];
Expand Down Expand Up @@ -111,16 +112,22 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator {
const edgeId = idCache.uniqueId(`${sourceId}_${targetId}`, undefined);

// create port for the source node
const sourceNode = this.idToSNode.get(sourceId);
let sourceNode = this.idToSNode.get(sourceId);
// if redundant gates are not shown, the source node propably is a description node
if (!this.options.getShowRedundantGates() && this.descriptionOfGate.has(sourceId)) {
sourceNode = this.descriptionOfGate.get(sourceId);
}
const sourcePortId = idCache.uniqueId(edgeId + "_port");
sourceNode?.children?.push(this.createFTAPort(sourcePortId, PortSide.SOUTH));
// for inhibit gates the condition is on the east side, all other edges are on the south side
const portSide = isInhibitGate(node) && targets.indexOf(target) === 1 ? PortSide.EAST : PortSide.SOUTH;
sourceNode?.children?.push(this.createFTAPort(sourcePortId, portSide));
Comment thread
Drakae marked this conversation as resolved.

// create port for source parent and edge to this port
let sourceParentPortId: string | undefined;
if (this.parentOfGate.has(sourceId)) {
const parent = this.parentOfGate.get(sourceId);
sourceParentPortId = idCache.uniqueId(edgeId + "_port");
parent?.children?.push(this.createFTAPort(sourceParentPortId, PortSide.SOUTH));
parent?.children?.push(this.createFTAPort(sourceParentPortId, PortSide.SOUTH));
const betweenEdgeId = idCache.uniqueId(edgeId + "_betweenEdge");
const e = this.generateFTEdge(
betweenEdgeId,
Expand All @@ -141,7 +148,11 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator {
targetPortId = this.nodeToPort.get(parent?.id ?? "")?.id;
} else {
// get the port id from the target node
const targetNode = this.idToSNode.get(targetId);
let targetNode = this.idToSNode.get(targetId);
// if redundant gates are not shown, the target node propably is a description node
if (!this.options.getShowRedundantGates() && this.descriptionOfGate.has(targetId)) {
targetNode = this.descriptionOfGate.get(targetId);
}
targetPortId = this.nodeToPort.get(targetNode?.id ?? "")?.id;
}

Expand Down Expand Up @@ -190,16 +201,13 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator {
};
}

protected generateGate(node: Gate, idCache: IdCache<AstNode>): FTANode {
protected generateGate(node: Gate, idCache: IdCache<AstNode>): SNode {
const gateNode = this.generateFTNode(node, idCache);
this.idToSNode.set(gateNode.id, gateNode);
if (!this.options.getShowGateDescriptions() || node.description === undefined) {
return gateNode;
}
// create node for gate description
const descriptionNodeId = idCache.uniqueId(node.name + "Description");
Comment thread
Drakae marked this conversation as resolved.
const label = getDescription(
node.description,
node.description ?? "",
Comment thread
Drakae marked this conversation as resolved.
this.options.getLabelManagement(),
this.options.getLabelShorteningWidth(),
descriptionNodeId,
Expand All @@ -220,6 +228,24 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator {
paddingRight: 10.0,
},
};
this.idToSNode.set(descriptionNode.id, descriptionNode);

// if redundandant gates should not be shown, only the description is displayed
if (!this.options.getShowRedundantGates() && !isInhibitGate(node) && node.children.length <= 1) {
// description node needs a port for incoming edges
const port = this.createFTAPort(idCache.uniqueId(descriptionNodeId + "_port"), PortSide.NORTH);
descriptionNode.children?.push(port);
this.nodeToPort.set(descriptionNode.id, port);

// map gate to its description node
this.descriptionOfGate.set(gateNode.id, descriptionNode);
return descriptionNode;
}

// if no description should be shown, the invisivle parent node is not needed
if (!this.options.getShowGateDescriptions() || node.description === undefined) {
return gateNode;
}

// create invisible edge from description to gate
const invisibleEdge = this.generateFTEdge(
Expand All @@ -244,15 +270,15 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator {
);

// order is important to have the descriptionNode above the gateNode
const children: SModelElement[] = [
const children = [
descriptionNode,
gateNode,
invisibleEdge,
port,
invisibleEdgeParetToDescription,
];

// create invisible node that contains the desciprion and gate node
// create invisible node that contains the description and gate node
const parent = {
type: FTA_NODE_TYPE,
id: parentId,
Expand All @@ -272,8 +298,6 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator {
};

// update maps
this.idToSNode.set(descriptionNode.id, descriptionNode);
this.descriptionOfGate.set(gateNode.id, descriptionNode);
this.parentOfGate.set(gateNode.id, parent);
this.nodeToPort.set(parent.id, port);
return parent;
Expand Down Expand Up @@ -317,7 +341,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator {
// single points of failure should be shown
if (set === spofsSet.id) {
const spofs = this.options.getSpofs();
includedInCutSet = spofs.includes(node.name);
includedInCutSet = spofs?.includes(node.name);
notConnected = !includedInCutSet;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class FtaLayoutConfigurator extends DefaultLayoutConfigurator {
"org.eclipse.elk.portConstraints": "FIXED_SIDE",
"org.eclipse.elk.spacing.portPort": "0.0",
"org.eclipse.elk.layered.nodePlacement.bk.fixedAlignment": "BALANCED",
"org.eclipse.elk.hierarchyHandling": "INCLUDE_CHILDREN",
};

if (sgraph.modelOrder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { SynthesisOptions, layoutCategory } from "../../synthesis-options.js";
const cutSetsID = "cutSets";
const showGateDescriptionsID = "showGateDescriptions";
const showComponentDescriptionsID = "showComponentDescriptions";
const showRedundantGatesID = "showRedundantGates";

const analysisCategoryID = "analysisCategory";

Expand Down Expand Up @@ -80,6 +81,22 @@ const showComponentDescriptionsOptions: ValuedSynthesisOption = {
currentValue: false,
};

/**
* Boolean option to toggle the visualization of redundant gates.
*/
const showRedundantGatesOptions: ValuedSynthesisOption = {
synthesisOption: {
id: showRedundantGatesID,
name: "Show Redundant Gates",
type: TransformationOptionType.CHECK,
initialValue: true,
currentValue: true,
values: [true, false],
category: layoutCategory,
},
currentValue: true,
};

/**
* Option to highlight the components of a cut set.
*/
Expand All @@ -102,7 +119,7 @@ export class FtaSynthesisOptions extends SynthesisOptions {
protected spofs: string[];
Comment thread
Drakae marked this conversation as resolved.
Outdated
constructor() {
super();
this.options.push(...[analysisCategoryOption, cutSets, showGateDescriptionsOptions, showComponentDescriptionsOptions]);
this.options.push(...[analysisCategoryOption, cutSets, showGateDescriptionsOptions, showComponentDescriptionsOptions, showRedundantGatesOptions]);
}

getShowGateDescriptions(): boolean {
Expand All @@ -113,6 +130,10 @@ export class FtaSynthesisOptions extends SynthesisOptions {
return this.getOption(showComponentDescriptionsID)?.currentValue;
}

getShowRedundantGates(): boolean {
return this.getOption(showRedundantGatesID)?.currentValue;
}

/**
* Updates the cutSets option with the availabe cut sets.
* @param values The currently avaiable cut sets.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ function createFaulTreeForHazard(scenarios: Map<string, LossScenario[]>, hazard:

// create top event
const topEvent = {
name: hazard.description,
name: hazard.name + ": " + hazard.description,
child: { ref: gate, $refText: gate.name },
$container: ftaModel,
$type: "TopEvent",
Expand Down
8 changes: 4 additions & 4 deletions extension/src-language-server/stpa/stpa.langium
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,10 @@ LossScenario:
(name=ID ('<' factor=CausalFactor '>')? 'for' (uca=[UCA] | uca=[Context]) description=STRING (list=HazardList)?);

CausalFactor returns string:
'controlAction' | 'inadequateOperation' | 'delayedOperation' | 'componentFailure' | 'changesOverTime' |
'conflictingCA' | 'wrongProcessInput' | 'disturbance' | 'processOutput' | 'delayedFeedback' |
'measurementInaccurate' | 'incorrectInformationProvided' | 'missingFeedback' | 'inadequateControlAlgorithm' |
'wrongProcessModel' | 'wrongControlInput';
'ControlAction' | 'InadequateOperation' | 'DelayedOperation' | 'ComponentFailure' | 'ChangesOverTime' |
'ConflictingCA' | 'WrongProcessInput' | 'Disturbance' | 'ProcessOutput' | 'DelayedFeedback' |
'MeasurementInaccurate' | 'IncorrectInformationProvided' | 'MissingFeedback' | 'InadequateControlAlgorithm' |
'WrongProcessModel' | 'WrongControlInput';
Comment on lines 145 to +149

HazardList:
'[' refs+=[Hazard:SubID] (',' refs+=[Hazard:SubID])*']';
Expand Down
2 changes: 1 addition & 1 deletion extension/src-webview/views-rendering.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ export function renderKnGate(node: SNodeImpl, k: number, n: number): VNode {
return (
<g>
<path d={path} />
<text x={midX - 7.0} y={botY - 4.5} text-anchor="middle" class-fta-text={true}>
<text x={midX} y={botY - 4.5} text-anchor="middle" class-fta-text={true}>
{`${k}/${n}`}
</text>
</g>
Expand Down
Loading