Skip to content

Commit a5712fc

Browse files
authoredMar 7, 2024
Fix FactorGraph cycle detection with nexec>1
1 parent ef9ebce commit a5712fc

File tree

3 files changed

+114
-17
lines changed

3 files changed

+114
-17
lines changed
 

‎src/scalib_ext/scalib/src/sasca/factor_graph.rs

+4-17
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ pub struct FactorGraph {
148148
pub(super) petgraph: petgraph::Graph<Node, EdgeId, petgraph::Undirected>,
149149
pub(super) var_graph_ids: VarVec<petgraph::graph::NodeIndex>,
150150
pub(super) factor_graph_ids: FactorVec<petgraph::graph::NodeIndex>,
151+
pub(super) cyclic_single: bool,
152+
pub(super) cyclic_multi: bool,
151153
}
152154

153155
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
@@ -486,25 +488,10 @@ impl FactorGraph {
486488
}
487489

488490
pub(super) fn is_cyclic(&self, multi_exec: bool) -> bool {
489-
if petgraph::algo::is_cyclic_undirected(&self.petgraph) {
490-
return true;
491-
}
492491
if multi_exec {
493-
return petgraph::algo::kosaraju_scc(&self.petgraph)
494-
.into_iter()
495-
.any(|scc| {
496-
scc.into_iter()
497-
.filter(|n| match self.petgraph[*n] {
498-
Node::Var(var_id) => {
499-
!self.vars.get_index(var_id.index()).unwrap().1.multi
500-
}
501-
Node::Factor(_) => false,
502-
})
503-
.count()
504-
> 1
505-
});
492+
self.cyclic_multi
506493
} else {
507-
return false;
494+
self.cyclic_single
508495
}
509496
}
510497

‎src/scalib_ext/scalib/src/sasca/fg_build.rs

+67
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ impl fg::FactorGraph {
5252
petgraph: petgraph::Graph::new_undirected(),
5353
var_graph_ids: VarVec::new(),
5454
factor_graph_ids: FactorVec::new(),
55+
cyclic_single: false,
56+
cyclic_multi: false,
5557
}
5658
}
5759
fn check_new_var(&self, name: &String) -> Result<(), GraphBuildError> {
@@ -213,6 +215,69 @@ impl fg::FactorGraph {
213215
);
214216
}
215217
}
218+
/// Return the "flattended" factor graph for 2 executions, which can then be used to check
219+
/// cyclicity for any nexec > 1.
220+
/// Needs correct .vars, .factors and .edges
221+
fn petgraph_2exec(
222+
&self,
223+
) -> petgraph::Graph<(Node, u32), super::factor_graph::EdgeId, petgraph::Undirected> {
224+
enum Either<T> {
225+
Single(T),
226+
Multi([T; 2]),
227+
}
228+
let mut graph = petgraph::Graph::new_undirected();
229+
let var_graph_ids = self
230+
.vars
231+
.values()
232+
.enumerate()
233+
.map(|(var_id, var)| {
234+
if var.multi {
235+
Either::Multi([
236+
graph.add_node((Node::Var(VarId::from_usize(var_id)), 0)),
237+
graph.add_node((Node::Var(VarId::from_usize(var_id)), 1)),
238+
])
239+
} else {
240+
Either::Single(graph.add_node((Node::Var(VarId::from_usize(var_id)), 0)))
241+
}
242+
})
243+
.collect::<VarVec<_>>();
244+
let factor_graph_ids = self
245+
.factors
246+
.values()
247+
.enumerate()
248+
.map(|(factor_id, factor)| {
249+
if factor.multi {
250+
Either::Multi([
251+
graph.add_node((Node::Factor(FactorId::from_usize(factor_id)), 0)),
252+
graph.add_node((Node::Factor(FactorId::from_usize(factor_id)), 1)),
253+
])
254+
} else {
255+
Either::Single(
256+
graph.add_node((Node::Factor(FactorId::from_usize(factor_id)), 0)),
257+
)
258+
}
259+
})
260+
.collect::<FactorVec<_>>();
261+
for (i, e) in self.edges.iter_enumerated() {
262+
match (&var_graph_ids[e.var], &factor_graph_ids[e.factor]) {
263+
(Either::Single(var_node), Either::Single(factor_node)) => {
264+
graph.add_edge(*var_node, *factor_node, i);
265+
}
266+
(Either::Multi(var_nodes), Either::Multi(factor_nodes)) => {
267+
for (var_node, factor_node) in var_nodes.iter().zip(factor_nodes.iter()) {
268+
graph.add_edge(*var_node, *factor_node, i);
269+
}
270+
}
271+
(Either::Multi(nodes_multi), Either::Single(node_single))
272+
| (Either::Single(node_single), Either::Multi(nodes_multi)) => {
273+
for node_multi in nodes_multi.iter() {
274+
graph.add_edge(*node_single, *node_multi, i);
275+
}
276+
}
277+
}
278+
}
279+
graph
280+
}
216281
}
217282
impl fg_parser::Expr {
218283
fn as_factor_expr<F>(
@@ -312,6 +377,8 @@ pub(super) fn build_graph(
312377
}
313378
}
314379
graph.add_graph_edges();
380+
graph.cyclic_single = petgraph::algo::is_cyclic_undirected(&graph.petgraph);
381+
graph.cyclic_multi = petgraph::algo::is_cyclic_undirected(&graph.petgraph_2exec());
315382
Ok(graph)
316383
}
317384

‎tests/test_factorgraph.py

+43
Original file line numberDiff line numberDiff line change
@@ -1603,3 +1603,46 @@ def test_sanity_or():
16031603
fg.sanity_check({}, {"x": [0, 0], "a": 0, "b": [0, 1]})
16041604
fg.sanity_check({}, {"x": [0, 0], "a": 0, "b": [1, 0]})
16051605
fg.sanity_check({}, {"x": [1, 1], "a": 1, "b": [1, 1]})
1606+
1607+
1608+
def test_cycle_detection_single_factor():
1609+
graph_desc = """
1610+
NC 2
1611+
VAR SINGLE x
1612+
VAR SINGLE y
1613+
PROPERTY x = !y
1614+
"""
1615+
1616+
fg = FactorGraph(graph_desc)
1617+
bp = BPState(fg, 2)
1618+
assert not bp.is_cyclic()
1619+
1620+
1621+
def test_cycle_detection_single_factor2():
1622+
graph_desc = """
1623+
NC 2
1624+
VAR SINGLE x
1625+
VAR SINGLE y
1626+
VAR SINGLE z
1627+
PROPERTY x = !y
1628+
PROPERTY x = y ^ z
1629+
"""
1630+
1631+
fg = FactorGraph(graph_desc)
1632+
bp = BPState(fg, 2)
1633+
assert bp.is_cyclic()
1634+
1635+
1636+
def test_cycle_detection_single_factor_with_multi():
1637+
graph_desc = """
1638+
NC 2
1639+
VAR SINGLE x
1640+
VAR SINGLE y
1641+
VAR MULTI z
1642+
PROPERTY x = !y
1643+
PROPERTY x = y ^ z
1644+
"""
1645+
1646+
fg = FactorGraph(graph_desc)
1647+
bp = BPState(fg, 2)
1648+
assert bp.is_cyclic()

0 commit comments

Comments
 (0)