Today is all about creating a decent algorithm for pattern-matching in a graph. Last time we found a decent method to tackle the problem (I think), but we are still pretty far from having a workable solution.

There was one consistency rule that bothered me a little, namely rule #3. Don't worry, I'll just copy-paste it below so there is no need to open a new tab in your browser. (I like to pretend there are people reading this :P)

Rule: |
Explanation: |
Visual Representation: |

`If`
`[A]Â TypedRelationInstance and`
`[A] [B]Â and`
`not [B] RelationTypeÂ and`
`[C] RelationType`
`then it must be that`
`[A] [C]` |
If there is a node [A] that is a TypedRelationInstance that has at least one other relation (to some normal node [B]) then that node must also have a relation to a node that defines the relation type. |

This rule is a little bit weird because we could interpret it in two ways:

- Blind variable matching: Find all combinations of
`[A]`

,`[B]`

and`[C]`

such that the if-pattern is valid and for every match enforce that`[A]`

has a connection to`[C]`

. - Following the arrows: Find all combinations of
`[A]`

,`[B]`

such thatÂ`[A]Â TypedRelationInstance andÂ [A] [B]Â andÂ not [B] RelationType`

and for every match enforce that`[A]`

has a connection to some`[C]`

such that`[C]`

Â Â`RelationType`

.

We tried to write it down using interpretation #2, but interpretation #1 is way easier and it also seems a bit more logical. To rewrite this rule using blind variable matching we need to modify it so the rules are doing what they are meant to do.Â Keep in mind that blind variable matching *only* looks at the variables used in the if-part.

Rule: |
Explanation: |
Visual Representation: |

`If`
`[A]Â TypedRelationInstance and`
`[A] [B]Â and`
`then it must be that`
`[A] [C]` |
If there is a node [A] that is a TypedRelationInstance that has at least one other relation then that node must have another relation to some other node. | |

`If`
`[A]Â TypedRelationInstance and`
`[A] [B]Â and`
`[A] [C]Â and`
`not [B] RelationType`
`then it must be that`
`[C] RelationType` |
If there is a node [A] that is aÂ TypedRelationInstance and has two other relations to node [B] and [C], and [B] is not a RelationType then [C] must be a RelationType. | |

`If`
`[A]Â TypedRelationInstance and`
`[A] [B]Â and`
`[A] [C]Â and`
`[B] RelationType`
`then it may not be that`
`[C]Â RelationType` |
If there is a node [A] that is aÂ TypedRelationInstance and has two other relations to node [B] and [C], and [B] is a RelationType then [C] may not be a RelationType. |

Yeah! That is way better! This also helps in building the cache! Hmm, what about unconnected patterns such asÂ "`If [A] [B] and [C] [D] then it must be that [E] [F]`

"? I couldn't care less about the `[E] [F]`

part, but that if-part is a bit troublesome. If the if-part is unconnected then a *lot* of matches may become possible. Hrmmm... there are a lot of ifs and buts here, but you know what? Screw it! Lets just force that the if-patterns must be weakly connected. It seems reasonable for most scenario's and we can always extend it to also support unconnected patterns sometime in the future. O wait! What about the 'not' relations? How do we interpret those for connectivity? I almost forgot about those! Simple not-operations are no issue at all, but as soon as we create a not-operation towards complex sub-patterns we'll be in a whole bunch of trouble:

Computing not-relations is not the issue, caching pattern-match results for not-relations is. Basically we have two options here. We can take the lazy-mans approach and say that we don't allow complex sub-patterns or we'll have to create a definition for sub-patterns which is usable for our algorithm. It would be the right thing to do, but it's just soooo much work :(. Ugh, okay let's at least try to support complex sub-patterns. |

- is the set of vertices (nodes).
- is a set of ordered pairs of vertices (edges/arrows).

- is a set of 'constant' vertices such that .
- is a set of vertices that our algorithm will interpret as variable.
- is a set of ordered pairs of vertices such that .
- is a set of ordered pairs of vertices such thatÂ that or algorithm with interpret as a 'not'-relation.

- For all there may not exist some other such that
- For all there may not exist some such that

- is the set of vertices such thatÂ . The set is constructed as: for every and for every
- is a set of ordered pairs of vertices that tells the algorithm what edges must exist. The set is constructed as: for every .
- is a set of ordered pairs of vertices that tells the algorithm what edges may not exist.Â The set is constructed as: for every .
- The function looks up the actual node for such thatÂ if andÂ if .

- for all it's true thatÂ
- for all it's true thatÂ

Now we'll have to detect sub-patterns, but first we need to define these so called sub-patterns. My body is not yet ready to pump out another boring sciency definition so let's use words and pictures instead. Who doesn't like pictures? Well I suppose not all pictures are fun, but I'll try to prevent creating pictures such as those from goatse.cx. For those not familiar with goatse.cx, it was a picture like the one on the right, just a little less flattering. Ahhh, you've gotta love the internet.

Ow my, I've lost focus for a bit. Right, right... "sub-patterns". A sub-pattern is a 'disconnected' part of a pattern (if seen as an undirected graph). If some part of the pattern is completely disconnected then we are talking about "permutation components". Wait! Pictures! We need pictures!

Lets create a pattern that may be bit weird, but valid according to our definition:

When we look at this pattern, we can clearly see three weakÂ components. We could separate it into three sub-patterns and end up with:

We call these "*permutation components*" because the matches we find for these three sub-patterns will help in creating permutations for the 6 variables such that we can create matches for the pattern. Unfortunately we can't just blindly create permutations of all matches we find for the three sub-patterns, because of the uniqueness-property of nodes. For example, we cant use a match from permutation component #1 where `[A]`

=`Alex`

.

There are actually more sub-patterns in this example if we take a better look at the not-relations. If we pretend that the not-relations do not exist we see more sub-patterns whom we are going to call "*filter components*":

These filter components are named as such because they help in filtering out possible matches. So let's take a look at how our algorithm should find all possible matches.

- For every filter component create a collection of variable-instantiations such that the sub-pattern for the filter-component is a match.
- For every permutation-component create valid permutations from the filter components but filter out matches if it isn't valid according to the not-relations.
- Create valid permutations from the permutation components to get all matches for the pattern.

Although I'm talking about "create a collection", there is often no need to actually create a new collection in memory. Most collections will be simple wrapper-collections of existing data-structures so there is probably no need to worry about memory-usage. This workflow is still overly simplified, but we're closer at creating an algorithm for pattern matching then when we started typing today. Yay! It's been a good day!

Leave a Reply