# `WolframModel.RuleAnalysis`
[🔗](https://github.com/sragli/wolfram_model/blob/main/lib/wolfram_model/rule_analysis.ex#L1)

Rule property analysis for WolframModel rules.

Provides predicates and metrics that characterise the structural properties of
a rule without running a full evolution — useful for classifying rules before
deciding how to use them.

# `rule`

```elixir
@type rule() :: WolframModel.rule()
```

# `arity`

```elixir
@spec arity(rule()) :: {[non_neg_integer()], [non_neg_integer()]}
```

Returns the arity of a rule as `{pattern_sizes, replacement_sizes}` where
each element is a sorted list of hyperedge sizes.

Useful for quickly comparing the "shape" of different rules.

# `canonical_form`

```elixir
@spec canonical_form(rule()) :: %{pattern: [[term()]], replacement: [[term()]]}
```

Returns a canonical form of a rule with variables renamed in first-appearance
order (depth-first, pattern first then replacement).

Two rules are structurally equivalent if and only if their canonical forms
are equal. Variables that appear only in the replacement (new-vertex generators)
are canonicalized separately after all shared variables.

    iex> r1 = %{pattern: [[1,2],[2,3]], replacement: [[1,3]], name: "a"}
    iex> r2 = %{pattern: [[10,20],[20,30]], replacement: [[10,30]], name: "b"}
    iex> WolframModel.RuleAnalysis.canonical_form(r1) == WolframModel.RuleAnalysis.canonical_form(r2)
    true

# `equivalent?`

```elixir
@spec equivalent?(rule(), rule()) :: boolean()
```

Returns `true` if `rule1` and `rule2` are structurally equivalent — i.e.
they represent the same rewriting rule up to a bijective renaming of
variables.

Note: this checks *syntactic* isomorphism based on first-appearance variable
order. It does not test semantic equivalence under all possible hypergraph
evolutions.

# `hyperedge_delta`

```elixir
@spec hyperedge_delta(rule()) :: integer()
```

Returns the net hyperedge count change for a single rule application.

A positive value means the rule grows the hypergraph, zero means it
restructures without changing edge count, and negative means it shrinks it.

# `introduces_new_vertices?`

```elixir
@spec introduces_new_vertices?(rule()) :: boolean()
```

Returns `true` if the replacement contains at least one atom that does not
appear in the pattern — indicating the rule introduces new vertices when
applied.

# `reversible?`

```elixir
@spec reversible?(rule()) :: boolean()
```

Returns `true` if the rule is structurally reversible: the multiset of
hyperedge sizes in the pattern equals the multiset in the replacement.

A reversible rule can (at least in principle) be run "backwards" by swapping
pattern and replacement.

# `self_complementary?`

```elixir
@spec self_complementary?(rule()) :: boolean()
```

Returns `true` if the rule is self-complementary: it has the same number of
hyperedges in the pattern and replacement, and the same multiset of hyperedge
sizes (i.e., the rule maps one configuration to a structurally identical one).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
