Skip to main content

pyNMMS

PyPI Python License CI Docs

An automated reasoner for the Non-Monotonic Multi-Succedent (NMMS) sequent calculus from Hlobil & Brandom 2025, Ch. 3.

Documentation | PyPI | GitHub

Installation

pip install pyNMMS

For development:

git clone https://github.com/bradleypallen/pyNMMS.git
cd pyNMMS
pip install -e ".[dev]"

The dev extra installs pytest, pytest-cov, Hypothesis, ruff, and mypy. Without it the property-based test module is skipped. make check runs lint, type check, and tests.

Quick Start

from pynmms import MaterialBase, NMMSReasoner

# Create a material base with defeasible inferences
base = MaterialBase(
    language={"A", "B", "C"},
    consequences={
        (frozenset({"A"}), frozenset({"B"})),  # A |~ B
        (frozenset({"B"}), frozenset({"C"})),  # B |~ C
    },
)

reasoner = NMMSReasoner(base)

# A derives B (base consequence)
result = reasoner.derives(frozenset({"A"}), frozenset({"B"}))
assert result.derivable  # True

# A does NOT derive C (nontransitivity — no [Mixed-Cut])
result = reasoner.derives(frozenset({"A"}), frozenset({"C"}))
assert not result.derivable  # False

# A, C does NOT derive B (nonmonotonicity — no [Weakening])
result = reasoner.derives(frozenset({"A", "C"}), frozenset({"B"}))
assert not result.derivable  # False

# Classical tautologies still hold (supraclassicality)
result = reasoner.derives(frozenset(), frozenset({"A | ~A"}))
assert result.derivable  # True

CLI

# Create a base and add consequences
pynmms tell -b base.json --create "A |~ B"
pynmms tell -b base.json "B |~ C"

# Query derivability
pynmms ask -b base.json "A => B"        # DERIVABLE
pynmms ask -b base.json "A => C"        # NOT DERIVABLE
pynmms ask -b base.json "A, C => B"     # NOT DERIVABLE

# Interactive REPL (no quotes needed around commands)
pynmms repl -b base.json
# pynmms> ask A => B
# DERIVABLE
# pynmms> ask A => C
# NOT DERIVABLE

Ontology Extension

The pynmms.onto subpackage extends propositional NMMS with ontology axiom schemas (subClassOf, range, domain, subPropertyOf, disjointWith, disjointProperties, jointCommitment), enabling ontology reasoning while preserving nonmonotonicity.

from pynmms.onto import OntoMaterialBase
from pynmms.reasoner import NMMSReasoner

base = OntoMaterialBase(language={"Man(socrates)", "hasChild(alice,bob)"})

# Register ontology axiom schemas
base.register_subclass("Man", "Mortal")       # {Man(x)} |~ {Mortal(x)}
base.register_range("hasChild", "Person")     # {hasChild(x,y)} |~ {Person(y)}
base.register_domain("hasChild", "Parent")    # {hasChild(x,y)} |~ {Parent(x)}
base.register_disjoint("Mortal", "Immortal")  # {Mortal(x), Immortal(x)} |~

r = NMMSReasoner(base, max_depth=15)

r.query(frozenset({"Man(socrates)"}), frozenset({"Mortal(socrates)"}))  # True
r.query(frozenset({"hasChild(alice,bob)"}), frozenset({"Person(bob)"}))  # True

# Nonmonotonic — extra premises defeat ontology inferences
r.query(
    frozenset({"Man(socrates)", "Immortal(socrates)"}),
    frozenset({"Mortal(socrates)"}),
)  # False
# CLI with --onto flag
pynmms tell -b onto_base.json --create --onto "atom Man(socrates)"
pynmms tell -b onto_base.json --onto --batch schemas.txt  # batch with schema lines
pynmms ask -b onto_base.json --onto "Man(socrates) => Mortal(socrates)"
pynmms repl --onto

Key Properties

  • Nonmonotonicity: Adding premises can defeat inferences (no Weakening)
  • Nontransitivity: Chaining good inferences can yield bad ones (no Mixed-Cut)
  • Supraclassicality: All classically valid sequents are derivable
  • Conservative Extension: Logical vocabulary doesn't change base-level relations
  • Explicitation Conditions: DD, II, AA, SS biconditionals hold

Implementation

Proof search strategy

