Commit 185f33927f4bd3767403e273debb0788a6c3862f

Authored by Brice Colombier
0 parents
Exists in master

Initial commit

Showing 15 changed files with 580 additions and 0 deletions

... ... @@ -0,0 +1 @@
  1 +*.pyc
0 2 \ No newline at end of file
... ... @@ -0,0 +1,21 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: clean.py
  7 +# Date: 2016-10-13
  8 +
  9 +
  10 +def clean(self):
  11 +
  12 + """Remove unconnected vertices"""
  13 +
  14 + to_delete = []
  15 +
  16 + for i in self.vs:
  17 + if self.neighbors(i, mode="ALL") == []:
  18 + to_delete.append(i["name"])
  19 +
  20 + self.delete_vertices(to_delete)
  21 + return self
erase_labels.py View file @ 185f339
... ... @@ -0,0 +1,20 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: erase_labels.py
  7 +# Date: 2016-10-13
  8 +
  9 +
  10 +def erase_labels(self, prim_out):
  11 +
  12 + """Erase labels on nodes"""
  13 +
  14 + for i in self.vs:
  15 + if not i["name"] in prim_out:
  16 + if not self.incident(i, mode="IN") == []:
  17 + i["forced"] = []
  18 + i["locks"] = []
  19 +
  20 + return self
find_optimal_nodes.py View file @ 185f339
... ... @@ -0,0 +1,116 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: find_optimal_nodes.py
  7 +# Date: 2016-10-13
  8 +
  9 +
  10 +def find_optimal_nodes(self, prim_out):
  11 +
  12 + """Find the optimal nodes to lock"""
  13 +
  14 + interesting_nodes = []
  15 + mean_dist_to_out = []
  16 +# color_optimal = "#EE0000"
  17 + color_optimal = "#DDDDDD"
  18 + h = self.clusters(mode="WEAK")
  19 + # For every cluster
  20 + for i in h:
  21 + # Save the names of the nodes in the cluster
  22 + cluster = self.vs[i]["name"]
  23 + # Find the outputs in the cluster
  24 + outputs_list = [temp for temp in cluster if temp in prim_out]
  25 + source_vertices = []
  26 + dist_list = []
  27 + # If there is more than one output in the cluster
  28 + if len(outputs_list) != 1:
  29 + # Find source vertices
  30 + for j in cluster:
  31 + if not self.incident(j, mode="IN"):
  32 + source_vertices.append(j)
  33 + # If there is only one source vertex, it locks all the outputs
  34 + if len(source_vertices) == 1:
  35 + node = source_vertices.pop()
  36 + self.vs.find(node)["color"] = color_optimal
  37 + self.vs.find(node)["cat"] = "lock"
  38 + interesting_nodes.append(node)
  39 + # If there are multiple source vertices
  40 + else:
  41 + # Compute the distance from source vertices to outputs
  42 + for j in source_vertices:
  43 + # dist_list : ['node name', [distances to outputs]]
  44 + dist_list.append([j, list(map(len, self.get_shortest_paths(j, outputs_list)))])
  45 + one_source = False
  46 + for j in reversed(dist_list):
  47 + # If one source vertex is connected to all the outputs
  48 + if 0 not in j[1]:
  49 + self.vs.find(j[0])["color"] = color_optimal
  50 + self.vs.find(j[0])["cat"] = "lock"
  51 + interesting_nodes.append(j[0])
  52 + one_source = True
  53 + break
  54 + # If there is no source vertex spanning all the outputs
  55 + if not one_source:
  56 + nb_locked_outputs = []
  57 + len_dist_list_1 = len(dist_list[0][1])
  58 + for j in dist_list:
  59 + nb_locked_outputs.append(len_dist_list_1 - j[1].count(0))
  60 + # Sort the cluster members according to
  61 + # the number of outputs they lock
  62 + dist_list = list(list(zip(*sorted(zip(nb_locked_outputs, dist_list))))[1])
  63 + detect_0s = dist_list[0][1]
  64 + for j in range(1, len(dist_list)):
  65 + detect_0s = [sum(x) for x
  66 + in zip(detect_0s, dist_list[j][1])]
  67 + # Sum the distances to outputs
  68 + # A zero here means an output is not reachable
  69 + if detect_0s.count(0):
  70 + print "Some outputs can not be locked"
  71 + print "An error occured during analysis"
  72 + else:
  73 + # reuses detect_0s for another purpose
  74 + detect_0s = []
  75 + node = dist_list.pop()
  76 + self.vs.find(node[0])["color"] = color_optimal
  77 + self.vs.find(node[0])["cat"] = "lock"
  78 + interesting_nodes.append(node[0])
  79 + detect_0s = node[1]
  80 + # Store the previous number of zeroes
  81 + # i.e. unlocked outputs
  82 + nb_0s = detect_0s.count(0)
  83 + while detect_0s.count(0):
  84 + node = dist_list.pop()
  85 + nb_0s = detect_0s.count(0)
  86 + detect_0s = [sum(x) for x
  87 + in zip(detect_0s, node[1])]
  88 + # If this node allows to lock more outputs:
  89 + # i.e. it removes zeroes from detect_0s
  90 + if detect_0s.count(0) < nb_0s:
  91 + self.vs.find(node[0])["color"] = color_optimal
  92 + self.vs.find(node[0])["cat"] = "lock"
  93 + interesting_nodes.append(node[0])
  94 + # If there is only one output in the cluster, pick the farthest node
  95 + else:
  96 + for j in cluster:
  97 + dist_list.append(self.eccentricity(j, mode="OUT"))
  98 + # Sort the cluster members according to their distance to outputs
  99 + cluster = list(list(zip(*sorted(zip(dist_list, cluster))))[1])
  100 + node = [temp for temp in cluster if temp not in outputs_list].pop()
  101 + interesting_nodes.append(node)
  102 + self.vs.find(node)["color"] = color_optimal
  103 + self.vs.find(node)["cat"] = "lock"
  104 +
  105 + # Compute the distance from selected nodes to outputs
  106 + for i in interesting_nodes:
  107 + if self.get_shortest_paths(i, outputs_list) != [[]]:
  108 + mean_dist_to_out.extend([o for o
  109 + in map(len, self.get_shortest_paths(i, outputs_list))
  110 + if o != 0])
  111 +
  112 + # To store the types of gates to insert
  113 + types = []
  114 + for i in interesting_nodes:
  115 + types.append(self.vs.find(i)["locks"])
  116 + return list(zip(interesting_nodes, types))
find_sequences.py View file @ 185f339
... ... @@ -0,0 +1,34 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: find_sequences.py
  7 +# Date: 2016-10-13
  8 +
  9 +
  10 +def find_sequences(self, prim_out):
  11 +
  12 + """Finds the nodes that can not propagate the locking value"""
  13 +
  14 + to_delete = []
  15 +
  16 + # Consider all edges
  17 + # Delete the edge if the target vertex (i.e. node)
  18 + # can not propagate a locking value
  19 + for i in self.es:
  20 + if not self.vs.find(i.target)["forced"][0] in self.vs.find(i.target)["locks"]:
  21 + to_delete.append(i)
  22 + self.delete_edges(to_delete)
  23 +
  24 + to_delete = []
  25 +
  26 + to_outputs = len(prim_out)*[[]]
  27 + for i in self.vs:
  28 + if not i["name"] in prim_out:
  29 + # If the array of the distances to outputs is [[],[],...,[],[]]
  30 + if self.get_shortest_paths(i, to=prim_out) == to_outputs:
  31 + to_delete.append(i)
  32 +
  33 + self.delete_vertices(to_delete)
  34 + return self
... ... @@ -0,0 +1,27 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: label.py
  7 +# Date: 2016-10-13
  8 +
  9 +import label_nonlin
  10 +import label_lin
  11 +import label_notbuf
  12 +import label_inputs
  13 +
  14 +
  15 +def label(self):
  16 +
  17 + """Label the graph"""
  18 +
  19 + self = label_nonlin.label_nonlin(self)
  20 + for _ in range(3):
  21 + self = label_inputs.label_inputs(self)
  22 + self = label_notbuf.label_notbuf(self)
  23 + self = label_inputs.label_inputs(self)
  24 + self = label_lin.label_lin(self)
  25 + self = label_inputs.label_inputs(self)
  26 +
  27 + return self
label_all.py View file @ 185f339
... ... @@ -0,0 +1,38 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: label_all.py
  7 +# Date: 2016-10-13
  8 +
  9 +import label
  10 +import find_sequences
  11 +import erase_labels
  12 +import random
  13 +
  14 +
  15 +def label_all(self, prim_out):
  16 +
  17 + """Label all graph nodes"""
  18 +
  19 + self = label.label(self)
  20 + self = find_sequences.find_sequences(self, prim_out)
  21 + size = 0
  22 + while size != len(self.vs) + len(self.es):
  23 + self = erase_labels.erase_labels(self, prim_out)
  24 + self = label.label(self)
  25 + self = find_sequences.find_sequences(self, prim_out)
  26 + size = len(self.vs) + len(self.es)
  27 +
  28 + for i in self.vs:
  29 + i["locks"] = []
  30 + i["forced"] = []
  31 + self = label.label(self)
  32 +
  33 + for i in self.clusters(mode="WEAK"):
  34 + if len(i) == 2:
  35 + for j in i:
  36 + self.vs[j]["locks"] = [random.randrange(0, 2)]
  37 +
  38 + return self
