Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
Co-authored-by: Eric Peterson <[email protected]>
Co-authored-by: Peter Karalekas <[email protected]>
  • Loading branch information
ecpeterson and karalekas committed Oct 27, 2022
0 parents commit 7823b02
Show file tree
Hide file tree
Showing 33 changed files with 6,540 additions and 0 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: main

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out anatevka
uses: actions/checkout@v2
with:
path: anatevka
- name: Initialize Lisp
run: |
sudo apt install sbcl
curl -o /tmp/quicklisp.lisp "http://beta.quicklisp.org/quicklisp.lisp"
sbcl --noinform --non-interactive \
--load /tmp/quicklisp.lisp \
--eval '(quicklisp-quickstart:install)'
WD=$(pwd | xargs dirname)
echo >> ~/.sbclrc
echo '#-quicklisp(let ((i(merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname))))(when(probe-file i)(load i)))' >> ~/.sbclrc
echo "#+quicklisp(push \"${WD}/\" ql:*local-project-directories*)" >> ~/.sbclrc
rm -f /tmp/quicklisp.lisp
- name: Run the test suite
run: |
# run the tests via the Makefile
cd anatevka
sbcl --version
sbcl --noinform --non-interactive --eval '(ql:quickload "anatevka-tests")'
make test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.fasl
.DS_Store
*~
7 changes: 7 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Copyright © 2022 Eric Peterson, Peter Karalekas, and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
61 changes: 61 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
SBCL_BIN=sbcl
SBCL_WORKSPACE?=2048
SBCL_OPTIONS=--noinform --no-userinit --no-sysinit --non-interactive
SBCL=$(SBCL_BIN) --dynamic-space-size $(SBCL_WORKSPACE) $(SBCL_OPTIONS)

# tell me where Quicklisp is
ifeq ($(HOME), /github/home)
# when running on GitHub Actions, use Docker filesystem location
QUICKLISP_HOME=/root/quicklisp
else
QUICKLISP_HOME=$(HOME)/quicklisp
endif
QUICKLISP_SETUP=$(QUICKLISP_HOME)/setup.lisp

# tell me where local projects are
ifeq ($(HOME), /github/home)
# when running on GitHub Actions, use Docker filesystem location
QUICKLISP_PROJECTS=/src
else
QUICKLISP_PROJECTS=../
endif

QUICKLISP=$(SBCL) --load $(QUICKLISP_SETUP) \
--eval '(push (truename ".") asdf:*central-registry*)' \
--eval "(push (truename \"$(QUICKLISP_PROJECTS)\") ql:*local-project-directories*)"

.PHONY: test
test:
$(QUICKLISP) \
--eval "(ql:quickload :anatevka-tests)" \
--eval "(asdf:test-system :anatevka)"

###
### clean targets, borrowed from QVM
###

# Clean the executables
clean:
rm -f qvm qvm-ng build-output.log system-index.txt

# Clean the Lisp cache, reindex local projects.
clean-cache:
@echo "Deleting $(LISP_CACHE)"
$(QUICKLISP) \
--eval "(ql:register-local-projects)"
rm -rf "$(LISP_CACHE)"

clean-qvm-cache:
@echo "Deleting $(QVM_LISP_CACHE)"
$(QUICKLISP) \
--eval "(ql:register-local-projects)"
rm -rf $(QVM_LISP_CACHE)

clean-quicklisp:
@echo "Cleaning up old projects in Quicklisp"
$(QUICKLISP) \
--eval '(ql-dist:clean (ql-dist:dist "quicklisp"))'

cleanall: clean clean-cache clean-quicklisp
@echo "All cleaned and reindexed."

123 changes: 123 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# `anatevka`

