Group the subgraphs found by shape, and draw them - #15
Merged
Merged
Conversation
Enumerating connected subgraphs is the first step of motif discovery.
This is the second. Until now the app reported {0, 1, 2} and {2, 3, 4}
as two answers without ever saying both are triangles, which is the
thing a motif actually is.
Shape.of finds a canonical form by brute force: every relabelling of the
subgraph's vertices is tried and the smallest adjacency bitstring kept.
Two subgraphs match when their canonical forms do. Industrial tools use
nauty, which earns its keep at the sizes they work at; a subgraph here
has at most a few vertices, so a few hundred relabellings cost
microseconds, need no library, and can be explained in a sentence.
Shape.classify groups them, most frequent first, and names the ones
people have names for: triangle, path, star, cycle, diamond, clique.
Checked by hand against the samples: the bowtie has exactly 2 triangles
among 6 connected triples, and cluster.txt contains exactly one K4.
One test was worthless when written. The invariance check -- that
relabelling a subgraph does not change its shape -- used a complete
subgraph, which is symmetric, so every relabelling gives the same answer
whether the form is canonical or not. It passed against an
implementation that kept an arbitrary relabelling instead of the
smallest. It now uses a triangle with a tail, and kills that mutation.
The results were a list of vertex sets. Grouped by shape they become a statement about the graph: six triangles and four paths, rather than ten sets of numbers. - ShapesPanel lists each shape drawn, named where people have a name for it, with how often it occurred. Vertices are dots rather than numbers, because which vertex is which is exactly what a shape ignores. - Choosing a shape picks its subgraphs out in the tree, which is the question the panel invites: where are they? - It shares the side slot with the history, on shortcut G, and choosing one puts the other away. - The status line now ends with how many distinct shapes were found. EsuSession classifies once and remembers, and can name the tree nodes having a given shape. ThemeTest passed on the new style classes without being touched, which is what it is for: the dark sheet had to gain matching rules or fail.
The shape drawings were wrong: a path and a triangle with a tail came out identical, and a cycle drew no edges at all. Shape.joins walked the pairs to find a bit position but never advanced the counter, so every pair read bit zero. Shapes with different edges therefore drew the same picture, and the panel was stating something false about the results. The method had no test. Its only caller is the drawing, the name test goes through degrees() instead, and every other test compares whole canonical forms, so nothing exercised it. It was found by looking at the running app. Three tests now cover it: a triangle joins all three of its pairs, every shape joins exactly as many pairs as it claims edges, and distinct shapes must produce distinct pictures. All three fail against the old code.
Ports Shape to TypeScript and puts the shapes beside the graph in the demo: a chip per shape, drawn with its count, and choosing one picks its subgraphs out in the tree. Choosing it again clears the choice. The Java classifier gained a fixed order among equal counts. Listing by insertion order would differ between the two implementations for no meaningful reason, and the fixtures compare the listings. java-golden.json now records, for each graph and size, the shapes found: name, edge count, how many, in order, and the edges each one draws. 30 entries. A TypeScript classifier that groups differently, names differently, orders differently or draws differently fails. Two mutations, caught by different tests, and neither test catches the other's bug: every pair reading the same bit -- the actual Java bug -- fails the DOM test, which counts the lines drawn against the edges the shape claims: star drew 6 edges, expected 3. the bit order reversed fails 11 equivalence cases. The edge count cannot see it, because reversing preserves how many bits are set. Three attempts were needed to get there. The first checked only that shapes drew differently, which passed against a default graph having a single shape. The second fixed the graph but still only checked distinctness, and three wrong drawings are still three different drawings.
Choosing a shape outlined its subgraphs in the tree, but they were still four text labels: 0123, 0124, 0234, 1234. Nothing showed what they look like, or that they look like each other. The shapes panel now draws them. Each subgraph appears as its own little graph, in the same style as the input graph -- vertices on a circle, numbered, joined where the graph joins them -- with its vertices written underneath. Identical shape, different vertices, which is the point of grouping them at all. Clicking one lights it up in the input graph, so it is clear where in the network that occurrence sits. Every instance is shown whatever step is on screen, because the shapes and their counts describe the finished search rather than how far the stepping has got. Selecting a shape at step 9 of 38 would otherwise show an empty grid: none of them has been reached yet.
The list had a fixed height chosen when a search found three shapes. A search finds two shapes at size three and around thirty-three at size six, so a fixed height is either mostly empty or cramped: at size four it showed five shapes in a box that fitted four, and scrolled. It now fits its contents up to eight rows and scrolls beyond that, leaving the rest of the panel to the drawings. The heading also says how many there are, since a group can hold thirty or more and the grid scrolls.
Brings the demo level with the desktop app: choosing a shape now draws every subgraph having it, each with its real vertex numbers, and clicking one shows it in the input graph. The drawings are numbered, unlike the shape thumbnails beside them, because the point of these is that the shape is the same and the vertices are not. Three tests, each killed by a matching mutation: drawing only the first instance, clicking a tile highlighting nothing, and asking for one shape's subgraphs but returning all of them. The count assertion also insists the chosen shape has more than one instance, since a single one would let the first of those pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Enumerating connected subgraphs is the first step of motif discovery. This is
the second.
The app reported
{0, 1, 2}and{2, 3, 4}as two answers without ever sayingboth are triangles — which is what a motif actually is. There was also nothing
to compare against a random network, because there were no shapes to count.
Both implementations get the feature: the desktop app on shortcut G, the
browser demo beside the graph.
Grouping
Shape.offinds a canonical form by brute force: every relabelling of asubgraph's vertices is tried and the smallest adjacency bitstring kept. Two
subgraphs match when their canonical forms do.
Industrial tools use
nauty, which earns its keep at the sizes they work at. Asubgraph here has at most a few vertices, so a few hundred relabellings cost
microseconds, need no C library, no JNI and no second port — and the method
fits in a sentence, which matters for a tool whose job is explaining.
Shapes are listed most frequent first, drawn, and named where people have a
name for them: triangle, path, star, cycle, diamond, clique.
Drawing what was found
A shape and a count still do not show you what the subgraphs look like, or that
they look like each other. Choosing a shape now draws every subgraph having it,
each keeping its real vertex numbers, in the same visual language as the input
graph. Identical shape, different vertices, which is the reason to group them.
Clicking one shows it in the input graph, so it is clear where in the network
that occurrence sits.
Every instance is shown whatever step is on screen: the shapes and their counts
describe the finished search, not how far the stepping has got. Selecting a
shape at step 9 of 38 would otherwise show an empty grid under a header saying
six.
Keeping the two implementations honest
java-golden.jsonnow records, per graph and size, the shapes found: name,edge count, how many, in what order, and the edges each one draws — 30 entries.
The Java classifier gained a fixed order among equal counts, since listing by
insertion order would differ between implementations for no useful reason.
What the tests learned
Four tests here were worthless as first written, and all four failed the same
way: the example chosen had too little variety to tell right from wrong.
relabelling gives the same answer whether the form is canonical or not. It
passed against an implementation keeping an arbitrary relabelling.
draws differently" was true of anything.
are still three different drawings.
once, so it now insists on more than one.
Two bugs are covered by different tests, neither catching the other's: every
pair reading the same bit fails the DOM check, which counts lines drawn against
the edges a shape claims (
star drew 6 edges, expected 3); the bit orderreversed fails 11 equivalence cases, which an edge count cannot see because
reversing preserves how many bits are set.
65 Java tests, 94 TypeScript.