diff --git a/src/main/java/cz/cvut/kbss/analysis/controller/FaultTreeController.java b/src/main/java/cz/cvut/kbss/analysis/controller/FaultTreeController.java index 560990c0..afb96f35 100755 --- a/src/main/java/cz/cvut/kbss/analysis/controller/FaultTreeController.java +++ b/src/main/java/cz/cvut/kbss/analysis/controller/FaultTreeController.java @@ -39,7 +39,7 @@ public List summaries() { public FaultTree find(@PathVariable(name = "faultTreeFragment") String faultTreeFragment) { log.info("> find - {}", faultTreeFragment); URI faultTreeUri = identifierService.composeIdentifier(Vocabulary.s_c_FaultTree, faultTreeFragment); - return repositoryService.findWithPropagation(faultTreeUri); + return repositoryService.findRequired(faultTreeUri); } @ResponseStatus(HttpStatus.CREATED) @@ -126,4 +126,13 @@ public void performCutSetAnalysis(@PathVariable(name = "faultTreeFragment") Stri log.info("> performCutSetAnalysis - {}", faultTreeFragment); repositoryService.performCutSetAnalysis(faultTreeUri); } + + @ResponseStatus(HttpStatus.NO_CONTENT) + @PutMapping(value = "/{faultTreeFragment}/evaluate") + public void evaluate(@PathVariable(name = "faultTreeFragment") String faultTreeFragment){ + URI faultTreeUri = identifierService.composeIdentifier(Vocabulary.s_c_FaultTree, faultTreeFragment); + log.info("> performCutSetAnalysis - {}", faultTreeFragment); + repositoryService.evaluate(faultTreeUri); + } + } diff --git a/src/main/java/cz/cvut/kbss/analysis/model/fta/CutSetExtractor.java b/src/main/java/cz/cvut/kbss/analysis/model/fta/CutSetExtractor.java index 71f17c20..0b315bff 100644 --- a/src/main/java/cz/cvut/kbss/analysis/model/fta/CutSetExtractor.java +++ b/src/main/java/cz/cvut/kbss/analysis/model/fta/CutSetExtractor.java @@ -94,6 +94,11 @@ public List extract(FaultEvent faultEvent){ return processAndGateScenarios(partScenarios); } + public List extractMinimalScenarios(FaultEvent faultEvent){ + List minScenarios = extract(faultEvent); + return extractMinimalScenarios(minScenarios); + } + protected List processAndGateScenarios(List> partScenarios){ List inds = new ArrayList<>(partScenarios.size()); partScenarios.forEach(l -> inds.add(0)); diff --git a/src/main/java/cz/cvut/kbss/analysis/model/fta/FTAMinimalCutSetEvaluation.java b/src/main/java/cz/cvut/kbss/analysis/model/fta/FTAMinimalCutSetEvaluation.java new file mode 100644 index 00000000..1ea1b4f7 --- /dev/null +++ b/src/main/java/cz/cvut/kbss/analysis/model/fta/FTAMinimalCutSetEvaluation.java @@ -0,0 +1,400 @@ +package cz.cvut.kbss.analysis.model.fta; + +import cz.cvut.kbss.analysis.model.FaultEvent; +import cz.cvut.kbss.analysis.model.FaultEventScenario; +import cz.cvut.kbss.analysis.model.FaultTree; +import cz.cvut.kbss.analysis.service.strategy.DirectFtaEvaluation; +import cz.cvut.kbss.analysis.service.util.Pair; +import lombok.extern.slf4j.Slf4j; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Slf4j +public class FTAMinimalCutSetEvaluation { + + protected CutSetExtractor cutSetExtractor = new CutSetExtractor(); + protected DirectFtaEvaluation directFtaEvaluation = new DirectFtaEvaluation(); + protected List minScenarios; + + /** + * Evaluate fault tree probability of top and intermediate nodes and extract min cut sets of top event. + * If parts of top or intermediate events are not independent minimal cut set evaluation method is used. Direct + * evaluation is used otherwise. + * + * @apiNote this method is not thread safe + * @param faultTree + */ + public void evaluate(FaultTree faultTree){ + minScenarios = null; + evaluate(faultTree.getManifestingEvent()); + + if(minScenarios == null) + minScenarios = cutSetExtractor.extractMinimalScenarios(faultTree.getManifestingEvent()); + + faultTree.setFaultEventScenarios(new HashSet<>(minScenarios)); + } + + + /** + * Traverse the input faultEvent and its parts and evaluate their probabilities. + * If parts of faultEvent are dependent minimal cut-set evaluation method is used. Direct method is used otherwise. + * @param faultEvent + * @return a pair where left is true if faultEvent contains dependent events, false otherwise. Right contains the set of all basic events in the subtree of faultEvent. + */ + public Pair> evaluate(FaultEvent faultEvent){ + if(faultEvent.getEventType() == FtaEventType.BASIC || faultEvent.getChildren() == null || faultEvent.getChildren().isEmpty()) + return Pair.of(false, Collections.singleton(faultEvent)); + + List>> l = faultEvent.getChildren().stream().map(this::evaluate).collect(Collectors.toList());// RECURSION !for() + boolean dependent = l.stream().anyMatch(p -> p.getFirst()); + + if(!dependent)// if some of the child events are dependent faultEvent is also dependent + dependent = isDepended(l.stream().map(p -> p.getSecond()).collect(Collectors.toList())); + + if(dependent){ // evaluate using minimal cut sets method + minScenarios = cutSetExtractor.extractMinimalScenarios(faultEvent); + double prob = evaluate(minScenarios); + faultEvent.setProbability(prob); + }else{//evaluate using direct method + directFtaEvaluation.propagateProbabilities(faultEvent); + } + Set references = new HashSet<>(); + l.forEach(p -> references.addAll(p.getSecond())); + return Pair.of(dependent, references) ; + } + + /** + * + * @param childReferences + * @return true if there is a FaultEvent present in at least two fault event sets in childReferences, false otherwise + */ + public boolean isDepended(List> childReferences){ + + Set set = new HashSet<>(); + for(int i = 0; i < childReferences.size() ; i ++) + for(FaultEvent fe : childReferences.get(i)) + if(!set.add(fe)) + return true; + return false; + } + + /** + * Evaluate probability of minScenarios. Evaluation processes the OR of ANDs of FaultEventScenario.scenarioParts + * @param minScenarios + * @return probability of provided minScenarios list + */ + public Double evaluate(List minScenarios){ + // This should be simple optimization, it should not cause different results + minScenarios.sort(Comparator.comparing(s -> s.getScenarioParts().size())); + + OpExpression sceExpression = or(minScenarios.stream().map( s -> and(s)).collect(Collectors.toList())); + log.trace(sceExpression.toString()); + + Expression transformed = sceExpression.transform(); + log.trace(transformed.toString()); + + return transformed.evaluate(); + } + + /** + * Converts scenario.scenarioParts to a list of variables + * @param scenario + * @return list of variables corresponding to scenario.scenarioParts + */ + protected static List toVars(FaultEventScenario scenario){ + return scenario.getScenarioParts().stream() + .map(fe -> var(fe)).toList(); + } + + /** + * Construct an AND OpExpression from scenario.scenarioParts + * @param scenario + * @return + */ + protected static OpExpression and(FaultEventScenario scenario){ + return and(toVars(scenario)); + } + + /** + * Construct an AND OpExpression from args + * @param args + * @return + */ + protected static OpExpression and(Expression ... args){ + return and(Stream.of(args).collect(Collectors.toList())); + } + + /** + * Construct an AND OpExpression from args + * @param args + * @return + */ + protected static OpExpression and(List args){ + return new OpExpression(args, GateType.AND); + } + + /** + * Merge args into a list, keep only one occurrence of each variable and construct AND OpExpression + * @param args + * @return + */ + protected static OpExpression and(List ... args){ + List merged = Stream.of(args).flatMap(List::stream).distinct().collect(Collectors.toList()); + return and(merged); + } + + + /** + * Construct an OR OpExpression from args + * @param args + * @return + */ + protected static OpExpression or(Expression ... args){ + return or(Stream.of(args).distinct().collect(Collectors.toList())); + } + + /** + * Construct an OR OpExpression from args + * @param args + * @return + */ + protected static OpExpression or(List args){ + return new OpExpression(args, GateType.OR); + } + + /** + * Construct Var from fe + * @param fe + * @return + */ + static Var var(FaultEvent fe){ + return new Var(false, fe); + } + + /** + * Construct negated Var from fe + * @param fe + * @return + */ + static Var not(FaultEvent fe){ + return new Var(true, fe); + } + + + + static abstract class Expression{ + /** + * negate the expression + * @return new expression which is a logical negation of this expression + */ + abstract Expression not(); + + /** + * evaluate probability of the expression. Requires that the grounded terms in the expression, + * i.e. the Vars, have specified FaultEvent with a specified probability. + * @return calculated probability + */ + abstract Double evaluate(); + + /** + * Apply Shannon decomposition of this expression, convert to normal disjunctive form. + * @return the converted expression + */ + abstract Expression transform(); + + /** + * @return the direct arguments of the expression. If this a Var expression, returns this. IF this is + * OpExpression it returns this.expressions + */ + abstract List args(); + + /** + * @return list of Vars in this expression + */ + abstract List vars(); + } + + static class OpExpression extends Expression { + List expressions; + GateType op; + + public OpExpression(List expressions, GateType op) { + this.expressions = expressions; + this.op = op; + } + + @Override + public Expression not(){ + switch (op){ + case OR: return and(expressions.stream().map(Expression::not).toList()); + case AND: return or(expressions.stream().map(Expression::not).toList()); + default: return null; + } + } + + /** + * Assumes that this expression is in disjunctive form. + * @return + */ + @Override + Expression transform() { + if(op == GateType.AND) + throw new UnsupportedOperationException("Cannot transform OpExpression with AND operator. Expected Expression in disjunctive form" ); + List rexpArgs = new ArrayList<>(); + rexpArgs.add(expressions.get(expressions.size() - 1)); + + for(int i = expressions.size() - 2; i > -1; i --){ + Expression lexp = expressions.get(i); + Expression notLexp = lexp.not().transform(); + OpExpression t = multiplyAndSimplify(notLexp, or(rexpArgs)); + + rexpArgs = new ArrayList<>(); + rexpArgs.add(lexp); + rexpArgs.addAll(t.expressions); + } + + return or(rexpArgs); + } + + /** + * + * @param l expected in disjunctive form + * @param r expected in disjunctive form + * @return expression obtained converting l AND r into disjunctive form and removing clauses that contain contradiction (e.g. A and !A) + */ + protected OpExpression multiplyAndSimplify(Expression l, Expression r){ + ArrayList expArgs = new ArrayList<>(); + + Map> mapOfMaps = new HashMap<>(); + r.args().forEach(e -> mapOfMaps.put(e, createMap(e))); + + for(Expression lexp : l.args()){ + Map lmap = createMap(lexp); + if(lmap == null)// filter clauses containing contradiction + continue; + + for(Expression rexp : r.args()){ + Map rmap = mapOfMaps.get(rexp); + if(rmap == null || isMergeContradiction(lmap, rmap)) // filter clauses containing contradiction + continue; + + Expression expPart = and(lexp.args(), rexp.args()); + expArgs.add(expPart); + } + } + + return new OpExpression(expArgs, GateType.OR); + } + + /** + * An auxiliary function that extracts a map between events and their usage in Var expressions in the input AND + * clause exp. + * @param exp assuming this is a Var or AND of Vars + * @return map of FaultEvent to Var or null if the input AND clause exp contains a contradiction, e.g. (A and !A) + */ + protected Map createMap(Expression exp){ + Map map = new HashMap<>(); + for(Var v : exp.vars()){ + Var otherV = map.put(v.event, v); + if(otherV != null && otherV.not != v.not) + return null; + } + return map; + } + + /** + * Assumes inputs are cross-reference maps of FaultEvents to Vars in two AND of Vars clauses. + * @param m1 + * @param m2 + * @return true is there is v1 in m1 and v2 in m2 such that v1 = !v2, false otherwise + */ + protected boolean isMergeContradiction(Map m1, Map m2){ + return m1.entrySet().stream().anyMatch(e -> { + Var otherV = m2.get(e.getKey()); + if(otherV != null && otherV.not != e.getValue().not) + return true; + return false; + }); + } + + @Override + Double evaluate() { + switch (op){ + case OR: return expressions.stream().mapToDouble(Expression::evaluate).sum(); + case AND: return expressions.stream().mapToDouble(Expression::evaluate).reduce(1, (d1, d2) -> d1*d2); + default: return null; + } + } + + @Override + List args() { + return expressions; + } + + @Override + List vars() { + return expressions.stream().flatMap(e -> e.vars().stream()).distinct().toList(); + } + + public String toString(){ + return String.format("(%s)", expressions.stream().map(Object::toString).collect(Collectors.joining(op == GateType.AND ? "*" : "+"))); + } + } + + static class Var extends Expression{ + boolean not; + FaultEvent event; + + List args = Collections.singletonList(this); + + public Var(boolean not, FaultEvent event) { + this.not = not; + this.event = event; + } + + @Override + Var not(){ + return new Var(!not, event); + } + + @Override + Expression transform() { + return this; + } + + @Override + Double evaluate() { + if(not) + return 1 - event.getProbability(); + return event.getProbability(); + } + + @Override + List args() { + return args; + } + + @Override + List vars() { + return args; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Var var)) return false; + return not == var.not && Objects.equals(event, var.event); + } + + @Override + public int hashCode() { + return Objects.hash(not, event); + } + + public String toString(){ + return (not ? "!" : "") + event.getName(); + } + } +} diff --git a/src/main/java/cz/cvut/kbss/analysis/service/FaultEventRepositoryService.java b/src/main/java/cz/cvut/kbss/analysis/service/FaultEventRepositoryService.java index 1075f4dc..f227a8e6 100755 --- a/src/main/java/cz/cvut/kbss/analysis/service/FaultEventRepositoryService.java +++ b/src/main/java/cz/cvut/kbss/analysis/service/FaultEventRepositoryService.java @@ -3,14 +3,12 @@ import cz.cvut.kbss.analysis.dao.FaultEventDao; import cz.cvut.kbss.analysis.dao.FaultTreeDao; import cz.cvut.kbss.analysis.dao.GenericDao; -import cz.cvut.kbss.analysis.exception.CalculationException; import cz.cvut.kbss.analysis.exception.LogicViolationException; import cz.cvut.kbss.analysis.model.Component; import cz.cvut.kbss.analysis.model.FailureMode; import cz.cvut.kbss.analysis.model.FaultEvent; -import cz.cvut.kbss.analysis.model.fta.FtaEventType; import cz.cvut.kbss.analysis.model.diagram.Rectangle; -import cz.cvut.kbss.analysis.service.strategy.GateStrategyFactory; +import cz.cvut.kbss.analysis.service.strategy.DirectFtaEvaluation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -20,7 +18,6 @@ import java.net.URI; import java.util.List; -import java.util.stream.Collectors; @Slf4j @Service @@ -69,21 +66,7 @@ public FaultEvent addInputEvent(URI eventUri, FaultEvent inputEvent) { @Transactional(readOnly = true) public Double propagateProbability(FaultEvent event) { log.info("> propagateProbability - {}", event); - - if (event.getEventType() == FtaEventType.INTERMEDIATE) { - List childProbabilities = event.getChildren().stream() - .map(this::propagateProbability).collect(Collectors.toList()); - - try { - double eventProbability = GateStrategyFactory.get(event.getGateType()).propagate(childProbabilities, event); - event.setProbability(eventProbability); - }catch (CalculationException ex){ - log.info(ex.getMessage()); - } - } - - Double resultProbability = event.getProbability(); - + Double resultProbability = new DirectFtaEvaluation().evaluate(event); log.info("< propagateProbability - {}", resultProbability); return resultProbability; } diff --git a/src/main/java/cz/cvut/kbss/analysis/service/FaultTreeRepositoryService.java b/src/main/java/cz/cvut/kbss/analysis/service/FaultTreeRepositoryService.java index e1cfd0d2..7b18d147 100755 --- a/src/main/java/cz/cvut/kbss/analysis/service/FaultTreeRepositoryService.java +++ b/src/main/java/cz/cvut/kbss/analysis/service/FaultTreeRepositoryService.java @@ -6,6 +6,7 @@ import cz.cvut.kbss.analysis.model.*; import cz.cvut.kbss.analysis.model.diagram.Rectangle; import cz.cvut.kbss.analysis.model.fta.CutSetExtractor; +import cz.cvut.kbss.analysis.model.fta.FTAMinimalCutSetEvaluation; import cz.cvut.kbss.analysis.model.fta.FtaEventType; import cz.cvut.kbss.analysis.model.fta.GateType; import cz.cvut.kbss.analysis.service.util.FaultTreeTraversalUtils; @@ -89,12 +90,6 @@ protected void prePersist(FaultTree instance) { } } - @Override - protected void preUpdate(FaultTree instance) { - super.preUpdate(instance); - propagateProbabilities(instance); - } - private void propagateProbabilities(FaultTree faultTree) { log.info("> propagateProbabilities - {}", faultTree); @@ -431,4 +426,25 @@ public FaultTree performCutSetAnalysis(URI faultTreeUri){ return faultTree; } + + @Transactional + public FaultTree evaluate(URI faultTreeUri) { + FaultTree faultTree = findRequired(faultTreeUri); + + if(faultTree.getFaultEventScenarios() != null) { + for (FaultEventScenario faultEventScenario : faultTree.getFaultEventScenarios()) + faultEventScenarioDao.remove(faultEventScenario); + faultTree.setFaultEventScenarios(null); + } + + FTAMinimalCutSetEvaluation evaluator = new FTAMinimalCutSetEvaluation(); + evaluator.evaluate(faultTree); + + if(faultTree.getFaultEventScenarios() != null) + for(FaultEventScenario scenario : faultTree.getFaultEventScenarios()){ + scenario.updateProbability(); + faultEventScenarioDao.persist(scenario); + } + return faultTree; + } } diff --git a/src/main/java/cz/cvut/kbss/analysis/service/strategy/DirectFtaEvaluation.java b/src/main/java/cz/cvut/kbss/analysis/service/strategy/DirectFtaEvaluation.java new file mode 100644 index 00000000..4f0a6d2c --- /dev/null +++ b/src/main/java/cz/cvut/kbss/analysis/service/strategy/DirectFtaEvaluation.java @@ -0,0 +1,36 @@ +package cz.cvut.kbss.analysis.service.strategy; + +import cz.cvut.kbss.analysis.exception.CalculationException; +import cz.cvut.kbss.analysis.model.FaultEvent; +import cz.cvut.kbss.analysis.model.fta.FtaEventType; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +public class DirectFtaEvaluation { + public Double evaluate(FaultEvent event){ + if (event.getEventType() == FtaEventType.INTERMEDIATE) { + List childProbabilities = event.getChildren().stream() + .map(this::evaluate).collect(Collectors.toList()); + propagateProbabilities(event, childProbabilities); + return event.getProbability(); + } + return event.getProbability(); + } + + public void propagateProbabilities(FaultEvent event, List childProbabilities){ + try { + double eventProbability = GateStrategyFactory.get(event.getGateType()).propagate(childProbabilities, event); + event.setProbability(eventProbability); + }catch (CalculationException ex){ + log.info(ex.getMessage()); + } + } + + public void propagateProbabilities(FaultEvent event){ + List childProbabilities = event.getChildren().stream().map(FaultEvent::getProbability).toList(); + propagateProbabilities(event, childProbabilities); + } +} diff --git a/src/test/java/cz/cvut/kbss/analysis/model/fta/FTAMinimalCutSetEvaluationTest.java b/src/test/java/cz/cvut/kbss/analysis/model/fta/FTAMinimalCutSetEvaluationTest.java new file mode 100644 index 00000000..381c2842 --- /dev/null +++ b/src/test/java/cz/cvut/kbss/analysis/model/fta/FTAMinimalCutSetEvaluationTest.java @@ -0,0 +1,154 @@ +package cz.cvut.kbss.analysis.model.fta; + +import cz.cvut.kbss.analysis.model.FaultEvent; +import cz.cvut.kbss.analysis.model.FaultTree; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static cz.cvut.kbss.analysis.model.fta.FTAMinimalCutSetEvaluation.*; +import static org.junit.jupiter.api.Assertions.*; + +class FTAMinimalCutSetEvaluationTest { + + + static List vars = IntStream.range(0,6).mapToObj(i -> (char)('a' + i) + "").map(n ->{ + FaultEvent fe = new FaultEvent(); + fe.setProbability(0.1); + fe.setName(n); + return fe; + }).map(f -> var(f)).collect(Collectors.toList()); + + static List expressions(){ + return Stream.of( + new TestParams( + or(vars), + "(a+(!a*b)+(!a*!b*c)+(!a*!b*!c*d)+(!a*!b*!c*!d*e)+(!a*!b*!c*!d*!e*f))", + 0.1 + (1-0.1)*0.1 + (1-0.1)*(1-0.1)*0.1 + (1-0.1)*(1-0.1)*(1-0.1)*0.1 + (1-0.1)*(1-0.1)*(1-0.1)*(1-0.1)*0.1 + (1-0.1)*(1-0.1)*(1-0.1)*(1-0.1)*(1-0.1)*0.1 + ), + new TestParams(or(vars.get(0), and(vars.get(1),vars.get(2))), "(a+(!a*b*c))", 0.1 + (1-0.1)*0.1*0.1), + new TestParams(or(and(vars.get(0),vars.get(1)), vars.get(2)), "((a*b)+(!a*c)+(a*!b*c))", 0.01 + (1-0.1)*0.1 + 0.1*(1-0.1)*0.1), + new TestParams(or(vars.get(0), and(vars.get(1), vars.get(3)), and(vars.get(2), vars.get(3), vars.get(4))), "(a+(!a*b*d)+(!a*!b*c*d*e))", + 0.1 + 0.9*0.1*0.1 + 0.9*0.9*0.1*0.1*0.1) + ).collect(Collectors.toList()); + } + + static class TestParams{ + + public TestParams(OpExpression exp, String expectedSerialization, Double expectedValue) { + this.exp = exp; + this.expectedSerialization = expectedSerialization; + this.expectedValue = expectedValue; + } + + OpExpression exp; + String expectedSerialization; + Double expectedValue; + } + + @ParameterizedTest + @MethodSource("cz.cvut.kbss.analysis.model.fta.FTAMinimalCutSetEvaluationTest#expressions") + void testExpression_transform(TestParams p){ + Expression exp = p.exp.transform(); + String expStr = exp.toString(); + + assertEquals(p.expectedSerialization, expStr); + double actual = exp.evaluate(); + assertTrue(actual == p.expectedValue || Math.abs(p.expectedValue - exp.evaluate()) < 0.0000000000000001); + } + + @Test + void testExpression_Evaluate(){ + double res = and(vars.get(0), vars.get(1)).evaluate(); + Double expected = 0.1*0.1; + System.out.println(Math.abs(expected - res)); + System.out.println(Double.MIN_VALUE); + assertTrue(Math.abs(expected - res) < Double.MIN_VALUE); + } + + + @Test + void test_evaluate_fault_tree(){ + FaultTree faultTree = new FaultTree(); + List bs = IntStream.range(0,5).mapToObj(i -> (char)('a' + i) + "").map(n -> { + FaultEvent fe = new FaultEvent(); + fe.setProbability(0.1); + fe.setName(n); + fe.setEventType(FtaEventType.BASIC); + return fe; + }).collect(Collectors.toList()); + + List is = IntStream.range(0,6).mapToObj(i -> "G" + i).map(n -> { + FaultEvent fe = new FaultEvent(); + fe.setName(n); + fe.setEventType(FtaEventType.INTERMEDIATE); + return fe; + }).collect(Collectors.toList()); + + faultTree.setManifestingEvent(is.get(0)); + is.get(0).setGateType(GateType.OR); + is.get(0).setChildren(Stream.of(is.get(1), is.get(2)).collect(Collectors.toSet())); + + is.get(1).setGateType(GateType.AND); + is.get(1).setChildren(Stream.of(is.get(3), is.get(4)).collect(Collectors.toSet())); + + is.get(3).setGateType(GateType.OR); + is.get(3).setChildren(Stream.of(bs.get(1), bs.get(2)).collect(Collectors.toSet())); + + is.get(4).setGateType(GateType.AND); + is.get(4).setChildren(Stream.of(bs.get(3), bs.get(4)).collect(Collectors.toSet())); + + is.get(2).setGateType(GateType.OR); + is.get(2).setChildren(Stream.of(bs.get(0), is.get(5)).collect(Collectors.toSet())); + + is.get(5).setGateType(GateType.AND); + is.get(5).setChildren(Stream.of(bs.get(1), bs.get(3)).collect(Collectors.toSet())); + + FTAMinimalCutSetEvaluation sut = new FTAMinimalCutSetEvaluation(); + sut.evaluate(faultTree); + + assertNotNull(faultTree.getFaultEventScenarios()); + assertEquals(3, faultTree.getFaultEventScenarios().size()); + + Set> scenarios = faultTree.getFaultEventScenarios().stream().map(s -> s.getScenarioParts()).collect(Collectors.toSet()); + assertTrue(scenarios.contains(Stream.of(bs.get(0)).collect(Collectors.toSet()))); + assertTrue(scenarios.contains(Stream.of(bs.get(1),bs.get(3)).collect(Collectors.toSet()))); + assertTrue(scenarios.contains(Stream.of(bs.get(2),bs.get(3), bs.get(4)).collect(Collectors.toSet()))); + + for(FaultEvent fe : is) + assertNotNull(fe.getProbability()); + + double maxError = 0.0000000000000001; + TestParams tp = expressions().get(3); + + double actual = is.get(0).getProbability(); + double expected = tp.expectedValue; + assertTrue(Math.abs(actual - expected) < maxError, String.format("Actual probability of node %s significantly deviates from expected, \n%f \n%f", is.get(0).getName(), actual, expected)); + + actual = is.get(1).getProbability(); + expected = (1 - 0.9*0.9)*(0.1*0.1); + assertTrue(Math.abs(actual - expected) < maxError, String.format("Actual probability of node %s significantly deviates from expected, \n%f \n%f", is.get(1).getName(), actual, expected)); + + actual = is.get(2).getProbability(); + expected = (1 - (1-0.1)*(1 - 0.1*0.1)); + assertTrue(Math.abs(actual - expected) < maxError, String.format("Actual probability of node %s significantly deviates from expected, \n%f \n%f", is.get(2).getName(), actual, expected)); + + actual = is.get(3).getProbability(); + expected = 1 - 0.9*0.9; + assertTrue(Math.abs(actual - expected) < maxError, String.format("Actual probability of node %s significantly deviates from expected, \n%f \n%f", is.get(3).getName(), actual, expected)); + + actual = is.get(4).getProbability(); + expected = (0.1*0.1); + assertTrue(Math.abs(actual - expected) < maxError, String.format("Actual probability of node %s significantly deviates from expected, \n%f \n%f", is.get(4).getName(), actual, expected)); + + actual = is.get(5).getProbability(); + expected = (0.1*0.1); + assertTrue(Math.abs(actual - expected) < maxError, String.format("Actual probability of node %s significantly deviates from expected, \n%f \n%f", is.get(5).getName(), actual, expected)); + } +} \ No newline at end of file