label_inputs.py View file @ 185f339
... ... @@ -0,0 +1,19 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: label_inputs.py
  7 +# Date: 2016-10-13
  8 +
  9 +
  10 +def label_inputs(self):
  11 +
  12 + """Labels the inputs correctly so that they are not deleted afterwards"""
  13 +
  14 + for i in self.vs:
  15 + if i["cat"] == "input":
  16 + if i["locks"]:
  17 + i["forced"] = [i["locks"][0]]
  18 +
  19 + return self
label_lin.py View file @ 185f339
... ... @@ -0,0 +1,42 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: label_lin.py
  7 +# Date: 2016-10-13
  8 +
  9 +
  10 +def label_lin(self):
  11 +
  12 + """Labelling the graph according to linear (XOR/XNOR) gates"""
  13 +
  14 + for i in self.vs:
  15 + # If the vertex has incoming edges
  16 + predec = i.predecessors()
  17 + if self.incident(i, mode="IN"):
  18 + # If the incoming edges are of xor or xnor type
  19 + if self.es.find(self.incident(i, mode="IN")[0])["name"] == "xor":
  20 + forced = [predec[0]["forced"], predec[1]["forced"]]
  21 + # If inputs are forced to the same value
  22 + if forced == [[0], [0]] or forced == [[1], [1]]:
  23 + i["forced"] = [0]
  24 + self.delete_edges(self.incident(i, mode="IN"))
  25 + # If inputs are forced to a different value
  26 + elif forced == [[0], [1]] or forced == [[1], [0]]:
  27 + i["forced"] = [1]
  28 + else:
  29 + raise Exception("The netlist probably contains too many XOR/XNOR gates.")
  30 +
  31 + elif self.es.find(self.incident(i, mode="IN")[0])["name"] == "xnor":
  32 + forced = [predec[0]["forced"], predec[1]["forced"]]
  33 + # If inputs are forced to the same value
  34 + if forced == [[0], [0]] or forced == [[1], [1]]:
  35 + i["forced"] = [1]
  36 + # If inputs are forced to a different value
  37 + elif forced == [[0], [1]] or forced == [[1], [0]]:
  38 + i["forced"] = [0]
  39 + else:
  40 + raise Exception("The netlist probably contains too many XOR/XNOR gates.")
  41 +
  42 + return self
label_nonlin.py View file @ 185f339
... ... @@ -0,0 +1,33 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: label_nonlin.py
  7 +# Date: 2016-10-13
  8 +
  9 +
  10 +def label_nonlin(self):
  11 +
  12 + """Labelling the graph according to non-linear (AND/NAND/OR/NOR) gates"""
  13 +
  14 + lock = {"and": [0, 0],
  15 + "or": [1, 1],
  16 + "nand": [0, 1],
  17 + "nor": [1, 0]}
  18 +
  19 + for i in self.es:
  20 + i_source = i.source
  21 + i_target = i.target
  22 + v_source_locks = self.vs.find(i_source)["locks"]
  23 + func = i["name"]
  24 +
  25 + if func in ["and", "or", "nand", "nor"]:
  26 + if v_source_locks == []:
  27 + self.vs.find(i_source)["locks"] = [lock[func][0]]
  28 + else:
  29 + v_source_locks.append(lock[func][0])
  30 + self.vs.find(i_source)["locks"] = list(set(v_source_locks))
  31 + self.vs.find(i_target)["forced"] = [lock[func][1]]
  32 +
  33 + return self
