backlog-qec 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- backlog_qec-0.5.0/.gitignore +16 -0
- backlog_qec-0.5.0/CHANGELOG.md +50 -0
- backlog_qec-0.5.0/CITATION.cff +25 -0
- backlog_qec-0.5.0/CONTRIBUTING.md +53 -0
- backlog_qec-0.5.0/LICENSE +21 -0
- backlog_qec-0.5.0/Makefile +101 -0
- backlog_qec-0.5.0/PKG-INFO +67 -0
- backlog_qec-0.5.0/README.fr.md +495 -0
- backlog_qec-0.5.0/README.md +467 -0
- backlog_qec-0.5.0/cpp/CMakeLists.txt +54 -0
- backlog_qec-0.5.0/cpp/README.md +76 -0
- backlog_qec-0.5.0/cpp/bindings/module.cpp +384 -0
- backlog_qec-0.5.0/cpp/include/backlog/decoder.hpp +316 -0
- backlog_qec-0.5.0/cpp/include/backlog/graph.hpp +86 -0
- backlog_qec-0.5.0/cpp/include/backlog/window.hpp +198 -0
- backlog_qec-0.5.0/cpp/src/decoder.cpp +834 -0
- backlog_qec-0.5.0/cpp/src/graph.cpp +132 -0
- backlog_qec-0.5.0/cpp/src/main.cpp +104 -0
- backlog_qec-0.5.0/cpp/src/reference.cpp +150 -0
- backlog_qec-0.5.0/cpp/src/window.cpp +832 -0
- backlog_qec-0.5.0/cpp/tests/test_decoder.cpp +664 -0
- backlog_qec-0.5.0/docs/decoder.md +462 -0
- backlog_qec-0.5.0/docs/pypi.md +42 -0
- backlog_qec-0.5.0/docs/related-work.md +133 -0
- backlog_qec-0.5.0/docs/roadmap.md +116 -0
- backlog_qec-0.5.0/pyproject.toml +42 -0
- backlog_qec-0.5.0/requirements.txt +5 -0
- backlog_qec-0.5.0/src/backlog/__init__.py +39 -0
- backlog_qec-0.5.0/src/backlog/decoder.py +332 -0
- backlog_qec-0.5.0/src/backlog/dem.py +283 -0
- backlog_qec-0.5.0/src/backlog/sinter_decoder.py +120 -0
- backlog_qec-0.5.0/tests/test_backlog.py +403 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.5.0 (2026-11-05)
|
|
4
|
+
|
|
5
|
+
- Correlated (two-pass) decoding in the whole-shot, sliding-window and
|
|
6
|
+
parallel-window decoders, exact against the reference decoder on the
|
|
7
|
+
reweighted graph. `correlated=True`, `alpha=`, `dem_correlations`,
|
|
8
|
+
`regularization_strength`, sinter decoder `backlog-uf-corr`.
|
|
9
|
+
- The regularized reweighting rule of Hernando de la Fuente et al.
|
|
10
|
+
(arXiv:2610.26785), and an exhaustive check of what it buys: every pair of
|
|
11
|
+
faults at d = 5 decoded by every decoder.
|
|
12
|
+
- `decompose_by_basis`: every error split into its X part and its Z part.
|
|
13
|
+
Correlated decoding needs it: on Stim's own decomposition it is worse than
|
|
14
|
+
plain decoding at d = 3, for PyMatching too (reported upstream as
|
|
15
|
+
[PyMatching#193](https://github.com/oscarhiggott/PyMatching/issues/193)).
|
|
16
|
+
- Parallel windows: a seam starts as soon as its two cores are done (no
|
|
17
|
+
barrier), PAUSE in spin loops, no spinning on hyperthreads, exceptions on
|
|
18
|
+
worker threads carried back to the caller. `make scaling` splits the
|
|
19
|
+
speedup into work inflation and idle time.
|
|
20
|
+
- Benchmarks: correlated accuracy and speed against PyMatching (same shots),
|
|
21
|
+
error suppression factor, fault enumeration, laptop scaling analysis.
|
|
22
|
+
- Quickstart notebook, wheels workflow for PyPI, citation file, contributing
|
|
23
|
+
notes, related-work survey, 26 mutants.
|
|
24
|
+
- Builds with Apple Clang and MSVC as well as GCC, and CI runs the C++ and
|
|
25
|
+
Python test suites on macOS and Windows on every push.
|
|
26
|
+
|
|
27
|
+
## 0.4.0 (2026-11-04)
|
|
28
|
+
|
|
29
|
+
- Parallel window decoding (core windows, then seams) on a thread pool, same
|
|
30
|
+
answer for any number of threads, clean under ThreadSanitizer.
|
|
31
|
+
- Scheduling model and projection of the cores needed for real time; real
|
|
32
|
+
speedup measured on 2 cores, then up to 8 threads (`make scaling`).
|
|
33
|
+
|
|
34
|
+
## 0.3.0 (2026-11-04)
|
|
35
|
+
|
|
36
|
+
- Sliding-window decoder for streaming, windows with the same structure
|
|
37
|
+
sharing one decoder; the backlog measured with 1 µs rounds.
|
|
38
|
+
|
|
39
|
+
## 0.2.0 (2026-11-04)
|
|
40
|
+
|
|
41
|
+
- Weighted, event-driven Union-Find (exact equivalent of synchronous rounds),
|
|
42
|
+
a reference decoder and differential fuzzing between them.
|
|
43
|
+
- Python package (pybind11), Stim error model conversion, sinter decoders.
|
|
44
|
+
- Accuracy gates and benchmarks against PyMatching, mutation testing.
|
|
45
|
+
|
|
46
|
+
## 0.1.0 (2026-11-04)
|
|
47
|
+
|
|
48
|
+
- Stim + PyMatching baseline and threshold plot, a first C++ Union-Find
|
|
49
|
+
decoder with a command-line tool, a 1D Union-Find core in VHDL checked bit
|
|
50
|
+
for bit, CI.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use backlog in your work, please cite it as below."
|
|
3
|
+
title: "backlog: a real-time Union-Find decoder for quantum error correction, from simulation to FPGA"
|
|
4
|
+
abstract: >-
|
|
5
|
+
An open-source weighted Union-Find decoder for the surface code, exact to a
|
|
6
|
+
synchronous reference, with correlated (two-pass) decoding and the
|
|
7
|
+
regularized reweighting rule, sliding and parallel window decoding for
|
|
8
|
+
streaming, Stim and sinter integration, and a VHDL implementation checked
|
|
9
|
+
bit for bit against the C++ model.
|
|
10
|
+
authors:
|
|
11
|
+
- family-names: "Dorélas"
|
|
12
|
+
given-names: "Jean-Cédrik"
|
|
13
|
+
type: software
|
|
14
|
+
license: MIT
|
|
15
|
+
version: "0.5.0"
|
|
16
|
+
date-released: "2026-11-05"
|
|
17
|
+
repository-code: "https://github.com/jcedrik/backlog"
|
|
18
|
+
keywords:
|
|
19
|
+
- quantum error correction
|
|
20
|
+
- surface code
|
|
21
|
+
- decoder
|
|
22
|
+
- union-find
|
|
23
|
+
- correlated decoding
|
|
24
|
+
- real-time decoding
|
|
25
|
+
- FPGA
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Contributing to backlog
|
|
2
|
+
|
|
3
|
+
Issues and pull requests are welcome: bug reports, new noise models or codes,
|
|
4
|
+
faster decoding, hardware work.
|
|
5
|
+
|
|
6
|
+
## Set up
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/jcedrik/backlog && cd backlog
|
|
10
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
11
|
+
make install # builds the C++ core, installs the package with test extras
|
|
12
|
+
make test # C++ fuzzing against the reference, pytest, CLI checks
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
You need a C++17 compiler, CMake 3.18+ and Python 3.10+. `make help` lists
|
|
16
|
+
every target.
|
|
17
|
+
|
|
18
|
+
## What a change needs to pass
|
|
19
|
+
|
|
20
|
+
The CI runs all of these on every push; run them locally first.
|
|
21
|
+
|
|
22
|
+
| Check | Command | What it guards |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| C++ tests | `make test-cpp` | the fast decoder equals the reference decoder edge for edge, on 75,000 random cases per fuzzer, including correlated decoding and both window decoders |
|
|
25
|
+
| Python tests | `make test-py` | accuracy gates against PyMatching on real Stim circuits, sinter integration, exhaustive fault checks |
|
|
26
|
+
| ThreadSanitizer | `make tsan` | no data race in the parallel window decoder |
|
|
27
|
+
| Mutation testing | `make mutants` | every injected bug (26 of them) makes the suite fail; a new feature should come with a mutant its tests kill |
|
|
28
|
+
| VHDL | `make hw-sim` | the hardware decoder agrees with the C++ model bit for bit |
|
|
29
|
+
|
|
30
|
+
The decoder has a specification: `ReferenceDecoder` (synchronous rounds,
|
|
31
|
+
written to be obviously correct). A change to the fast decoder must keep the
|
|
32
|
+
two identical; a change to the semantics changes the reference first.
|
|
33
|
+
|
|
34
|
+
## Benchmarks
|
|
35
|
+
|
|
36
|
+
Benchmark scripts write their CSVs and figures to `results/`. If a change
|
|
37
|
+
moves a published number, rerun the script that produced it and commit the
|
|
38
|
+
new results with the change, with the machine it ran on.
|
|
39
|
+
|
|
40
|
+
## Releasing (maintainer)
|
|
41
|
+
|
|
42
|
+
Wheels for Linux, macOS and Windows are built by `.github/workflows/wheels.yml`.
|
|
43
|
+
To publish to PyPI without storing a token, once:
|
|
44
|
+
|
|
45
|
+
1. on pypi.org, Account settings > Publishing > Add a new pending publisher:
|
|
46
|
+
project `backlog-qec`, owner `jcedrik`, repository `backlog`, workflow
|
|
47
|
+
`wheels.yml`, environment `pypi`;
|
|
48
|
+
2. on GitHub, Settings > Environments > New environment `pypi`.
|
|
49
|
+
|
|
50
|
+
Then a release is: bump the version in `pyproject.toml` and
|
|
51
|
+
`src/backlog/__init__.py`, add its entry to `CHANGELOG.md`, commit, then push a
|
|
52
|
+
signed tag (`git tag -s v0.5.0 -m "backlog 0.5.0" && git push origin v0.5.0`).
|
|
53
|
+
The PyPI page shows `docs/pypi.md`, so its links must be absolute.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jean-Cédrik Dorélas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
PY ?= python3
|
|
2
|
+
|
|
3
|
+
.PHONY: help deps install cpp test test-cpp test-py test-cli mutants tsan quick baseline plot \
|
|
4
|
+
compare plots streaming streaming-plot parallel parallel-plot scaling latency correlated \
|
|
5
|
+
correlated-speed correlated-plot faults faults-plot quickstart hw hw-sim all clean
|
|
6
|
+
|
|
7
|
+
help: ## list targets
|
|
8
|
+
@grep -E '^[a-z-]+:.*##' $(MAKEFILE_LIST) | sed -E 's/:.*## /\t/'
|
|
9
|
+
|
|
10
|
+
deps: ## install the Python dependencies of the experiments
|
|
11
|
+
$(PY) -m pip install -r requirements.txt
|
|
12
|
+
|
|
13
|
+
install: ## build the C++ core and install the backlog Python package (use a virtualenv)
|
|
14
|
+
$(PY) -m pip install ".[test]"
|
|
15
|
+
|
|
16
|
+
cpp: ## build the C++ command-line decoder and the C++ test suite
|
|
17
|
+
cmake -S cpp -B cpp/build -DCMAKE_BUILD_TYPE=Release
|
|
18
|
+
cmake --build cpp/build -j
|
|
19
|
+
|
|
20
|
+
test: test-cpp test-py test-cli ## all tests except the slow sweeps (run make install first)
|
|
21
|
+
|
|
22
|
+
test-cpp: cpp ## C++ unit tests + 75,000 fuzzed cases, fast decoder == reference decoder
|
|
23
|
+
cd cpp/build && ctest --output-on-failure
|
|
24
|
+
|
|
25
|
+
test-py: ## pytest: accuracy gates against PyMatching, sinter integration
|
|
26
|
+
$(PY) -m pytest -q
|
|
27
|
+
|
|
28
|
+
test-cli: cpp ## command-line decoder on graphs exported from Stim, gated against PyMatching
|
|
29
|
+
./scripts/run_tests.sh
|
|
30
|
+
|
|
31
|
+
mutants: ## mutation testing: each of 26 injected bugs must make the test suite fail (~10 min)
|
|
32
|
+
$(PY) scripts/mutation_test.py
|
|
33
|
+
|
|
34
|
+
tsan: ## C++ test suite under ThreadSanitizer: any data race fails the run (~5 min)
|
|
35
|
+
cmake -S cpp -B cpp/build-tsan -DCMAKE_BUILD_TYPE=Debug -DBACKLOG_TSAN=ON
|
|
36
|
+
cmake --build cpp/build-tsan -j --target backlog_tests
|
|
37
|
+
TSAN_OPTIONS=halt_on_error=1 ./cpp/build-tsan/backlog_tests
|
|
38
|
+
|
|
39
|
+
quick: ## tiny PyMatching threshold sweep into results/quick/ (smoke test, seconds)
|
|
40
|
+
cd python && $(PY) baseline_threshold.py --quick && $(PY) plot_threshold.py --results-dir ../results/quick
|
|
41
|
+
|
|
42
|
+
baseline: ## PyMatching-only threshold sweep (a few minutes), see results/threshold.png
|
|
43
|
+
cd python && $(PY) baseline_threshold.py --max-shots 300000 --max-errors 1000
|
|
44
|
+
|
|
45
|
+
plot: ## redraw results/threshold.png from results/threshold_data.csv
|
|
46
|
+
cd python && $(PY) plot_threshold.py
|
|
47
|
+
|
|
48
|
+
compare: ## sinter sweep, PyMatching vs weighted and unweighted UF (long, resumable)
|
|
49
|
+
cd python && $(PY) threshold_comparison.py collect && $(PY) threshold_comparison.py plot
|
|
50
|
+
|
|
51
|
+
plots: ## redraw the comparison figures from results/threshold_comparison.csv
|
|
52
|
+
cd python && $(PY) threshold_comparison.py plot
|
|
53
|
+
|
|
54
|
+
streaming: ## sliding windows: accuracy vs buffer, per-window latency, backlog (minutes, idle machine)
|
|
55
|
+
cd python && $(PY) streaming_benchmark.py accuracy && $(PY) streaming_benchmark.py latency \
|
|
56
|
+
&& $(PY) streaming_benchmark.py plot
|
|
57
|
+
|
|
58
|
+
streaming-plot: ## redraw results/streaming.png from the CSVs
|
|
59
|
+
cd python && $(PY) streaming_benchmark.py plot
|
|
60
|
+
|
|
61
|
+
parallel: ## parallel windows: accuracy, 1 vs 2 thread speedup, cores needed (minutes, idle machine)
|
|
62
|
+
cd python && $(PY) parallel_benchmark.py accuracy && $(PY) parallel_benchmark.py speedup \
|
|
63
|
+
&& $(PY) parallel_benchmark.py project && $(PY) parallel_benchmark.py plot
|
|
64
|
+
|
|
65
|
+
parallel-plot: ## redraw results/parallel.png from the saved data
|
|
66
|
+
cd python && $(PY) parallel_benchmark.py plot
|
|
67
|
+
|
|
68
|
+
scaling: ## real speedup with 1, 2, 4, ... threads up to every core here, into results/scaling/
|
|
69
|
+
cd python && $(PY) parallel_benchmark.py scaling
|
|
70
|
+
|
|
71
|
+
correlated: ## correlated decoding vs plain and vs PyMatching, same shots (~15 min on 2 cores)
|
|
72
|
+
$(PY) python/correlated_benchmark.py accuracy && $(PY) python/correlated_benchmark.py decomposition
|
|
73
|
+
|
|
74
|
+
correlated-speed: ## decoding time of plain and correlated decoders (idle machine)
|
|
75
|
+
$(PY) python/correlated_benchmark.py speed
|
|
76
|
+
|
|
77
|
+
correlated-plot: ## redraw results/correlated.png and print the Lambda table
|
|
78
|
+
$(PY) python/correlated_benchmark.py plot
|
|
79
|
+
|
|
80
|
+
faults: ## exhaustive fault enumeration, d = 3 and 5: which pairs each decoder gets wrong (~6 min)
|
|
81
|
+
$(PY) python/fault_distance.py
|
|
82
|
+
|
|
83
|
+
faults-plot: ## redraw results/fault_distance.png
|
|
84
|
+
$(PY) python/fault_distance.py --plot
|
|
85
|
+
|
|
86
|
+
quickstart: ## rebuild and run examples/quickstart.ipynb (needs nbformat, nbclient, ipykernel)
|
|
87
|
+
$(PY) examples/make_quickstart.py
|
|
88
|
+
|
|
89
|
+
latency: ## decoding time vs PyMatching, d = 3 to 13 (run on an idle machine)
|
|
90
|
+
cd python && $(PY) latency_benchmark.py
|
|
91
|
+
|
|
92
|
+
hw: ## VHDL: bit-exact simulation, synthesis, place and route
|
|
93
|
+
./hardware/run_hw.sh
|
|
94
|
+
|
|
95
|
+
hw-sim: ## VHDL: simulation only
|
|
96
|
+
./hardware/run_hw.sh sim
|
|
97
|
+
|
|
98
|
+
all: test tsan mutants quick hw-sim ## everything CI runs
|
|
99
|
+
|
|
100
|
+
clean: ## remove build outputs
|
|
101
|
+
rm -rf cpp/build cpp/build-tsan hardware/build cpp/testdata* results/quick build _skbuild
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: backlog-qec
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Real-time Union-Find decoder for quantum error correction: correlated, streaming, parallel, with Stim and sinter integration
|
|
5
|
+
Keywords: quantum error correction,decoder,union-find,correlated decoding,real-time decoding,stim,sinter,surface code,fpga
|
|
6
|
+
Author: Jean-Cédrik Dorélas
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: C++
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
14
|
+
Project-URL: Homepage, https://github.com/jcedrik/backlog
|
|
15
|
+
Project-URL: Documentation, https://github.com/jcedrik/backlog/blob/main/docs/decoder.md
|
|
16
|
+
Project-URL: Issues, https://github.com/jcedrik/backlog/issues
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: numpy>=1.24
|
|
19
|
+
Requires-Dist: stim>=1.14
|
|
20
|
+
Requires-Dist: sinter>=1.14
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
23
|
+
Requires-Dist: pymatching>=2.3; extra == "test"
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# backlog
|
|
27
|
+
|
|
28
|
+
**A real-time quantum error correction decoder: weighted Union-Find for the
|
|
29
|
+
surface code, with correlated decoding, streaming window decoders and Stim and
|
|
30
|
+
sinter integration.**
|
|
31
|
+
|
|
32
|
+
- As fast as PyMatching on one core, within 0 to 20% of its accuracy.
|
|
33
|
+
- Correlated (two-pass) decoding, with the regularized rule of
|
|
34
|
+
[arXiv:2610.26785](https://arxiv.org/abs/2610.26785): 32 to 67% fewer
|
|
35
|
+
logical errors than plain Union-Find at d = 7, within a few percent of
|
|
36
|
+
PyMatching's correlated matching, faster than it from d = 13.
|
|
37
|
+
- Sliding-window and parallel-window decoders for streaming.
|
|
38
|
+
- Tested edge for edge against a reference implementation, with exhaustive
|
|
39
|
+
fault enumeration, ThreadSanitizer and mutation testing.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install backlog-qec
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import backlog, stim
|
|
47
|
+
|
|
48
|
+
circuit = stim.Circuit.generated("surface_code:rotated_memory_z", distance=5, rounds=5,
|
|
49
|
+
after_clifford_depolarization=0.003)
|
|
50
|
+
dets, obs = circuit.compile_detector_sampler().sample(100_000, separate_observables=True)
|
|
51
|
+
|
|
52
|
+
decoder = backlog.UnionFindDecoder.from_circuit(circuit, correlated=True)
|
|
53
|
+
print("logical error rate:", (decoder.decode_batch(dets) != obs).any(axis=1).mean())
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
As sinter decoders: `backlog-uf`, `backlog-uf-corr` (pass
|
|
57
|
+
`custom_decoders=backlog.sinter_decoders()`).
|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
Results, how it works and how to reproduce everything:
|
|
62
|
+
[github.com/jcedrik/backlog](https://github.com/jcedrik/backlog) ·
|
|
63
|
+
[quickstart notebook](https://github.com/jcedrik/backlog/blob/main/examples/quickstart.ipynb) ·
|
|
64
|
+
[how the decoder works](https://github.com/jcedrik/backlog/blob/main/docs/decoder.md) ·
|
|
65
|
+
[state of the art](https://github.com/jcedrik/backlog/blob/main/docs/related-work.md)
|
|
66
|
+
|
|
67
|
+
MIT license. To cite: [CITATION.cff](https://github.com/jcedrik/backlog/blob/main/CITATION.cff).
|