Skip to content

Commit

Permalink
Merge pull request #61 from kbss-cvut/feature/59-implement-simple-fau…
Browse files Browse the repository at this point in the history
…lt-tree-cutsets

Feature/59 implement simple fault tree cutsets
  • Loading branch information
kostobog authored Feb 22, 2024
2 parents 54433c3 + 3bc3d70 commit 93f0b83
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,12 @@ public FaultTree generateFunctionalDependenciesFaultTree(@PathVariable("function
log.info("> generateFunctionalDependenciesFaultTree - {}, {}", functionFragment, faultTreeName);
return repositoryService.generateFunctionDependencyTree(functionUri,faultTreeName);
}

@ResponseStatus(HttpStatus.NO_CONTENT)
@PutMapping(value = "/{faultTreeFragment}/cutsets")
public void performCutSetAnalysis(@PathVariable(name = "faultTreeFragment") String faultTreeFragment){
URI faultTreeUri = identifierService.composeIdentifier(Vocabulary.s_c_FaultTree, faultTreeFragment);
log.info("> performCutSetAnalysis - {}", faultTreeFragment);
repositoryService.performCutSetAnalysis(faultTreeUri);
}
}
13 changes: 13 additions & 0 deletions src/main/java/cz/cvut/kbss/analysis/dao/FaultEventScenarioDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cz.cvut.kbss.analysis.dao;

import cz.cvut.kbss.analysis.config.conf.PersistenceConf;
import cz.cvut.kbss.analysis.model.FaultEventScenario;
import cz.cvut.kbss.jopa.model.EntityManager;
import org.springframework.stereotype.Repository;

