How to choose a reasoning engine#

This guide shows you how to pick and configure the engine that evaluates a rule, when the default boolean answer is not enough and you need a probabilistic or possibilistic verdict instead.

You select the engine on the call, not on the graph: every fg.eval.evaluate(...) and fg.eval.explain(...) call takes an engine= keyword, and the probabilistic and possibilistic engines additionally take a config= wrapper. The same rule and the same facts can be evaluated under any supported engine.

Prerequisites#

  • A FactGraph with a schema and facts (see How to write and read facts).
  • A rule or RuleExpr to evaluate (see How to evaluate a rule and read the proof).
  • For any engine other than native, the matching adapter package must be importable. The non-native engines are registered by importing their adapter package; until then the engine name is rejected at evaluation time with the message <engine> evaluator not registered; import factgraph.adapters.<engine> first.

Decide which engine you need#

The accepted engine names are fixed: native, souffle, problog, pyreason. Pick by the kind of answer you want back on each row’s certainty:

  • native (the default when engine= is omitted): boolean derivation. Use it for plain “does this hold” questions.
  • souffle: boolean derivation evaluated through the Souffle Datalog adapter. Same boolean verdict as native, different backend.
  • problog: probabilistic derivation. Each row carries a probability. Use it when facts or branches have probabilities attached.
  • pyreason: possibilistic derivation over time. Each row carries a [lower, upper] interval. Use it for interval-bounded, temporal reasoning.

Only problog and pyreason consume a config=. Passing config= to native or souffle is rejected.

Steps#

  1. Run with the default engine to get a boolean answer. Omitting engine= resolves to native:

    result = fg.eval.evaluate(rule, head=rule)
    row = result[0]
    row.certainty.kind  # "boolean"
  2. To use the Souffle backend instead, import its adapter once, then pass engine="souffle":

    import factgraph.adapters.souffle  # registers the "souffle" evaluator
    
    result = fg.eval.evaluate(rule, head=rule, engine="souffle")
  3. For a probabilistic answer, import the ProbLog adapter and pass engine="problog":

    import factgraph.adapters.problog  # registers the "problog" evaluator
    
    result = fg.eval.evaluate(rule, head=rule, engine="problog")
    result[0].certainty.kind  # "probabilistic"
  4. To attach branch probabilities, pass a ProbLogConfig as config= instead of engine=. The SDK derives engine="problog" from the wrapper, so you do not need both:

    from factgraph.sdk import ProbLogConfig
    
    expr = left.as_("left") | right.as_("right")
    result = fg.eval.evaluate(
        expr,
        head=left,
        config=ProbLogConfig(case_probabilities={"left": 0.7}),
    )

    case_probabilities keys are branch ids and the values must be in (0, 1]. For a single rule (not a RuleExpr), branch-specific case_probabilities are rejected; pass a bare ProbLogConfig() or use rule_params keyed by the rule’s id.

  5. For a possibilistic, time-aware answer, import the PyReason adapter and either pass engine="pyreason" or a PyReasonConfig as config=:

    import factgraph.adapters.pyreason  # registers the "pyreason" evaluator
    from factgraph.sdk import PyReasonConfig
    
    result = fg.eval.evaluate(
        rule,
        head=rule,
        config=PyReasonConfig(timestep_delay=0, iteration_count=1),
    )
    result[0].certainty.kind  # "possibilistic"

    PyReasonConfig carries the time and interval settings: timestep_delay (>= 0), iteration_count (>= 1), and optional [lower, upper] interval bounds (derived_bound, head_bound, atom_bounds, case_bounds). derived_bound and head_bound cannot both be set.

  6. If you want both engine= and config= to be explicit, they must agree. Setting engine="problog" with a PyReasonConfig (or vice versa) raises SDKStoreError because the wrapper’s engine does not match engine=.

    To verify the engine actually ran: read result.engine on the returned EvaluateResult, or result[0].certainty.kind to confirm the verdict shape ("boolean", "probabilistic", or "possibilistic").

Variations#

  • Same rule, every engine: call fg.eval.evaluate(rule, head=rule, engine=engine) in a loop over ("native", "souffle", "problog", "pyreason") to compare verdicts side by side. Native and Souffle return boolean rows, ProbLog returns probabilities, PyReason returns intervals.
  • Inspect a config without evaluating: pass a ProbLogConfig, PyReasonConfig, or SemanticsProfile to fg.eval.preview_config(...) to see its lowered canonical preview before committing to a run.
  • Explain under a chosen engine: fg.eval.explain(...) takes the same engine= keyword and requires a closed head. See How to evaluate a rule and read the proof.

Note: engine_options= and registry= are not accepted on fg.eval.evaluate(...); configure semantics through config= instead.

See also: Engines and semantics for every engine name, every ProbLogConfig and PyReasonConfig field with defaults, and the certainty-per-engine table; Evaluation and explanation for fg.eval.evaluate and EvaluateResult; Errors and error codes for SDKStoreError; and About reasoning, evaluation, and evidence for why the engine is a per-call choice.

comments powered by Disqus