Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
F
Fault Tree Project
Manage
Activity
Members
Code
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Locked files
Deploy
Releases
Model registry
Analyze
Contributor analytics
Repository analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
STAR 2021 Group 6
Fault Tree Project
Commits
7e820933
Commit
7e820933
authored
4 years ago
by
Leendertz, R.R. (Rens, Student M-CS)
Browse files
Options
Downloads
Patches
Plain Diff
added a python file capable of converting an attack tree to a simpler version
parent
5013b93d
No related branches found
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
.gitignore
+3
-1
3 additions, 1 deletion
.gitignore
transformations/simplify.py
+223
-0
223 additions, 0 deletions
transformations/simplify.py
with
226 additions
and
1 deletion
.gitignore
+
3
−
1
View file @
7e820933
.ionide/
\ No newline at end of file
.ionide/
.idea/
fault-tree-examples.iml
This diff is collapsed.
Click to expand it.
transformations/simplify.py
0 → 100644
+
223
−
0
View file @
7e820933
import
json
import
os
,
argparse
#create a new node of any type
def
makeAnd
(
name
,
causes
):
return
{
"
name
"
:
name
,
"
causes
"
:
causes
,
"
gate
"
:{
"
type
"
:
"
and
"
}}
def
makeOr
(
name
,
causes
):
return
{
"
name
"
:
name
,
"
causes
"
:
causes
,
"
gate
"
:{
"
type
"
:
"
or
"
}}
def
makeVote
(
name
,
causes
,
amount
):
return
{
"
name
"
:
name
,
"
causes
"
:
causes
,
"
gate
"
:{
"
type
"
:
"
vote
"
,
"
count
"
:
amount
}}
def
updateReferences
(
newNodes
,
nodes
,
name
,
newName
):
"""
if a name of a node is updated in the process, this fixes any references that got mismatched
"""
for
n
in
newNodes
:
if
"
causes
"
not
in
newNodes
[
n
]:
continue
causes
=
newNodes
[
n
][
"
causes
"
]
for
i
in
range
(
len
(
causes
)):
if
causes
[
i
]
==
name
:
causes
[
i
]
=
newName
for
n
in
nodes
:
if
"
causes
"
not
in
n
:
continue
causes
=
n
[
"
causes
"
]
for
i
in
range
(
len
(
causes
)):
if
causes
[
i
]
==
name
:
causes
[
i
]
=
newName
def
updateName
(
name
,
gate
,
addition
):
"""
Extend a name to be more specific, used for gates with a lot of children
"""
if
name
.
startswith
(
gate
):
parts
=
name
.
split
(
'
_
'
,
2
)
parts
[
1
]
+=
addition
return
"
_
"
.
join
(
parts
)
else
:
return
gate
+
"
_
"
+
addition
+
"
_
"
+
name
def
takeN
(
ls
,
n
,
base
=
[],
offset
=
0
):
"""
Collects all possible subsets of size N of a list, and yields each exactly once.
Use an iterator or next() to retrieve these subsets
"""
if
n
<=
0
:
yield
base
else
:
for
i
in
range
(
offset
,
len
(
ls
)):
yield
from
takeN
(
ls
,
n
-
1
,
base
+
[
ls
[
i
]],
i
+
1
)
def
reduceVote
(
node
):
"""
Reduce a voting gate to a large subtree of ands and ors
First, collect all possible subsets of children of the right size and put them into an AND gate.
Then have an or gate as a parent for all those and gates.
"""
name
=
node
[
"
name
"
]
count
=
node
[
"
gate
"
][
"
count
"
]
causes
=
node
[
"
causes
"
]
ands
=
[]
# all possible subsets of size count.
for
i
,
subset
in
enumerate
(
takeN
(
causes
,
count
)):
# voting gate fails if all nodes in this subset fail, thus an and
andN
=
makeAnd
(
"
vote_
"
+
str
(
i
)
+
"
_
"
+
name
,
subset
)
# reduce the and if it has too many children.
newName
=
reduceAnd
(
andN
)
ands
.
append
(
newName
)
# voting gate fails if any of the and gates fails.
orN
=
makeOr
(
"
vote_or_
"
+
name
,
ands
)
# new name of this subtree is the name of the or gate.
return
reduceOr
(
orN
)
def
reduceAnd
(
node
):
"""
reduce 1 AND gate with many children to many AND gates with 2 children.
"""
name
=
node
[
"
name
"
]
causes
=
node
[
"
causes
"
]
if
len
(
causes
)
==
1
:
# an and gate of 1 child is simply that child.
return
causes
[
0
]
if
len
(
causes
)
==
2
:
# this is a simple and gate. add it to the new tree and return name.
newNodes
[
name
]
=
node
return
name
halfway
=
len
(
causes
)
//
2
left
,
right
=
causes
[:
halfway
],
causes
[
halfway
:]
left
=
reduceAnd
(
makeAnd
(
updateName
(
name
,
"
and
"
,
"
l
"
),
left
))
right
=
reduceAnd
(
makeAnd
(
updateName
(
name
,
"
and
"
,
"
r
"
),
right
))
return
addNode
(
makeAnd
(
updateName
(
name
,
"
and
"
,
""
),
[
left
,
right
]))
def
reduceOr
(
node
):
"""
reduce 1 OR gate with many children to many OR gates with 2 children.
"""
name
=
node
[
"
name
"
]
causes
=
node
[
"
causes
"
]
if
len
(
causes
)
==
1
:
# an or gate of 1 child is simply that child.
return
causes
[
0
]
if
len
(
causes
)
==
2
:
# this is a simple or gate. add it to the new tree and return name.
newNodes
[
name
]
=
node
return
name
#split it in half
halfway
=
len
(
causes
)
//
2
left
,
right
=
causes
[:
halfway
],
causes
[
halfway
:]
left
=
reduceOr
(
makeOr
(
updateName
(
name
,
"
or
"
,
"
l
"
),
left
))
right
=
reduceOr
(
makeOr
(
updateName
(
name
,
"
or
"
,
"
r
"
),
right
))
return
addNode
(
makeOr
(
updateName
(
name
,
"
or
"
,
""
),
[
left
,
right
]))
def
reduceLeaf
(
node
):
"""
Insert leaf nodes into the new tree
"""
return
addNode
(
node
)
def
addNode
(
node
):
"""
Adds a node to the new tree.
"""
name
=
node
[
"
name
"
]
if
name
in
newNodes
:
print
(
"
2 nodes with identical names detected:
"
,
name
)
exit
()
newNodes
[
name
]
=
node
return
name
def
reduce
(
node
):
"""
Takes a node from the original, converts it to some set of nodes for the new tree.
"""
if
"
gate
"
not
in
node
:
return
reduceLeaf
(
node
)
mapping
=
{
"
or
"
:
reduceOr
,
"
and
"
:
reduceAnd
,
"
vote
"
:
reduceVote
}
return
mapping
[
node
[
"
gate
"
][
"
type
"
]](
node
)
def
simplify
(
json
):
"""
Converts a standard attack tree to a simple one suitable for a binary decision diagram.
Fixes issues caused by nodes changing their names.
"""
global
newNodes
newNodes
=
{}
nodes
=
json
for
node
in
nodes
:
name
=
node
[
"
name
"
]
newName
=
reduce
(
node
)
if
name
!=
newName
:
updateReferences
(
newNodes
,
nodes
,
name
,
newName
)
return
list
(
newNodes
.
values
())
######## DOT
def
toDot
(
out
):
def
formatNode
(
node
):
nodeString
=
'"
{}
"
[ shape = {} ]
'
if
"
gate
"
not
in
node
:
return
nodeString
.
format
(
node
[
"
name
"
],
"
circle
"
)
if
node
[
'
gate
'
][
'
type
'
]
==
'
and
'
:
return
nodeString
.
format
(
node
[
"
name
"
],
"
trapezium
"
)
if
node
[
'
gate
'
][
'
type
'
]
==
'
or
'
:
return
nodeString
.
format
(
node
[
"
name
"
],
"
cilinder
"
)
if
node
[
'
gate
'
][
'
type
'
]
==
'
vote
'
:
return
nodeString
.
format
(
node
[
"
name
"
],
"
square
"
)
return
nodeString
.
format
(
node
[
"
name
"
],
"
star
"
)
def
formatEdge
(
ns
):
n1
,
n2
=
ns
return
'"
{}
"
->
"
{}
"'
.
format
(
n1
,
n2
)
nodes
=
list
(
map
(
formatNode
,
out
))
edges
=
[]
for
n
in
out
:
if
"
causes
"
not
in
n
:
continue
for
c
in
n
[
"
causes
"
]:
edges
.
append
((
n
[
"
name
"
],
c
))
edges
=
list
(
map
(
formatEdge
,
edges
))
return
"
digraph G{{
\n\t
{}
\n\t
{}
\n
}}
"
.
format
(
"
\n\t
"
.
join
(
nodes
),
"
\n\t
"
.
join
(
edges
))
############# IO
def
readJSON
(
fileName
):
with
open
(
fileName
)
as
f
:
return
json
.
loads
(
f
.
read
())
def
writeJSON
(
fileName
,
data
):
with
open
(
fileName
,
'
w
'
)
as
f
:
json
.
dump
(
data
,
f
,
indent
=
4
)
def
writeDOT
(
fileName
,
data
):
with
open
(
fileName
,
'
w
'
)
as
f
:
f
.
write
(
data
)
def
convertFile
(
inpFile
,
outFile
,
dotFile
=
None
):
inp
=
readJSON
(
inpFile
)
out
=
simplify
(
inp
)
writeJSON
(
outFile
,
out
)
if
dotFile
:
writeDOT
(
dotFile
,
toDot
(
out
))
if
__name__
==
"
__main__
"
:
parser
=
argparse
.
ArgumentParser
(
description
=
'
Simplify attack trees
'
,
epilog
=
"""
If the input is a directory, output (and dot) have to be directories too.
If the input is a file, but output/dot is a directory, the name of the input file will be used as name for the created file.
"""
)
parser
.
add_argument
(
'
input_file
'
,
metavar
=
'
INfile/dir
'
,
type
=
str
,
help
=
'
A file/directory to be converted
'
)
parser
.
add_argument
(
'
output_file
'
,
metavar
=
'
OUTfile/dir
'
,
type
=
str
,
help
=
'
The file/directory to store the new attack tree.
'
)
parser
.
add_argument
(
'
--dot
'
,
metavar
=
"
DOTfile/dir
"
,
type
=
str
,
help
=
"
Optionally, also create a DOT file of the new attack tree.
"
)
parsed
=
parser
.
parse_args
()
inpFile
=
parsed
.
input_file
outFile
=
parsed
.
output_file
dotFile
=
parsed
.
dot
doDot
=
dotFile
is
not
None
if
not
os
.
path
.
isfile
(
inpFile
):
if
not
os
.
path
.
isdir
(
inpFile
):
raise
FileNotFoundError
(
"
the specified input file/directory does not exist
"
)
if
not
os
.
path
.
isdir
(
outFile
):
raise
NotADirectoryError
(
"
if the input is a directory, the output should be too!
"
)
if
doDot
and
not
os
.
path
.
isdir
(
dotFile
):
raise
NotADirectoryError
(
"
if the input is a directory, the DOT should be too!
"
)
# an entire directory at once
for
file
in
os
.
scandir
(
inpFile
):
if
file
.
path
.
lower
().
endswith
(
"
.json
"
)
and
file
.
is_file
():
basename
=
os
.
path
.
basename
(
file
)
output
=
os
.
path
.
join
(
outFile
,
basename
)
if
doDot
:
dot
=
os
.
path
.
join
(
dotFile
,
os
.
path
.
splitext
(
basename
)[
0
]
+
"
.dot
"
)
else
:
dot
=
None
convertFile
(
file
,
output
,
dot
)
exit
()
#single file
basename
=
os
.
path
.
basename
(
inpFile
)
if
os
.
path
.
isdir
(
outFile
):
outFile
=
os
.
path
.
join
(
outFile
,
basename
)
if
doDot
:
dot
=
os
.
path
.
join
(
dotFile
,
os
.
path
.
splitext
(
basename
)[
0
]
+
"
.dot
"
)
else
:
dot
=
None
convertFile
(
inpFile
,
outFile
,
dot
)
\ No newline at end of file
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment