Skip to content

PathPattern

relationalai.semantics.std.path
PathPattern(*args: Ref | Concept | Chain | PathPattern | Relationship, model=None)

A reusable specification of a pattern that can match paths through a knowledge graph.

Construct a PathPattern with Model.path (or its standalone convenience form path), optionally refine it with PathPattern.where or repeat it with PathPattern.repeat, then call PathPattern.all_paths to enumerate every matching path.

Positional arguments are classified by type:

Path pattern arguments are joined via path-join semantics: the last field of an argument unifies with the first slot of the next argument. For example, if A, B, and C are nodes and r1 and r2 are edges, then

  • m.path(A, r1, B) implies that A == r1[0] and r1[-1] == B,
  • m.path(A, B, C) implies that A == B == C, and
  • m.path(r1, r2) implies that r1[-1] == r2[0].

All node identifiers before the first edge and all node identifiers after the final edge of a path pattern act as the explicit src and dst endpoints of the path, respectively. Only these node identifiers may be referenced outside the path pattern, e.g., m.where(m.path(src, Person.follows.repeat(1, 3), dst).all_paths()).select(src, dst). All other node identifiers are referred to as interior nodes and are not yet accessible outside the path pattern.

All occurrences of the same Concept within a single m.path(...) refer to the same variable, just like elsewhere in PyRel. Reusing a Concept or Ref at more than one node position is rejected with an error. For example, m.path(Person.follows, Person.works_with) is invalid because the concept Person appears in two node positions, and x = Person.ref(); m.path(x.follows, x.works_with) is invalid because the reference x appears in two node positions. If the intent is to keep node positions independent, use distinct Refs: x, y = Person.ref(), Person.ref(); m.path(x.follows, y.works_with).

Important: The following two repeat forms have different semantics:

  • m.path(C.r.repeat(N)) — repeats the edge r only. Note that only the first node must be of type C; intermediate nodes are not constrained to type C.
  • m.path(C.r).repeat(N) — repeats the pattern C.r: every intermediate node must be of type C.

Parameters

  • *args

    (Ref | Concept | Chain | PathPattern | Relationship, default: ()) - One or more arguments defining the path pattern to be matched. Each argument may be a node (Concept or Ref), an edge (Chain or Relationship), or a nested sub-path (PathPattern).
  • model

    (Model, default: None) - The Model this path pattern belongs to. When omitted, the active model is resolved automatically, which errors if multiple models are defined in this Python process.

Examples

Person = m.Concept("Person")
source, destination = Person.ref(), Person.ref()
m.path(Person.relationship)
m.path(source.relationship)
m.path(source.relationship, destination)
m.path(source.relationship.repeat(1, 10))
m.path(source.relationship.repeat(1, 10), destination)
m.path(Person.relationship).repeat(3)
m.path(Person.follows, path(Employee.follows).repeat(min=3, max=5))

Notes

Most users should not instantiate this class directly. Prefer Model.path (or its standalone convenience form path).

Methods

.where()

PathPattern.where(*args: Expression | Match | Not) -> PathPattern

Attach filters and constraints to this path pattern.

We currently support only local filters, which are those that reference a single node or the endpoints of a single edge. They are routed onto that node or hop and are applied during traversal, pruning the search space. Non-local filters (also called cross-hop filters) are those that are not local to a single node or a single edge’s endpoints, and are currently unsupported except for those that reference only the path’s endpoints (src and dst). That special case is allowed, but the filter is applied as a post-filter after all matching paths are enumerated and does not prune the traversal.

Filters defined here (called attached filters) differ from those placed in an enclosing Model.where (called enclosing filters) in two primary ways:

  • because only the path’s endpoints unify with the enclosing scope, attached filters can reference interior nodes, whereas enclosing filters cannot.
  • attached filters are applied during traversal, which more efficiently prunes the traversal space, whereas enclosing filters are applied after enumeration.

Parameters:

  • *args

    (Expression | Match | Not, default: ()) - Zero or more of the following constraints on a single node, a single edge’s endpoints, or the pattern’s endpoints only: a boolean Expression, a Match (a | b), or a Not (not_(...)).

Returns:

  • PathPattern - This pattern (self), to allow chaining.

Examples:

# Constrain the source node.
m.path(x, Person.follows.repeat(1, 3)).where(x.name == "Alice")
# Constrain an interior node's concept.
m.path(x.follows, y.follows).where(Criminal(y))
# Constrain the endpoints of a single edge.
m.path(x.follows, y.follows).where(x.age < y.age)
# A src/dst cross-hop filter (applied after enumeration).
m.path(src.follows.repeat(1, 10), dst).where(src.age < dst.age)
# PathInternalError: a cross-hop filter that references an interior node ``z``.
m.path(src.follows, y.follows, z.follows, dst).where(src.age < z.age)

.repeat()

PathPattern.repeat(
min_or_exact: int | None = None, max: int | None = None, *, min: int | None = None
) -> PathPattern

Match the specified number of repetitions of this pattern.

Repeats the whole path pattern: For example, given a concept Person with property follows, m.path(Person.follows).repeat(3) repeats the pattern Person.follows three times with independent Person instances, effectively m.path(Person.ref().follows, Person.ref().follows, Person.ref().follows).

Note that the whole pattern is repeated, including its start: For example, in m.path(Criminal.follows).repeat(2), the start Criminal repeats together with the edge follows, effectively m.path(Criminal.ref().follows, Criminal.ref().follows) — so every intermediate node must be of type Criminal. See Chain.repeat for how this differs from a repeated chain.

Important: Note that no node identifier may be used both inside and outside a repeated pattern, due to ambiguity of which repetition the identifier refers to. For example, the following are disallowed due to Criminal appearing both inside and outside the repeated pattern: >>> m.where( … p := m.path(Criminal.follows).repeat(2,5).all_paths(), … Criminal.age > 18 … ).select(p, p.nodes[“index”], Person(p.nodes).name)

>>> m.where( … p := m.path(Criminal.follows).repeat(2,5).all_paths(), … ).select(Criminal)

>>> repeated_pattern = m.path(Criminal.follows).repeat(2,5) >>> m.where( … p := m.path(repeated_pattern).where(Criminal.age > 18).all_paths() … ).select(p, p.nodes[“index”], Person(p.nodes).name)

Referencing a node identifier in the where attached to the repeated pattern is allowed and refers to every repetition (so this example matches chains of Criminals who are all above age 18): >>> m.where( … p := m.path(Criminal.follows).where(Criminal.age > 18).repeat(2,5) … ).select(p, p.nodes[“index”], Person(p.nodes).name)

You may reference the source endpoint of a repeated pattern’s first repetition or the destination endpoint of its last repetition by introducing a node identifier before or after the repeated pattern, respectively: >>> src, dst = Criminal.ref(), Criminal.ref() >>> m.where( … p := m.path(src, m.path(Criminal.follows).repeat(2,5), dst).all_paths() … ).select(src, dst)

An explicit finite upper bound is required; unbounded repeats are not currently supported.

Parameters:

  • min_or_exact

    (int, default: None) - When it is the only argument, the exact number of repetitions. Otherwise the lower bound (paired with max).
  • max

    (int, default: None) - The inclusive upper bound on the number of repetitions.
  • min

    (int, default: None) - Keyword-only alternative to the positional lower bound. Cannot be combined with a positional min_or_exact.

Returns:

  • PathPattern - This pattern (self), to allow chaining.

Raises:

  • ValueError - If no argument is given; if a bound is negative; if min > max; if only one of min / max is supplied; or if both a positional min and the min= keyword are given.

Examples:

pattern.repeat(3) # exactly 3 times -> (3, 3)
pattern.repeat(1, 20) # between 1 and 20 -> (1, 20)
pattern.repeat(min=1, max=20) # keyword form -> (1, 20)
pattern.repeat(max=20) # ValueError: an explicit min is required
pattern.repeat(min=1) # ValueError: an explicit max is required

.all_paths()

PathPattern.all_paths() -> DerivedColumn

Enumerate every path matching this pattern.

Returns one PathTraversal per path matching the specified pattern. Enumeration uses walk semantics: nodes and edges may repeat, so cycles in the graph can result in arbitrarily many traversals.

Returns:

  • DerivedColumn - A column of PathTraversal values, one per matching path. Use it as a relation inside Model.where / Model.select, and read the per-path details (length, nodes, relationships, relationship_fields) via the PathTraversal properties.

Examples:

p = m.path(Person.follows.repeat(1, 3)).where(Person.name == "Alice").all_paths()
m.where(p).select(
p, p.length, p.nodes["index"], Person(p.nodes).name.alias("node")
).inspect()
# p length index node
# 0 6w6baylbPzQBFBq7X+77Ug 1 0 Alice
# 1 6w6baylbPzQBFBq7X+77Ug 1 1 Bob
# 2 IBUHvCZTYTKnBRjIey8suw 2 0 Alice
# 3 IBUHvCZTYTKnBRjIey8suw 2 1 Bob
# 4 IBUHvCZTYTKnBRjIey8suw 2 2 David

Each distinct p value identifies one path; rows sharing a p describe the nodes visited along it.

Notes:

Only the path’s src and dst unify with the enclosing scope. Filters on interior nodes must be attached with PathPattern.where on the pattern; a filter referencing interior nodes placed in an enclosing Model.where cannot be routed into the path and raises.

Performance: Enumerating all paths is worst-case exponential in the size of the graph and thus may be EXTREMELY EXPENSIVE! Try to make your path pattern as selective as possible, and run on smaller test data when building your model and query for path count first. Confirm that your graph is acyclic via Graph.is_acyclic. Unless you need variable repeats or enumeration of nodes and edges along the path, consider using regular PyRel to express your query instead of .all_paths()