Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/59 implement simple fault tree cutsets #61

Merged
merged 8 commits into from
Feb 22, 2024
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());
}
}
Loading