ReferenceDomain knowledge
Domain knowledge
Domain knowledge: prior constraints on causal structure. Import from ergodic.knowledge.
DomainKnowledge records what a user knows before looking at the data, the
edges that must or must not exist, the temporal order of variables, which
variables are roots or sinks, and which pairs share a hidden confounder. A
skeleton restriction (restrict_to) caps which pairs may be adjacent at all.
It is an immutable value object built up with small methods, and two knowledge
objects combine with & (more knowledge means fewer admissible graphs).
Hard constraints are the focus for now. The energy and log_prior hooks
are where soft, probabilistic knowledge will attach once the Bayesian work
starts; they raise for the moment so nothing depends on a half-built scoring rule.
Also exported from ergodic: InconsistentKnowledgeError, DomainKnowledge.
InconsistentKnowledgeError#
Domain knowledge contradicts itself (for example a required edge cycle).
DomainKnowledge#
class DomainKnowledge(
variables: frozenset[str] = <factory>,
required: frozenset[tuple[str, str]] = <factory>,
forbidden: frozenset[tuple[str, str]] = <factory>,
precedes: frozenset[tuple[str, str]] = <factory>,
roots: frozenset[str] = <factory>,
sinks: frozenset[str] = <factory>,
confounded: frozenset[frozenset[str]] = <factory>,
allowed_adjacencies: frozenset[frozenset[str]] | None = None,
) -> None
Prior constraints on a causal graph over named variables.
| Field | Type | Default |
|---|---|---|
variables |
frozenset[str] |
factory |
required |
frozenset[tuple[str, str]] |
factory |
forbidden |
frozenset[tuple[str, str]] |
factory |
precedes |
frozenset[tuple[str, str]] |
factory |
roots |
frozenset[str] |
factory |
sinks |
frozenset[str] |
factory |
confounded |
frozenset[frozenset[str]] |
factory |
allowed_adjacencies |
frozenset[frozenset[str]] | None |
None |
DomainKnowledge.with_variables#
def DomainKnowledge.with_variables(*names: str) -> DomainKnowledge
Return knowledge that also lists names in variables.
Every builder records the names it mentions there; this adds names without adding a constraint.
DomainKnowledge.require#
def DomainKnowledge.require(cause: str, effect: str) -> DomainKnowledge
Require a directed edge cause -> effect.
DomainKnowledge.forbid#
def DomainKnowledge.forbid(cause: str, effect: str) -> DomainKnowledge
Forbid the directed edge cause -> effect (the reverse stays allowed).
DomainKnowledge.forbid_adjacency#
def DomainKnowledge.forbid_adjacency(a: str, b: str) -> DomainKnowledge
Forbid any edge between two variables, in either direction.
DomainKnowledge.order#
def DomainKnowledge.order(before: str, after: str) -> DomainKnowledge
Record that before precedes after in time, so after cannot cause it.
DomainKnowledge.with_tiers#
def DomainKnowledge.with_tiers(
tiers: Sequence[Iterable[str]],
) -> DomainKnowledge
Add a tiered order: no variable in a later tier causes one in an earlier tier.
DomainKnowledge.root#
def DomainKnowledge.root(*names: str) -> DomainKnowledge
Mark variables as roots: no incoming directed edges.
DomainKnowledge.sink#
def DomainKnowledge.sink(*names: str) -> DomainKnowledge
Mark variables as sinks: no outgoing directed edges.
DomainKnowledge.confound#
def DomainKnowledge.confound(a: str, b: str) -> DomainKnowledge
Record that a and b share a hidden common cause.
DomainKnowledge.forbid_between#
def DomainKnowledge.forbid_between(
group_a: Iterable[str],
group_b: Iterable[str],
) -> DomainKnowledge
Forbid every directed edge from group_a into group_b.
DomainKnowledge.restrict_to#
def DomainKnowledge.restrict_to(
skeleton: MixedGraph | Iterable[tuple[str, str]],
) -> DomainKnowledge
Allow adjacencies only on the pairs of skeleton.
skeleton is a MixedGraph (its edges name
the pairs, whatever the marks) or an iterable of (u, v) pairs. A
pair outside the allow-list is banned in every form: directed either
way, bidirected, or undirected. Restricting an already restricted
object keeps the intersection, so a second call only narrows the
support. An empty iterable allows no adjacencies at all.
DomainKnowledge.combine#
def DomainKnowledge.combine(other: DomainKnowledge) -> DomainKnowledge
Combine two knowledge objects by taking the union of their constraints.
Skeleton allow-lists intersect: an adjacency survives only when both
sides allow it, and None (unrestricted) defers to the other side.
DomainKnowledge.conflicts#
def DomainKnowledge.conflicts() -> list[str]
Return a list of internal contradictions, empty when consistent.
DomainKnowledge.validate#
def DomainKnowledge.validate() -> None
Raise InconsistentKnowledgeError if the knowledge contradicts itself.
DomainKnowledge.permits_edge#
def DomainKnowledge.permits_edge(cause: str, effect: str) -> bool
Return True if a directed edge cause -> effect is allowed.
Precedence is read transitively: with A before B and B
before C, an edge C -> A is not permitted. Under a skeleton
restriction, a pair outside the allow-list fails in both directions.
DomainKnowledge.requires_edge#
def DomainKnowledge.requires_edge(cause: str, effect: str) -> bool
Return True if cause -> effect was required with require().
Only the stated pair counts; nothing is inferred from tiers or other constraints.
DomainKnowledge.precedence#
def DomainKnowledge.precedence() -> frozenset[tuple[str, str]]
Every (before, after) pair, with the order read transitively.
The projection discovery reads to orient: (a, b) here means b
cannot be an ancestor of a, which in a latent-variable search
(FCI) pins an arrowhead at b on any surviving adjacency.
DomainKnowledge.violations#
def DomainKnowledge.violations(graph: MixedGraph) -> list[str]
Return the ways graph breaks this knowledge, empty when consistent.
DomainKnowledge.is_consistent#
def DomainKnowledge.is_consistent(graph: MixedGraph) -> bool
Return True if graph satisfies every constraint.
DomainKnowledge.apply_to#
def DomainKnowledge.apply_to(graph: MixedGraph) -> MixedGraph
Enforce the hard constraints on graph: drop forbidden, add required edges.
A pair forbidden in both directions loses its edge whatever the marks; a single-direction ban only drops the matching directed edge. Under a skeleton restriction, every edge on a pair outside the allow-list is dropped too. Edges left undirected or with a circle are oriented when only one direction stays permitted. The result is returned as a new graph.
DomainKnowledge.edge_mask#
def DomainKnowledge.edge_mask(
variables: Sequence[str] | None = None,
) -> tuple[npt.NDArray[np.bool_], list[str]]
Return a boolean matrix of permitted directed edges and its variable order.
mask[i, j] is True when an edge from variable i to variable
j is allowed. The diagonal is False, and a skeleton restriction
zeroes both directions of every pair outside its allow-list. Useful as
a search mask for score-based and continuous-optimization discovery.
DomainKnowledge.energy#
def DomainKnowledge.energy(graph: MixedGraph) -> float
Penalty for graph under soft knowledge. Not implemented; raises.
DomainKnowledge.log_prior#
def DomainKnowledge.log_prior(graph: MixedGraph) -> float
Log prior of graph under soft knowledge. Not implemented; raises.