Models

evidencelib keeps the model explicit. The same expression can mean different things depending on the frame.

DST

frame = Frame.dst(["A", "B", "C"])

DST uses exhaustive and mutually exclusive hypotheses. A & B is therefore empty, and frame.elements() generates the classical power set.

Use DST when: exactly one hypothesis can be true, for example one label selected from a known set of classes.

Common workflows use dempster(), yager(), pcr5(), pcr6(), and pignistic().

Free DSmT

frame = Frame.dsmt(["A", "B", "C"])

Free DSmT keeps hypotheses exhaustive but allows overlap. A & B can be a real state, and frame.elements() generates the hyper-power set.

Use free DSmT when: categories are vague, overlapping, or not safely separable.

In this model, A, B, and A & B can all carry distinct mass. dsmc() keeps mass on intersections instead of treating them as conflict.

Hybrid DSmT

frame = Frame.hybrid(["E", "M", "H"], empty=["E&H"])

Hybrid DSmT adds explicit constraints. Some intersections can be impossible, or new knowledge can make a hypothesis empty.

1. Define the constraints

Use empty for proposition expressions that are impossible. Use exclusive=True when every pair of atoms is mutually exclusive, or pass atom groups to exclusive when only selected intersections are impossible.

Frame.hybrid(["A", "B"], exclusive=True)
Frame.hybrid(["A", "B", "C"], empty=["A & B"])
Frame.hybrid(["A", "B", "C"], exclusive=[("A", "B")])

The last two calls impose the same pairwise constraint. empty is more useful for compound expressions, while exclusive is concise for atom groups.

Use hybrid DSmT when: most hypotheses can overlap, but some combinations are impossible or have become impossible.

2. Inspect the constrained domain

Constraints are closed automatically. If E&H is empty, every Venn region contained in that intersection is removed, including E&M&H.

from evidencelib import Frame

frame = Frame.hybrid(["E", "M", "H"], empty=["E&H"])
E, M, H = frame.symbols()

assert not E & H
assert not E & M & H
assert frame.region_count == 5
assert len(frame.elements()) == 13

The user declares the physical constraint. The frame computes its closure, so higher-order intersections do not need to be listed separately.

3. Assign masses and apply DSmH

Create static source assignments directly on the constrained frame when no source assigns mass to an impossible proposition. Apply dsmh() to transfer products whose intersection is forbidden to the corresponding disjunction.

sensor = frame.mass({E: 0.6, E | M: 0.4})
expert = frame.mass({H: 0.5, M: 0.3, E | M | H: 0.2})

fused = sensor.dsmh(expert)
assert fused.total_mass == 1.0

print({name: round(value, 2) for name, value in fused.to_dict().items()})
# {'E': 0.12, 'E&M': 0.18, 'E|H': 0.3,
#  'E|M': 0.08, 'M': 0.12, 'M&H': 0.2}

Here, the product of the masses assigned to E and H cannot remain on E&H. DSmH transfers it to E|H; the other products remain on propositions permitted by the hybrid model.

Static and dynamic constraints

For a static model, create sources directly on the constrained frame when they assign no mass to impossible propositions. For constraints discovered after the sources were elicited, keep the source masses on their original frame and pass the target model explicitly:

source = Frame.dsmt(["A", "B", "C"])
A, B, C = source.symbols()
m1 = source.mass({A & B: 1.0})
m2 = source.mass({A & B: 1.0})

target = Frame.hybrid(["A", "B", "C"], empty=["A&B"])
result = m1.dsmh(m2, model=target)
assert result.to_dict() == {"A|B": 1.0}

The original A&B expression is essential here: the S2 term uses u(A&B) = A|B. Creating the masses on target would discard that provenance. Target projection may add constraints, but it cannot make Venn regions possible that were absent from the source model; constraint relaxation requires a new source model and re-elicited/reconstructed masses.

Element growth

DSmT proposition spaces grow quickly:

  • Frame.dsmt(["A", "B"]).elements() has 5 elements.

  • Frame.dsmt(["A", "B", "C"]).elements() has 19 elements.

  • Frame.dsmt(["A", "B", "C", "D"]).elements() has 167 elements.

  • A five-atom free model already has 7,580 elements.

Frame.elements() has a safety limit. Pass max_count=None only when you really want the full closure.