Quantomatic

Quantomatic is a diagrammatic proof assistant, meaning it provides machine-support for reasoning with diagrammatic languages (check out some of our papers). It allows users to draw diagrams and build up proofs using diagrammatic rewrite rules. It's easier to show you what that means than to tell you, so download it and try it out!

Download JAR (all platforms) GitHub Project »

Download

You can choose between:

Quantomatic requires java to run, which can be downloaded here, or found in your package manager.

Building from source

If you wish to download the source code directly, you will need:

You will then need to clone Quantomatic and build/run the frontend using sbt:

git clone git://github.com/Quantomatic/quantomatic.git -b integration
cd ./scala
sbt run

Note this assumes you want the latest development version. To get source for the latest stable version, remove -b integration from the git clone command above. You will see a number of options once sbt run is complete. Choose QuantoDerive to run the main GUI.

Sample projects

The Using Quantomatic section below gives instructions for getting it up and running with a sample project. This section provides additional sample projects to get you started.

Stabilizer ZX-calculus project

This contains all of the Stabilizer ZX-calculus axioms and some useful theorems and simplification procedures. It also includes sample graphs and derivations.

Download zip Clone from GitHub »

Bialgebra project

This project contains the axioms for a (variable-arity) commutative bialgebra and a strongly-normalising simplification procedure.

Download zip Clone from GitHub »

Using Quantomatic

To start experimenting with Quantomatic, download the sample project below and unzip it somewhere on your computer.

Download sample project zip

Once you have downloaded the sample project, open it in Quantomatic with File > Open Project... by selecting (or navigating to) the directory in the file browser and clicking Open. At this point, you should be seeing something like this:


You can change the font size etc. by going to the Window menu and choosing Increase UI scaling or Decrease UI scaling accordingly.

Graph editing

Expand the graphs folder on the left and double-click sample.qgraph. This will open the graph editor:


You can interact with this as you would expect: Drag nodes around, copy and paste, use the = and - keys to zoom in and out, etc.. Double-click nodes (or edges) to edit data. One handy trick is to let Quantomatic do some automatic layout for you. Notice how there are a bunch of nodes clustered up in a particularly unhelpful way in the above graph? Try selecting them and holding down the R key to relax the node positions:


That's better! Next, have a look at the tools we have available:


From left-to-right, these are: Select, Add Vertex, Add Boundary, Add Edge, and Edit !-Boxes. We have already been using the Select tool, so click on the Add Vertex tool. Notice now the vertex types at the bottom of the screen is now enabled. Pick a vertex type:


Since the sample project is set up for the ZX-calculus, the available types are well... Z and X. We'll ignore the <wire> type for now. Once a type is selected, clicking on the screen will add vertices of that type.

Similar to the vertex tool is the boundary tool. These create dummy vertices which serve as free edges, or boundaries to a graph. These are important for creating rules. Note that these don't become proper inputs/outputs until we connect some edges to them. To do that, try out the edge tool:


Edges are created by clicking and dragging from one vertex to another. They can either be directed or undirected, which is set by the checkbox in the lower-right corner. Some theories can have multiple types of edges, which you can pick from the Edge Type drop-down, but the ZX-calculus only allows one edge type, so no need to mess with it.

The tool that needs the most explanation is the Edit !-Box tool. !-boxes (or "bang-boxes") allow you to mark sub-graphs that can be repeated any number of times. With the !-box tool selected, click and drag around some vertices to create a new !-box:


To add are remove nodes from a !-box, click and drag from upper-left corner of the !-box to the thing you want to add/remove. This also works for other !-boxes. To nest one !-box inside another, click and drag from the parent's corner to the child's:


Just like with nodes, repeating the process will remove the child !-box from the parent. QuantoDerive tries its best to only draw !-boxes around nodes that are actually inside that !-box, but sometimes things can go wrong. For example, if a vertex ends up between two vertices that are both in a !-box bx0, it might appear to be in bx0 as well. This can get confusing, so QuantoDerive tries to help you out:


The node v3 above is not actually in bx0, so QuantoDerive is giving you a (not-so-subtle) hint to move it somewhere else.