label_notbuf.py View file @ 185f339
... ... @@ -0,0 +1,36 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: label_notbuf.py
  7 +# Date: 2016-10-13
  8 +
  9 +
  10 +def label_notbuf(self):
  11 +
  12 + """Labelling the graph according to buffers and inverters"""
  13 +
  14 + for i in self.es:
  15 + i_source = i.source
  16 + i_target = i.target
  17 + v_target_locks = self.vs.find(i_target)["locks"]
  18 + v_source_forced = self.vs.find(i_source)["forced"]
  19 + e_label = i["name"]
  20 +
  21 + if e_label == "buf":
  22 + self.vs.find(i_source)["locks"] = list(set(v_target_locks))
  23 + self.vs.find(i_target)["forced"] = v_source_forced
  24 +
  25 + elif e_label == "not":
  26 + if v_target_locks in [[0], [1]]:
  27 + self.vs.find(i_source)["locks"].append(v_target_locks[0] ^ 1)
  28 + self.vs.find(i_source)["locks"] = list(set(self.vs.find(i_source)["locks"]))
  29 + elif v_target_locks == [0, 1]:
  30 + self.vs.find(i_source)["locks"].append(0)
  31 + self.vs.find(i_source)["locks"].append(1)
  32 + self.vs.find(i_source)["locks"] = list(set(self.vs.find(i_source)["locks"]))
  33 + if v_source_forced:
  34 + self.vs.find(i_target)["forced"] = [v_source_forced[0] ^ 1]
  35 +
  36 + return self
label_undefined_nodes.py View file @ 185f339
... ... @@ -0,0 +1,56 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: label_undefined_nodes.py
  7 +# Date: 2016-10-13
  8 +
  9 +import random
  10 +import label_notbuf
  11 +
  12 +
  13 +def label_undefined_nodes(self, list_nodes_to_lock):
  14 +
  15 + """Label undefined graph nodes"""
  16 +
  17 + problematic_nodes = []
  18 + problematic_clusters = []
  19 +
  20 + for i, j in enumerate(list(zip(*list_nodes_to_lock)[1])):
  21 + if j == []:
  22 + problematic_nodes.append(list(zip(*list_nodes_to_lock)[0])[i])
  23 +
  24 + for i in self.clusters(mode="WEAK"):
  25 + problematic_cluster = False
  26 + for j in i:
  27 + if self.vs[j]["name"] in problematic_nodes:
  28 + problematic_cluster = True
  29 + if problematic_cluster:
  30 + problematic_clusters.append(i)
  31 +
  32 + for i in problematic_clusters:
  33 + for j in i:
  34 + for k in self.incident(j, mode="IN"):
  35 + if self.es[k]["label"] in ["and", "nand"]:
  36 + for m in self.vs[j].predecessors():
  37 + m["locks"] = [0]
  38 + elif self.es[k]["label"] in ["or", "nor"]:
  39 + for m in self.vs[j].predecessors():
  40 + m["locks"] = [1]
  41 +
  42 + label_notbuf.label_notbuf(self)
  43 +
  44 + for i in problematic_clusters:
  45 + for j in i:
  46 + if self.vs[j]["cat"] == "lock" and self.vs[j]["locks"] == []:
  47 + rand_bit = [random.randrange(0, 2)]
  48 + self.vs[j]["locks"] = rand_bit
  49 +
  50 + for i in problematic_nodes:
  51 + list_nodes_to_lock[[x[0] for x in list_nodes_to_lock].index(i)][1] = self.vs.find(i)["locks"]
  52 +
  53 + for i in list_nodes_to_lock:
  54 + i[1] = [i[1][0]]
  55 +
  56 + return self, list_nodes_to_lock
... ... @@ -0,0 +1,59 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: locking.py
  7 +# Date: 2016-10-13
  8 +
  9 +import clean
  10 +import label_all
  11 +import find_optimal_nodes
  12 +import label_undefined_nodes
  13 +import modify_nodes_lock
  14 +
  15 +import sys
  16 +sys.path.append("../Parsers/")
  17 +
  18 +import build_bench
  19 +import build_blif
  20 +import build_edif
  21 +import build_slif
  22 +import build_verilog_rtl
  23 +import build_verilog_struct
  24 +import build_vhd_rtl
  25 +import build_vhd_struct
  26 +import build_xilinx
  27 +
  28 +
  29 +def locking(g, name, prim_in, prim_out, nodes, overhead):
  30 +
  31 + """Implementation of logic locking"""
  32 +
  33 + h = g.copy()
  34 + g = clean.clean(g)
  35 + g = label_all.label_all(g, prim_out)
  36 + list_nodes_to_lock = find_optimal_nodes.find_optimal_nodes(g, prim_out)
  37 + list_nodes_to_lock = [list(elem) for elem in list_nodes_to_lock]
  38 +
  39 + g, list_nodes_to_lock = label_undefined_nodes.label_undefined_nodes(g, list_nodes_to_lock)
  40 + key = str(list(reversed(sum(list(zip(*list_nodes_to_lock)[1]),[]))))[1:-1].replace(", ","")
  41 +
  42 + print "nodes:", len(nodes)
  43 + print "nodes to lock:", len(list_nodes_to_lock)
  44 +
  45 + if len(list_nodes_to_lock) > overhead * len(nodes):
  46 + print "Overhead is too low to allow total locking"
  47 + print "some outputs will remain unaltered."
  48 + list_nodes_to_lock = list_nodes_to_lock[:overhead * len(nodes)]
  49 + h = modify_nodes_lock.modify_nodes_lock(h, list_nodes_to_lock)
  50 +
  51 + if len(key) != len(list_nodes_to_lock):
  52 + raise Exception("Undefined V_locks, cannot generate the modified netlist.")
  53 + return h, key
  54 +
  55 +if __name__ == "__main__":
  56 + for name in ["c432"]:
  57 + g, prim_in, prim_out, nodes = build_bench.build(name)
  58 + _, b = locking(g, name, prim_in, prim_out, nodes, 0.05)
  59 + print b
