diff --git a/src/argumentation/probabilistic/epistemic.py b/src/argumentation/probabilistic/epistemic.py index 7fca614..f69b13a 100644 --- a/src/argumentation/probabilistic/epistemic.py +++ b/src/argumentation/probabilistic/epistemic.py @@ -944,10 +944,9 @@ def update_assignment( changed = True if not belief_assignment_satisfies(graph, assignment): raise ValueError("updated assignment cannot satisfy the graph constraints") - return { - argument: round(assignment[argument], 12) - for argument in sorted(graph.arguments) - } + # Return exactly the validated values: rounding here could move a value + # across an exact constraint bound after validation. + return {argument: assignment[argument] for argument in sorted(graph.arguments)} def project_to_constellation_praf(graph: EpistemicGraph) -> ProbabilisticAF: diff --git a/tests/probabilistic/test_epistemic.py b/tests/probabilistic/test_epistemic.py index 33fa213..0e34988 100644 --- a/tests/probabilistic/test_epistemic.py +++ b/tests/probabilistic/test_epistemic.py @@ -74,7 +74,11 @@ def test_update_assignment_clamps_evidence_and_propagates_fragment() -> None: ), ) - assert update_assignment(graph, {"a": 0.8}) == {"a": 0.8, "b": 0.8, "c": 0.2} + # Values are returned exactly as validated (issue #16), so c is the float + # 1 - 0.8 rather than a rounded 0.2. + assert update_assignment(graph, {"a": 0.8}) == pytest.approx( + {"a": 0.8, "b": 0.8, "c": 0.2} + ) def test_update_assignment_respects_explicit_belief_constraints() -> None: diff --git a/tests/probabilistic/test_epistemic_contracts.py b/tests/probabilistic/test_epistemic_contracts.py index 6df555c..9df60c6 100644 --- a/tests/probabilistic/test_epistemic_contracts.py +++ b/tests/probabilistic/test_epistemic_contracts.py @@ -20,6 +20,7 @@ from argumentation.probabilistic.epistemic import ( AndTerm, ArgumentTerm, + BeliefConstraint, EpistemicGraph, Influence, InfluenceKind, @@ -30,6 +31,7 @@ OrTerm, ProbabilityTerm, Term, + belief_assignment_satisfies, least_squares_update_labelling, parse_term, update_assignment, @@ -164,6 +166,43 @@ def tracer(frame: FrameType, event: str, arg: object) -> object: sys.settrace(None) +@pytest.mark.parametrize( + "influences", + [(), (Influence("a", "b", InfluenceKind.NEGATIVE),)], + ids=["exact-bound", "negative-influence-from-exact-bound"], +) +def test_update_assignment_returns_values_that_satisfy_exact_bounds( + influences: tuple[Influence, ...], +) -> None: + """Issue #16: the returned assignment must satisfy the graph constraints + (Hunter & Thimm 2017 epistemic constraints), including an exact bound + finer than 12 decimal places and a negative influence ``b <= 1 - a`` + computed from it.""" + value = 0.1234567890123 + graph = EpistemicGraph( + frozenset({"a", "b"}), + influences=frozenset(influences), + constraints=(BeliefConstraint("a", value, value),), + ) + + result = update_assignment(graph, {}) + + assert result["a"] == value + assert belief_assignment_satisfies(graph, result) + + +def test_update_assignment_coarse_bound_control() -> None: + """Control: a bound with few decimals is returned unchanged.""" + graph = EpistemicGraph( + frozenset({"a"}), constraints=(BeliefConstraint("a", 0.25, 0.25),) + ) + + result = update_assignment(graph, {}) + + assert result == {"a": 0.25} + assert belief_assignment_satisfies(graph, result) + + def test_update_assignment_rejects_contradictory_influences() -> None: """Issue #42: with P(a) = 0.8, a positive influence a -> b demands P(b) >= 0.8 and a negative one demands P(b) <= 0.2 (Hunter & Thimm 2017