Skip to content

Commit

Permalink
Handle negative values below MIN_PROBA in belief propagation.
Browse files Browse the repository at this point in the history
This was causing some issues in extreme numerically unstable cases.

Fixes #86
  • Loading branch information
Gaëtan Cassiers committed Feb 20, 2023
1 parent 146bd46 commit d912f13
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 2 deletions.
28 changes: 28 additions & 0 deletions src/scalib_ext/scalib-py/src/factor_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,34 @@ impl BPState {
self.get_inner_mut().propagate_factor_all(factor_id);
Ok(())
}
pub fn set_belief_from_var(
&mut self,
py: Python,
var: &str,
factor: &str,
distr: PyObject,
) -> PyResult<()> {
let edge_id = self.get_edge_named(var, factor)?;
let bp = self.get_inner_mut();
let distr = obj2distr(py, distr, bp.get_graph().edge_multi(edge_id))?;
bp.set_belief_from_var(edge_id, distr)
.map_err(|e| PyTypeError::new_err(e.to_string()))?;
Ok(())
}
pub fn set_belief_to_var(
&mut self,
py: Python,
var: &str,
factor: &str,
distr: PyObject,
) -> PyResult<()> {
let edge_id = self.get_edge_named(var, factor)?;
let bp = self.get_inner_mut();
let distr = obj2distr(py, distr, bp.get_graph().edge_multi(edge_id))?;
bp.set_belief_to_var(edge_id, distr)
.map_err(|e| PyTypeError::new_err(e.to_string()))?;
Ok(())
}
pub fn propagate_factor(
&mut self,
factor: &str,
Expand Down
14 changes: 14 additions & 0 deletions src/scalib_ext/scalib/src/sasca/belief_propagation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,20 @@ impl BPState {
pub fn get_belief_from_var(&self, edge: EdgeId) -> &Distribution {
&self.belief_from_var[edge]
}
pub fn set_belief_from_var(
&mut self,
edge: EdgeId,
belief: Distribution,
) -> Result<(), BPError> {
self.check_distribution(&belief, self.graph.edge_multi(edge))?;
self.belief_from_var[edge] = belief;
Ok(())
}
pub fn set_belief_to_var(&mut self, edge: EdgeId, belief: Distribution) -> Result<(), BPError> {
self.check_distribution(&belief, self.graph.edge_multi(edge))?;
self.belief_to_var[edge] = belief;
Ok(())
}
// Propagation type:
// belief to var -> var
// var -> belief to func
Expand Down
8 changes: 6 additions & 2 deletions src/scalib_ext/scalib/src/sasca/bp_compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,12 @@ impl Distribution {
/// Normalize sum to one, and make values not too small
pub fn regularize(&mut self) {
self.for_each_ignore(|mut d, _| {
let norm_f = 1.0 / (d.sum() + MIN_PROBA * d.len() as f64);
d.mapv_inplace(|x| (x + MIN_PROBA) * norm_f);
let (sum, min) = d
.iter()
.fold((0.0f64, 0.0f64), |(sum, min), x| (sum + x, min.min(*x)));
let offset = -min + MIN_PROBA;
let norm_f = 1.0 / (sum + offset * d.len() as f64);
d.mapv_inplace(|x| (x + offset) * norm_f);
})
}
pub fn make_non_zero_signed(&mut self) {
Expand Down
6 changes: 6 additions & 0 deletions src/scalib_ext/scalib/src/sasca/factor_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ impl FactorGraph {
pub fn var_multi(&self, var: VarId) -> bool {
self.var(var).multi
}
pub fn factor_multi(&self, factor: FactorId) -> bool {
self.factor(factor).multi
}
pub fn edge_multi(&self, edge: EdgeId) -> bool {
self.factor_multi(self.edges[edge].factor)
}
pub fn range_vars(&self) -> impl Iterator<Item = VarId> {
(0..self.vars.len()).map(VarId::from_idx)
}
Expand Down
177 changes: 177 additions & 0 deletions tests/test_factorgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,3 +727,180 @@ def test_cyclic():
g = FactorGraph(graph_multi_cyclic)
assert BPState(g, 1, {"p": np.array([0], dtype=np.uint32)}).is_cyclic() == False
assert BPState(g, 2, {"p": np.array([0, 0], dtype=np.uint32)}).is_cyclic() == True


def test_and_rounding_error_simple():
# simple reproduction of issue #86
factor_graph = """NC 16
VAR MULTI A
VAR MULTI B
VAR MULTI C
PROPERTY P: C = A & B
"""
priors = {
"A": [
1.0 + 2**-52,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
1.0,
],
"C": [
0.0,
2.666666666666667,
2.666666666666667,
0.0,
2.666666666666667,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
],
}
fg = FactorGraph(factor_graph)
bp = BPState(fg, 1)
for k, v in priors.items():
bp._inner.set_belief_from_var(k, "P", np.array([v]))
bp.propagate_factor("P")
assert (bp.get_belief_to_var("B", "P") >= 0.0).all()


def test_and_rounding_error_simple():
# test case of issue #86
factor_graph = """NC 16
VAR MULTI K0
VAR MULTI K1
VAR MULTI L1
VAR MULTI N1
VAR MULTI B
VAR MULTI C
VAR MULTI N0
VAR MULTI A
VAR MULTI L2
VAR MULTI L3
PUB SINGLE IV
PROPERTY P1: L1 = K0 ^ K1
PROPERTY P2: N1 = !K1
PROPERTY P3: B = N1 & IV
PROPERTY P4: C = B ^ K0
PROPERTY P5: N0 = !K0
PROPERTY P6: A = N0 & K1
PROPERTY P7: L2 = A ^ C
PROPERTY P8: L3 = K1 ^ C"""
priors = {
"A": [
0.0,
0.25,
0.25,
0.0,
0.25,
0.0,
0.0,
0.0,
0.25,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
],
"C": [
0.0,
0.0,
0.0,
0.1666666667,
0.0,
0.1666666667,
0.1666666667,
0.0,
0.0,
0.1666666667,
0.1666666667,
0.0,
0.1666666667,
0.0,
0.0,
0.0,
],
"L1": [
0.0,
0.0,
0.0,
0.1666666667,
0.0,
0.1666666667,
0.1666666667,
0.0,
0.0,
0.1666666667,
0.1666666667,
0.0,
0.1666666667,
0.0,
0.0,
0.0,
],
"L2": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.25,
0.0,
0.0,
0.0,
0.25,
0.0,
0.25,
0.25,
0.0,
],
"L3": [
0.0,
0.0,
0.0,
0.1666666667,
0.0,
0.1666666667,
0.1666666667,
0.0,
0.0,
0.1666666667,
0.1666666667,
0.0,
0.1666666667,
0.0,
0.0,
0.0,
],
}
fg = FactorGraph(factor_graph)
bp = BPState(fg, 1, public_values={"IV": 0xC})
for k, v in priors.items():
bp.set_evidence(k, distribution=np.array([v]))
bp.bp_loopy(5, initialize_states=False)
assert (bp.get_distribution("K0") >= 0.0).all()
assert (bp.get_distribution("K1") >= 0.0).all()

0 comments on commit d912f13

Please sign in to comment.