Rule editing

Rule editing is similar, except now we work with two graphs side-by-side:


Here the blue outline shows which graph has the keyboard focus, e.g. for copy-and-paste and R (relaxing). To make a valid rule, the LHS and RHS should have identical boundaries and !-boxes. Not only should the numbers match, but also the labels. For example, in the rule above, both sides have two boundary vertices, labelled b0 and b1.

Rules are created by selecting File > New Axiom. Soon, you will also be able to create rules from derivations, i.e. theorems.

Starting a derivation

To start a new derivation, open a graph that will serve as a starting point, and select Derive > Start derivation.


This opens up the derivation editor. Nothing is happening yet because no rules have been added. Click the + button in the Rewrite pane to add some rules.


I usually add all of them because...why not? You can also search through your rules using the "Filter" bar at the top. As soon as you add some rules, QuantoDerive will start eagerly searching for matchings:


The more cores your computer has, the more fun this part is! Currently, QuantoDerive will find up to 50 matchings for each rule, which you can browse through using the left and right arrow buttons. Also note that selecting subgraphs on the left will refine the search. One you find the rewrite you want, click Apply. This will create a proof step, which you can view at any time by clicking on it in the history view:


The piece of graph that was removed is highlighted on the LHS, while the new piece of graph that was added is shown on the RHS. Rewriting always takes place at the end of a chain of rewrite steps, which is called a proof head. The derivation editor is designed so that you can try rules out, and if they don't work, you can back up and try other things without ever losing your work. This means that histories can branch and have multiple heads. To create a new head, click a rewrite step somewhere in the path, and click the New Proof Head button in the toolbar:


Using the simplifier

When you are tired of applying rewrites one at a time, click on a proof head, and switch over the the simplify panel. Notice there isn't a whole lot to see. That's because we haven't loaded any simplification procedures, aka simprocs yet. These are little pieces of code that define a simplification strategy. On the left, open the simprocs folder and double-click basic-simp.py:


This one's as basic as they come. It loads a some rules from the project into a list called simps, then it will try to apply rules from simps until either it runs out of rules to apply, or you stop it. That's what REDUCE(simps) does. Click the button in the toolbar to execute this code, which will register the procedure basic-simp with QuantoDerive. Now, go back to your derivation and click the refresh button:


There it is, ready to go. Select basic-simp and click play:


Well, that didn't get us very far. It is basic, after all. Go back to simprocs and open rotate-simp.py and execute it. Now, back in our derivation, click the refresh button again and run rotate-simp.


That's better! Of course, your results may vary depending on what nodes you added earlier.

So, that's pretty much all there is to it. Play around with creating some new rules or even a new project.

The simproc API

Simplification procedures are implemented in Python, via a built-in interpreter in Quantomatic based in jython. The following is a (non-exhaustive) list of python bindings provided by Quantomatic, via the quanto.util.Scripting object. The API is a work-in-progress, and subject to change and/or expansion as more features from the simplifier are exposed. If you wish to get the most up-to-date look at what bindings are available, have a look at the soure code.

Generally, a simproc file will construct an object extending the class quanto.rewrite.Simproc, then register it with a name. When this simproc is called from the GUI, it will call the method simp(), which takes a graph as its input and returns a lazy list of rewrite steps. These steps consist of a new graph, as well as the matched rule which produced it. First some basic functions:

    load_rule(path)
      load a rule located in 'path'. Note this path is relative to the
      project root, and the .qrule extension should be omitted.

    load_rules(paths)
      same, but takes a list of paths and returns a list of rules. In
      other words, it is shorthand for 'map(load_rule, paths)'.

    register_simproc(name, simproc)
      takes a simproc object and registered with the given name. This
      makes it available in the GUI simplifier.

    load_graph(path)
      load a graph located in 'path'. Again this path is relative to the
      project root, and the .qgraph extension should be omitted. This is
      rarely used in simprocs, but useful for testing or batch processing.

    save_graph(graph, path)
      save a graph to the given 'path'. Paths follow the same rules as
      above.
  