modify_nodes_lock.py View file @ 185f339
... ... @@ -0,0 +1,72 @@
  1 +# Author: Brice Colombier
  2 +# Laboratoire Hubert Curien
  3 +# 42000 Saint-Etienne - France
  4 +# Contact: b.colombier@univ-st-etienne.fr
  5 +# Project: Demonstrator
  6 +# File: modify_nodes_lock.py
  7 +# Date: 2016-10-13
  8 +
  9 +
  10 +def modify_nodes_lock(self, list_nodes_to_lock):
  11 +
  12 + """Modify the graph nodes to make them lockable"""
  13 +
  14 + lock = {0: "and",
  15 + 1: "or"}
  16 +
  17 + # Handle vertices
  18 + for i in list_nodes_to_lock:
  19 + attributes = self.vs.find(i[0]).attributes()
  20 + attributes["name"] = "K"+i[0]
  21 + attributes["label"] = attributes["name"]
  22 + self.add_vertex(name=attributes["name"])
  23 + for j in attributes:
  24 + self.vs[len(self.vs)-1][j] = attributes[j]
  25 + self.vs[len(self.vs)-1]["color"] = "orange"
  26 + self.vs[len(self.vs)-1]["cat"] = "input"
  27 + attributes["name"] = i[0]+"_mod"
  28 + attributes["label"] = attributes["name"]
  29 + self.add_vertex(name=attributes["name"])
  30 + for j in attributes:
  31 + self.vs[len(self.vs)-1][j] = attributes[j]
  32 + self.vs[len(self.vs)-1]["color"] = "lightblue"
  33 + self.vs[len(self.vs)-1]["cat"] = "mod"
  34 + # Handle edges
  35 + for i in list_nodes_to_lock:
  36 + # Copy outgoing edges to mod node
  37 + for j in self.incident(self.vs.find(i[0]), mode="OUT"):
  38 + self.add_edge(i[0]+"_mod", self.vs[self.es[j].target]["label"],
  39 + label=self.es[j]["label"],
  40 + width=5,
  41 + arrow_size=2,
  42 + label_size=30,
  43 + color="#FF0000",
  44 + cat="lock")
  45 + edges_to_delete = []
  46 + for i in list_nodes_to_lock:
  47 + # Delete copied edges from the original node
  48 + for j in self.incident(self.vs.find(i[0]), mode="OUT"):
  49 + edges_to_delete.append((i[0], self.vs[self.es[j].target]["label"]))
  50 + self.delete_edges(edges_to_delete)
  51 + for i in list_nodes_to_lock:
  52 + if i[1] in [[0], [1]]:
  53 + # The node should be forced to 0
  54 + # An AND gate will be used
  55 + self.add_edge("K"+i[0], i[0]+"_mod",
  56 + label=lock[i[1][0]],
  57 + width=5,
  58 + arrow_size=2,
  59 + label_size=30,
  60 + color="#FF0000",
  61 + cat="lock")
  62 + self.add_edge(i[0], i[0]+"_mod",
  63 + label=lock[i[1][0]],
  64 + width=5,
  65 + arrow_size=2,
  66 + label_size=30,
  67 + color="#FF0000",
  68 + cat="lock")
  69 + else:
  70 + raise ValueError("The node has no Vforced value !")
  71 +
  72 + return self
... ... @@ -0,0 +1,6 @@
  1 +# Graph analysis-based logic locking
  2 +#### Authors: Brice Colombier, Lilian Bossuet, David Hély
  3 +#### Laboratoire Hubert Curien
  4 +#### 18 rue Pr. Benoît Lauras, 42000 Saint-Etienne, FRANCE
  5 +
  6 +Source code associated to [this research article](https://hal.archives-ouvertes.fr/ujm-01180564)