`anatevka` is a Common Lisp package which houses [a distributed variant of Edmonds's blossom algorithm](https://arxiv.org/abs/2210.14277) for producing minimum-weight perfect matchings, written in [`aether`](https://github.com/dtqec/aether)'s application layer.

## Overview

`anatevka` contains an executable description of a distributed solver for minimum-weight perfect matchings on graphs.
The solver is written on top of [`aether`](https://github.com/dtqec/aether), so that one can simulate the algorithm running on large networks and infer the performance characteristics of such scenarios.
The package is also implemented _extensibly_, so as to allow application-specific behaviors to be written into packages derived from this one.

The original motivation for the development of this package was [Fowler's program](https://arxiv.org/abs/1307.1740) for using a distributed solver for minimum-weight perfect matching to perform quantum error correction.
This package provides the first implementation of the core distributed solver, incompletely described by Fowler.

## Installation

`anatevka` is a Lisp package which relies on other Lisp packages. You'll need to:

1. Install a Lisp environment.
[Some convenient instructions](https://github.com/quil-lang/qvm/blob/master/doc/lisp-setup.md) for this can be found as part of the QVM Lisp package.
2. This also depends on [`aether`](https://github.com/dtqec/aether), another Eigenware package.
Install this software somewhere locally, where ASDF can find it, perhaps using [QuickLisp](http://quicklisp.org/).

## Example

In this section we consider an example instantiation of the solver inside of an `aether` simulation.
To abbreviate the code examples, we assume we have imported the `anatevka` package.

The behavior of the solver is guided by three classes:

1. A `dryad` class which implements the interface in `dryad-api.lisp`.
The `dryad` is responsible for creating the worker nodes which embody graph vertices and managing edge discovery.
We provide an example implementation in `dryad.lisp` for a centralized `dryad` managing a fully-connected, weighted graph.
2. A `blossom-node` class which enacts the individual steps in the blossom algorithm.
We provide a stock implementation in `node.lisp`; users need only provide a subclass if they want to deviate from the standard behavior of the algorithm.
3. An `id` class which is used to uniquely tag the vertices in the graph and from which the edge weight between any two vertices can be computed via `anatevka::vertex-vertex-distance`.
The repository provides no such stock class; we will implement a version below with static edge weights.

In our example, we will use the stock `dryad` and `blossom-node` implementations, which leaves only the `id` class to define.
The following definition provides eight valid `demo-id` instances and a function which computes the edge weights between them:

```lisp
(defstruct demo-id
"A wrapper for a vertex ID used in the Mathematica blossom demo."
(value nil :type (integer 1 8)))
(defmethod anatevka::vertex-vertex-distance ((id-v demo-id) (id-w demo-id))
(let ((v (demo-id-value id-v))
(w (demo-id-value id-w)))
;; index into the following weighted adjacency matrix
(aref #2A(( 0 40 52 50 46 70 36 46)
(40 0 34 54 28 64 20 6)
(52 34 0 28 34 24 2 30)
(50 54 28 0 42 18 36 8)
(46 28 34 42 0 14 80 22)
(70 64 24 18 14 0 22 64)
(36 20 2 36 80 22 0 80)
(46 6 30 8 22 64 80 0))
(1- v) (1- w))))
```

**Note:** Given the example adjacency matrix provided, one possible minumum-weight perfect matching consists of the pairs (1, 2), (3, 7), (4, 8), and (5, 6), which altogether has weight 64.

Having established the class which carries the graph definition, we wrap a solver in a simulation and invoke the simulation to extract a minimum-weight perfect matching:

```lisp
(let* ((simulation (make-simulation))
;; aether requires us to bind `*local-courier*' before spawning processes.
(*local-courier* (make-courier :processing-clock-rate 300))
;; The edges discovered by the algorithm will be announced on this address.
(match-address (register))
;; This process manages graph discovery.
(dryad (spawn-process 'dryad
:process-clock-rate 20
:debug? t
:match-address match-address)))
;; Set up the core simulation components: the network host and the dryad.
(simulation-add-event simulation
(make-event :callback *local-courier* :time 0))
(simulation-add-event simulation (make-event :callback dryad :time 0))
;; Prime the dryad with messages to spawn workers for the eight vertices.
(loop :for j :from 1 :to 8
:for id := (make-demo-id :value j)
:do (send-message (process-public-address dryad)
(anatevka::make-message-sow :id id)))
;; Run simulation until maximally matched (i.e., until the dryad terminates).
(simulation-run simulation :canary (canary-process dryad))
;; Read out the match edges from the `match-address' mailbox.
(labels ((drain-match-address (&optional acc)
(receive-message (match-address message)
(message-reap
(drain-match-address (list* (message-reap-ids message) acc)))
(otherwise
acc))))
;; Calculate the weight of the matching.
(loop :for (left right) :in (drain-match-address)
:do (format t "~d --~02d-- ~d~%"
(demo-id-value left)
(vertex-vertex-distance left right)
(demo-id-value right))
:sum (anatevka::vertex-vertex-distance left right))))
```

which prints

```
8 -- 8-- 4
7 -- 2-- 3
6 --14-- 5
2 --40-- 1
```

and emits the return value `64`.

## License

`anatevka` is made available under the MIT license.
See `LICENSE.md` in the source tree for more information.

## See also

+ [ArXiv preprint](https://arxiv.org/abs/2210.14277)
+ [GitHub repository](https://github.com/dtqec/anatevka)
1 change: 1 addition & 0 deletions VERSION.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"1.0.0"
27 changes: 27 additions & 0 deletions anatevka-tests.asd
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
;;;; anatevka-tests.asd

(asdf:defsystem #:anatevka-tests
:description "Regression tests for Anatevka."
:author "Eric Peterson <[email protected]>, Peter Karalekas <[email protected]>"
:depends-on (#:anatevka
#:fiasco
#:uiop
#:closer-mop
)
:perform (asdf:test-op (o s)
(uiop:symbol-call ':anatevka-tests
'#:run-anatevka-tests))
:pathname "tests/"
:serial t
:components ((:file "package")
(:file "suite")
(:file "node")
(:module "operations"
:serial t
:components ((:file "graft")
(:file "augment")
(:file "expand")
(:file "contract")
(:file "multireweight")
(:file "reweight")))
(:file "blossom")))
34 changes: 34 additions & 0 deletions anatevka.asd
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
;;;; anatevka.asd
;;;;
;;;; Author: Eric Peterson, Peter Karalekas

(asdf:defsystem #:anatevka
:description "A distributed blossom algorithm for minimum-weight perfect matching."
:author "Eric Peterson <[email protected]>, Peter Karalekas <[email protected]>"
:version (:read-file-form "VERSION.txt")
:pathname "src/"
:depends-on (#:alexandria
(:version #:aether "1.1.0")
)
:in-order-to ((asdf:test-op (asdf:test-op #:anatevka-tests)))
:around-compile (lambda (compile)
(let (#+sbcl(sb-ext:*derive-function-types* t))
(funcall compile)))
:serial t
:components ((:file "package")
(:file "utilities")
(:file "logger")
(:file "dryad-api")
(:file "node")
(:file "supervisor")
(:file "lock")
(:module "operations"
:serial t
:components ((:file "scan")
(:file "graft")
(:file "augment")
(:file "expand")
(:file "contract")
(:file "multireweight")
(:file "reweight")))
(:file "dryad")))
43 changes: 43 additions & 0 deletions src/dryad-api.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
;;;; dryad-api.lisp
;;;;
;;;; Specifies the message types used to communicate with the DRYAD.
;;;; See dryad.lisp and matchmaker.lisp for information on the participants.

(in-package #:anatevka)

;;; messages between the dryad and the external world

(defstruct (message-sow (:include message))
"Instructs a `DRYAD' to inject a new vertex with the indicated id."
(id nil :type t))

(defstruct (message-reap (:include message))
"Reported by a `DRYAD' to its `MATCH-ADDRESS' with a pair of IDs that participate in the calculated matching."
(ids nil :type list))

;;; messages between the dryad and its managed blossoms

(defstruct (message-discover (:include message))
"Sent from a blossom process to a `DRYAD' to query for a list of other blossom processes to which it should send PINGs."
(address nil :type address)
(id nil :type t)
(internal-weight nil :type real) ; NOTE: a little surprised that this isn't (REAL 0)
(repeat? nil :type boolean))

(defstruct (message-discovery (:include message))
"The response to a DISCOVER message.
CHANNELS-TO-TRY: The addresses to send PINGs to."
(channels-to-try nil :type list))

(defstruct (message-wilt (:include message))
"An instruction to a `BLOSSOM-NODE' to cease operation as a process.")

(defstruct (message-sprout (:include message))
"Whenever a vertex participates in an augmentation, we are guaranteed that it has been assigned a match (possibly after any parent blossom undergoes expansion). Upon augmentation, it sends this message to the DRYAD to indicate that it no longer need consider this vertex to be \"live\"."
(address nil :type address))

;; NOTE: This message is essentially unused in the reference implementation, but it can be useful when implementing an online solver.
(defstruct (message-wilting (:include message))
"When a `BLOSSOM-NODE' wilts, it notifies its parent `DRYAD' to remove it from consideration."
(address nil :type address))
Loading

0 comments on commit 7823b02

Please sign in to comment.