The reasoner uses root-first backward proof search with memoization and backtracking. This is related to but distinct from the deterministic proof-search procedure in Definition 20 of the Ch. 3 appendix. Definition 20 specifies a deterministic decomposition: find the first complex sentence (alphabetically, left side first), apply the corresponding rule, repeat until all leaves are atomic, then check axioms. Our implementation instead tries each complex sentence in sorted order with backtracking — if decomposing one sentence fails to produce a proof, it backtracks and tries the next. Both approaches are correct because all NMMS rules are invertible (Proposition 27): if a sequent is derivable, any order of rule application will find the proof. Our approach adds memoization and depth-limiting as practical safeguards.

  • 8 Ketonen-style propositional rules with third top sequent (compensates for working with sets rather than multisets, per Proposition 21)
  • Memoization keyed on (frozenset, frozenset) pairs; cycle detection via pre-marking entries as False before recursion
  • Depth-limited (default 25) to guarantee termination
  • Deterministic rule application order (sorted iteration) for reproducible results

Design decisions

  • Propositional core with ontology axiom schemas in pynmms.onto subpackage
  • Sets (frozensets), not multisets — Contraction is built in (per Proposition 21)
  • Sentences represented as strings, parsed on demand by a recursive descent parser producing frozen Sentence dataclass AST nodes
  • Base consequences use exact syntactic match — no subset/superset matching, which is what enforces the no-Weakening property
  • Containment (Γ ∩ Δ ≠ ∅) checked automatically as an axiom schema
  • No runtime dependencies beyond the Python standard library

Known limitations

  • Depth limit can cause false negatives for deeply nested valid sequents
  • No incremental/persistent cache between queries
  • Multi-premise rules ([L→], [L∨], [R∧]) each generate 3 subgoals, giving worst-case exponential branching
  • Flat proof trace only — no structured proof tree or proof certificates
  • Formula strings re-parsed at each proof step (no pre-compilation)
  • Does not implement NMMS\ctr (contraction-free variant, Section 3.2.3), Monotonicity Box (□, Section 3.3.1), or classicality operator (⌈cl⌉, Section 3.3.2)

Test suite

538 tests across 20 test files:

  • Propositional core (331 tests): Syntax parsing (including the strict atom grammar and quoted atoms), MaterialBase construction/serialization, individual rule correctness, axiom derivability, structural properties (nonmonotonicity, nontransitivity, supraclassicality, DD/II/AA/SS), soundness audit, CLI integration, logging/tracing, Ch. 3 worked examples, Hypothesis property-based tests, cross-validation against ROLE.jl ground truth
  • Ontology extension (207 tests): Ontology sentence parsing, OntoMaterialBase construction/validation, seven ontology schema types (subClassOf, range, domain, subPropertyOf, disjointWith, disjointProperties, jointCommitment), nonmonotonicity and non-transitivity of schemas, lazy evaluation, NMMSReasoner integration, CommitmentStore, CLI --onto integration, JSON output/exit codes, batch mode, annotations, legacy equivalence, logging

Benchmarks

bench/ is a standard-library benchmark package. make bench (or python -m bench, --quick for reduced sizes) runs three sections and writes a JSON record with timestamp, git SHA, and environment to bench/results/:

  • antecedent_scaling — query cost versus antecedent size |Γ|
  • schema_scaling — axiom-check cost versus number of ontology schemas
  • query_complexity — proof cost versus number of connectives in the query

The committed records are the regression baseline for reasoner and base changes.

Theoretical Background

This implements the NMMS sequent calculus from:

  • Hlobil, U., & Brandom, R. B. (2025). Reasons for logic, logic for reasons: Pragmatics, semantics, and conceptual roles. Routledge.

NMMS codifies open reason relations — consequence relations where Monotonicity and Transitivity can fail. The material base encodes defeasible material inferences among atomic sentences, and the Ketonen-style logical rules extend this to compound sentences while preserving nonmonotonicity.

License

MIT

Metadata

Release files for pyNMMS 0.6.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyNMMS 0.6.2
File Size Uploaded
pynmms-0.6.2.tar.gz 111.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyNMMS 0.6.2
File Interpreter ABI Platform
pynmms-0.6.2-py3-none-any.whl Python 3 none any Details

Total release size: 144.6 kB

Release files / pynmms-0.6.2.tar.gz

Download URL pynmms-0.6.2.tar.gz
Size 111.4 kB
Tags Source
SHA-256 checksum
How to use checksums
e4ad7c1b350566e3864116c43b84d37d077eb8e134b1e093124d9f4e9dd92dd2
BLAKE2b-256 checksum
How to use checksums
b341d7ce100d426aad9fcda99a2f4f9aac8422a3d414e2d206f98d59b4f7f2c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.11

Release files / pynmms-0.6.2-py3-none-any.whl

Download URL pynmms-0.6.2-py3-none-any.whl
Size 33.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
25bbb74e47c07567f91c1e408d1306b7391fec03cb432025f3991821fb7d9e7f
BLAKE2b-256 checksum
How to use checksums
f7ca66e853186463012f008342a36ac2db765f06c1206fb00eebe1e32f8eff6d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.11
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page