@Repository
public class FaultEventScenarioDao extends BaseDao<FaultEventScenario> {
protected FaultEventScenarioDao(EntityManager em, PersistenceConf config) {
super(FaultEventScenario.class, em, config);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ public boolean equals(Object o) {
if(! (o instanceof AbstractEntity))
return false;
AbstractEntity that = (AbstractEntity) o;
if(uri == null && that.uri == null)
return super.equals(o);
return Objects.equals(uri, that.uri);
}

@Override
public int hashCode() {
return Objects.hash(uri.toString());
return uri != null ? Objects.hash(uri.toString()) : super.hashCode();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cz.cvut.kbss.analysis.model;

import cz.cvut.kbss.analysis.util.Vocabulary;
import cz.cvut.kbss.jopa.model.annotations.FetchType;
import cz.cvut.kbss.jopa.model.annotations.OWLClass;
import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty;
import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty;
Expand All @@ -14,10 +15,42 @@
@Setter
public class FaultEventScenario extends AnalysisProduct {

@OWLObjectProperty(iri = Vocabulary.s_p_has_part)
@OWLObjectProperty(iri = Vocabulary.s_p_has_part, fetch = FetchType.EAGER)
private Set<FaultEvent> scenarioParts;

@OWLDataProperty(iri = Vocabulary.s_p_hasProbability)
private Double probability;


public FaultEventScenario() {
}

public FaultEventScenario(Set<FaultEvent> scenarioParts) {
this.scenarioParts = scenarioParts;
}

public void updateProbability(){
setProbability(calculateProbability());
}

public Double calculateProbability(){
Double prob = 1.;
for(FaultEvent part : scenarioParts){
prob = prob * part.getProbability();
}
return prob;
}

public boolean isEmptyScenario(){
return scenarioParts == null || scenarioParts.isEmpty();
}

/**
*
* @param faultEventScenario
* @return returns true if all the parts of the faultEventScenario are included in this faultEventScenario
*/
public boolean contains(FaultEventScenario faultEventScenario){
return getScenarioParts().containsAll(faultEventScenario.getScenarioParts());
}
}
2 changes: 1 addition & 1 deletion src/main/java/cz/cvut/kbss/analysis/model/FaultTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class FaultTree extends NamedEntity {
@OWLObjectProperty(iri = Vocabulary.s_p_hasFailureModesTable, cascade = CascadeType.ALL)
private FailureModesTable failureModesTable;

@OWLObjectProperty(iri = Vocabulary.s_p_has_scenario)
@OWLObjectProperty(iri = Vocabulary.s_p_has_scenario, fetch = FetchType.EAGER)
private Set<FaultEventScenario> faultEventScenarios;

@Override
Expand Down
124 changes: 124 additions & 0 deletions src/main/java/cz/cvut/kbss/analysis/model/fta/CutSetExtractor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class CutSetExtractor {
private static final Logger LOG = LoggerFactory.getLogger(CutSetExtractor.class);


/**
*
* @return null if the input is valid to extract cut sets from the fault tree, otherwise returns error message
*/
public Consumer<Logger> validateTree(FaultTree faultTree){
if(faultTree == null)
return (l) -> l.warn("invalid input - input tree is null");
if(faultTree.getManifestingEvent() == null)
return (l) -> l.warn("invalid input - input tree is <{}> does not have a root event.", faultTree.getUri());


return null;
}

public List<FaultEventScenario> extractMinimalScenarios(FaultTree faultTree){
Consumer<Logger> errorMessage = validateTree(faultTree);
if(errorMessage != null){
errorMessage.accept(LOG);
return null;
}
List<FaultEventScenario> scenarios = extract(faultTree.getManifestingEvent()).stream()
.filter(s -> !s.isEmptyScenario()).toList();
scenarios = extractMinimalScenarios(scenarios);
return scenarios;
}

public List<FaultEventScenario> extractMinimalScenarios(List<FaultEventScenario> allScenarios){
Map<FaultEvent, List<FaultEventScenario>> map = new HashMap<>();
Set<FaultEventScenario> nonMinimalScenarios = new HashSet<>();

for(int i = 0; i < allScenarios.size() ; i ++){
for(FaultEvent faultEvent : allScenarios.get(i).getScenarioParts()) {
List<FaultEventScenario> feScenarios = map.get(faultEvent);
if(feScenarios == null){
feScenarios = new ArrayList<>();
map.put(faultEvent, feScenarios);
}
feScenarios.add(allScenarios.get(i));
}
}

for(Map.Entry<FaultEvent, List<FaultEventScenario>> e : map.entrySet()){
if(e.getValue().size() < 1)
continue;
List<FaultEventScenario> scenarios = e.getValue()
.stream().filter(fes -> !nonMinimalScenarios.contains(fes))
.sorted(Comparator.comparing((FaultEventScenario fes) -> fes.getScenarioParts().size()).reversed())
.toList();

for( int i = 0; i < scenarios.size() - 1; i ++ ){
for( int j = i + 1; j < scenarios.size(); j ++ ){
if(scenarios.get(i).contains(scenarios.get(j))){
nonMinimalScenarios.add(scenarios.get(i));
break;
}
}
}
}
List<FaultEventScenario> minimalScenarios = new ArrayList<>(allScenarios);
minimalScenarios.removeAll(nonMinimalScenarios);
return minimalScenarios;
}


public List<FaultEventScenario> extract(FaultEvent faultEvent){
if(faultEvent.getGateType() == null || faultEvent.getChildren() == null || faultEvent.getChildren().isEmpty())
return Collections.singletonList(new FaultEventScenario(Collections.singleton(faultEvent)));

Stream<List<FaultEventScenario>> partScenariosStream = faultEvent.getChildren().stream().map(this::extract);// RECURSION !

if(GateType.OR.equals(faultEvent.getGateType()))
return partScenariosStream.flatMap(l -> l.stream()).toList();

if(!GateType.AND.equals(faultEvent.getGateType()))
throw new IllegalArgumentException(String.format("Cannot extract cut sets from fault tree, tree contains unsupported gate type \"%s\"", faultEvent.getGateType()));

List<List<FaultEventScenario>> partScenarios = partScenariosStream.filter(l -> !l.isEmpty()).toList();
return processAndGateScenarios(partScenarios);
}

protected List<FaultEventScenario> processAndGateScenarios(List<List<FaultEventScenario>> partScenarios){
List<Integer> inds = new ArrayList<>(partScenarios.size());
partScenarios.forEach(l -> inds.add(0));
List<FaultEventScenario> andScenarios = new ArrayList<>();
int i = 0;
while( i < inds.size()){
//create and add new scenario
FaultEventScenario mergedScenario = new FaultEventScenario(new HashSet<>());
for(int j = 0; j < inds.size(); j ++ )
mergedScenario.getScenarioParts()
.addAll(partScenarios.get(j).get(inds.get(j)).getScenarioParts());
andScenarios.add(mergedScenario);

//goto next combination
while(i < inds.size()){
int ii = inds.get(i) + 1;
if(ii < partScenarios.get(i).size()) {
inds.set(i, ii);
i = 0;
break;
}
inds.set(i, 0);
i ++;
}
}
return andScenarios;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package cz.cvut.kbss.analysis.service;

import cz.cvut.kbss.analysis.dao.FaultEventScenarioDao;
import cz.cvut.kbss.analysis.dao.FaultTreeDao;
import cz.cvut.kbss.analysis.dao.GenericDao;
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.FtaEventType;
import cz.cvut.kbss.analysis.model.fta.GateType;
import cz.cvut.kbss.analysis.service.util.FaultTreeTraversalUtils;
Expand All @@ -25,6 +27,7 @@
public class FaultTreeRepositoryService extends BaseRepositoryService<FaultTree> {

private final FaultTreeDao faultTreeDao;
private final FaultEventScenarioDao faultEventScenarioDao;
private final FaultEventRepositoryService faultEventRepositoryService;
private final FunctionRepositoryService functionRepositoryService;
private final IdentifierService identifierService;
Expand All @@ -34,12 +37,14 @@ public class FaultTreeRepositoryService extends BaseRepositoryService<FaultTree>
@Autowired
public FaultTreeRepositoryService(@Qualifier("defaultValidator") Validator validator,
FaultTreeDao faultTreeDao,
FaultEventScenarioDao faultEventScenarioDao,
FaultEventRepositoryService faultEventRepositoryService,
FunctionRepositoryService functionRepositoryService,
IdentifierService identifierService
) {
super(validator);
this.faultTreeDao = faultTreeDao;
this.faultEventScenarioDao = faultEventScenarioDao;
this.faultEventRepositoryService = faultEventRepositoryService;
this.functionRepositoryService = functionRepositoryService;
this.identifierService = identifierService;
Expand Down Expand Up @@ -406,4 +411,24 @@ private void setFaultEventTypes(boolean isBasic, FaultEvent fEvent){
public List<FaultTree> findAllSummaries(){
return ((FaultTreeDao)getPrimaryDao()).findAllSummaries();
}

@Transactional
public FaultTree performCutSetAnalysis(URI faultTreeUri){
FaultTree faultTree = findRequired(faultTreeUri);
CutSetExtractor extractor = new CutSetExtractor();
List<FaultEventScenario> scenarios = extractor.extractMinimalScenarios(faultTree);

if(faultTree.getFaultEventScenarios() != null)
for(FaultEventScenario faultEventScenario : faultTree.getFaultEventScenarios())
faultEventScenarioDao.remove(faultEventScenario);

for(FaultEventScenario scenario : scenarios){
scenario.updateProbability();
faultEventScenarioDao.persist(scenario);
}
faultTree.setFaultEventScenarios(new HashSet<>(scenarios));
getPrimaryDao().update(faultTree);

return faultTree;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package cz.cvut.kbss.analysis.model.fta;

import cz.cvut.kbss.analysis.model.FaultEvent;
import cz.cvut.kbss.analysis.model.FaultEventScenario;
import org.junit.jupiter.api.Test;

import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.*;

class CutSetExtractorTest {

@Test
void testExtractMinimalScenarios_ScenarioList() {
List<FaultEventScenario> faultEventScenarios = Arrays.asList(
create(1),
create(2,4),
create(3,4,5),
create(2,4,5)
);

testExtractionOfMinimalFaultEvent(faultEventScenarios, set(0,1, 2));

faultEventScenarios = Arrays.asList(
create(1),
create(2,4,5),
create(2,4),
create(3,4,5),
create(2,4,5),
create(3,4,5,6)
);

testExtractionOfMinimalFaultEvent(faultEventScenarios, set(0,2,3));
}

void testExtractionOfMinimalFaultEvent(List<FaultEventScenario> faultEventScenarios, Set<Integer> extractedScenarios){
List<FaultEventScenario> minimalFaultEventScenarios = new CutSetExtractor().extractMinimalScenarios(faultEventScenarios);
assertEquals(extractedScenarios.size(), minimalFaultEventScenarios.size());

for(int i = 0; i < faultEventScenarios.size(); i ++){
FaultEventScenario scenario = faultEventScenarios.get(i);
if(extractedScenarios.contains(i))
assertTrue(minimalFaultEventScenarios.contains(scenario),
String.format("Scenario at index %d \"%s\" should a part of the result of " +
"minimalFaultEventScenarios but it isn't.", i, scenario.getScenarioParts().toString()));
else
assertFalse(minimalFaultEventScenarios.contains(scenario),
String.format("Scenario at index %d \"%s\" should not be a part of the result of " +
"minimalFaultEventScenarios but it is.", i, scenario.getScenarioParts().toString()));
}
}

private FaultEventScenario create(Integer ... ints){
return new FaultEventScenario(Stream.of(ints).map(this::createFaultEvent).collect(Collectors.toSet()));
}

private FaultEvent createFaultEvent(int i){
FaultEvent fe = new FaultEvent();
fe.setUri(createURI(i));
return fe;
}
private URI createURI(int i){
return URI.create("http://" + i);
}

private Set<Integer> set(Integer ... ints){
return Stream.of(ints).collect(Collectors.toSet());
}
}

0 comments on commit 93f0b83

Please sign in to comment.