The easiest way to construct a simproc object is to chain together some of the simpler, built-in simprocs into more complicated ones. Here's a list of the current built-in simprocs:

    EMPTY
      a trivial simproc which does nothing

    REWRITE(rules)
      takes a rule or list of rules and applies the first rule that
      matches.

    REWRITE_METRIC(rules, f)
      takes a rule or list of rules and a function f from graphs to
      integers. It will apply the first rule that matches *and*
      (strictly) reduces the value of f.

    REWRITE_WEAK_METRIC(rules, f)
      same as above, except the rewrite only needs to be non-increasing
      on f.

    REWRITE_TARGETED(rule, v, f)
      takes a rule, the name of a vertex 'v' within the rule (as a string),
      and a targeting function 'f' from graphs to a vertex name. It will
      apply the rule on a graph 'g' by first attempting to match 'v' on
      'f(g)', then matching the rest.
  

Simprocs are combined via two combinators:

    s >> t
      apply simproc 's' until it produces no new rewrites, then apply 't'

    REPEAT(s)
      apply simproc 's' until it yields no new rewrites, then repeat until
      applying 's' yields no rewrites at all.
  

It is so comment to wrap REWRITE_XXXX in REPEAT that certain shorthands have been introduced:

    REDUCE(rules) := REPEAT(REWRITE(rules))

    REDUCE_METRIC(rules, f) := REPEAT(REWRITE_METRIC(rules, f))

    REDUCE_WEAK_METRIC(rules, f) := REPEAT(REWRITE_METRIC(rules, f))

    REDUCE_TARGETED(rule, v, f) := REPEAT(REWRITE_TARGETED(rule, v, f))
  

That's basically it. Since the interpreter is jython under the hood, simprocs can access all of the data associated with graphs, vertices, etc. Here are a few methods of Quantomatic's Graph type which are handy for writing metrics or targeting functions:

    graph.typeOf(v)
      returns a string containing the type of the vertex, e.g. 'Z' or 'X'

    graph.isBoundary(v)
      returns true if the given vertex is a boundary

    graph.isAdjacentToType(v, str)
      returns true if the vertex is adjacent to a vertex of the given type

    graph.isAdjacentToBoundary(v)
      returns true if the given vertex is touching the boundary.
  

A couple of the built-in methods also have wrapper functions that enable more compact and 'pythonic' code to be written:

    verts(graph)
      return a list of vertices in the graph, in a python-iterable format.

    vertex_angle_is(graph, v, str)
      returns true if the angle of v is equal to the given string value.
  

You can see most of these functions in action in the simproc rotate-simp.py:

    simps0 = load_rules([
      "axioms/red_copy", "axioms/green_copy",
      "axioms/red_sp", "axioms/green_sp",
      "axioms/hopf",
      "axioms/red_scalar", "axioms/green_scalar",
      "axioms/red_loop", "axioms/green_loop"])

    simps = simps0 + load_rules(["axioms/green_id", "axioms/red_id"])

    green_id_inv = load_rule("axioms/green_id").inverse()
    red_id_inv = load_rule("axioms/red_id").inverse()
    rotate = load_rule("theorems/rotate_targeted")

    def num_boundary_X(g):
      return len([v for v in verts(g)
        if g.isBoundary(v) and g.isAdjacentToType(v, 'X')])

    def next_rotation_Z(g):
      vs = [(g.arity(v),v) for v in verts(g)
        if g.typeOf(v) == 'Z' and
           vertex_angle_is(g, v, '0') and
           not g.isAdjacentToBoundary(v)]
      if (len(vs) == 0): return None
      else: return min(vs)[1]


    simproc = (
      REDUCE(simps) >>
      REDUCE_METRIC(green_id_inv, num_boundary_X) >>
      REPEAT(
        REDUCE_TARGETED(rotate, "v10", next_rotation_Z) >>
        REDUCE(simps0)
      ) >>
      REDUCE(simps)
    )

    register_simproc("rotate-simp", simproc)
  

Copyright © 2018. Quantomatic project.