├── tierkreis
├── tests
│ ├── __init__.py
│ ├── py.typed
│ ├── cli
│ │ ├── __init__.py
│ │ ├── data
│ │ │ ├── n
│ │ │ ├── input1
│ │ │ ├── input2
│ │ │ ├── data.json
│ │ │ ├── factorial
│ │ │ └── sample_graph
│ │ └── test_run_workflow.py
│ ├── controller
│ │ ├── __init__.py
│ │ ├── test_graphdata.py
│ │ ├── main.py
│ │ ├── test_codegen.py
│ │ ├── partial_graphdata.py
│ │ ├── test_models.py
│ │ └── test_read_loop_trace.py
│ ├── errors
│ │ ├── __init__.py
│ │ └── failing_worker
│ │ │ ├── README.md
│ │ │ ├── pyproject.toml
│ │ │ ├── main.py
│ │ │ └── stubs.py
│ ├── executor
│ │ ├── __init__.py
│ │ ├── stubs.py
│ │ └── test_hpc_executor.py
│ ├── test_labels.py
│ ├── idl
│ │ ├── namespace1.tsp
│ │ ├── namespace1.py
│ │ ├── test_idl.py
│ │ └── stubs_output.py
│ └── conftest.py
├── tierkreis
│ ├── py.typed
│ ├── cli
│ │ ├── __init__.py
│ │ └── run_workflow.py
│ ├── worker
│ │ ├── __init__.py
│ │ └── storage
│ │ │ ├── __init__.py
│ │ │ ├── protocol.py
│ │ │ ├── in_memory.py
│ │ │ └── filestorage.py
│ ├── builtins
│ │ └── __init__.py
│ ├── controller
│ │ ├── data
│ │ │ ├── __init__.py
│ │ │ └── core.py
│ │ ├── executor
│ │ │ ├── __init__.py
│ │ │ ├── protocol.py
│ │ │ ├── multiple.py
│ │ │ └── in_memory_executor.py
│ │ ├── storage
│ │ │ ├── __init__.py
│ │ │ ├── adjacency.py
│ │ │ ├── in_memory.py
│ │ │ └── filestorage.py
│ │ └── consts.py
│ ├── _version.py
│ ├── exceptions.py
│ ├── hpc.py
│ ├── consts.py
│ ├── value.py
│ ├── __init__.py
│ ├── models.py
│ ├── labels.py
│ ├── executor.py
│ ├── idl
│ │ └── spec.py
│ └── storage.py
├── .gitignore
├── docs
│ ├── api-docs
│ │ ├── .gitignore
│ │ ├── _templates
│ │ │ └── autosummary
│ │ │ │ ├── base.rst
│ │ │ │ ├── class.rst
│ │ │ │ └── module.rst
│ │ ├── index.rst
│ │ └── conf.py
│ └── _static
│ │ ├── Quantinuum_logo_black.png
│ │ ├── Quantinuum_logo_white.png
│ │ └── custom.css
├── pyproject.toml
├── CITATION.bib
└── CHANGELOG.md
├── tierkreis_workers
├── __init__.py
├── aer_worker
│ ├── __init__.py
│ ├── README.md
│ └── pyproject.toml
├── nexus_worker
│ ├── __init__.py
│ ├── .gitignore
│ ├── README.md
│ └── pyproject.toml
├── pytket_worker
│ ├── __init__.py
│ ├── README.md
│ ├── pyproject.toml
│ └── test_main.py
├── ibmq_worker
│ ├── README.md
│ └── pyproject.toml
├── quantinuum_worker
│ ├── README.md
│ └── pyproject.toml
├── README.md
└── qulacs_worker
│ ├── pyproject.toml
│ └── main.py
├── examples
├── example_workers
│ ├── openssl_worker
│ │ ├── test
│ │ │ ├── _done
│ │ │ ├── numbits
│ │ │ ├── .gitignore
│ │ │ ├── passphrase
│ │ │ ├── args
│ │ │ └── tierkreis.sh
│ │ └── main.sh
│ ├── qsci_worker
│ │ ├── qsci
│ │ │ ├── __init__.py
│ │ │ └── active_space.py
│ │ └── pyproject.toml
│ ├── chemistry_worker
│ │ ├── chemistry
│ │ │ ├── __init__.py
│ │ │ └── active_space.py
│ │ ├── stubs.py
│ │ └── main.py
│ ├── genrsa
│ ├── scipy_worker
│ │ ├── pyproject.toml
│ │ └── main.py
│ ├── substitution_worker
│ │ ├── pyproject.toml
│ │ ├── main.py
│ │ └── stubs.py
│ ├── auth_worker
│ │ ├── pyproject.toml
│ │ └── stubs.py
│ ├── error_worker
│ │ ├── stubs.py
│ │ └── main.py
│ └── hello_world_worker
│ │ ├── stubs.py
│ │ └── main.py
├── data
│ ├── simple.qasm
│ └── ghz_state_n23.qasm
├── non_standard_tkr_dir.py
├── nexus_polling.py
├── scipy_graph.py
├── simulate_parallel.py
├── hello_world_graph.py
├── error_handling_graph.py
└── pytket_graph.py
├── tierkreis_visualization
├── tierkreis_visualization
│ ├── py.typed
│ ├── __init__.py
│ ├── data
│ │ ├── __init__.py
│ │ ├── models.py
│ │ ├── function.py
│ │ ├── map.py
│ │ ├── workflows.py
│ │ └── loop.py
│ ├── routers
│ │ ├── __init__.py
│ │ ├── models.py
│ │ ├── frontend.py
│ │ └── navigation.py
│ ├── static
│ │ ├── dist
│ │ │ └── .gitignore
│ │ ├── eventstream.js
│ │ ├── tokens.js
│ │ ├── favicon.svg
│ │ ├── macros.breadcrumbs.html.css
│ │ ├── network.js
│ │ ├── utils.js
│ │ ├── workflows.html.css
│ │ └── _types.js
│ ├── templates
│ │ ├── index.html
│ │ ├── unavailable.jinja
│ │ ├── loop.jinja
│ │ ├── map.jinja
│ │ ├── macros
│ │ │ ├── common_head.html
│ │ │ └── header.html
│ │ ├── workflows.html
│ │ ├── function.jinja
│ │ ├── fallback.jinja
│ │ └── eval.jinja
│ ├── config.py
│ ├── cli.py
│ ├── visualize_graph.py
│ ├── main.py
│ ├── app_config.py
│ ├── app.py
│ └── storage.py
├── frontend
│ ├── src
│ │ ├── vite-env.d.ts
│ │ ├── data
│ │ │ ├── constants.ts
│ │ │ └── logs.ts
│ │ ├── lib
│ │ │ └── utils.ts
│ │ ├── edges
│ │ │ ├── types.ts
│ │ │ └── index.ts
│ │ ├── components
│ │ │ ├── ui
│ │ │ │ ├── skeleton.tsx
│ │ │ │ ├── separator.tsx
│ │ │ │ ├── input.tsx
│ │ │ │ ├── hover-card.tsx
│ │ │ │ └── tooltip.tsx
│ │ │ ├── types.ts
│ │ │ ├── info.tsx
│ │ │ ├── layout.tsx
│ │ │ └── workflow-sidebar.tsx
│ │ ├── nodes
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── hide_children.ts
│ │ ├── hooks
│ │ │ └── use-mobile.ts
│ │ ├── main.tsx
│ │ └── error.tsx
│ ├── tsconfig.node.json
│ ├── .gitignore
│ ├── index.html
│ ├── vite.config.ts
│ ├── .eslintrc.cjs
│ ├── components.json
│ ├── tsconfig.json
│ ├── LICENSE
│ ├── package.json
│ └── README.md
├── .gitignore
├── README.md
└── pyproject.toml
├── pyrightconfig.json
├── devenv.yaml
├── docs
├── source
│ ├── _static
│ │ └── visualization
│ │ │ ├── Expanded.png
│ │ │ ├── overview.png
│ │ │ ├── Debugging.png
│ │ │ └── node_states.png
│ ├── worker
│ │ ├── native_workers
│ │ │ └── index.md
│ │ ├── hello_stubs.py
│ │ └── index.md
│ ├── installation.md
│ ├── index.md
│ ├── getting_started.md
│ ├── conf.py
│ └── tutorial
│ │ ├── auth_stubs.py
│ │ └── eval.md
└── justfile
├── infra
└── slurm_local
│ ├── register_cluster.sh
│ ├── .env
│ ├── update_slurmfiles.sh
│ ├── sbatch
│ ├── USAGE.md
│ ├── slurmdbd.conf
│ ├── LICENSE
│ ├── main.py
│ └── docker-entrypoint.sh
├── .github
├── dependabot.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── workflows
│ ├── push_main.yml
│ ├── run_check_matrix.yml
│ ├── release.yml
│ ├── pr_main.yml
│ └── ci.yml
└── actions
│ └── setup_deps
│ └── action.yml
├── .dockerignore
├── .cargo
└── config.toml
├── .envrc
├── pyproject.toml
├── devenv.nix
└── README.md
/tierkreis/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tests/py.typed:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/py.typed:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/.gitignore:
--------------------------------------------------------------------------------
1 | ./venv
2 |
--------------------------------------------------------------------------------
/tierkreis/tests/cli/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tests/cli/data/n:
--------------------------------------------------------------------------------
1 | 5
2 |
--------------------------------------------------------------------------------
/tierkreis_workers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tests/controller/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tests/errors/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tests/executor/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/cli/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/worker/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tests/cli/data/input1:
--------------------------------------------------------------------------------
1 | test
2 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/builtins/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis_workers/aer_worker/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis_workers/nexus_worker/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis_workers/pytket_worker/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/controller/data/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/worker/storage/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/example_workers/openssl_worker/test/_done:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/example_workers/qsci_worker/qsci/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/controller/executor/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/controller/storage/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/example_workers/openssl_worker/test/numbits:
--------------------------------------------------------------------------------
1 | 4096
--------------------------------------------------------------------------------
/tierkreis/tests/cli/data/input2:
--------------------------------------------------------------------------------
1 | {"a": 5, "b": "string"}
--------------------------------------------------------------------------------
/tierkreis/tierkreis/_version.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.7.3"
2 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/py.typed:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis_workers/aer_worker/README.md:
--------------------------------------------------------------------------------
1 | # aer_worker
2 |
--------------------------------------------------------------------------------
/tierkreis_workers/nexus_worker/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | venv
--------------------------------------------------------------------------------
/examples/example_workers/chemistry_worker/chemistry/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/example_workers/openssl_worker/test/.gitignore:
--------------------------------------------------------------------------------
1 | outputs
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis_workers/pytket_worker/README.md:
--------------------------------------------------------------------------------
1 | # pytket_worker
2 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/data/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/routers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/example_workers/openssl_worker/test/passphrase:
--------------------------------------------------------------------------------
1 | "dummypassphrase"
--------------------------------------------------------------------------------
/tierkreis/docs/api-docs/.gitignore:
--------------------------------------------------------------------------------
1 | _autosummary
2 | _build
3 | generated
--------------------------------------------------------------------------------
/pyrightconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "reportUnnecessaryIsInstance": "none"
3 | }
4 |
--------------------------------------------------------------------------------
/tierkreis_workers/ibmq_worker/README.md:
--------------------------------------------------------------------------------
1 | # IBMQ Backend Compilation Worker
2 |
--------------------------------------------------------------------------------
/tierkreis/tests/errors/failing_worker/README.md:
--------------------------------------------------------------------------------
1 | A simple worker that just fails.
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/static/dist/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/devenv.yaml:
--------------------------------------------------------------------------------
1 | inputs:
2 | nixpkgs:
3 | url: github:NixOS/nixpkgs/nixpkgs-unstable
4 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tierkreis_workers/quantinuum_worker/README.md:
--------------------------------------------------------------------------------
1 | # Quantinuum Backend Compilation Worker
2 |
--------------------------------------------------------------------------------
/tierkreis/tests/cli/data/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "a_value" : 5,
3 | "a_string": "string"
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/tierkreis_workers/README.md:
--------------------------------------------------------------------------------
1 | # tierkreis_workers
2 |
3 | A collection of workers for tierkreis.
4 |
--------------------------------------------------------------------------------
/examples/example_workers/genrsa:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | openssl genrsa --aes128 --passout stdin 4096
3 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/exceptions.py:
--------------------------------------------------------------------------------
1 | class TierkreisError(Exception):
2 | """An error thrown in the Tierkreis library."""
3 |
--------------------------------------------------------------------------------
/docs/source/_static/visualization/Expanded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Quantinuum/tierkreis/HEAD/docs/source/_static/visualization/Expanded.png
--------------------------------------------------------------------------------
/docs/source/_static/visualization/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Quantinuum/tierkreis/HEAD/docs/source/_static/visualization/overview.png
--------------------------------------------------------------------------------
/docs/source/_static/visualization/Debugging.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Quantinuum/tierkreis/HEAD/docs/source/_static/visualization/Debugging.png
--------------------------------------------------------------------------------
/tierkreis/docs/_static/Quantinuum_logo_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Quantinuum/tierkreis/HEAD/tierkreis/docs/_static/Quantinuum_logo_black.png
--------------------------------------------------------------------------------
/tierkreis/docs/_static/Quantinuum_logo_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Quantinuum/tierkreis/HEAD/tierkreis/docs/_static/Quantinuum_logo_white.png
--------------------------------------------------------------------------------
/docs/source/_static/visualization/node_states.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Quantinuum/tierkreis/HEAD/docs/source/_static/visualization/node_states.png
--------------------------------------------------------------------------------
/tierkreis/docs/api-docs/_templates/autosummary/base.rst:
--------------------------------------------------------------------------------
1 | {{ name | escape | underline}}
2 |
3 | .. currentmodule:: {{ module }}
4 |
5 | .. auto{{ objtype }}:: {{ objname }}
6 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/hpc.py:
--------------------------------------------------------------------------------
1 | from tierkreis.controller.executor.hpc.job_spec import JobSpec, ResourceSpec, MpiSpec
2 |
3 | __all__ = ["JobSpec", "ResourceSpec", "MpiSpec"]
4 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/controller/consts.py:
--------------------------------------------------------------------------------
1 | import os
2 | from pathlib import Path
3 |
4 | BODY_PORT = "body"
5 | PACKAGE_PATH = Path(os.path.dirname(os.path.realpath(__file__)))
6 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/consts.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 |
4 | PACKAGE_PATH = Path(__file__).parent.parent
5 | TESTS_PATH = PACKAGE_PATH / "tests"
6 | TKR_DIR_KEY = "TKR_DIR"
7 |
--------------------------------------------------------------------------------
/tierkreis/tests/test_labels.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from tierkreis import Labels
4 |
5 |
6 | def test_instantiate_raises() -> None:
7 | with pytest.raises(RuntimeError):
8 | Labels()
9 |
--------------------------------------------------------------------------------
/infra/slurm_local/register_cluster.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | docker exec slurmctld bash -c "/usr/bin/sacctmgr --immediate add cluster name=linux" && \
5 | docker-compose restart slurmdbd slurmctld
6 |
--------------------------------------------------------------------------------
/tierkreis_visualization/.gitignore:
--------------------------------------------------------------------------------
1 | # Python-generated files
2 | __pycache__/
3 | *.py[oc]
4 | build/
5 | dist/*
6 | wheels/
7 | *.egg-info
8 | .idea
9 |
10 | # Virtual environments
11 | .venv
12 |
13 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/value.py:
--------------------------------------------------------------------------------
1 | from typing import Generic, TypeVar
2 |
3 | from pydantic import BaseModel
4 |
5 |
6 | T = TypeVar("T")
7 |
8 |
9 | class Value(BaseModel, Generic[T]):
10 | value: T
11 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/data/constants.ts:
--------------------------------------------------------------------------------
1 | export const URL = "http://localhost:8000/workflows";
2 | export const nodeName = "-";
3 | export const nodeWidth = 200;
4 | export const nodeHeight = 150;
5 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "uv"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | labels:
8 | - "python"
9 | - "dependencies"
10 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/edges/types.ts:
--------------------------------------------------------------------------------
1 | export type PyEdge = {
2 | id: string | number;
3 | from_node: string | number;
4 | to_node: string | number;
5 | from_port: string;
6 | to_port: string;
7 | value?: unknown;
8 | };
9 |
--------------------------------------------------------------------------------
/tierkreis_workers/nexus_worker/README.md:
--------------------------------------------------------------------------------
1 | # Nexus worker
2 |
3 | ## Authentication
4 |
5 | The Nexus API requires re-authentication every 30 days.
6 |
7 | ```bash
8 | uv run python -c "from qnexus.client.auth import login; login()"
9 | ```
10 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/routers/models.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 | from tierkreis_visualization.data.models import PyNode, PyEdge
3 |
4 |
5 | class PyGraph(BaseModel):
6 | nodes: list[PyNode]
7 | edges: list[PyEdge]
8 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/__init__.py:
--------------------------------------------------------------------------------
1 | from tierkreis.labels import Labels
2 | from tierkreis.worker.worker import Worker
3 | from tierkreis.value import Value
4 | from tierkreis.controller import run_graph
5 |
6 | __all__ = ["Labels", "Worker", "Value", "run_graph"]
7 |
--------------------------------------------------------------------------------
/infra/slurm_local/.env:
--------------------------------------------------------------------------------
1 | # Slurm git repo tag. See https://github.com/SchedMD/slurm/tags
2 | SLURM_TAG=slurm-21-08-6-1
3 |
4 | # Image version used to tag the container at build time (Typically matches the
5 | # Slurm tag in semantic version form)
6 | IMAGE_TAG=21.08.6
7 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/models.py:
--------------------------------------------------------------------------------
1 | from tierkreis.controller.data.core import EmptyModel
2 | from tierkreis.controller.data.models import TKR, portmapping
3 | from tierkreis.controller.data.types import Struct
4 |
5 | __all__ = ["EmptyModel", "TKR", "portmapping", "Struct"]
6 |
--------------------------------------------------------------------------------
/docs/justfile:
--------------------------------------------------------------------------------
1 | sphinx_opts := ""
2 | sphinx_build := "uv run sphinx-build"
3 | source_dir := "source"
4 | build_dir := "build"
5 |
6 | default:
7 | @just --list
8 |
9 | build:
10 | {{sphinx_build}} -M html "{{source_dir}}" "{{build_dir}}" {{sphinx_opts}}
11 |
--------------------------------------------------------------------------------
/examples/data/simple.qasm:
--------------------------------------------------------------------------------
1 | OPENQASM 2.0;
2 | include "hqslib1.inc";
3 |
4 | qreg q[2];
5 | creg c[2];
6 | U1q(0.20000000000000012*pi,0.0*pi) q[0];
7 | U1q(0.5*pi,0.5*pi) q[1];
8 | RZZ(0.5*pi) q[0],q[1];
9 | measure q[0] -> c[0];
10 | U1q(0.5*pi,0.0*pi) q[1];
11 | measure q[1] -> c[1];
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 |
2 | **/target
3 | **/.git
4 | **/.venv
5 | **/venv
6 | **/.vscode
7 | **/.pytest-cache
8 | **/.mypy-cache
9 | **/__pycache__
10 | **/.direnv
11 | **/.DS_Store
12 | **/Thumbs.db
13 |
14 | **/.ruff_cache
15 | **/build
16 | **/tests
17 | **/Dockerfile
18 | .env
19 | cqcpython-secret.txt
--------------------------------------------------------------------------------
/examples/example_workers/scipy_worker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "scipy-worker"
3 | version = "0.1.0"
4 | requires-python = ">=3.12"
5 | dependencies = [
6 | "scipy>=1.16.3",
7 | "tierkreis",
8 | ]
9 |
10 | [tool.uv.sources]
11 | tierkreis = { path = "../../../tierkreis", editable = true }
12 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/edges/index.ts:
--------------------------------------------------------------------------------
1 | import type { Edge, EdgeTypes } from "@xyflow/react";
2 | import CustomEdge from "./customEdge";
3 | export const initialEdges = [] as Edge[];
4 | export const edgeTypes = {
5 | "custom-edge": CustomEdge,
6 | // Add your custom edge types here!
7 | } satisfies EdgeTypes;
8 |
--------------------------------------------------------------------------------
/examples/example_workers/substitution_worker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "substitution-worker"
3 | version = "0.1.0"
4 | requires-python = ">=3.12"
5 | dependencies = [
6 | "pydantic>=2.11.7",
7 | "pytket>=2.9.3",
8 | "tierkreis",
9 | ]
10 |
11 | [tool.uv.sources]
12 | tierkreis = { path = "../../../tierkreis" }
13 |
--------------------------------------------------------------------------------
/tierkreis/tests/controller/test_graphdata.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from tierkreis.exceptions import TierkreisError
3 | from tierkreis.controller.data.graph import GraphData
4 |
5 |
6 | def test_only_one_output():
7 | with pytest.raises(TierkreisError):
8 | g = GraphData()
9 | g.output({"one": g.const(1)})
10 | g.output({"two": g.const(2)})
11 |
--------------------------------------------------------------------------------
/examples/example_workers/auth_worker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "auth-worker"
3 | version = "0.1.0"
4 | requires-python = ">=3.12"
5 | dependencies = [
6 | "cryptography>=45.0.7",
7 | "pydantic>=2.11.7",
8 | "pyscrypt>=1.6.2",
9 | "tierkreis",
10 | ]
11 |
12 | [tool.uv.sources]
13 | tierkreis = { path = "../../../tierkreis", editable = true }
14 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/labels.py:
--------------------------------------------------------------------------------
1 | """Core Tierkreis Label type."""
2 |
3 |
4 | class Labels:
5 | """Special port labels used by builtin functions."""
6 |
7 | def __init__(self):
8 | raise RuntimeError("Do not instantiate")
9 |
10 | THUNK = "thunk"
11 | VALUE = "value"
12 | VARIANT_VALUE = "variant_value"
13 | CONTINUE = "continue"
14 | BREAK = "break"
15 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4 | return (
5 |
10 | )
11 | }
12 |
13 | export { Skeleton }
14 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/executor.py:
--------------------------------------------------------------------------------
1 | from tierkreis.controller.executor.shell_executor import ShellExecutor
2 | from tierkreis.controller.executor.uv_executor import UvExecutor
3 | from tierkreis.controller.executor.multiple import MultipleExecutor
4 | from tierkreis.controller.executor.hpc.pjsub import PJSUBExecutor
5 |
6 |
7 | __all__ = ["ShellExecutor", "UvExecutor", "MultipleExecutor", "PJSUBExecutor"]
8 |
--------------------------------------------------------------------------------
/examples/example_workers/openssl_worker/main.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ## To test run source ./test/tierkreis.sh ./test/args && ./main.sh
4 |
5 | numbits=$(cat $input_numbits_file)
6 | openssl genrsa -out $output_private_key_file -aes128 -passout "file:$input_passphrase_file" $numbits
7 | openssl rsa -in $output_private_key_file -passin "file:$input_passphrase_file" -pubout -out $output_public_key_file
8 |
--------------------------------------------------------------------------------
/examples/example_workers/qsci_worker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "qsci-worker"
3 | version = "0.1.0"
4 | requires-python = ">=3.12"
5 | dependencies = [
6 | "pydantic>=2.11.9",
7 | "pytket>=2.9.3",
8 | "pytket-qiskit>=0.71.0",
9 | "qiskit-ibm-runtime==0.41.1",
10 | "tierkreis",
11 | ]
12 |
13 | [tool.uv.sources]
14 | tierkreis = { path = "../../../tierkreis", editable = true }
15 |
--------------------------------------------------------------------------------
/tierkreis/tests/errors/failing_worker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "failing_worker"
3 | version = "0.1.0"
4 | description = "Worker that will always fail. Used for error testing."
5 | readme = "README.md"
6 | requires-python = ">=3.12"
7 | dependencies = [
8 | "pydantic>=2.10.6",
9 | "tierkreis",
10 | ]
11 |
12 | [tool.uv.sources]
13 | tierkreis = { path = "../../../", editable = true }
14 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"],
10 | "baseUrl": ".",
11 | "paths": {
12 | "@/*": [
13 | "./src/*"
14 | ]
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | !lib
26 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/components/types.ts:
--------------------------------------------------------------------------------
1 | export interface InfoProps {
2 | type: "Logs" | "Errors";
3 | content: string;
4 | }
5 | export interface Workflow {
6 | id: string;
7 | name: string;
8 | start_time: string;
9 | }
10 |
11 | export interface HandleProps {
12 | handles: string[];
13 | id: string;
14 | isOpen: boolean;
15 | hoveredId: string;
16 | setHoveredId: (id: string) => void;
17 | }
18 |
--------------------------------------------------------------------------------
/infra/slurm_local/update_slurmfiles.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | restart=false
6 |
7 | for var in "$@"
8 | do
9 | if [ "$var" = "slurmdbd.conf" ] || [ "$var" = "slurm.conf" ]
10 | then
11 | export SLURM_TMP=$(cat $var)
12 | docker exec slurmctld bash -c "echo \"$SLURM_TMP\" >/etc/slurm/\"$var\""
13 | restart=true
14 | fi
15 | done
16 | if $restart; then docker-compose restart; fi
17 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Tierkreis Frontend
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/source/worker/native_workers/index.md:
--------------------------------------------------------------------------------
1 | # Prepackaged workers
2 |
3 | These workers are natively available as stubs for use in Tierkreis Graphs.
4 |
5 | ```{warning}
6 | Currently only stubs are included. To use the workers during execution they need to be installed first.
7 | ```
8 |
9 | ```{toctree}
10 | aer_worker.md
11 | ibmq_worker.md
12 | nexus_worker.md
13 | pytket_worker.md
14 | quantinuum_worker.md
15 | qulacs_worker.md
16 | ```
17 |
--------------------------------------------------------------------------------
/tierkreis/docs/api-docs/index.rst:
--------------------------------------------------------------------------------
1 | Tierkreis Python API Documentation
2 | ==================================
3 |
4 | This is the API documentation for the Tierkreis Python package.
5 |
6 |
7 | .. autosummary::
8 | :toctree: generated
9 | :template: autosummary/module.rst
10 | :recursive:
11 |
12 | tierkreis
13 |
14 |
15 | Indices and tables
16 | ~~~~~~~~~~~~~~~~~~
17 |
18 | * :ref:`genindex`
19 | * :ref:`modindex`
20 | * :ref:`search`
21 |
--------------------------------------------------------------------------------
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [registries.artifactory]
2 | index = "https://quantinuumsw.jfrog.io/artifactory/git/rust_local.git"
3 |
4 | [net]
5 | git-fetch-with-cli = true
6 |
7 | # This allows building the type-check (pyo3) module on MacOSX "Apple Silicon"
8 | # (cargo build from root directory ignores any config.toml in subdirectories.)
9 | [target.aarch64-apple-darwin]
10 | rustflags = [
11 | "-C", "link-arg=-undefined",
12 | "-C", "link-arg=dynamic_lookup",
13 | ]
14 |
--------------------------------------------------------------------------------
/examples/example_workers/openssl_worker/test/args:
--------------------------------------------------------------------------------
1 | {
2 | "function_name": "genrsa",
3 | "inputs": {
4 | "passphrase": "./test/passphrase",
5 | "numbits": "./test/numbits"
6 | },
7 | "outputs": {
8 | "public_key": "./test/outputs/public_key",
9 | "private_key": "./test/outputs/private_key"
10 | },
11 | "output_dir": "./test/outputs",
12 | "done_path": "./test/_done",
13 | "error_path": "./test/_error",
14 | "logs_path": "./test/logs"
15 | }
16 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/nodes/index.ts:
--------------------------------------------------------------------------------
1 | import type { NodeTypes } from "@xyflow/react";
2 |
3 | import { DefaultNode } from "./default";
4 | import { EvalNode } from "./eval";
5 | import { MapNode } from "./map";
6 | import { AppNode } from "./types";
7 |
8 | export const initialNodes = [] as AppNode[];
9 |
10 | export const nodeTypes = {
11 | "eval-node": EvalNode,
12 | "map-node": MapNode,
13 | "default-node": DefaultNode,
14 | } satisfies NodeTypes;
15 |
--------------------------------------------------------------------------------
/examples/example_workers/error_worker/stubs.py:
--------------------------------------------------------------------------------
1 | """Code generated from error_worker namespace. Please do not edit."""
2 |
3 | from typing import NamedTuple
4 | from tierkreis.controller.data.models import TKR
5 |
6 |
7 | class fail(NamedTuple):
8 | @staticmethod
9 | def out() -> type[TKR[str]]: # noqa: F821 # fmt: skip
10 | return TKR[str] # noqa: F821 # fmt: skip
11 |
12 | @property
13 | def namespace(self) -> str:
14 | return "error_worker"
15 |
--------------------------------------------------------------------------------
/docs/source/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | Tierkreis consist of multiple packages:
4 |
5 | - `tierkreis`: the core functionality including the
6 | - `tierkreis-visualization`: the visualization library which is necessary for the debug view
7 | - `tierkreis-*-worker`: independent [workers](./core_concepts)
8 |
9 | The libraries can be installed with pip:
10 | ```
11 | pip install tierkreis
12 | pip install tierkreis-visualization
13 | pip install tkr-*-worker
14 | ```
15 |
16 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/templates/index.html:
--------------------------------------------------------------------------------
1 | {% from "macros/common_head.html" import common_head_macro with context %}
2 | {% from "macros/header.html" import header_macro with context %}
3 |
4 |
5 | Tierkreis visualization
6 | {{ common_head_macro() }}
7 |
8 |
9 |
10 | {{ header_macro() }}
11 |
12 | All running workflows
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tierkreis/tests/executor/stubs.py:
--------------------------------------------------------------------------------
1 | """Code generated from slurm_mpi_worker namespace. Please do not edit."""
2 |
3 | from typing import NamedTuple
4 | from types import NoneType
5 | from tierkreis.controller.data.models import TKR
6 |
7 |
8 | class mpi_rank_info(NamedTuple):
9 | @staticmethod
10 | def out() -> type[TKR[str | NoneType]]: # noqa: F821 # fmt: skip
11 | return TKR[str | NoneType] # noqa: F821 # fmt: skip
12 |
13 | @property
14 | def namespace(self) -> str:
15 | return "slurm_mpi_worker"
16 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/vite.config.ts:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import tailwindcss from "@tailwindcss/vite";
3 | import { defineConfig } from "vite";
4 | import react from "@vitejs/plugin-react";
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [react(), tailwindcss()],
9 | build: {
10 | outDir: "../tierkreis_visualization/static/dist",
11 | },
12 | resolve: {
13 | alias: {
14 | "@": path.resolve(__dirname, "./src"),
15 | },
16 | },
17 | base: "",
18 | });
19 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/config.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from pydantic_settings import BaseSettings
4 | from starlette.templating import Jinja2Templates
5 |
6 |
7 | class Settings(BaseSettings):
8 | tierkreis_path: Path = Path.home() / ".tierkreis" / "checkpoints"
9 | graph_specifier: str | None = None
10 |
11 |
12 | CONFIG = Settings()
13 | CONFIG.tierkreis_path.mkdir(parents=True, exist_ok=True)
14 | templates = Jinja2Templates(directory=(Path(__file__).parent / "templates").absolute())
15 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/static/eventstream.js:
--------------------------------------------------------------------------------
1 | function connectToStream(url, nodes, edges) {
2 | let eventSource = new EventSource(url);
3 | eventSource.onopen = (ev) => {
4 | console.log("opening event source");
5 | console.log(ev);
6 | };
7 | eventSource.addEventListener("message", (ev) => {
8 | data = JSON.parse(ev["data"]);
9 | for (let node in data.nodes) {
10 | jsNode = createJSNode(data.nodes[node]);
11 | nodes.update(jsNode);
12 | network.redraw();
13 | }
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": false,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "",
8 | "css": "src/index.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
--------------------------------------------------------------------------------
/tierkreis/tests/errors/failing_worker/main.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from sys import argv
3 | from tierkreis import Worker
4 |
5 | logger = logging.getLogger(__name__)
6 | worker = Worker("failing_worker")
7 |
8 |
9 | @worker.task()
10 | def fail() -> int:
11 | logger.error("Raising an error now...")
12 | raise ValueError("Worker failed!")
13 |
14 |
15 | @worker.task()
16 | def wont_fail() -> int:
17 | return 0
18 |
19 |
20 | @worker.task()
21 | def exit_code_1() -> int:
22 | exit(1)
23 |
24 |
25 | if __name__ == "__main__":
26 | worker.app(argv)
27 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/routers/frontend.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from fastapi import APIRouter
3 | from fastapi.responses import FileResponse
4 | from fastapi.staticfiles import StaticFiles
5 |
6 | PACKAGE_DIR = Path(__file__).parent.parent.absolute()
7 | assets = StaticFiles(directory=PACKAGE_DIR / "static" / "dist" / "assets", html=True)
8 | router = APIRouter()
9 |
10 |
11 | @router.get("/{path:path}")
12 | def read_root():
13 | return FileResponse(
14 | PACKAGE_DIR / "static" / "dist" / "index.html", media_type="text/html"
15 | )
16 |
--------------------------------------------------------------------------------
/docs/source/worker/hello_stubs.py:
--------------------------------------------------------------------------------
1 | """Code generated from hello_world_worker namespace. Please do not edit."""
2 |
3 | from typing import NamedTuple
4 | from tierkreis.controller.data.models import TKR
5 |
6 |
7 | class greet(NamedTuple):
8 | greeting: TKR[str] # noqa: F821 # fmt: skip
9 | subject: TKR[str] # noqa: F821 # fmt: skip
10 |
11 | @staticmethod
12 | def out() -> type[TKR[str]]: # noqa: F821 # fmt: skip
13 | return TKR[str] # noqa: F821 # fmt: skip
14 |
15 | @property
16 | def namespace(self) -> str:
17 | return "hello_world_worker"
18 |
--------------------------------------------------------------------------------
/examples/example_workers/substitution_worker/main.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from sys import argv
3 |
4 |
5 | from tierkreis import Worker
6 | from pytket._tket.circuit import Circuit
7 | from sympy import Symbol
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 | worker = Worker("substitution_worker")
12 |
13 |
14 | @worker.task()
15 | def substitute(circuit: Circuit, a: float, b: float, c: float) -> Circuit:
16 | circuit.symbol_substitution({Symbol("a"): a, Symbol("b"): b, Symbol("c"): c})
17 | return circuit
18 |
19 |
20 | if __name__ == "__main__":
21 | worker.app(argv)
22 |
--------------------------------------------------------------------------------
/examples/example_workers/error_worker/main.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.12"
3 | # dependencies = ["pydantic", "tierkreis"]
4 | #
5 | # [tool.uv.sources]
6 | # tierkreis = { path = "../../../tierkreis", editable = true }
7 | # ///
8 | import logging
9 | from sys import argv
10 | from tierkreis import Worker
11 |
12 | logger = logging.getLogger(__name__)
13 | worker = Worker("error_worker")
14 |
15 |
16 | @worker.task()
17 | def fail() -> str:
18 | raise Exception("I refuse!")
19 | return "I failed to refuse"
20 |
21 |
22 | if __name__ == "__main__":
23 | worker.app(argv)
24 |
--------------------------------------------------------------------------------
/examples/example_workers/hello_world_worker/stubs.py:
--------------------------------------------------------------------------------
1 | """Code generated from hello_world_worker namespace. Please do not edit."""
2 |
3 | from typing import NamedTuple
4 | from tierkreis.controller.data.models import TKR
5 |
6 |
7 | class greet(NamedTuple):
8 | greeting: TKR[str] # noqa: F821 # fmt: skip
9 | subject: TKR[str] # noqa: F821 # fmt: skip
10 |
11 | @staticmethod
12 | def out() -> type[TKR[str]]: # noqa: F821 # fmt: skip
13 | return TKR[str] # noqa: F821 # fmt: skip
14 |
15 | @property
16 | def namespace(self) -> str:
17 | return "hello_world_worker"
18 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/static/tokens.js:
--------------------------------------------------------------------------------
1 | const colors = {
2 | neutral :{
3 | 0: "#ffffff",
4 | 50 :"#fafafa",
5 | 100 :"#f4f4f5",
6 | 200 :"#e4e4e7",
7 | 300 :"#d4d4d8",
8 | 400 :"#a1a1aa",
9 | 500 :"#71717a",
10 | 600 :"#52525b",
11 | 700 :"#3f3f46",
12 | 800: "#27272a",
13 | 900 :"#18181b",
14 | 950 :"#09090b",
15 | },
16 | green: {
17 | primary: "#bbf7d0"
18 | },
19 | red: {
20 | primary: "#fca5a5"
21 | },
22 | amber: {
23 | primary: "#fde68a"
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/tierkreis/tests/idl/namespace1.tsp:
--------------------------------------------------------------------------------
1 | model ListItem {
2 | i: uint8;
3 | }
4 |
5 | model NestedType {
6 | city: string;
7 | }
8 |
9 | model IncludedType {
10 | nested: NestedType;
11 | }
12 |
13 | @portmapping
14 | model A {
15 | name: Record;
16 | age: uint8;
17 | }
18 |
19 | model B {
20 | name: Record;
21 | age: uint8;
22 | }
23 |
24 | model C {
25 | a: Array;
26 | b: B;
27 | t: T;
28 | included: IncludedType;
29 | ol: Array;
30 | }
31 |
32 | interface TestNamespace {
33 | foo(a: integer, b: string): A;
34 | bar(): B;
35 | z(c: C): C;
36 | }
37 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/static/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/data/models.py:
--------------------------------------------------------------------------------
1 | from typing import Literal, Any
2 | from pydantic import BaseModel
3 |
4 | NodeStatus = Literal["Not started", "Started", "Error", "Finished"]
5 |
6 |
7 | class PyNode(BaseModel):
8 | id: int
9 | status: NodeStatus
10 | function_name: str
11 | node_location: str = ""
12 | value: Any | None = None
13 | started_time: str
14 | finished_time: str
15 |
16 |
17 | class PyEdge(BaseModel):
18 | from_node: int
19 | from_port: str
20 | to_node: int
21 | to_port: str
22 | value: Any | None = None
23 | conditional: bool = False
24 |
--------------------------------------------------------------------------------
/tierkreis/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def pytest_addoption(parser):
5 | parser.addoption(
6 | "--runslow", action="store_true", default=False, help="run slow tests"
7 | )
8 |
9 |
10 | def pytest_configure(config):
11 | config.addinivalue_line("markers", "slow: mark test as slow to run")
12 |
13 |
14 | def pytest_collection_modifyitems(config, items):
15 | if config.getoption("--runslow"):
16 | return
17 | skip_slow = pytest.mark.skip(reason="need --runslow option to run")
18 | for item in items:
19 | if "slow" in item.keywords:
20 | item.add_marker(skip_slow)
21 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/static/macros.breadcrumbs.html.css:
--------------------------------------------------------------------------------
1 | .badge {
2 | padding: 0.25rem 0.5rem;
3 | background-color: var(--neutral-0);
4 | border-radius: 0.5rem;
5 | color: var(--neutral-700);
6 | font-size: 0.75rem;
7 | font-weight: 500;
8 | letter-spacing: -0.02em;
9 | text-decoration: none;
10 | display: block;
11 | width: min-content;
12 | text-wrap: nowrap;
13 | border: 1px solid var(--neutral-300);
14 |
15 | }
16 | .badge:hover {
17 | background-color: var(--neutral-100);
18 | transition-property: background-color;
19 | transition-duration: 150ms;
20 | }
21 |
--------------------------------------------------------------------------------
/tierkreis_workers/qulacs_worker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "tkr_qulacs_worker"
3 | description = "A tierkreis worker for Qulacs."
4 | version = "0.1.0"
5 | requires-python = ">=3.12"
6 | dependencies = [
7 | "pytket-qulacs>=0.41.0",
8 | "tierkreis",
9 | ]
10 |
11 | [build-system]
12 | requires = ["hatchling"]
13 | build-backend = "hatchling.build"
14 |
15 | [tool.hatch.build.targets.sdist]
16 | include = [
17 | "*.py",
18 | ]
19 | [tool.hatch.build.targets.wheel]
20 | include = [
21 | "*.py",
22 | ]
23 |
24 | [tool.uv.sources]
25 | tierkreis = { workspace = true }
26 |
27 | [project.scripts]
28 | tkr_aer_worker = "main:main"
--------------------------------------------------------------------------------
/examples/example_workers/hello_world_worker/main.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.12"
3 | # dependencies = ["pydantic", "tierkreis"]
4 | #
5 | # [tool.uv.sources]
6 | # tierkreis = { path = "../../../tierkreis", editable = true }
7 | # ///
8 | import logging
9 | from sys import argv
10 | from tierkreis import Worker
11 |
12 | logger = logging.getLogger(__name__)
13 | worker = Worker("hello_world_worker")
14 |
15 |
16 | @worker.task()
17 | def greet(greeting: str, subject: str) -> str:
18 | logger.info("%s %s", greeting, subject)
19 | return greeting + subject
20 |
21 |
22 | if __name__ == "__main__":
23 | worker.app(argv)
24 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/worker/storage/protocol.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from typing import Protocol
3 |
4 | from tierkreis.controller.data.location import WorkerCallArgs
5 |
6 |
7 | class WorkerStorage(Protocol):
8 | def resolve(self, path: Path | str) -> Path: ...
9 | def read_call_args(self, path: Path) -> WorkerCallArgs: ...
10 | def read_input(self, path: Path) -> bytes: ...
11 | def write_output(self, path: Path, value: bytes) -> None: ...
12 | def glob(self, path_string: str) -> list[str]: ...
13 | def mark_done(self, path: Path) -> None: ...
14 | def write_error(self, path: Path, error_logs: str) -> None: ...
15 |
--------------------------------------------------------------------------------
/tierkreis_workers/aer_worker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "tkr_aer_worker"
3 | version = "0.1.4"
4 | description = "A tierkreis worker for Aer."
5 | readme = "README.md"
6 | requires-python = ">=3.12"
7 | dependencies = [
8 | "tierkreis",
9 | "pytket-qiskit",
10 | ]
11 |
12 | [build-system]
13 | requires = ["hatchling"]
14 | build-backend = "hatchling.build"
15 |
16 | [tool.hatch.build.targets.sdist]
17 | include = [
18 | "*.py",
19 | ]
20 | [tool.hatch.build.targets.wheel]
21 | include = [
22 | "*.py",
23 | ]
24 |
25 | [tool.uv.sources]
26 | tierkreis = { workspace = true }
27 |
28 | [project.scripts]
29 | tkr_aer_worker = "main:main"
30 |
--------------------------------------------------------------------------------
/tierkreis_visualization/README.md:
--------------------------------------------------------------------------------
1 | # Tierkreis visualizer
2 |
3 | WIP browser based visualizer.
4 |
5 | ```
6 | uv run fastapi dev tierkreis_visualization/main.py
7 | ```
8 |
9 | for the development server.
10 |
11 | ## bun-react-template
12 |
13 | To install dependencies:
14 |
15 | ```bash
16 | bun install
17 | ```
18 |
19 | To start a development server:
20 |
21 | ```bash
22 | bun dev
23 | ```
24 |
25 | To run for production:
26 |
27 | ```bash
28 | bun start
29 | ```
30 |
31 | This project was created using `bun init` in bun v1.2.11. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
32 |
33 | To run with fast api:
34 | ```bash
35 | just prod serve
36 | ```
37 |
--------------------------------------------------------------------------------
/tierkreis_workers/nexus_worker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "tkr_nexus_worker"
3 | version = "0.1.4"
4 | description = "A tierkreis worker for interacting with Nexus."
5 | readme = "README.md"
6 | requires-python = ">=3.12"
7 | dependencies = [
8 | "tierkreis",
9 | "qnexus==0.18.2",
10 | ]
11 |
12 | [build-system]
13 | requires = ["hatchling"]
14 | build-backend = "hatchling.build"
15 |
16 | [tool.hatch.build.targets.sdist]
17 | include = [
18 | "*.py",
19 | ]
20 | [tool.hatch.build.targets.wheel]
21 | include = [
22 | "*.py",
23 | ]
24 |
25 | [tool.uv.sources]
26 | tierkreis = { workspace = true }
27 |
28 | [project.scripts]
29 | tkr_nexus_worker = "main:main"
30 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/hooks/use-mobile.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | const MOBILE_BREAKPOINT = 768
4 |
5 | export function useIsMobile() {
6 | const [isMobile, setIsMobile] = React.useState(undefined)
7 |
8 | React.useEffect(() => {
9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10 | const onChange = () => {
11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12 | }
13 | mql.addEventListener("change", onChange)
14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15 | return () => mql.removeEventListener("change", onChange)
16 | }, [])
17 |
18 | return !!isMobile
19 | }
20 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/cli.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 | import argparse
3 |
4 |
5 | from tierkreis_visualization.main import start
6 |
7 |
8 | class TierkreisVizCli:
9 | @staticmethod
10 | def add_subcommand(
11 | main_parser: argparse._SubParsersAction[argparse.ArgumentParser],
12 | ) -> None:
13 | parser: argparse.ArgumentParser = main_parser.add_parser(
14 | "viz",
15 | description="Runs the tierkreis visualization.",
16 | )
17 | parser.set_defaults(func=TierkreisVizCli.execute)
18 |
19 | @staticmethod
20 | def execute(args: argparse.Namespace) -> None:
21 | start()
22 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/visualize_graph.py:
--------------------------------------------------------------------------------
1 | import uvicorn
2 |
3 | from tierkreis.builder import GraphBuilder
4 | from tierkreis.controller.data.graph import GraphData
5 |
6 | from tierkreis_visualization.app import app_from_graph_data
7 |
8 |
9 | def visualize_graph(graph_data: GraphData | GraphBuilder) -> None:
10 | """Visualize a computation graph in a web browser.
11 |
12 | :param graph_data: The computation graph to visualize.
13 | :type graph_data: GraphData | GraphBuilder
14 | """
15 | if isinstance(graph_data, GraphBuilder):
16 | graph_data = graph_data.get_data()
17 | app = app_from_graph_data(graph_data)
18 | uvicorn.run(app)
19 |
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | layout_uv() {
2 | if [[ -d ".venv" ]]; then
3 | VIRTUAL_ENV="$(pwd)/.devenv/state/venv"
4 | fi
5 |
6 | if [[ -z $VIRTUAL_ENV || ! -d $VIRTUAL_ENV ]]; then
7 | log_status "No virtual environment exists. Executing \`uv venv\` to create one."
8 | uv venv
9 | VIRTUAL_ENV="$(pwd)/.devenv/state/venv"
10 | fi
11 |
12 | PATH_add "$VIRTUAL_ENV/bin"
13 | export UV_ACTIVE=1 # or VENV_ACTIVE=1
14 | export VIRTUAL_ENV
15 | }
16 |
17 | source_url "https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc" "sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0="
18 |
19 | use devenv
20 | layout_uv
21 |
22 | unset PS1
23 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/components/info.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | DialogContent,
3 | DialogDescription,
4 | DialogHeader,
5 | DialogTitle,
6 | } from "@/components/ui/dialog";
7 | import { InfoProps } from "./types";
8 |
9 | export function NodeInfo(props: { info: InfoProps }) {
10 | return (
11 |
12 |
13 | {props.info.type}
14 |
15 |
16 |
17 |
{props.info.content}
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/tierkreis/tests/controller/main.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.12"
3 | # dependencies = ["pydantic", "tierkreis"]
4 | #
5 | # [tool.uv.sources]
6 | # tierkreis = { path = "../../../tierkreis", editable = true }
7 | # ///
8 | from pathlib import Path
9 | from time import sleep
10 | from sys import argv
11 |
12 | from tierkreis import Worker, Value
13 |
14 | worker = Worker("tests_worker")
15 |
16 |
17 | @worker.task()
18 | def sleep_and_return[T](*, output: T) -> Value[T]:
19 | sleep(10)
20 | return Value(value=output)
21 |
22 |
23 | def main() -> None:
24 | node_definition_path = argv[1]
25 | worker.run(Path(node_definition_path))
26 |
27 |
28 | if __name__ == "__main__":
29 | main()
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for Tierkreis
4 | title: "[Feat]"
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Is your feature request related to a problem? Please describe.
11 | A clear and concise description of what the problem is.
12 | *Example I'm always frustrated when [...]*
13 |
14 | ## Describe the solution you'd like
15 | A clear and concise description of what you want to happen.
16 |
17 | ## Describe alternatives you've considered
18 | A clear and concise description of any alternative solutions or features you've considered.
19 |
20 | ## Additional context
21 | Add any other context or screenshots about the feature request here.
22 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/routers/navigation.py:
--------------------------------------------------------------------------------
1 | from uuid import UUID
2 |
3 | from tierkreis.controller.data.location import Loc
4 |
5 |
6 | def breadcrumb_links(crumbs: list[str]) -> list[tuple[str, str]]:
7 | url_path = ""
8 | links: list[tuple[str, str]] = []
9 | for crumb in crumbs:
10 | url_path = url_path + crumb
11 | links.append((crumb, url_path))
12 |
13 | return links
14 |
15 |
16 | def breadcrumbs(workflow_id: UUID, node_location: Loc) -> list[tuple[str, str]]:
17 | node_location_strs: list[str] = [f".{x}" for x in node_location.split(".")]
18 | static_links = ["/workflows", f"/{workflow_id}/nodes/-"]
19 | return breadcrumb_links(static_links + node_location_strs[1:])
20 |
--------------------------------------------------------------------------------
/.github/workflows/push_main.yml:
--------------------------------------------------------------------------------
1 | name: Merge to main
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | docs:
11 | runs-on: ubuntu-24.04
12 | permissions:
13 | pages: write
14 | id-token: write
15 |
16 | steps:
17 | - name: Checkout code
18 | uses: actions/checkout@v4
19 |
20 | - name: Set up deps
21 | uses: ./.github/actions/setup_deps
22 |
23 | - name: Build docs
24 | run: just docs
25 |
26 | - name: Upload static files as artifact
27 | uses: actions/upload-pages-artifact@v3
28 | with:
29 | path: docs/build/html
30 |
31 | - name: Deploy to GitHub Pages
32 | uses: actions/deploy-pages@v4
33 |
--------------------------------------------------------------------------------
/tierkreis_visualization/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "tierkreis_visualization"
3 | version = "0.1.6"
4 | description = "Browser based visualization tool for tierkreis"
5 | readme = "README.md"
6 | requires-python = ">=3.12"
7 | dependencies = [
8 | "fastapi[standard]>=0.115.11",
9 | "jinja2>=3.1.6",
10 | "pydantic-settings>=2.8.1",
11 | "tierkreis",
12 | "watchfiles>=1.0.4",
13 | ]
14 |
15 | [build-system]
16 | requires = ["hatchling"]
17 | build-backend = "hatchling.build"
18 |
19 | [tool.uv.sources]
20 | tierkreis = { workspace = true }
21 |
22 | [project.scripts]
23 | tkr-vis = "tierkreis_visualization.main:start"
24 | tkr-vis-dev = "tierkreis_visualization.main:dev"
25 | tkr-vis-graph = "tierkreis_visualization.main:graph"
26 |
--------------------------------------------------------------------------------
/infra/slurm_local/sbatch:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [ -z "$5" ]; then
3 | echo "Usage: sbatch " -o output.log -e error.log script_file
4 | echo "Any further arguments will be discarded"
5 | exit 1
6 | fi
7 | SCRIPT_FILE=$(basename "$5")
8 |
9 | NODE_PATH=$(sed -n -E '$ s#.*([0-9a-fA-F-]{36}/[^/]+)/definition$#\1#p' "$5")
10 | WORKFLOW_ID=$(echo "$NODE_PATH" | cut -d'/' -f1)
11 | docker cp "$5" slurmctld:/data/"$SCRIPT_FILE"
12 | docker exec slurmctld sh -c 'rm -rf /data/*.log && \
13 | ln -s $HOME/.tierkreis/checkpoints/$2/logs /data/logs.log && \
14 | ln -s $HOME/.tierkreis/checkpoints/$3/errors /data/errors.log && \
15 | sbatch --output=logs.log --error=errors.log --chdir=/data /data/$1' sh "$SCRIPT_FILE" "$WORKFLOW_ID" "$NODE_PATH"
16 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/main.py:
--------------------------------------------------------------------------------
1 | from sys import argv
2 |
3 | import uvicorn
4 |
5 |
6 | def start() -> None:
7 | uvicorn.run("tierkreis_visualization.app:get_filestorage_app")
8 |
9 |
10 | def dev() -> None:
11 | uvicorn.run("tierkreis_visualization.app:get_filestorage_app", reload=True)
12 |
13 |
14 | def graph() -> None:
15 | """Visualize a computation graph in a web browser.
16 |
17 | Entrypoint for the project script tkr-vis-graph.
18 | """
19 | reload_path = argv[1].split(":", 1)[0]
20 | uvicorn.run(
21 | "tierkreis_visualization.app:get_graph_data_app",
22 | reload=True,
23 | reload_includes=reload_path,
24 | )
25 |
26 |
27 | if __name__ == "__main__":
28 | start()
29 |
--------------------------------------------------------------------------------
/tierkreis/docs/_static/custom.css:
--------------------------------------------------------------------------------
1 | .wy-side-nav-search,
2 | .wy-nav-top {
3 | background: #5A46BE;
4 | }
5 |
6 | .wy-grid-for-nav,
7 | .wy-body-for-nav,
8 | .wy-nav-side,
9 | .wy-side-scroll,
10 | .wy-menu,
11 | .wy-menu-vertical {
12 | background-color: #FFFFFF;
13 | }
14 |
15 | .wy-menu-vertical a:hover {
16 | background-color: #d9d9d9;
17 | }
18 |
19 | .btn-link:visited,
20 | .btn-link,
21 | a:visited,
22 | .a.reference.external,
23 | .a.reference.internal,
24 | .wy-menu-vertical a,
25 | .wy-menu-vertical li,
26 | .wy-menu-vertical ul,
27 | .span.pre,
28 | .sig-param,
29 | .std.std-ref,
30 |
31 |
32 | html[data-theme=light] {
33 | --pst-color-inline-code: rgb(199, 37, 78) !important;
34 | }
35 |
36 | .sig-name {
37 | font-size: 1.25rem;
38 | }
39 |
--------------------------------------------------------------------------------
/tierkreis_workers/ibmq_worker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "tkr_ibmq_worker"
3 | version = "0.1.0"
4 | description = "A tierkreis worker for compilation of ibmq backends."
5 | readme = "README.md"
6 | requires-python = ">=3.12"
7 | dependencies = [
8 | "tierkreis",
9 | "pytket",
10 | "pytket-qiskit",
11 | ]
12 |
13 | [build-system]
14 | requires = ["hatchling"]
15 | build-backend = "hatchling.build"
16 |
17 | [tool.hatch.build.targets.sdist]
18 | include = [
19 | "*.py",
20 | ]
21 | [tool.hatch.build.targets.wheel]
22 | include = [
23 | "*.py",
24 | ]
25 |
26 | [tool.uv.sources]
27 | tierkreis = { workspace = true }
28 | pytket-qiskit = { git = "https://github.com/CQCL/pytket-qiskit" }
29 |
30 | [project.scripts]
31 | tkr_ibmq_worker = "main:main"
32 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/data/function.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 | from tierkreis.controller.data.location import Loc
3 | from tierkreis.controller.storage.protocol import ControllerStorage
4 | from tierkreis.exceptions import TierkreisError
5 |
6 |
7 | class FunctionDefinition(BaseModel):
8 | has_error: bool = False
9 | error_message: str | None = None
10 |
11 |
12 | def get_function_node(storage: ControllerStorage, loc: Loc) -> FunctionDefinition:
13 | parent = loc.parent()
14 | if parent is None:
15 | raise TierkreisError("Func node must have parent.")
16 | if not storage.node_has_error(loc):
17 | return FunctionDefinition()
18 | return FunctionDefinition(has_error=True, error_message=storage.read_errors(loc))
19 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/templates/unavailable.jinja:
--------------------------------------------------------------------------------
1 | {% from "macros/breadcrumbs.html" import breadcrumbs_macro with context %}
2 | {% from "macros/common_head.html" import common_head_macro with context %}
3 | {% from "macros/header.html" import header_macro with context %}
4 |
5 |
6 | Tierkreis visualization: node unavailable
7 | {{ common_head_macro() }}
8 |
9 |
10 | {{ header_macro() }}
11 |
12 | {{ breadcrumbs_macro(breadcrumbs) }}
13 | Node unavailable
15 |
16 | The node at the path {{ node_location }} is not started and has no data available.
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.github/workflows/run_check_matrix.yml:
--------------------------------------------------------------------------------
1 | name: Reusable setup for command
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | command_name:
7 | required: true
8 | type: string
9 | command:
10 | required: true
11 | type: string
12 |
13 | jobs:
14 | run:
15 | name: ${{ inputs.command_name }}
16 | runs-on: ubuntu-24.04
17 | strategy:
18 | matrix:
19 | python-version: ["3.12", "3.13"]
20 |
21 | steps:
22 | - name: Checkout code
23 | uses: actions/checkout@v4
24 |
25 | - name: Setup deps
26 | uses: ./.github/actions/setup_deps
27 | with:
28 | python_version: ${{ matrix.python-version }}
29 |
30 | - name: ${{ inputs.command_name }}
31 | run: ${{ inputs.command }}
32 |
--------------------------------------------------------------------------------
/tierkreis_workers/quantinuum_worker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "tkr_quantinuum_worker"
3 | version = "0.1.0"
4 | description = "A tierkreis worker for compilation of quanitnuum backends."
5 | readme = "README.md"
6 | requires-python = ">=3.12"
7 | dependencies = [
8 | "tierkreis",
9 | "pytket",
10 | "pytket-quantinuum",
11 | ]
12 |
13 | [build-system]
14 | requires = ["hatchling"]
15 | build-backend = "hatchling.build"
16 |
17 | [tool.hatch.build.targets.sdist]
18 | include = [
19 | "*.py",
20 | ]
21 | [tool.hatch.build.targets.wheel]
22 | include = [
23 | "*.py",
24 | ]
25 |
26 | [tool.uv.sources]
27 | tierkreis = { workspace = true }
28 | pytket-quantinuum = { git = "https://github.com/CQCL/pytket-quantinuum" }
29 |
30 | [project.scripts]
31 | tkr_quantinuum_worker = "main:main"
32 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.uv.workspace]
2 | members = [
3 | "tierkreis",
4 | "tierkreis_visualization",
5 | "tierkreis_workers/*worker",
6 | ]
7 |
8 |
9 | [dependency-groups]
10 | # Default dev dependency group
11 | dev = [
12 | { include-group = "lint" },
13 | { include-group = "test" },
14 | { include-group = "docs" },
15 | { include-group = "examples"}
16 | ]
17 | lint = [
18 | "isort>=6.0.1",
19 | "ruff>=0.12.5",
20 | "pyright>=1.1.400",
21 | ]
22 | test = [
23 | "pytest>=6.2,<9",
24 | "pytest-asyncio>=0.16,<0.17",
25 | "pytest-cov>=5.0,<6",
26 | "pydantic>=2.9.2",
27 | ]
28 | docs = [
29 | "sphinx>=8.2",
30 | "myst-parser>=4.0.1",
31 | "sphinx-autodoc2>=0.5.0",
32 | "myst-nb>=1.3.0",
33 | "furo>=2025.7.19",
34 | ]
35 | examples = ["pyscf>=2.9.0"]
36 |
--------------------------------------------------------------------------------
/examples/example_workers/substitution_worker/stubs.py:
--------------------------------------------------------------------------------
1 | """Code generated from substitution_worker namespace. Please do not edit."""
2 |
3 | from typing import NamedTuple
4 | from tierkreis.controller.data.models import TKR, OpaqueType
5 |
6 |
7 | class substitute(NamedTuple):
8 | circuit: TKR[OpaqueType["pytket._tket.circuit.Circuit"]] # noqa: F821 # fmt: skip
9 | a: TKR[float] # noqa: F821 # fmt: skip
10 | b: TKR[float] # noqa: F821 # fmt: skip
11 | c: TKR[float] # noqa: F821 # fmt: skip
12 |
13 | @staticmethod
14 | def out() -> type[TKR[OpaqueType["pytket._tket.circuit.Circuit"]]]: # noqa: F821 # fmt: skip
15 | return TKR[OpaqueType["pytket._tket.circuit.Circuit"]] # noqa: F821 # fmt: skip
16 |
17 | @property
18 | def namespace(self) -> str:
19 | return "substitution_worker"
20 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | workflow_dispatch:
5 | release:
6 | types: [created]
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-24.04
11 | environment: pypi
12 | permissions:
13 | id-token: write
14 |
15 | steps:
16 | - name: Checkout code
17 | uses: actions/checkout@v4
18 |
19 | - name: Set up deps
20 | uses: ./.github/actions/setup_deps
21 | with:
22 | include_node: "yes"
23 |
24 | - name: Build frontend
25 | run: just prod
26 |
27 | - name: Build Python package
28 | run: uv build --all-packages
29 |
30 | - name: Publish package distributions to PyPI
31 | uses: pypa/gh-action-pypi-publish@release/v1
32 | with:
33 | verbose: true
34 | skip-existing: true
35 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/templates/loop.jinja:
--------------------------------------------------------------------------------
1 | {% from "macros/breadcrumbs.html" import breadcrumbs_macro with context %}
2 | {% from "macros/common_head.html" import common_head_macro with context %}
3 | {% from "macros/header.html" import header_macro with context %}
4 |
5 |
6 |
7 | Tierkreis visualization: LOOP node
8 | {{ common_head_macro() }}
9 |
10 |
11 |
12 |
13 | {{ header_macro() }}
14 |
15 | {{ breadcrumbs_macro(breadcrumbs, true) }}
16 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/components/layout.tsx:
--------------------------------------------------------------------------------
1 | import { NodeInfo } from "@/components/info";
2 | import { Dialog } from "@/components/ui/dialog";
3 | import { SidebarProvider } from "@/components/ui/sidebar";
4 | import { WorkflowSidebar } from "@/components/workflow-sidebar";
5 | import { InfoProps, Workflow } from "@/components/types";
6 |
7 | export default function Layout({
8 | children,
9 | workflows,
10 | workflowId,
11 | info,
12 | }: {
13 | children: React.ReactNode;
14 | workflows: Workflow[];
15 | workflowId: string;
16 | info: InfoProps;
17 | }) {
18 | return (
19 |
20 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 |
4 | /* Tailwind */
5 | "baseUrl": ".",
6 | "paths": {
7 | "@/*": ["./src/*"]
8 | },
9 |
10 | "target": "ES2020",
11 | "useDefineForClassFields": true,
12 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
13 | "module": "ESNext",
14 | "skipLibCheck": true,
15 |
16 | /* Bundler mode */
17 | "moduleResolution": "bundler",
18 | "allowImportingTsExtensions": true,
19 | "resolveJsonModule": true,
20 | "isolatedModules": true,
21 | "noEmit": true,
22 | "jsx": "react-jsx",
23 |
24 | /* Linting */
25 | "strict": true,
26 | "noUnusedLocals": true,
27 | "noUnusedParameters": true,
28 | "noFallthroughCasesInSwitch": true
29 | },
30 | "include": ["src"],
31 | "references": [{ "path": "./tsconfig.node.json" }]
32 | }
33 |
--------------------------------------------------------------------------------
/docs/source/index.md:
--------------------------------------------------------------------------------
1 | # tierkreis documentation
2 |
3 | tierkreis `[ˈtiːɐ̯ˌkʁaɪ̯s]` is an HPC native workflow management system (WMS) for asynchronous hybrid jobs.
4 | tierkreis combines task-based workers with context dependent execution on various storage implementations.
5 |
6 | | WMS | Multi-Language | Multi-Executor | HPC-Support | Visualization | Repeat until convergence | Proprietary Workers |
7 | | --------- | :------------: | :------------: | :---------: | :-----------: | :----------------------: | :-----------------: |
8 | | tierkreis | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
9 |
10 | ## Quick Start
11 |
12 | ## Contents
13 |
14 | ```{toctree}
15 | :maxdepth: 3
16 | installation.md
17 | core_concepts.md
18 | getting_started.md
19 | visualization.md
20 | worker/index
21 | apidocs/index
22 | ```
23 |
--------------------------------------------------------------------------------
/infra/slurm_local/USAGE.md:
--------------------------------------------------------------------------------
1 | # How to run the slurm test
2 |
3 | For setup see the [README](README.md).
4 | In short:
5 |
6 | - install docker
7 | - From `infra/slurm_local`
8 | - make the local sbatch script executable `chmod +x sbatch`
9 | - build the containers with `docker compose build`
10 | - ensure the containers are running `docker compose up -d`
11 | - This will mount `~/.tierkreis` inside the containers on `/root/tierkreis/` and the tierkreis directory to `/tierkreis`
12 |
13 | To run the test we emulate `sbatch`.
14 | The devenv contains a script for this (same as the sbatch script), if you want, link it to a location in your path to make it available elsewhere.
15 |
16 | Run the test:
17 |
18 | ```
19 | pytest tierkreis/tests/executor/test_hpc_executor.py
20 | ```
21 |
22 | **Caveats**:
23 |
24 | - mpi workers is nondeterministic due to allocation from slurm
25 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | function Separator({
9 | className,
10 | orientation = "horizontal",
11 | decorative = true,
12 | ...props
13 | }: React.ComponentProps) {
14 | return (
15 |
25 | )
26 | }
27 |
28 | export { Separator }
29 |
--------------------------------------------------------------------------------
/infra/slurm_local/slurmdbd.conf:
--------------------------------------------------------------------------------
1 | #
2 | # Example slurmdbd.conf file.
3 | #
4 | # See the slurmdbd.conf man page for more information.
5 | #
6 | # Archive info
7 | #ArchiveJobs=yes
8 | #ArchiveDir="/tmp"
9 | #ArchiveSteps=yes
10 | #ArchiveScript=
11 | #JobPurge=12
12 | #StepPurge=1
13 | #
14 | # Authentication info
15 | AuthType=auth/munge
16 | #AuthInfo=/var/run/munge/munge.socket.2
17 | #
18 | # slurmDBD info
19 | DbdAddr=slurmdbd
20 | DbdHost=slurmdbd
21 | #DbdPort=6819
22 | SlurmUser=slurm
23 | #MessageTimeout=300
24 | DebugLevel=4
25 | #DefaultQOS=normal,standby
26 | LogFile=/var/log/slurm/slurmdbd.log
27 | PidFile=/var/run/slurmdbd/slurmdbd.pid
28 | #PluginDir=/usr/lib/slurm
29 | #PrivateData=accounts,users,usage,jobs
30 | #TrackWCKey=yes
31 | #
32 | # Database info
33 | StorageType=accounting_storage/mysql
34 | StorageHost=mysql
35 | StorageUser=slurm
36 | StoragePass=password
37 | #StorageLoc=slurm_acct_db
38 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/templates/map.jinja:
--------------------------------------------------------------------------------
1 | {% from "macros/breadcrumbs.html" import breadcrumbs_macro with context %}
2 | {% from "macros/common_head.html" import common_head_macro with context %}
3 | {% from "macros/header.html" import header_macro with context %}
4 |
5 |
6 |
7 | Tierkreis visualization: MAP node
8 | {{ common_head_macro() }}
9 |
10 |
11 |
12 |
13 | {{ header_macro() }}
14 |
15 | {{ breadcrumbs_macro(breadcrumbs, true) }}
16 |
17 |
18 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/tierkreis/tests/controller/test_codegen.py:
--------------------------------------------------------------------------------
1 | from types import NoneType
2 | import pytest
3 | from tierkreis.codegen import format_generic_type
4 | from tierkreis.controller.data.types import PType
5 | from tierkreis.idl.models import GenericType
6 |
7 | formats: list[tuple[type[PType], str]] = [
8 | (bool, "bool"),
9 | (int, "int"),
10 | (float, "float"),
11 | (str, "str"),
12 | (bytes, "bytes"),
13 | (NoneType, "NoneType"),
14 | (list[str], "list[str]"),
15 | (list[str | list[str | int]], "list[Union[str, list[Union[str, int]]]]"),
16 | (tuple[str], "tuple[str]"),
17 | (
18 | tuple[str | list[str | int], NoneType],
19 | "tuple[Union[str, list[Union[str, int]]], NoneType]",
20 | ),
21 | ]
22 |
23 |
24 | @pytest.mark.parametrize("ttype,expected", formats)
25 | def test_format_ttype(ttype: type[PType], expected: str):
26 | generic_type = GenericType.from_type(ttype)
27 |
28 | assert format_generic_type(generic_type, False, False) == expected
29 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/templates/macros/common_head.html:
--------------------------------------------------------------------------------
1 | {% macro common_head_macro() -%}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {%- endmacro %}
18 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/controller/executor/protocol.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from typing import Protocol
3 |
4 |
5 | class ControllerExecutor(Protocol):
6 | """The executor protocol defines how to run workers.
7 |
8 | An executor is responsible for running a worker(binary) in a given environment.
9 |
10 | """
11 |
12 | def run(self, launcher_name: str, worker_call_args_path: Path) -> None:
13 | """Run the node defined by the node_definition path.
14 |
15 | Specifies the worker to run by its launcher name.
16 | For example the function "builtins.iadd" will call the builtins worker's iadd function.
17 | The call arguments for the function call are retrieved retrieved from its location.
18 |
19 | :param launcher_name: module description of launcher to run.
20 | :type launcher_name: str
21 | :param worker_call_args_path: Location of the worker call args.
22 | :type worker_call_args_path: Path
23 | """
24 |
25 | ...
26 |
--------------------------------------------------------------------------------
/tierkreis/tests/idl/namespace1.py:
--------------------------------------------------------------------------------
1 | from typing import NamedTuple
2 | from tierkreis.controller.data.models import portmapping
3 | from tierkreis import Worker
4 | from tierkreis.controller.data.types import PType
5 |
6 | worker = Worker("TestNamespace")
7 |
8 |
9 | class ListItem(NamedTuple):
10 | i: int
11 |
12 |
13 | class NestedType(NamedTuple):
14 | city: str
15 |
16 |
17 | class IncludedType(NamedTuple):
18 | nested: NestedType
19 |
20 |
21 | @portmapping
22 | class A(NamedTuple):
23 | name: dict[str, str]
24 | age: int
25 |
26 |
27 | class B(NamedTuple):
28 | name: dict[str, str]
29 | age: int
30 |
31 |
32 | class C[T: PType](NamedTuple):
33 | a: list[int]
34 | b: B
35 | t: T
36 | included: IncludedType
37 | ol: list[ListItem]
38 |
39 |
40 | @worker.task()
41 | def foo(a: int, b: str) -> A: ...
42 | @worker.task()
43 | def bar() -> B: ...
44 | @worker.task()
45 | def z[T: PType](c: C[T]) -> C[T]: ...
46 |
47 |
48 | expected_namespace = worker.namespace
49 |
--------------------------------------------------------------------------------
/tierkreis_workers/pytket_worker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "tkr_pytket_worker"
3 | version = "0.1.5"
4 | description = "A tierkreis worker for pytket."
5 | readme = "README.md"
6 | requires-python = ">=3.12"
7 | dependencies = [
8 | "tierkreis",
9 | "pytket",
10 | "qnexus",
11 | "pytket_qirpass",
12 | ]
13 |
14 | [project.optional-dependencies]
15 | backend-qiskit = [
16 | "pytket-qiskit",
17 | ]
18 | backend-quantinuum = [
19 | "pytket-quantinuum",
20 | ]
21 |
22 |
23 | [build-system]
24 | requires = ["hatchling"]
25 | build-backend = "hatchling.build"
26 |
27 | [tool.hatch.build.targets.sdist]
28 | include = [
29 | "*.py",
30 | ]
31 | [tool.hatch.build.targets.wheel]
32 | include = [
33 | "*.py",
34 | ]
35 |
36 | [tool.uv.sources]
37 | tierkreis = { workspace = true }
38 | pytket-qiskist = { git = "https://github.com/CQCL/pytket-qiskit" }
39 | pytket-quantinuum = { git = "https://github.com/CQCL/pytket-quantinuum" }
40 |
41 | [project.scripts]
42 | tkr_pytket_worker = "main:main"
43 |
--------------------------------------------------------------------------------
/tierkreis/docs/api-docs/_templates/autosummary/class.rst:
--------------------------------------------------------------------------------
1 | ..
2 | Custom class template to make sphinx-autosummary list the full API doc after
3 | the summary. See https://github.com/sphinx-doc/sphinx/issues/7912
4 |
5 | {{ name | escape | underline}}
6 |
7 | .. currentmodule:: {{ module }}
8 |
9 | .. autoclass:: {{ objname }}
10 | :members:
11 | :show-inheritance:
12 | :inherited-members:
13 |
14 | {% block methods %}
15 | {% if methods %}
16 | .. rubric:: {{ _('Methods') }}
17 |
18 | .. autosummary::
19 | :nosignatures:
20 | {% for item in methods %}
21 | {%- if not item.startswith('_') %}
22 | ~{{ name }}.{{ item }}
23 | {%- endif -%}
24 | {%- endfor %}
25 | {% endif %}
26 | {% endblock %}
27 |
28 | {% block attributes %}
29 | {% if attributes %}
30 | .. rubric:: {{ _('Attributes') }}
31 |
32 | .. autosummary::
33 | {% for item in attributes %}
34 | ~{{ name }}.{{ item }}
35 | {%- endfor %}
36 | {% endif %}
37 | {% endblock %}
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve Tierkreis. Thank you!
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Describe the bug
11 | A clear and concise description of what the bug is
12 |
13 | ## To Reproduce
14 | Steps to reproduce the behavior including a minimal reproducible example:
15 |
16 | ** Configuration
17 | 1. Workflow
18 | 2. Executor
19 | 3. Workers
20 |
21 | ## Expected behavior
22 | A clear and concise description of what you expected to happen.
23 |
24 |
25 | ## Desktop (please complete the following information):
26 | - OS: [e.g. Ubuntu 24 `lsb_release -a`]
27 | - Python Version
28 | - UV Version
29 |
30 | Run `pip freeze` and put the output in the `` element below.
31 |
32 |
33 |
34 |
35 |
36 | ## Additional context & Logs
37 | Add any other context about the problem here.
38 | - Output of log files in `~/.tierkreis/checkpoints//logs` or `~/.tierkreis/checkpoints///errors`
39 |
--------------------------------------------------------------------------------
/docs/source/getting_started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | ## Step by step tutorials
4 |
5 | A sequence of tutorials that cover the fundamentals of writing and operating Tierkreis workflows.
6 |
7 | [Our first graph](tutorial/builtins.md)
8 |
9 | [Graph inputs and outputs](tutorial/inputs.md)
10 |
11 | [Nested graphs using Eval](tutorial/eval.md)
12 |
13 | [Iteration using Loop](tutorial/loop.md)
14 |
15 | [Parallel computation using Map](tutorial/map.md)
16 |
17 | ## Worker examples
18 |
19 | Tutorials on writing workers that provide additional tasks.
20 | For worker libraries see [this document](workers.md)
21 |
22 | ### Tierkreis Python library
23 |
24 | [Hello world worker](worker/hello_world.md)
25 |
26 | [Complex types in Tierkreis Python workers](worker/complex_types.md)
27 |
28 | ### Other Workers
29 |
30 | [External workers with an IDL](worker/external_workers.md)
31 |
32 | ## Executors
33 |
34 | [Overview](executors/overview.md)
35 |
36 | [Shell Executors](executors/shell.md)
37 |
38 | [HPC Executors](executors/hpc.md)
39 |
--------------------------------------------------------------------------------
/tierkreis/tests/errors/failing_worker/stubs.py:
--------------------------------------------------------------------------------
1 | """Code generated from failing_worker namespace. Please do not edit."""
2 |
3 | from typing import NamedTuple
4 | from tierkreis.controller.data.models import TKR
5 |
6 |
7 | class fail(NamedTuple):
8 | @staticmethod
9 | def out() -> type[TKR[int]]: # noqa: F821 # fmt: skip
10 | return TKR[int] # noqa: F821 # fmt: skip
11 |
12 | @property
13 | def namespace(self) -> str:
14 | return "failing_worker"
15 |
16 |
17 | class wont_fail(NamedTuple):
18 | @staticmethod
19 | def out() -> type[TKR[int]]: # noqa: F821 # fmt: skip
20 | return TKR[int] # noqa: F821 # fmt: skip
21 |
22 | @property
23 | def namespace(self) -> str:
24 | return "failing_worker"
25 |
26 |
27 | class exit_code_1(NamedTuple):
28 | @staticmethod
29 | def out() -> type[TKR[int]]: # noqa: F821 # fmt: skip
30 | return TKR[int] # noqa: F821 # fmt: skip
31 |
32 | @property
33 | def namespace(self) -> str:
34 | return "failing_worker"
35 |
--------------------------------------------------------------------------------
/tierkreis/tests/cli/data/factorial:
--------------------------------------------------------------------------------
1 | {"nodes":[{"value":-1,"inputs":{},"type":"const","outputs":["value"]},{"value":1,"inputs":{},"type":"const","outputs":["value"]},{"name":"n","inputs":{},"type":"input","outputs":["n"]},{"name":"factorial","inputs":{},"type":"input","outputs":["factorial"]},{"function_name":"builtins.igt","inputs":{"a":[2,"n"],"b":[1,"value"]},"type":"function","outputs":["value"]},{"function_name":"builtins.iadd","inputs":{"a":[0,"value"],"b":[2,"n"]},"type":"function","outputs":["value"]},{"graph":[3,"factorial"],"inputs":{"n":[5,"value"],"factorial":[3,"factorial"]},"type":"eval","outputs":["factorial_output"]},{"function_name":"builtins.itimes","inputs":{"a":[2,"n"],"b":[6,"factorial_output"]},"type":"function","outputs":["value"]},{"value":1,"inputs":{},"type":"const","outputs":["value"]},{"pred":[4,"value"],"if_true":[7,"value"],"if_false":[8,"value"],"type":"ifelse","inputs":{},"outputs":["value"]},{"inputs":{"factorial_output":[9,"value"]},"type":"output","outputs":[]}],"fixed_inputs":{},"graph_inputs":["n","factorial"],"graph_output_idx":10}
--------------------------------------------------------------------------------
/examples/non_standard_tkr_dir.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from uuid import UUID
3 |
4 | from tierkreis.consts import PACKAGE_PATH
5 | from tierkreis import run_graph
6 | from tierkreis.controller.executor.multiple import MultipleExecutor
7 | from tierkreis.storage import FileStorage, read_outputs
8 | from tierkreis.executor import UvExecutor, ShellExecutor
9 |
10 | from signing_graph import signing_graph
11 |
12 |
13 | storage = FileStorage(
14 | UUID(int=108),
15 | do_cleanup=True,
16 | tierkreis_directory=Path.home() / ".tierkreis" / "checkpoints2",
17 | )
18 | uv = UvExecutor(PACKAGE_PATH / ".." / "examples" / "example_workers", storage.logs_path)
19 | shell = ShellExecutor(
20 | PACKAGE_PATH / ".." / "examples" / "example_workers", storage.workflow_dir
21 | )
22 | executor = MultipleExecutor(uv, {"shell": shell}, {"openssl_worker": "shell"})
23 |
24 | run_graph(storage, executor, signing_graph(), {}, polling_interval_seconds=0.1)
25 | out = read_outputs(signing_graph(), storage)
26 | print(out)
27 | assert isinstance(out, bool)
28 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/idl/spec.py:
--------------------------------------------------------------------------------
1 | """Combinators for the Tierkreis worker IDL.
2 |
3 | We use
4 | https://typespec.io/docs/language-basics/models/
5 | https://typespec.io/docs/language-basics/interfaces/
6 | as well as an extra decorator @portmapping.
7 | """
8 |
9 | from tierkreis.idl.models import Interface, Method, Model, TypedArg
10 |
11 | from tierkreis.idl.parser import lit, seq
12 | from tierkreis.idl.type_symbols import generic_t, ident, type_symbol
13 |
14 |
15 | type_decl = ((ident << lit(":")) & type_symbol).map(lambda x: TypedArg(*x))
16 | model = seq(
17 | lit("@portmapping").opt().map(lambda x: x is not None) << lit("model"),
18 | generic_t,
19 | lit("{") >> type_decl.rep(lit(";")) << lit("}"),
20 | ).map(lambda x: Model(*x))
21 | method = seq(
22 | generic_t,
23 | lit("(") >> type_decl.rep(lit(",")) << lit(")") << lit(":"),
24 | type_symbol,
25 | ).map(lambda x: Method(*x))
26 | interface = (
27 | (lit("interface") >> ident << lit("{")) & method.rep(lit(";")) << lit("}")
28 | ).map(lambda x: Interface(*x))
29 | spec = model.rep() & interface
30 |
--------------------------------------------------------------------------------
/.github/workflows/pr_main.yml:
--------------------------------------------------------------------------------
1 | name: PR against main
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | lint:
11 | uses: ./.github/workflows/run_check_matrix.yml
12 | with:
13 | command_name: lint
14 | command: just lint
15 |
16 | test:
17 | uses: ./.github/workflows/run_check_matrix.yml
18 | with:
19 | command_name: test
20 | command: just test
21 |
22 | test-workers:
23 | uses: ./.github/workflows/run_check_matrix.yml
24 | with:
25 | command_name: test-workers
26 | command: just test-workers
27 |
28 | examples:
29 | uses: ./.github/workflows/run_check_matrix.yml
30 | with:
31 | command_name: examples
32 | command: just examples
33 |
34 | docs:
35 | uses: ./.github/workflows/run_check_matrix.yml
36 | with:
37 | command_name: docs
38 | command: just docs
39 |
40 | check-stubs:
41 | uses: ./.github/workflows/run_check_matrix.yml
42 | with:
43 | command_name: check-stubs
44 | command: just check-stubs
45 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6 | return (
7 |
18 | )
19 | }
20 |
21 | export { Input }
22 |
--------------------------------------------------------------------------------
/devenv.nix:
--------------------------------------------------------------------------------
1 | { pkgs, lib, ... }:
2 |
3 | {
4 |
5 | packages = [
6 | pkgs.just
7 | pkgs.graphviz
8 | ] ++ lib.optionals pkgs.stdenv.isDarwin (with pkgs.darwin.apple_sdk; [
9 | frameworks.CoreServices
10 | frameworks.CoreFoundation
11 | frameworks.Security
12 | frameworks.SystemConfiguration
13 | ]);
14 |
15 | git-hooks.hooks = {
16 | pyright.enable = true;
17 | ruff.enable = true;
18 | ruff-format.enable = true;
19 | };
20 |
21 | # https://devenv.sh/languages/
22 | languages.python = {
23 | enable = true;
24 | uv.enable = true;
25 | };
26 |
27 | languages.rust.enable = true;
28 |
29 | languages.javascript = {
30 | enable = true;
31 | bun.enable = true;
32 | bun.install.enable = true;
33 | directory = "./tierkreis_visualization";
34 | };
35 |
36 | # This allows building the type-check (pyo3) module on MacOSX "Apple Silicon"
37 | enterShell =
38 | if pkgs.stdenv.isDarwin && pkgs.stdenv.isAarch64 then ''
39 | export RUSTFLAGS="$RUSTFLAGS -C link-arg=-undefined -C link-arg=dynamic_lookup"
40 | '' else '''';
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/templates/workflows.html:
--------------------------------------------------------------------------------
1 | {% from "macros/common_head.html" import common_head_macro with context %}
2 | {% from "macros/header.html" import header_macro with context %}
3 | {% from "macros/breadcrumbs.html" import breadcrumbs_macro with context %}
4 |
5 |
6 | Tierkreis visualization: workflows
7 | {{ common_head_macro() }}
8 |
9 |
10 | {{ header_macro() }}
11 |
12 | {{ breadcrumbs_macro([["/workflows"]]) }}
13 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/infra/slurm_local/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Giovanni Torres
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.
22 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/static/network.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | /**
4 | * Connect to an event stream. Pass in a nodes and edges list to mutate.
5 | * @param {PyNode[]} nodes - Event stream url to subscribe to.
6 | * @param {PyEdge[]} edges - Network vis library.
7 | * @param {string} name - List of graph edges.
8 | */
9 |
10 |
11 |
12 | function createNetwork(nodes, edges, name) {
13 | const visnodes = new vis.DataSet(nodes.map(createJSNode));
14 | const visedges = new vis.DataSet(edges.map(createJSEdge));
15 |
16 | const container = document.getElementById("mynetwork");
17 | const data = { nodes: visnodes, edges: visedges };
18 | const options = {
19 | layout: { hierarchical: { direction: "LR", sortMethod: "directed" } },
20 | nodes: {
21 | margin: 12,
22 | }
23 | };
24 |
25 | const network = new vis.Network(container, data, options);
26 | network.on("doubleClick", function (params) {
27 | let nodes = params.nodes;
28 | if (nodes.length === 1) {
29 | window.location.href = `${window.location.href}.${name}${nodes[0]}`;
30 | }
31 | });
32 | return [network, visnodes, visedges];
33 | }
34 |
--------------------------------------------------------------------------------
/tierkreis/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "tierkreis"
3 | description = "Python client and utilities for tierkreis."
4 | readme = "README.md"
5 | version = "2.0.9"
6 | authors = [
7 | { name = "Seyon Sivarajah", email = "seyon.sivarajah@quantinuum.com" },
8 | { name = "Lukas Heidemann", email = "lukas.heidemann@quantinuum.com" },
9 | { name = "John Children", email = "john.children@quantinuum.com" },
10 | { name = "Alan Lawrence", email = "alan.lawrence@quantinuum.com" },
11 | { name = "Matthew Burke", email = "matthew.burke@quantinuum.com" },
12 | { name = "Philipp Seitz", email = "philipp.seitz@quantinuum.com" },
13 | ]
14 |
15 | requires-python = ">=3.12"
16 | dependencies = [
17 | "pydantic~=2.5",
18 | ]
19 |
20 | [dependency-groups]
21 | dev = [
22 | "ipykernel>=6.29.5",
23 | "pytest",
24 | ]
25 | build = ["build[uv]"]
26 |
27 | [build-system]
28 | requires = ["hatchling"]
29 | build-backend = "hatchling.build"
30 |
31 | [tool.ruff]
32 | target-version = "py312"
33 | extend-exclude = []
34 |
35 | [tool.pyright]
36 | include = ["."]
37 | exclude = []
38 | ignore = []
39 |
40 | [project.scripts]
41 | tkr = "tierkreis.cli.tkr:main"
42 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 webkid GmbH
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.
22 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/nodes/types.ts:
--------------------------------------------------------------------------------
1 | import { InfoProps } from "@/components/types";
2 | import { Edge, type Node } from "@xyflow/react";
3 |
4 | export type PyNode = {
5 | id: string | number;
6 | status: "Not started" | "Started" | "Error" | "Finished";
7 | function_name: string;
8 | node_location: string;
9 | value?: unknown;
10 | started_time: string;
11 | finished_time: string;
12 | };
13 | export type BackendNode = Node<{
14 | name: string;
15 | status: "Not started" | "Started" | "Error" | "Finished";
16 | handles: {
17 | inputs: string[];
18 | outputs: string[];
19 | };
20 | hidden_handles?: {
21 | inputs: string[];
22 | outputs: string[];
23 | };
24 | hidden_edges?: Edge[];
25 | workflowId: string;
26 | node_location: string;
27 | id: string;
28 | title: string;
29 | label?: string;
30 | pinned: boolean;
31 | value: string | null;
32 | setInfo?: (info: InfoProps) => void;
33 | is_expanded: boolean;
34 | isTooltipOpen: boolean;
35 | hoveredId: string;
36 | setHoveredId: (id: string) => void;
37 | started_time: string;
38 | finished_time: string;
39 | }>;
40 | export type AppNode = BackendNode;
41 |
--------------------------------------------------------------------------------
/tierkreis/tests/cli/data/sample_graph:
--------------------------------------------------------------------------------
1 | {"nodes": [{"value": 0, "inputs": {}, "type": "const", "outputs": ["value"]}, {"value": 6, "inputs": {}, "type": "const", "outputs": ["value"]}, {"value": {"nodes": [{"name": "doubler_input", "inputs": {}, "type": "input", "outputs": ["doubler_input"]}, {"name": "intercept", "inputs": {}, "type": "input", "outputs": ["intercept"]}, {"value": 2, "inputs": {}, "type": "const", "outputs": ["value"]}, {"function_name": "builtins.itimes", "inputs": {"a": [0, "doubler_input"], "b": [2, "value"]}, "type": "function", "outputs": ["value"]}, {"function_name": "builtins.iadd", "inputs": {"a": [3, "value"], "b": [1, "intercept"]}, "type": "function", "outputs": ["value"]}, {"inputs": {"doubler_output": [4, "value"]}, "type": "output", "outputs": []}], "fixed_inputs": {}, "graph_inputs": ["intercept", "doubler_input"], "graph_output_idx": 5}, "inputs": {}, "type": "const", "outputs": []}, {"graph": [2, "value"], "inputs": {"doubler_input": [1, "value"], "intercept": [0, "value"]}, "type": "eval", "outputs": ["doubler_output"]}, {"inputs": {"simple_eval_output": [3, "doubler_output"]}, "type": "output", "outputs": []}], "fixed_inputs": {}, "graph_inputs": [], "graph_output_idx": 4}
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/app_config.py:
--------------------------------------------------------------------------------
1 | from contextlib import asynccontextmanager
2 | from enum import Enum
3 | from typing import Callable
4 | from uuid import UUID
5 | import webbrowser
6 | from fastapi import FastAPI
7 | import starlette.datastructures
8 | from tierkreis.controller.storage.filestorage import ControllerFileStorage
9 | from tierkreis.controller.storage.graphdata import GraphDataStorage
10 | from tierkreis.controller.storage.protocol import ControllerStorage
11 |
12 |
13 | @asynccontextmanager
14 | async def lifespan(app: FastAPI):
15 | if app.state.storage_type == StorageType.GRAPHDATA:
16 | webbrowser.open("http://localhost:8000/", new=0, autoraise=False)
17 | yield
18 |
19 |
20 | class StorageType(Enum):
21 | FILESTORAGE = ControllerFileStorage
22 | GRAPHDATA = GraphDataStorage
23 |
24 |
25 | class State(starlette.datastructures.State):
26 | """Typed App state information."""
27 |
28 | get_storage_fn: Callable[[UUID], ControllerStorage]
29 | storage_type: StorageType
30 |
31 |
32 | class App(FastAPI):
33 | """FastAPI App with custom state."""
34 |
35 | state: State # pyright: ignore[reportIncompatibleVariableOverride]
36 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/nodes/hide_children.ts:
--------------------------------------------------------------------------------
1 | import { BackendNode } from "./types";
2 | import { Edge } from "@xyflow/react";
3 |
4 | export function hideChildren(
5 | nodeId: string,
6 | oldNodes: BackendNode[],
7 | oldEdges: Edge[]
8 | ) {
9 | let hidden_edges: Edge[] = [];
10 | oldNodes = oldNodes
11 | .map((node) => {
12 | if (node.parentId?.startsWith(nodeId)) {
13 | return undefined;
14 | }
15 | if (node.id === nodeId) {
16 | hidden_edges = node.data.hidden_edges ?? [];
17 | node.position = { x: 0, y: 0 };
18 | node.data.handles = node.data.hidden_handles ?? {
19 | inputs: [],
20 | outputs: [],
21 | };
22 | node.data.is_expanded = false;
23 | node.style = {
24 | width: 180,
25 | height: 124,
26 | };
27 | }
28 | return node;
29 | })
30 | .filter((node): node is BackendNode => node !== undefined);
31 |
32 | oldEdges.filter(
33 | (edge) =>
34 | edge.target.startsWith(nodeId + ":") &&
35 | edge.source.startsWith(nodeId + ":")
36 | );
37 | return {
38 | nodes: [...oldNodes],
39 | edges: [...oldEdges, ...hidden_edges],
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/tierkreis/docs/api-docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | # See https://www.sphinx-doc.org/en/master/usage/configuration.html
3 |
4 |
5 | project = "Tierkreis Python"
6 | copyright = "2024, Quantinuum"
7 | author = "Quantinuum"
8 |
9 | extensions = [
10 | "sphinx.ext.napoleon",
11 | "sphinx.ext.autodoc",
12 | "sphinx.ext.coverage",
13 | "sphinx.ext.autosummary",
14 | "sphinx.ext.viewcode",
15 | "sphinx.ext.intersphinx",
16 | ]
17 |
18 | html_theme = "sphinx_book_theme"
19 |
20 | html_title = "Tierkreis python package API documentation."
21 |
22 | html_theme_options = {
23 | "repository_url": "https://github.com/CQCL/tierkreis",
24 | "use_repository_button": True,
25 | "navigation_with_keys": True,
26 | "logo": {
27 | "image_light": "_static/Quantinuum_logo_black.png",
28 | "image_dark": "_static/Quantinuum_logo_white.png",
29 | },
30 | }
31 |
32 | html_static_path = ["../_static"]
33 | html_css_files = ["custom.css"]
34 |
35 | autosummary_generate = True
36 |
37 | templates_path = ["_templates"]
38 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
39 |
40 | intersphinx_mapping = {
41 | "python": ("https://docs.python.org/3/", None),
42 | }
43 |
--------------------------------------------------------------------------------
/examples/example_workers/qsci_worker/qsci/active_space.py:
--------------------------------------------------------------------------------
1 | def get_frozen(
2 | mo_occ: list[int],
3 | n_cas: int,
4 | n_elecas: int,
5 | ) -> list[int]:
6 | frozen = []
7 | n_orbs = len(mo_occ)
8 | n_core = get_n_core(mo_occ, n_elecas)
9 | if n_core + n_cas > n_orbs:
10 | raise ValueError("active space is larger than basis set")
11 | for i in range(n_orbs):
12 | # print(i, i < n_core, i >= n_core + n_cas)
13 | if i < n_core or i >= n_core + n_cas:
14 | frozen.append(i)
15 | return frozen
16 |
17 |
18 | def get_n_core(
19 | mo_occ: list[int],
20 | n_elecas: int,
21 | ) -> int:
22 | n_elec = int(sum(mo_occ))
23 | n_core = (n_elec - n_elecas) // 2
24 | return n_core
25 |
26 |
27 | def get_n_active(
28 | mo_occ: list[int],
29 | n_cas: int,
30 | n_elecas: int,
31 | ) -> int:
32 | n_frozen = len(get_frozen(mo_occ, n_cas, n_elecas))
33 | n_active = len(mo_occ) - n_frozen
34 | return n_active
35 |
36 |
37 | def get_n_virtual(
38 | mo_occ: list[int],
39 | n_cas: int,
40 | n_elecas: int,
41 | ) -> int:
42 | n_frozen = len(get_frozen(mo_occ, n_cas, n_elecas))
43 | n_core = get_n_core(mo_occ, n_elecas)
44 | n_virtual = n_frozen - n_core
45 | return n_virtual
46 |
--------------------------------------------------------------------------------
/examples/example_workers/chemistry_worker/chemistry/active_space.py:
--------------------------------------------------------------------------------
1 | def get_frozen(
2 | mo_occ: list[int],
3 | n_cas: int,
4 | n_elecas: int,
5 | ) -> list[int]:
6 | frozen = []
7 | n_orbs = len(mo_occ)
8 | n_core = get_n_core(mo_occ, n_elecas)
9 | if n_core + n_cas > n_orbs:
10 | raise ValueError("active space is larger than basis set")
11 | for i in range(n_orbs):
12 | # print(i, i < n_core, i >= n_core + n_cas)
13 | if i < n_core or i >= n_core + n_cas:
14 | frozen.append(i)
15 | return frozen
16 |
17 |
18 | def get_n_core(
19 | mo_occ: list[int],
20 | n_elecas: int,
21 | ) -> int:
22 | n_elec = int(sum(mo_occ))
23 | n_core = (n_elec - n_elecas) // 2
24 | return n_core
25 |
26 |
27 | def get_n_active(
28 | mo_occ: list[int],
29 | n_cas: int,
30 | n_elecas: int,
31 | ) -> int:
32 | n_frozen = len(get_frozen(mo_occ, n_cas, n_elecas))
33 | n_active = len(mo_occ) - n_frozen
34 | return n_active
35 |
36 |
37 | def get_n_virtual(
38 | mo_occ: list[int],
39 | n_cas: int,
40 | n_elecas: int,
41 | ) -> int:
42 | n_frozen = len(get_frozen(mo_occ, n_cas, n_elecas))
43 | n_core = get_n_core(mo_occ, n_elecas)
44 | n_virtual = n_frozen - n_core
45 | return n_virtual
46 |
--------------------------------------------------------------------------------
/examples/nexus_polling.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from uuid import UUID
3 | from qnexus import AerConfig
4 | from pytket.qasm.qasm import circuit_from_qasm
5 |
6 | from tierkreis.consts import PACKAGE_PATH
7 | from tierkreis.controller import run_graph
8 | from tierkreis.graphs.nexus.submit_poll import nexus_submit_and_poll
9 | from tierkreis.storage import FileStorage, read_outputs
10 | from tierkreis.executor import UvExecutor
11 |
12 | aer_config = AerConfig()
13 | circuit = circuit_from_qasm(Path(__file__).parent / "data" / "ghz_state_n23.qasm")
14 | circuits = [circuit]
15 |
16 |
17 | def main():
18 | g = nexus_submit_and_poll()
19 | storage = FileStorage(UUID(int=107), do_cleanup=True)
20 | executor = UvExecutor(PACKAGE_PATH / ".." / "tierkreis_workers", storage.logs_path)
21 |
22 | run_graph(
23 | storage,
24 | executor,
25 | g,
26 | {
27 | "project_name": "2025-tkr-test",
28 | "job_name": "job-1",
29 | "circuits": circuits,
30 | "n_shots": [30] * len(circuits),
31 | "backend_config": aer_config,
32 | },
33 | polling_interval_seconds=0.1,
34 | )
35 | res = read_outputs(g, storage)
36 | print(res)
37 |
38 |
39 | if __name__ == "__main__":
40 | main()
41 |
--------------------------------------------------------------------------------
/infra/slurm_local/main.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.12"
3 | # dependencies = ["tierkreis", "mpi4py"]
4 | # [tool.uv.sources]
5 | # tierkreis = { path = "/tierkreis", editable = true }
6 | # ///
7 | import logging
8 | import socket
9 | from sys import argv
10 |
11 | from tierkreis import Worker
12 | from mpi4py import MPI # type: ignore
13 |
14 | logger = logging.getLogger(__name__)
15 | worker = Worker("slurm_mpi_worker")
16 |
17 | comm = MPI.COMM_WORLD
18 |
19 |
20 | def _proc_info() -> dict[str, int | str]:
21 | """Returns a dictionary with the current process's details."""
22 | comm = MPI.COMM_WORLD
23 | rank = comm.Get_rank()
24 | hostname = socket.gethostname()
25 | return {"rank": rank, "hostname": hostname}
26 |
27 |
28 | @worker.task()
29 | def mpi_rank_info() -> str | None:
30 | comm = MPI.COMM_WORLD
31 | rank = comm.Get_rank()
32 | size = comm.Get_size()
33 | info = _proc_info()
34 | all_processes_info = comm.gather(info, root=0)
35 | print(all_processes_info)
36 | if rank == 0:
37 | return "\n".join(
38 | f"Rank {info['rank']} out of {size} on {info['hostname']}."
39 | for info in all_processes_info
40 | )
41 | return None
42 |
43 |
44 | if __name__ == "__main__":
45 | worker.app(argv)
46 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/static/utils.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | /**
4 | * Creates a JSEdge from a PyEdge model.
5 | * @param {PyEdge} py_edge
6 | * @returns {JSEdge}
7 | */
8 | function createJSEdge(py_edge) {
9 | return {
10 | id: `${py_edge.from_port}->${py_edge.to_port}:${py_edge.from_node}->${py_edge.to_node}`,
11 | from: py_edge.from_node,
12 | to: py_edge.to_node,
13 | title: `${py_edge.from_port}->${py_edge.to_port}`,
14 | label: `${py_edge.from_port}->${py_edge.to_port}`,
15 | arrows: "to",
16 | dashes: py_edge.conditional,
17 | };
18 | }
19 |
20 | /**
21 | * Creates a JSNode from a PyNode model.
22 | * @param {PyNode} py_node
23 | * @returns {JSNode} A newly created user.
24 | */
25 | function createJSNode(py_node) {
26 | return {
27 | id: py_node.id,
28 | title: `Function name: ${py_node.function_name}\nStatus: ${py_node.status}`,
29 | label: py_node.function_name,
30 | status: py_node.status,
31 | color: {
32 | "Not started": colors.neutral[0],
33 | Started: colors.amber.primary,
34 | Error: colors.red.primary,
35 | Finished: colors.green.primary,
36 | }[py_node.status],
37 | shape: ["input", "output"].includes(py_node.function_name)
38 | ? "ellipse"
39 | : "box",
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/examples/example_workers/chemistry_worker/stubs.py:
--------------------------------------------------------------------------------
1 | """Code generated from chemistry_worker namespace. Please do not edit."""
2 |
3 | from typing import NamedTuple, Protocol
4 | from tierkreis.controller.data.models import TKR
5 | from tierkreis.controller.data.types import Struct
6 |
7 |
8 | class CompleteActiveSpace(Struct, Protocol):
9 | n: int # noqa: F821 # fmt: skip
10 | n_ele: int # noqa: F821 # fmt: skip
11 |
12 |
13 | class Hamiltonian(Struct, Protocol):
14 | h0: float # noqa: F821 # fmt: skip
15 | h1: list[list[float]] # noqa: F821 # fmt: skip
16 | h2: list[list[list[list[float]]]] # noqa: F821 # fmt: skip
17 |
18 |
19 | class Molecule(Struct, Protocol):
20 | basis: str # noqa: F821 # fmt: skip
21 | charge: int # noqa: F821 # fmt: skip
22 | geometry: list[tuple[str, list[float]]] # noqa: F821 # fmt: skip
23 |
24 |
25 | class make_ham(NamedTuple):
26 | molecule: TKR[Molecule] # noqa: F821 # fmt: skip
27 | mo_occ: TKR[list[int]] # noqa: F821 # fmt: skip
28 | cas: TKR[CompleteActiveSpace] # noqa: F821 # fmt: skip
29 |
30 | @staticmethod
31 | def out() -> type[TKR[Hamiltonian]]: # noqa: F821 # fmt: skip
32 | return TKR[Hamiltonian] # noqa: F821 # fmt: skip
33 |
34 | @property
35 | def namespace(self) -> str:
36 | return "chemistry_worker"
37 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/templates/macros/header.html:
--------------------------------------------------------------------------------
1 | {% macro header_macro(links) -%}
2 |
20 | {%- endmacro %}
21 |
--------------------------------------------------------------------------------
/.github/actions/setup_deps/action.yml:
--------------------------------------------------------------------------------
1 | name: Set up dependencies for tasks
2 |
3 | inputs:
4 | python_version:
5 | description: "Python version to use."
6 | required: false
7 | default: "3.12"
8 | type: string
9 | include_node:
10 | description: "Install node, bun etc.."
11 | type: string
12 | required: false
13 | default: "no"
14 | include_just:
15 | description: "Install the just task runner."
16 | type: string
17 | required: false
18 | default: "yes"
19 |
20 | runs:
21 | using: "composite"
22 | steps:
23 | - name: Set up uv & venv
24 | uses: astral-sh/setup-uv@v5
25 | with:
26 | version: "0.6.6"
27 | enable-cache: true
28 | cache-suffix: ${{ inputs.python_version }}
29 | python-version: ${{ inputs.python_version }}
30 | cache-dependency-glob: uv.lock
31 |
32 | - name: Set up just
33 | if: inputs.include_just == 'yes'
34 | uses: extractions/setup-just@v3
35 |
36 | - name: Set up node
37 | uses: actions/setup-node@v5
38 | if: inputs.include_node == 'yes'
39 | with:
40 | node-version: "24.8.0"
41 | package-manager-cache: false
42 |
43 | - name: Set up bun
44 | uses: oven-sh/setup-bun@v2
45 | if: inputs.include_node == 'yes'
46 | with:
47 | bun-version: "1.2.13"
48 | no-cache: true
49 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/data/logs.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 |
3 | import { URL } from "./constants";
4 |
5 | const fetchText = async (
6 | workflowId: string,
7 | node_location: string,
8 | type: "errors" | "logs"
9 | ) => {
10 | const url = `${URL}/${workflowId}/nodes/${node_location}/${type}`;
11 | const response = await fetch(url, {
12 | method: "GET",
13 | headers: { Accept: "application/text" },
14 | });
15 | if (!response.ok) {
16 | throw new Error("Network response was not ok");
17 | }
18 | return response.text();
19 | };
20 |
21 | export const useLogs = (
22 | workflowId: string,
23 | node_location: string,
24 | node_title: string
25 | ) => {
26 | return useQuery({
27 | queryKey: ["logs", workflowId, node_location],
28 | queryFn: () => fetchText(workflowId, node_location, "logs"),
29 | enabled: !!workflowId && !!node_location && node_title == "Function",
30 | });
31 | };
32 |
33 | export const useErrors = (
34 | workflowId: string,
35 | node_location: string,
36 | status: "Not started" | "Started" | "Error" | "Finished"
37 | ) => {
38 | return useQuery({
39 | queryKey: ["errors", workflowId, node_location],
40 | queryFn: () => fetchText(workflowId, node_location, "errors"),
41 | enabled: !!workflowId && !!node_location && status === "Error",
42 | });
43 | };
44 |
--------------------------------------------------------------------------------
/examples/example_workers/openssl_worker/test/tierkreis.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Boilerplate for reading WorkerCallArgs in Bash.
4 |
5 | worker_call_args_file=$1
6 | checkpoints_directory=~/.tierkreis/checkpoints
7 |
8 | echo "START openssl_worker $checkpoints_directory/$worker_call_args_file"
9 | echo $(jq "." $checkpoints_directory/$worker_call_args_file)
10 |
11 | function_name=$checkpoints_directory/$(jq -r ".function_name" $checkpoints_directory/$worker_call_args_file)
12 | done_path=$checkpoints_directory/$(jq -r ".done_path" $checkpoints_directory/$worker_call_args_file)
13 | error_path=$checkpoints_directory/$(jq -r ".error_path" $checkpoints_directory/$worker_call_args_file)
14 | logs_path=$checkpoints_directory/$(jq -r ".logs_path" $checkpoints_directory/$worker_call_args_file)
15 | output_dir=$checkpoints_directory/$(jq -r ".output_dir" $checkpoints_directory/$worker_call_args_file)
16 |
17 | while read key ; do
18 | declare "input_${key}_file"="$checkpoints_directory/$(jq -r ".inputs.$key" $checkpoints_directory/$worker_call_args_file)"
19 | done < <(jq -r '.inputs|keys[]' $checkpoints_directory/$worker_call_args_file)
20 |
21 | while read key ; do
22 | declare "output_${key}_file"="$checkpoints_directory/$(jq -r ".outputs.$key" $checkpoints_directory/$worker_call_args_file)"
23 | done < <(jq -r '.outputs|keys[]' $checkpoints_directory/$worker_call_args_file)
24 |
--------------------------------------------------------------------------------
/examples/scipy_graph.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from typing import NamedTuple
3 | from uuid import UUID
4 | from tierkreis.builder import GraphBuilder
5 | from tierkreis.controller.data.core import EmptyModel
6 | from tierkreis.controller.data.models import TKR, OpaqueType
7 | from example_workers.scipy_worker.stubs import (
8 | transpose,
9 | reshape,
10 | linspace,
11 | add_point,
12 | eval_point,
13 | )
14 | from tierkreis.storage import FileStorage, read_outputs
15 | from tierkreis.executor import UvExecutor
16 | from tierkreis import run_graph
17 |
18 | NDArray = OpaqueType["numpy.ndarray"]
19 |
20 |
21 | class ScipyOutputs(NamedTuple):
22 | a: TKR[NDArray]
23 | p: TKR[float]
24 |
25 |
26 | g = GraphBuilder(EmptyModel, ScipyOutputs)
27 | onedim = g.task(linspace(g.const(0), g.const(10)))
28 |
29 | pointed = g.task(add_point(onedim, g.const(0)))
30 | scalar = g.task(eval_point(pointed))
31 |
32 | twodim = g.task(reshape(onedim, g.const([5, 10])))
33 | a = g.task(transpose(twodim))
34 | g.outputs(ScipyOutputs(a, scalar))
35 |
36 |
37 | if __name__ == "__main__":
38 | storage = FileStorage(UUID(int=207), do_cleanup=True, name="scipy_graph")
39 | executor = UvExecutor(Path(__file__).parent / "example_workers", storage.logs_path)
40 | run_graph(storage, executor, g, {})
41 |
42 | outputs = read_outputs(g, storage)
43 | print(outputs)
44 |
--------------------------------------------------------------------------------
/examples/data/ghz_state_n23.qasm:
--------------------------------------------------------------------------------
1 | OPENQASM 2.0;
2 | include "qelib1.inc";
3 | qreg q[23];
4 | creg c[23];
5 | creg meas[23];
6 | h q[0];
7 | cx q[0],q[1];
8 | cx q[1],q[2];
9 | cx q[2],q[3];
10 | cx q[3],q[4];
11 | cx q[4],q[5];
12 | cx q[5],q[6];
13 | cx q[6],q[7];
14 | cx q[7],q[8];
15 | cx q[8],q[9];
16 | cx q[9],q[10];
17 | cx q[10],q[11];
18 | cx q[11],q[12];
19 | cx q[12],q[13];
20 | cx q[13],q[14];
21 | cx q[14],q[15];
22 | cx q[15],q[16];
23 | cx q[16],q[17];
24 | cx q[17],q[18];
25 | cx q[18],q[19];
26 | cx q[19],q[20];
27 | cx q[20],q[21];
28 | cx q[21],q[22];
29 | barrier q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8],q[9],q[10],q[11],q[12],q[13],q[14],q[15],q[16],q[17],q[18],q[19],q[20],q[21],q[22];
30 | measure q[0] -> meas[0];
31 | measure q[1] -> meas[1];
32 | measure q[2] -> meas[2];
33 | measure q[3] -> meas[3];
34 | measure q[4] -> meas[4];
35 | measure q[5] -> meas[5];
36 | measure q[6] -> meas[6];
37 | measure q[7] -> meas[7];
38 | measure q[8] -> meas[8];
39 | measure q[9] -> meas[9];
40 | measure q[10] -> meas[10];
41 | measure q[11] -> meas[11];
42 | measure q[12] -> meas[12];
43 | measure q[13] -> meas[13];
44 | measure q[14] -> meas[14];
45 | measure q[15] -> meas[15];
46 | measure q[16] -> meas[16];
47 | measure q[17] -> meas[17];
48 | measure q[18] -> meas[18];
49 | measure q[19] -> meas[19];
50 | measure q[20] -> meas[20];
51 | measure q[21] -> meas[21];
52 | measure q[22] -> meas[22];
--------------------------------------------------------------------------------
/tierkreis/tierkreis/controller/executor/multiple.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from tierkreis.controller.executor.protocol import ControllerExecutor
4 | from tierkreis.exceptions import TierkreisError
5 |
6 |
7 | class MultipleExecutor:
8 | """Composes multiple executors into a single object.
9 |
10 | Implements: :py:class:`tierkreis.controller.executor.protocol.ControllerExecutor`
11 | """
12 |
13 | def __init__(
14 | self,
15 | default: ControllerExecutor,
16 | executors: dict[str, ControllerExecutor],
17 | assignments: dict[str, str],
18 | ) -> None:
19 | self.default = default
20 | self.executors = executors
21 | self.assignments = assignments
22 |
23 | def run(self, launcher_name: str, worker_call_args_path: Path) -> None:
24 | executor_name = self.assignments.get(launcher_name, None)
25 | # If there is no assignment for the worker, use the default.
26 | if executor_name is None:
27 | return self.default.run(launcher_name, worker_call_args_path)
28 |
29 | executor = self.executors.get(executor_name)
30 | if executor is None:
31 | raise TierkreisError(
32 | f"{launcher_name} is assigned to non-existent executor name: {executor_name}."
33 | )
34 |
35 | return executor.run(launcher_name, worker_call_args_path)
36 |
--------------------------------------------------------------------------------
/tierkreis/tests/controller/partial_graphdata.py:
--------------------------------------------------------------------------------
1 | from tierkreis.controller.data.graph import GraphData
2 |
3 |
4 | def ternary_add() -> GraphData:
5 | g = GraphData()
6 | a = g.input("a")
7 | b = g.input("b")
8 | c = g.input("c")
9 |
10 | step_one = g.func("builtins.iadd", {"a": a, "b": b})("value")
11 | step_two = g.func("builtins.iadd", {"a": step_one, "b": c})("value")
12 |
13 | g.output({"ternary_add_output": step_two})
14 | return g
15 |
16 |
17 | def double_partial() -> GraphData:
18 | g = GraphData()
19 |
20 | one = g.const(1)
21 | two = g.const(2)
22 | three = g.const(3)
23 | termary_add_const = g.const(ternary_add())
24 |
25 | add_add_1 = g.eval(termary_add_const, {"a": one})("body")
26 | add_3 = g.eval(add_add_1, {"b": two})("body")
27 | six = g.eval(add_3, {"c": three})("ternary_add_output")
28 |
29 | g.output({"double_partial_output": six})
30 | return g
31 |
32 |
33 | def partial_intersection() -> GraphData:
34 | g = GraphData()
35 | one = g.const(1)
36 | two = g.const(2)
37 | three = g.const(3)
38 | termary_add_const = g.const(ternary_add())
39 |
40 | add_add_1 = g.eval(termary_add_const, {"a": one})("body")
41 | add_3 = g.eval(add_add_1, {"a": one, "b": two})("body")
42 | six = g.eval(add_3, {"c": three})("ternary_add_output")
43 |
44 | g.output({"double_partial_output": six})
45 | return g
46 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/components/workflow-sidebar.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Sidebar,
3 | SidebarContent,
4 | SidebarGroup,
5 | SidebarGroupContent,
6 | SidebarGroupLabel,
7 | SidebarMenu,
8 | SidebarMenuButton,
9 | SidebarMenuItem,
10 | } from "@/components/ui/sidebar";
11 | import { Link } from "react-router";
12 | import { Workflow } from "@/components/types";
13 |
14 | export function WorkflowSidebar({
15 | workflows,
16 | workflowId,
17 | }: {
18 | workflows: Workflow[];
19 | workflowId: string;
20 | }) {
21 | return (
22 |
23 |
24 |
25 | Workflows
26 |
27 |
28 | {workflows.map((workflow) => (
29 |
30 |
34 |
35 | {workflow.name}
36 |
37 |
38 |
39 | ))}
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/tierkreis/tests/controller/test_models.py:
--------------------------------------------------------------------------------
1 | from types import NoneType
2 | from typing import NamedTuple
3 | import pytest
4 | from tierkreis.controller.data.models import PModel, dict_from_pmodel, portmapping
5 | from tierkreis.controller.data.types import PType
6 | from tests.controller.test_types import ptypes
7 |
8 |
9 | @portmapping
10 | class NamedPModel(NamedTuple):
11 | a: bool
12 | b: int
13 | c: float
14 | d: str
15 | e: NoneType
16 | f: list[str]
17 | g: bytes
18 | h: tuple[int, str]
19 | i: list[list[int] | int]
20 |
21 |
22 | @pytest.mark.parametrize("pmodel", ptypes)
23 | def test_dict_from_pmodel_unnested(pmodel: PModel):
24 | assert dict_from_pmodel(pmodel) == {"value": pmodel}
25 |
26 |
27 | named_p_model = NamedPModel(
28 | a=True,
29 | b=5,
30 | c=56.7,
31 | d="test",
32 | e=None,
33 | f=["one", "two", "three"],
34 | g=b"test bytes",
35 | h=(5, "test"),
36 | i=[[], 2],
37 | )
38 | named_p_model_expected = {
39 | "a": True,
40 | "b": 5,
41 | "c": 56.7,
42 | "d": "test",
43 | "e": None,
44 | "f": ["one", "two", "three"],
45 | "g": b"test bytes",
46 | "h": (5, "test"),
47 | "i": [[], 2],
48 | }
49 |
50 | pmodels = [(named_p_model, named_p_model_expected)]
51 |
52 |
53 | @pytest.mark.parametrize("pmodel,expected", pmodels)
54 | def test_dict_from_pmodel_nested(pmodel: PModel, expected: dict[str, PType]):
55 | assert dict_from_pmodel(pmodel) == expected
56 |
--------------------------------------------------------------------------------
/examples/example_workers/chemistry_worker/main.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.12"
3 | # dependencies = ["pydantic", "tierkreis", "pyscf"]
4 | #
5 | # [tool.uv.sources]
6 | # tierkreis = { path = "../../../tierkreis", editable = true }
7 | # ///
8 | import logging
9 | from sys import argv
10 | from typing import NamedTuple
11 |
12 |
13 | from chemistry.active_space import get_frozen
14 | from chemistry.molecule import extract_hamiltonian_rhf
15 |
16 | from tierkreis import Worker
17 |
18 | logger = logging.getLogger(__name__)
19 |
20 | worker = Worker("chemistry_worker")
21 |
22 |
23 | class Molecule(NamedTuple):
24 | geometry: list[tuple[str, list[float]]]
25 | basis: str
26 | charge: int
27 |
28 |
29 | class CompleteActiveSpace(NamedTuple):
30 | n: int
31 | n_ele: int
32 |
33 |
34 | class Hamiltonian(NamedTuple):
35 | h0: float
36 | h1: list[list[float]]
37 | h2: list[list[list[list[float]]]]
38 |
39 |
40 | @worker.task()
41 | def make_ham(
42 | molecule: Molecule,
43 | mo_occ: list[int],
44 | cas: CompleteActiveSpace,
45 | ) -> Hamiltonian:
46 | # Construct the frozen orbital lists.
47 | frozen = get_frozen(mo_occ, cas.n, cas.n_ele)
48 | h0, h1, h2 = extract_hamiltonian_rhf(
49 | molecule.geometry,
50 | molecule.basis,
51 | charge=molecule.charge,
52 | frozen=frozen,
53 | )
54 | return Hamiltonian(h0=h0, h1=h1.tolist(), h2=h2.tolist())
55 |
56 |
57 | if __name__ == "__main__":
58 | worker.app(argv)
59 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/controller/data/core.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from typing import (
3 | Annotated,
4 | Any,
5 | Callable,
6 | Literal,
7 | NamedTuple,
8 | Protocol,
9 | SupportsIndex,
10 | get_args,
11 | get_origin,
12 | runtime_checkable,
13 | )
14 |
15 |
16 | PortID = str
17 | NodeIndex = int
18 | ValueRef = tuple[NodeIndex, PortID]
19 | SerializationFormat = Literal["bytes", "json", "unknown"]
20 |
21 |
22 | class EmptyModel(NamedTuple): ...
23 |
24 |
25 | @runtime_checkable
26 | class RestrictedNamedTuple[T](Protocol):
27 | """A NamedTuple whose members are restricted to being of type T."""
28 |
29 | def _asdict(self) -> dict[str, T]: ...
30 | def __getitem__(self, key: SupportsIndex, /) -> T: ...
31 |
32 |
33 | @dataclass
34 | class Serializer:
35 | serializer: Callable[[Any], Any]
36 | serialization_method: SerializationFormat = "bytes"
37 |
38 |
39 | @dataclass
40 | class Deserializer:
41 | deserializer: Callable[[Any], Any]
42 | serialization_method: SerializationFormat = "bytes"
43 |
44 |
45 | def get_t_from_args[T](t: type[T], hint: type | None) -> T | None:
46 | if hint is None or get_origin(hint) is not Annotated:
47 | return None
48 |
49 | for arg in get_args(hint):
50 | if isinstance(arg, t):
51 | return arg
52 |
53 |
54 | def get_serializer(hint: type | None):
55 | return get_t_from_args(Serializer, hint)
56 |
57 |
58 | def get_deserializer(hint: type | None):
59 | return get_t_from_args(Deserializer, hint)
60 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/static/workflows.html.css:
--------------------------------------------------------------------------------
1 | .workflowlist {
2 | display: flex;
3 | flex-direction: column;
4 | gap:0.05rem;
5 | }
6 |
7 | .workflowlist__item {
8 | padding: 0.75rem;
9 | border-radius: 0.25rem;
10 | background: var(--neutral-0);
11 | border: 1px solid var(--neutral-200);
12 | text-decoration: none;
13 | }
14 |
15 | .workflowlist__item:hover {
16 | background: var(--neutral-100);
17 | transition-property: background;
18 | transition-duration: 150ms;
19 | cursor: pointer;
20 | }
21 | .workflowlist__item__title {
22 | display: flex;
23 | align-items: center;
24 | gap: 0.5rem;
25 | text-decoration: none;
26 | margin-bottom: 0.25rem;
27 |
28 | }
29 | .workflowlist__item__title::before {
30 | content: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2218%22%20height%3D%2218%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%23737373%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20class%3D%22lucide%20lucide-workflow-icon%20lucide-workflow%22%3E%3Crect%20width%3D%228%22%20height%3D%228%22%20x%3D%223%22%20y%3D%223%22%20rx%3D%222%22%2F%3E%3Cpath%20d%3D%22M7%2011v4a2%202%200%200%200%202%202h4%22%2F%3E%3Crect%20width%3D%228%22%20height%3D%228%22%20x%3D%2213%22%20y%3D%2213%22%20rx%3D%222%22%2F%3E%3C%2Fsvg%3E");
31 | }
32 |
33 | .workflowlist__item__id {
34 | color: var(--neutral-400);
35 | font-size: 0.675rem;
36 | margin-left: 1.5rem;
37 | text-decoration: none;
38 | }
39 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tierkreis-frontend",
3 | "version": "0.0.0",
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite build --watch",
7 | "build": "tsc && vite build",
8 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@dagrejs/dagre": "^1.1.4",
13 | "@radix-ui/react-dialog": "^1.1.14",
14 | "@radix-ui/react-hover-card": "^1.1.14",
15 | "@radix-ui/react-separator": "^1.1.7",
16 | "@radix-ui/react-slot": "^1.2.3",
17 | "@radix-ui/react-tooltip": "^1.2.7",
18 | "@tailwindcss/vite": "^4.1.7",
19 | "@tanstack/react-query": "^5.81.5",
20 | "@tanstack/react-query-devtools": "^5.82.0",
21 | "@xyflow/react": "^12.7.0",
22 | "class-variance-authority": "^0.7.1",
23 | "clsx": "^2.1.1",
24 | "lucide-react": "^0.511.0",
25 | "react": "^18.2.0",
26 | "react-dom": "^18.2.0",
27 | "react-router": "^7.6.2",
28 | "tailwind-merge": "^3.3.0",
29 | "tw-animate-css": "^1.3.5"
30 | },
31 | "license": "MIT",
32 | "devDependencies": {
33 | "@types/node": "^22.15.23",
34 | "@types/react": "^18.2.53",
35 | "@types/react-dom": "^18.2.18",
36 | "@typescript-eslint/eslint-plugin": "^6.20.0",
37 | "@typescript-eslint/parser": "^6.20.0",
38 | "@vitejs/plugin-react": "^5.0.0",
39 | "eslint": "^8.56.0",
40 | "eslint-plugin-react-hooks": "^4.6.0",
41 | "eslint-plugin-react-refresh": "^0.4.5",
42 | "typescript": "^5.3.3",
43 | "vite": "^7.1.11"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tierkreis/CITATION.bib:
--------------------------------------------------------------------------------
1 | @INPROCEEDINGS {10025549,
2 | author = {S. Sivarajah and L. Heidemann and A. Lawrence and R. Duncan},
3 | booktitle = {2022 IEEE/ACM Third International Workshop on Quantum Computing Software (QCS)},
4 | title = {Tierkreis: a Dataflow Framework for Hybrid Quantum-Classical Computing},
5 | year = {2022},
6 | volume = {},
7 | issn = {},
8 | pages = {12-21},
9 | abstract = {We present Tierkreis, a higher-order dataflow graph program representation and runtime designed for compositional, quantum-classical hybrid algorithms. The design of the system is motivated by the remote nature of quantum computers, the need for hybrid algorithms to involve cloud and distributed computing, and the long-running nature of these algorithms. The graph-based representation reflects how designers reason about and visualise algorithms, and allows automatic parallelism and asynchronicity. A strong, static type system and higher-order semantics allow for high expressivity and compositionality in the program. The flexible runtime protocol enables third-party developers to add functionality using any language or environment. With Tierkreis, quantum software developers can easily build, visualise, verify, test, and debug complex hybrid workflows, and immediately deploy them to the cloud or a custom distributed environment.},
10 | keywords = {visualization;runtime;protocols;codes;software algorithms;buildings;semantics},
11 | doi = {10.1109/QCS56647.2022.00007},
12 | url = {https://doi.ieeecomputersociety.org/10.1109/QCS56647.2022.00007},
13 | publisher = {IEEE Computer Society},
14 | address = {Los Alamitos, CA, USA},
15 | month = {nov}
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/tierkreis/docs/api-docs/_templates/autosummary/module.rst:
--------------------------------------------------------------------------------
1 | ..
2 | Custom module template to make sphinx-autosummary list the full API doc after
3 | the summary. See https://github.com/sphinx-doc/sphinx/issues/7912
4 |
5 | {{ name | escape | underline}}
6 |
7 | .. automodule:: {{ fullname }}
8 |
9 | {% block attributes %}
10 | {% if attributes %}
11 | .. rubric:: Module attributes
12 |
13 | .. autosummary::
14 | :toctree:
15 | {% for item in attributes %}
16 | {{ item }}
17 | {%- endfor %}
18 | {% endif %}
19 | {% endblock %}
20 |
21 | {% block functions %}
22 | {% if functions %}
23 | .. rubric:: {{ _('Functions') }}
24 |
25 | .. autosummary::
26 | :toctree:
27 | :nosignatures:
28 | {% for item in functions %}
29 | {{ item }}
30 | {%- endfor %}
31 | {% endif %}
32 | {% endblock %}
33 |
34 | {% block classes %}
35 | {% if classes %}
36 | .. rubric:: {{ _('Classes') }}
37 |
38 | .. autosummary::
39 | :toctree:
40 | :nosignatures:
41 | {% for item in classes %}
42 | {{ item }}
43 | {%- endfor %}
44 | {% endif %}
45 | {% endblock %}
46 |
47 | {% block exceptions %}
48 | {% if exceptions %}
49 | .. rubric:: {{ _('Exceptions') }}
50 |
51 | .. autosummary::
52 | :toctree:
53 | {% for item in exceptions %}
54 | {{ item }}
55 | {%- endfor %}
56 | {% endif %}
57 | {% endblock %}
58 |
59 | {% block modules %}
60 | {% if modules %}
61 | .. autosummary::
62 | :toctree:
63 | :recursive:
64 | {% for item in modules %}
65 | {{ item }}
66 | {%- endfor %}
67 | {% endif %}
68 | {% endblock %}
69 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/worker/storage/in_memory.py:
--------------------------------------------------------------------------------
1 | import fnmatch
2 | import json
3 | import logging
4 | from pathlib import Path
5 |
6 | from tierkreis.controller.data.location import WorkerCallArgs
7 | from tierkreis.controller.storage.in_memory import (
8 | ControllerInMemoryStorage,
9 | InMemoryFileData,
10 | )
11 | from tierkreis.exceptions import TierkreisError
12 |
13 |
14 | logger = logging.getLogger(__name__)
15 |
16 |
17 | class InMemoryWorkerStorage:
18 | def __init__(self, controller_storage: ControllerInMemoryStorage) -> None:
19 | self.controller_storage = controller_storage
20 |
21 | def resolve(self, path: Path | str) -> Path:
22 | return Path(path)
23 |
24 | def read_call_args(self, path: Path) -> WorkerCallArgs:
25 | bs = self.controller_storage.files[path].value
26 | return WorkerCallArgs(**json.loads(bs))
27 |
28 | def read_input(self, path: Path) -> bytes:
29 | return self.controller_storage.files[path].value
30 |
31 | def write_output(self, path: Path, value: bytes) -> None:
32 | self.controller_storage.files[path] = InMemoryFileData(value)
33 |
34 | def glob(self, path_string: str) -> list[str]:
35 | files = [str(x) for x in self.controller_storage.files.keys()]
36 | matching = fnmatch.filter(files, path_string)
37 | return matching
38 |
39 | def mark_done(self, path: Path) -> None:
40 | self.controller_storage.touch(path)
41 |
42 | def write_error(self, path: Path, error_logs: str) -> None:
43 | logger.error(error_logs)
44 | raise TierkreisError("Error occured when running graph in-memory.")
45 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/data/map.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 | from tierkreis.controller.data.location import Loc
3 | from tierkreis.controller.storage.adjacency import outputs_iter
4 | from tierkreis.controller.storage.protocol import ControllerStorage
5 | from tierkreis.controller.data.graph import Map
6 | from tierkreis.exceptions import TierkreisError
7 | from tierkreis_visualization.data.eval import check_error
8 | from tierkreis_visualization.data.models import PyEdge, PyNode
9 |
10 |
11 | class MapNodeData(BaseModel):
12 | nodes: list[PyNode]
13 | edges: list[PyEdge]
14 |
15 |
16 | def get_map_node(
17 | storage: ControllerStorage, loc: Loc, map: Map, errored_nodes: list[Loc]
18 | ) -> MapNodeData:
19 | parent = loc.parent()
20 | if parent is None:
21 | raise TierkreisError("MAP node must have parent.")
22 |
23 | first_ref = next(x for x in map.inputs.values() if x[1] == "*")
24 | map_eles = outputs_iter(storage, parent.N(first_ref[0]))
25 | nodes: list[PyNode] = []
26 | for i, ele in map_eles:
27 | node = PyNode(
28 | id=i,
29 | status="Started",
30 | function_name=ele,
31 | node_location=loc.M(i),
32 | started_time=storage.read_started_time(loc.M(i)) or "",
33 | finished_time=storage.read_finished_time(loc.M(i)) or "",
34 | )
35 | if check_error(loc.M(i), errored_nodes):
36 | node.status = "Error"
37 | elif storage.is_node_finished(loc.M(i)):
38 | node.status = "Finished"
39 | nodes.append(node)
40 |
41 | return MapNodeData(nodes=nodes, edges=[])
42 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/controller/storage/adjacency.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from typing import assert_never
3 |
4 | from tierkreis.controller.data.core import PortID, ValueRef
5 | from tierkreis.controller.data.graph import (
6 | NodeDef,
7 | )
8 | from tierkreis.controller.data.location import Loc
9 | from tierkreis.controller.storage.protocol import ControllerStorage
10 |
11 | logger = logging.getLogger(__name__)
12 |
13 |
14 | def in_edges(node: NodeDef) -> dict[PortID, ValueRef]:
15 | parents = {k: v for k, v in node.inputs.items()}
16 |
17 | match node.type:
18 | case "eval":
19 | parents["body"] = node.graph
20 | case "loop":
21 | parents["body"] = node.body
22 | case "map":
23 | parents["body"] = node.body
24 | case "ifelse":
25 | parents["pred"] = node.pred
26 | case "eifelse":
27 | parents["pred"] = node.pred
28 | parents["body_true"] = node.if_true
29 | parents["body_false"] = node.if_false
30 | case "const" | "function" | "input" | "output":
31 | pass
32 | case _:
33 | assert_never(node)
34 |
35 | return parents
36 |
37 |
38 | def unfinished_inputs(
39 | storage: ControllerStorage, loc: Loc, node: NodeDef
40 | ) -> list[ValueRef]:
41 | ins = in_edges(node).values()
42 | ins = [x for x in ins if x[0] >= 0] # inputs at -1 already finished
43 | return [x for x in ins if not storage.is_node_finished(loc.N(x[0]))]
44 |
45 |
46 | def outputs_iter(storage: ControllerStorage, loc: Loc) -> list[tuple[int, PortID]]:
47 | eles = storage.read_output_ports(loc)
48 | return [(int(x.split("-")[-1]), x) for x in eles]
49 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/worker/storage/filestorage.py:
--------------------------------------------------------------------------------
1 | import json
2 | from glob import glob
3 | import os
4 | from pathlib import Path
5 |
6 | from tierkreis.consts import TKR_DIR_KEY
7 | from tierkreis.controller.data.location import WorkerCallArgs
8 |
9 |
10 | class WorkerFileStorage:
11 | def __init__(self, tierkreis_dir: Path | None = None) -> None:
12 | if tierkreis_dir is not None:
13 | self.tierkreis_dir = tierkreis_dir
14 | elif tierkreis_dir_str := os.environ.get(TKR_DIR_KEY):
15 | self.tierkreis_dir = Path(tierkreis_dir_str).resolve()
16 | else:
17 | self.tierkreis_dir = Path.home() / ".tierkreis" / "checkpoints"
18 |
19 | def resolve(self, path: Path | str) -> Path:
20 | path = Path(path)
21 | return path if path.is_absolute() else self.tierkreis_dir / path
22 |
23 | def read_call_args(self, path: Path) -> WorkerCallArgs:
24 | with open(self.resolve(path), "r") as fh:
25 | return WorkerCallArgs(**json.loads(fh.read()))
26 |
27 | def read_input(self, path: Path) -> bytes:
28 | with open(self.resolve(path), "rb") as fh:
29 | return fh.read()
30 |
31 | def write_output(self, path: Path, value: bytes) -> None:
32 | with open(self.resolve(path), "wb+") as fh:
33 | fh.write(value)
34 |
35 | def glob(self, path_string: str) -> list[str]:
36 | return glob(str(self.resolve(path_string)))
37 |
38 | def mark_done(self, path: Path) -> None:
39 | self.resolve(path).touch()
40 |
41 | def write_error(self, path: Path, error_logs: str) -> None:
42 | with open(self.resolve(path), "w+") as f:
43 | f.write(error_logs)
44 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2 | import React, { Suspense } from "react";
3 | import ReactDOM from "react-dom/client";
4 | import {
5 | createBrowserRouter,
6 | RouterProvider,
7 | type LoaderFunction,
8 | type Params,
9 | } from "react-router";
10 |
11 | import App from "./App";
12 |
13 | import { ReactFlowProvider } from "@xyflow/react";
14 | import "@xyflow/react/dist/style.css";
15 | import "./index.css";
16 | import ErrorPage from "./error";
17 |
18 | const queryClient = new QueryClient();
19 |
20 | const workflowId: LoaderFunction = ({ params }: { params: Params }) => {
21 | return { params };
22 | };
23 |
24 | const element = (
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | const router = createBrowserRouter([
34 | {
35 | path: "/",
36 | element: element,
37 | errorElement: ,
38 | },
39 | {
40 | path: "/:workflowId",
41 | element: element,
42 | loader: workflowId,
43 | errorElement: ,
44 | },
45 | {
46 | path: "*/:workflowId",
47 | element: element,
48 | loader: workflowId,
49 | errorElement: ,
50 | },
51 | {
52 | path: "*",
53 | element: element,
54 | errorElement: ,
55 | },
56 | ]);
57 |
58 | ReactDOM.createRoot(document.getElementById("root")!).render(
59 |
60 |
61 |
62 | );
63 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/data/workflows.py:
--------------------------------------------------------------------------------
1 | import os
2 | from datetime import datetime
3 | from uuid import UUID
4 |
5 | from pydantic import BaseModel
6 |
7 | from tierkreis.controller.data.location import Loc
8 | from tierkreis_visualization.app_config import StorageType
9 | from tierkreis_visualization.config import CONFIG
10 | from tierkreis_visualization.storage import file_storage_fn
11 |
12 |
13 | class WorkflowDisplay(BaseModel):
14 | id: UUID
15 | id_int: int
16 | name: str | None
17 | start_time: str
18 |
19 |
20 | def get_workflows(storage_type: StorageType) -> list[WorkflowDisplay]:
21 | if storage_type == StorageType.GRAPHDATA:
22 | return [
23 | WorkflowDisplay(
24 | id=UUID(int=0),
25 | id_int=0,
26 | name="tmp",
27 | start_time=datetime.now().isoformat(),
28 | )
29 | ]
30 | return get_workflows_from_disk()
31 |
32 |
33 | def get_workflows_from_disk() -> list[WorkflowDisplay]:
34 | folders = os.listdir(CONFIG.tierkreis_path)
35 | folders.sort()
36 | workflows: list[WorkflowDisplay] = []
37 | for folder in folders:
38 | try:
39 | id = UUID(folder)
40 | metadata = file_storage_fn(CONFIG.tierkreis_path)(id).read_metadata(Loc(""))
41 | name = metadata["name"] or "workflow"
42 | start = metadata.get("start_time", datetime.now().isoformat())
43 | workflows.append(
44 | WorkflowDisplay(id=id, id_int=int(id), name=name, start_time=start)
45 | )
46 | except (TypeError, ValueError):
47 | continue
48 |
49 | return workflows
50 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # For the full list of built-in configuration values, see the documentation:
4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
5 |
6 | # -- Project information -----------------------------------------------------
7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8 |
9 | project = "tierkreis"
10 | copyright = "2025, Quantinuum"
11 | author = "Quantinuum"
12 |
13 | # -- General configuration ---------------------------------------------------
14 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
15 |
16 | extensions = ["autodoc2", "myst_nb"]
17 | autodoc2_packages = [
18 | "../../tierkreis/tierkreis",
19 | {
20 | "path": "../../tierkreis_workers/aer_worker/main.py",
21 | "module": "aer_worker",
22 | },
23 | {
24 | "path": "../../tierkreis_workers/nexus_worker/main.py",
25 | "module": "nexus_worker",
26 | },
27 | {
28 | "path": "../../tierkreis_workers/pytket_worker/main.py",
29 | "module": "pytket_worker",
30 | },
31 | ]
32 | autodoc2_hidden_objects = ["private"]
33 |
34 | templates_path = ["_templates"]
35 | exclude_patterns = []
36 |
37 |
38 | # -- Options for HTML output -------------------------------------------------
39 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
40 |
41 | html_theme = "furo"
42 | html_static_path = ["_static"]
43 | html_favicon = "_static/quantinuum_favicon.svg"
44 |
45 | # -- Notebook options --------------------------------------------------------
46 |
47 | nb_execution_raise_on_error = True
48 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/app.py:
--------------------------------------------------------------------------------
1 | from sys import argv
2 | from tierkreis.controller.data.graph import GraphData
3 | from tierkreis_visualization.app_config import App, StorageType, lifespan
4 | from tierkreis_visualization.config import CONFIG
5 | from tierkreis_visualization.storage import (
6 | file_storage_fn,
7 | from_graph_data_storage_fn,
8 | graph_data_storage_fn,
9 | )
10 | from tierkreis_visualization.routers.frontend import assets
11 | from tierkreis_visualization.routers.workflows import router as workflows_router
12 | from tierkreis_visualization.routers.frontend import router as frontend_router
13 |
14 |
15 | def get_app():
16 | app = App(lifespan=lifespan)
17 | app.include_router(workflows_router, prefix="/workflows")
18 | app.mount("/assets/", assets, name="frontend_assets")
19 | app.include_router(frontend_router)
20 |
21 | return app
22 |
23 |
24 | def get_filestorage_app():
25 | app = get_app()
26 | app.state.get_storage_fn = file_storage_fn(CONFIG.tierkreis_path)
27 | app.state.storage_type = StorageType.FILESTORAGE
28 | return app
29 |
30 |
31 | def get_graph_data_app():
32 | app = get_app()
33 | graph_specifier = argv[1] if len(argv) > 1 else CONFIG.graph_specifier
34 |
35 | if graph_specifier is None:
36 | return app
37 |
38 | app.state.get_storage_fn = graph_data_storage_fn(graph_specifier)[0]
39 | app.state.storage_type = StorageType.GRAPHDATA
40 | return app
41 |
42 |
43 | def app_from_graph_data(graph_data: GraphData):
44 | app = get_app()
45 | app.state.get_storage_fn = from_graph_data_storage_fn(graph_data)
46 | app.state.storage_type = StorageType.GRAPHDATA
47 | return app
48 |
--------------------------------------------------------------------------------
/examples/simulate_parallel.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import time
3 | from pytket.qasm.qasm import circuit_from_qasm
4 | from tierkreis.graphs.simulate.compile_simulate import compile_simulate
5 | from uuid import UUID
6 |
7 | from tierkreis.consts import PACKAGE_PATH
8 | from tierkreis.controller import run_graph
9 | from tierkreis.storage import FileStorage, read_outputs
10 | from tierkreis.executor import UvExecutor
11 |
12 | simulator_name = "aer"
13 | circuit = circuit_from_qasm(Path(__file__).parent / "data" / "ghz_state_n23.qasm")
14 | circuits = [circuit] * 3
15 |
16 |
17 | def main():
18 | g = compile_simulate()
19 | storage = FileStorage(UUID(int=107), do_cleanup=True)
20 | executor = UvExecutor(PACKAGE_PATH / ".." / "tierkreis_workers", storage.logs_path)
21 | inputs = {
22 | "circuits": circuits,
23 | "n_shots": [3] * len(circuits),
24 | "simulator_name": simulator_name,
25 | "compilation_optimisation_level": 2,
26 | }
27 |
28 | print("Simulating using aer...")
29 | start = time.time()
30 | run_graph(storage, executor, g, inputs, polling_interval_seconds=0.1)
31 | print(f"time taken: {time.time() - start}")
32 | res = read_outputs(g, storage)
33 | assert isinstance(res, list)
34 | print(len(res))
35 |
36 | inputs["simulator_name"] = "qulacs"
37 |
38 | print("Simulating using qulacs...")
39 | storage.clean_graph_files()
40 | start = time.time()
41 | run_graph(storage, executor, g, inputs, polling_interval_seconds=0.1)
42 | print(f"time taken: {time.time() - start}")
43 | res = read_outputs(g, storage)
44 | assert isinstance(res, list)
45 | print(len(res))
46 |
47 |
48 | if __name__ == "__main__":
49 | main()
50 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | function HoverCard({
7 | ...props
8 | }: React.ComponentProps) {
9 | return
10 | }
11 |
12 | function HoverCardTrigger({
13 | ...props
14 | }: React.ComponentProps) {
15 | return (
16 |
17 | )
18 | }
19 |
20 | function HoverCardContent({
21 | className,
22 | align = "center",
23 | sideOffset = 4,
24 | ...props
25 | }: React.ComponentProps) {
26 | return (
27 |
28 |
38 |
39 | )
40 | }
41 |
42 | export { HoverCard, HoverCardTrigger, HoverCardContent }
43 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/error.tsx:
--------------------------------------------------------------------------------
1 | import { Link, useRouteError, isRouteErrorResponse } from "react-router";
2 | import MyLogo from "./quantinuum_logo.svg"; //
3 |
4 | export default function ErrorPage() {
5 | const error = useRouteError();
6 | let errorMessage: string;
7 | if (isRouteErrorResponse(error)) {
8 | errorMessage = error.statusText;
9 | } else if (error instanceof Error) {
10 | errorMessage = error.message;
11 | } else if (typeof error === "string") {
12 | errorMessage = error;
13 | } else {
14 | errorMessage = "An unknown error occurred";
15 | }
16 |
17 | return (
18 |
19 |
20 |
Oops! 😱
21 |
Sorry, an unexpected error has occurred.
22 |
23 | {errorMessage}
24 |
25 |
Go back to the homepage
26 |
27 |
30 |
31 | );
32 | }
33 |
34 | // Basic inline styles for layout
35 | // In a real app, you'd likely use CSS classes
36 | const containerStyle: React.CSSProperties = {
37 | display: "flex",
38 | flexDirection: "column",
39 | alignItems: "center",
40 | justifyContent: "center",
41 | minHeight: "100vh",
42 | textAlign: "center",
43 | padding: "20px",
44 | boxSizing: "border-box",
45 | };
46 |
47 | const contentStyle: React.CSSProperties = {
48 | flexGrow: 1,
49 | display: "flex",
50 | flexDirection: "column",
51 | justifyContent: "center",
52 | };
53 |
54 | const footerStyle: React.CSSProperties = {
55 | marginTop: "auto",
56 | padding: "20px 0",
57 | };
58 |
--------------------------------------------------------------------------------
/tierkreis_workers/pytket_worker/test_main.py:
--------------------------------------------------------------------------------
1 | from pytket._tket.unit_id import Bit
2 | from pytket.backends.backendresult import BackendResult
3 | from pytket.utils.outcomearray import OutcomeArray
4 | from pytket._tket.circuit import Circuit
5 | from pytket.extensions.qiskit.backends.aer import AerBackend
6 |
7 | from .main import backend_result_from_dict, backend_result_to_dict
8 |
9 |
10 | def test_backend_result_conversion() -> None:
11 | original_result = BackendResult(
12 | shots=OutcomeArray.from_readouts([[0, 1, 0], [1, 0, 1], [1, 1, 1]]),
13 | c_bits=[Bit("a", 0), Bit("b", 0), Bit("b", 1)],
14 | )
15 | result_dict = backend_result_to_dict(original_result)
16 | reconstructed_result = backend_result_from_dict(result_dict)
17 | assert (
18 | original_result.get_shots().tolist()
19 | == reconstructed_result.get_shots().tolist()
20 | )
21 | assert original_result.c_bits == reconstructed_result.c_bits
22 | assert result_dict == {
23 | "a": ["0", "1", "1"],
24 | "b": ["10", "01", "11"],
25 | }
26 |
27 |
28 | def _deterministic_circuit() -> Circuit:
29 | circuit = Circuit(3)
30 | circuit.add_c_register("a", 1)
31 | circuit.add_c_register("b", 2)
32 | circuit.X(0)
33 | circuit.X(1)
34 | circuit.Measure(0, Bit("a", 0))
35 | circuit.Measure(1, Bit("b", 0))
36 | circuit.Measure(2, Bit("b", 1))
37 | return circuit
38 |
39 |
40 | def test_deterministic_circuit() -> None:
41 | circuit = _deterministic_circuit()
42 | result = AerBackend().run_circuit(circuit, 10)
43 | result_dict = backend_result_to_dict(result)
44 | assert result_dict == {"a": ["1"] * 10, "b": ["10"] * 10}
45 | assert result == backend_result_from_dict(result_dict)
46 |
--------------------------------------------------------------------------------
/examples/hello_world_graph.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.12"
3 | # dependencies = ["tierkreis"]
4 | #
5 | # [tool.uv.sources]
6 | # tierkreis = { path = "../tierkreis", editable = true }
7 | # ///
8 | from pathlib import Path
9 |
10 | from tierkreis.builder import GraphBuilder
11 | from tierkreis.controller.data.location import Loc
12 | from tierkreis.cli.run_workflow import run_workflow
13 | from tierkreis.controller.data.models import TKR
14 |
15 | from example_workers.hello_world_worker.stubs import greet
16 |
17 | root_loc = Loc()
18 |
19 |
20 | def hello_graph() -> GraphBuilder:
21 | """A graph that greets the subject."""
22 |
23 | # We build a graph that takes a single string as input
24 | # and produces a single string as output.
25 | g = GraphBuilder(TKR[str], TKR[str])
26 |
27 | # We add a constant that yields the string "hello ".
28 | hello = g.const("hello ")
29 |
30 | # We import and call the "greet" task from our worker.
31 | output = g.task(greet(greeting=hello, subject=g.inputs))
32 |
33 | # We assign the output of the greet task to the output of the whole graph.
34 | g.outputs(output)
35 |
36 | return g
37 |
38 |
39 | def main() -> None:
40 | """Configure our workflow execution and run it to completion."""
41 | run_workflow(
42 | hello_graph().data,
43 | {"value": "world!"},
44 | name="hello_world",
45 | run_id=100, # Assign a fixed uuid for our workflow.
46 | registry_path=Path(__file__).parent
47 | / "example_workers", # Look for workers in the `example_workers` directory.
48 | use_uv_worker=True,
49 | polling_interval_seconds=0.1,
50 | print_output=True,
51 | )
52 |
53 |
54 | if __name__ == "__main__":
55 | main()
56 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/static/_types.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {Object} PyNode
3 | * @property {number | string} id - The node ID.
4 | * @property {"Not started" | "Started" | "Error" | "Finished"} status - The node name.
5 | * @property {string} function_name - The node label..
6 | */
7 |
8 | /**
9 | * @typedef {Object} PyEdge
10 | * @property {number | string} from_node - The edge name.
11 | * @property {string} from_port - The edge arrow
12 | * @property {number | string} to_node - The edge name.
13 | * @property {string} to_port - The edge label.
14 | * @property {boolean} conditional - The edge doesn't always run.
15 | */
16 |
17 | /**
18 | * @typedef {Object} JSNode
19 | * @property {number | string} id - The node ID.
20 | * @property {string} title - The node name.
21 | * @property {string} label - The node label..
22 | * @property {string} shape - Node shape.
23 | * @property {string} color - Node colour.
24 | * @property {"Not started" | "Started" | "Error" | "Finished"} status - Node status
25 | */
26 |
27 | /**
28 | * @typedef {Object} JSEdge
29 | * @property {string} id - The edge ID.
30 | * @property {number | string} from - The edge name.
31 | * @property {number | string} to - The edge label..
32 | * @property {string} title - The edge name.
33 | * @property {string} label - Node shape.
34 | * @property {string} arrows - The edge arrow.
35 | * @property {boolean} dashes - If the edge should be dashed.
36 | */
37 |
38 | /**
39 | * @typedef {Object} JSGraph
40 | * @property {JSNode[]} nodes - The edge ID.
41 | * @property {JSEdge[]} edges - The edge name.
42 | */
43 |
44 | /**
45 | * @typedef {Object} PyGraph
46 | * @property {PyNode[]} nodes - The edge ID.
47 | * @property {PyEdge[]} edges - The edge name.
48 | */
49 |
--------------------------------------------------------------------------------
/docs/source/tutorial/auth_stubs.py:
--------------------------------------------------------------------------------
1 | """Code generated from auth_worker namespace. Please do not edit."""
2 |
3 | from typing import NamedTuple
4 | from tierkreis.controller.data.models import TKR
5 |
6 |
7 | class EncryptionResult(NamedTuple):
8 | ciphertext: TKR[str] # noqa: F821 # fmt: skip
9 | time_taken: TKR[float] # noqa: F821 # fmt: skip
10 |
11 |
12 | class SigningResult(NamedTuple):
13 | hex_signature: TKR[str] # noqa: F821 # fmt: skip
14 | time_taken: TKR[float] # noqa: F821 # fmt: skip
15 |
16 |
17 | class encrypt(NamedTuple):
18 | plaintext: TKR[str] # noqa: F821 # fmt: skip
19 | work_factor: TKR[int] # noqa: F821 # fmt: skip
20 |
21 | @staticmethod
22 | def out() -> type[EncryptionResult]: # noqa: F821 # fmt: skip
23 | return EncryptionResult # noqa: F821 # fmt: skip
24 |
25 | @property
26 | def namespace(self) -> str:
27 | return "auth_worker"
28 |
29 |
30 | class sign(NamedTuple):
31 | private_key: TKR[bytes] # noqa: F821 # fmt: skip
32 | passphrase: TKR[bytes] # noqa: F821 # fmt: skip
33 | message: TKR[str] # noqa: F821 # fmt: skip
34 |
35 | @staticmethod
36 | def out() -> type[SigningResult]: # noqa: F821 # fmt: skip
37 | return SigningResult # noqa: F821 # fmt: skip
38 |
39 | @property
40 | def namespace(self) -> str:
41 | return "auth_worker"
42 |
43 |
44 | class verify(NamedTuple):
45 | public_key: TKR[bytes] # noqa: F821 # fmt: skip
46 | signature: TKR[str] # noqa: F821 # fmt: skip
47 | message: TKR[str] # noqa: F821 # fmt: skip
48 |
49 | @staticmethod
50 | def out() -> type[TKR[bool]]: # noqa: F821 # fmt: skip
51 | return TKR[bool] # noqa: F821 # fmt: skip
52 |
53 | @property
54 | def namespace(self) -> str:
55 | return "auth_worker"
56 |
--------------------------------------------------------------------------------
/examples/example_workers/auth_worker/stubs.py:
--------------------------------------------------------------------------------
1 | """Code generated from auth_worker namespace. Please do not edit."""
2 |
3 | from typing import NamedTuple
4 | from tierkreis.controller.data.models import TKR
5 |
6 |
7 | class EncryptionResult(NamedTuple):
8 | ciphertext: TKR[str] # noqa: F821 # fmt: skip
9 | time_taken: TKR[float] # noqa: F821 # fmt: skip
10 |
11 |
12 | class SigningResult(NamedTuple):
13 | hex_signature: TKR[str] # noqa: F821 # fmt: skip
14 | time_taken: TKR[float] # noqa: F821 # fmt: skip
15 |
16 |
17 | class encrypt(NamedTuple):
18 | plaintext: TKR[str] # noqa: F821 # fmt: skip
19 | work_factor: TKR[int] # noqa: F821 # fmt: skip
20 |
21 | @staticmethod
22 | def out() -> type[EncryptionResult]: # noqa: F821 # fmt: skip
23 | return EncryptionResult # noqa: F821 # fmt: skip
24 |
25 | @property
26 | def namespace(self) -> str:
27 | return "auth_worker"
28 |
29 |
30 | class sign(NamedTuple):
31 | private_key: TKR[bytes] # noqa: F821 # fmt: skip
32 | passphrase: TKR[bytes] # noqa: F821 # fmt: skip
33 | message: TKR[str] # noqa: F821 # fmt: skip
34 |
35 | @staticmethod
36 | def out() -> type[SigningResult]: # noqa: F821 # fmt: skip
37 | return SigningResult # noqa: F821 # fmt: skip
38 |
39 | @property
40 | def namespace(self) -> str:
41 | return "auth_worker"
42 |
43 |
44 | class verify(NamedTuple):
45 | public_key: TKR[bytes] # noqa: F821 # fmt: skip
46 | signature: TKR[str] # noqa: F821 # fmt: skip
47 | message: TKR[str] # noqa: F821 # fmt: skip
48 |
49 | @staticmethod
50 | def out() -> type[TKR[bool]]: # noqa: F821 # fmt: skip
51 | return TKR[bool] # noqa: F821 # fmt: skip
52 |
53 | @property
54 | def namespace(self) -> str:
55 | return "auth_worker"
56 |
--------------------------------------------------------------------------------
/examples/error_handling_graph.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.12"
3 | # dependencies = ["tierkreis"]
4 | #
5 | # [tool.uv.sources]
6 | # tierkreis = { path = "../tierkreis", editable = true }
7 | # ///
8 | from pathlib import Path
9 | from uuid import UUID
10 |
11 | from tierkreis.builder import GraphBuilder
12 | from tierkreis.controller import run_graph
13 | from tierkreis.controller.data.core import EmptyModel
14 | from tierkreis.controller.data.location import Loc
15 | from tierkreis.controller.data.models import TKR
16 | from tierkreis.controller.storage.filestorage import ControllerFileStorage
17 | from tierkreis.controller.executor.uv_executor import UvExecutor
18 |
19 | from example_workers.error_worker.stubs import fail
20 |
21 | root_loc = Loc()
22 |
23 |
24 | def error_graph() -> GraphBuilder:
25 | """A graph that errors."""
26 |
27 | g = GraphBuilder(EmptyModel, TKR[str])
28 | output = g.task(fail())
29 | g.outputs(output)
30 | return g
31 |
32 |
33 | def main() -> None:
34 | """Configure our workflow execution and run it to completion."""
35 | # Assign a fixed uuid for our workflow.
36 | workflow_id = UUID(int=103)
37 | storage = ControllerFileStorage(workflow_id, name="error_handling", do_cleanup=True)
38 |
39 | # Look for workers in the `example_workers` directory.
40 | registry_path = Path(__file__).parent / "example_workers"
41 | executor = UvExecutor(registry_path=registry_path, logs_path=storage.logs_path)
42 | print("Starting workflow at location:", storage.logs_path)
43 | run_graph(
44 | storage,
45 | executor,
46 | error_graph().data,
47 | {"value": "world!"},
48 | polling_interval_seconds=0.1,
49 | )
50 | output = storage.read_errors(root_loc)
51 | print(output)
52 |
53 |
54 | if __name__ == "__main__":
55 | main()
56 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Python package CI
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - main
7 | push:
8 | branches:
9 | - main
10 | env:
11 | UV_VERSION: "0.6.6"
12 |
13 | jobs:
14 |
15 | lint:
16 | name: Lint
17 | runs-on: ubuntu-24.04
18 | strategy:
19 | matrix:
20 | python-version: ['3.12']
21 | steps:
22 | - name: Checkout code
23 | uses: actions/checkout@v4
24 | with:
25 | fetch-depth: '0'
26 |
27 | - name: Set up uv & venv
28 | uses: astral-sh/setup-uv@v5
29 | with:
30 | version: ${{ env.UV_VERSION }}
31 | enable-cache: true
32 | python-version: ${{ matrix.python-version }}
33 | cache-dependency-glob: uv.lock
34 |
35 | - name: Setup just
36 | uses: extractions/setup-just@v3
37 |
38 | - name: Lint project
39 | run: |
40 | just lint
41 |
42 | test:
43 | name: Test
44 | runs-on: ubuntu-24.04
45 | strategy:
46 | matrix:
47 | python-version: ['3.12']
48 |
49 | steps:
50 | - name: Checkout code
51 | uses: actions/checkout@v4
52 | with:
53 | fetch-depth: '0'
54 |
55 | - name: Set up uv & venv
56 | uses: astral-sh/setup-uv@v5
57 | with:
58 | version: ${{ env.UV_VERSION }}
59 | enable-cache: true
60 | python-version: ${{ matrix.python-version }}
61 | cache-dependency-glob: uv.lock
62 |
63 | - name: Setup just
64 | uses: extractions/setup-just@v3
65 |
66 | - name: Lint project
67 | run: |
68 | just test
69 |
70 | - name: Upload pytest test results
71 | uses: actions/upload-artifact@master
72 | with:
73 | name: pytest-results-${{ matrix.python-version }}
74 | path: htmlcov/
75 |
--------------------------------------------------------------------------------
/examples/example_workers/scipy_worker/main.py:
--------------------------------------------------------------------------------
1 | import io
2 | import os
3 | import pickle
4 | from sys import argv
5 | from typing import Annotated, NamedTuple
6 | import numpy as np
7 |
8 | from tierkreis.controller.data.core import Deserializer, Serializer
9 | from tierkreis.worker.worker import Worker
10 |
11 | worker = Worker("scipy_worker")
12 |
13 |
14 | def save(a: np.ndarray) -> bytes:
15 | with io.BytesIO() as bs:
16 | np.save(bs, a)
17 | return bs.getvalue()
18 |
19 |
20 | def load(bs: bytes) -> np.ndarray:
21 | with io.BytesIO() as bi:
22 | bi.write(bs)
23 | bi.seek(0)
24 | return np.load(bi, encoding="bytes")
25 |
26 |
27 | SER_METHOD = os.environ.get("SER_METHOD")
28 | if SER_METHOD == "dumps":
29 | ser = Serializer(np.ndarray.dumps)
30 | deser = Deserializer(pickle.loads)
31 | elif SER_METHOD == "tolist":
32 | ser = Serializer(np.ndarray.tolist, "json")
33 | deser = Deserializer(np.array, "json")
34 | elif SER_METHOD == "save":
35 | ser = Serializer(save)
36 | deser = Deserializer(load)
37 | else:
38 | ser = None
39 | deser = None
40 |
41 | NDArray = Annotated[np.ndarray, ser, deser]
42 |
43 |
44 | class PointedArray(NamedTuple):
45 | a: NDArray
46 | p: int
47 |
48 |
49 | @worker.task()
50 | def add_point(a: NDArray, p: int) -> PointedArray:
51 | return PointedArray(a, p)
52 |
53 |
54 | @worker.task()
55 | def eval_point(pa: PointedArray) -> float:
56 | return pa.a.item(pa.p)
57 |
58 |
59 | @worker.task()
60 | def linspace(start: float, stop: float, num: int = 50) -> NDArray:
61 | return np.linspace(start, stop, num=num)
62 |
63 |
64 | @worker.task()
65 | def transpose(a: NDArray) -> NDArray:
66 | return a.transpose()
67 |
68 |
69 | @worker.task()
70 | def reshape(a: NDArray, shape: int | list[int]) -> NDArray:
71 | return np.reshape(a, shape)
72 |
73 |
74 | if __name__ == "__main__":
75 | worker.app(argv)
76 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/storage.py:
--------------------------------------------------------------------------------
1 | from importlib.util import module_from_spec, spec_from_file_location
2 | from pathlib import Path
3 | from sys import argv
4 | import sys
5 | from typing import Callable
6 | from uuid import UUID
7 |
8 | from tierkreis.controller.data.graph import GraphData
9 | from tierkreis.controller.storage.filestorage import ControllerFileStorage
10 | from tierkreis.controller.storage.graphdata import GraphDataStorage
11 | from tierkreis.controller.storage.protocol import ControllerStorage
12 |
13 |
14 | def file_storage_fn(tkr_dir: Path) -> Callable[[UUID], ControllerStorage]:
15 | def inner(workflow_id: UUID):
16 | return ControllerFileStorage(
17 | workflow_id=workflow_id, tierkreis_directory=tkr_dir
18 | )
19 |
20 | return inner
21 |
22 |
23 | def graph_data_storage_fn(
24 | graph_specifier: str,
25 | ) -> tuple[Callable[[UUID], ControllerStorage], Path]:
26 | graph_specifier = argv[1]
27 | mod_path, var = graph_specifier.rsplit(":", 1)
28 | spec = spec_from_file_location("tkr_tmp.graph", mod_path)
29 |
30 | if spec is None:
31 | raise ValueError(f"File is not a Python module: {mod_path}")
32 |
33 | module = module_from_spec(spec)
34 | sys.modules["tkr_tmp.graph"] = module
35 | loader = spec.loader
36 |
37 | if loader is None:
38 | raise ValueError("Could not get loader from module.")
39 |
40 | loader.exec_module(module)
41 | graph = getattr(module, var).data
42 |
43 | def inner(workflow_id: UUID) -> ControllerStorage:
44 | return GraphDataStorage(UUID(int=0), graph=graph)
45 |
46 | return inner, Path(mod_path)
47 |
48 |
49 | def from_graph_data_storage_fn(
50 | graph_data: GraphData,
51 | ) -> Callable[[UUID], ControllerStorage]:
52 | def inner(workflow_id: UUID) -> ControllerStorage:
53 | return GraphDataStorage(UUID(int=0), graph=graph_data)
54 |
55 | return inner
56 |
--------------------------------------------------------------------------------
/tierkreis/tests/idl/test_idl.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pytest
3 | from tierkreis.exceptions import TierkreisError
4 | from tierkreis.idl.models import GenericType
5 | from tierkreis.namespace import Namespace
6 | from tierkreis.idl.type_symbols import type_symbol
7 | import tests.idl.namespace1
8 |
9 | type_symbols = [
10 | ("uint8", GenericType(int, [])),
11 | ("string", GenericType(str, [])),
12 | ("Array", GenericType(list, [GenericType(int, [])])),
13 | (
14 | "Record>",
15 | GenericType(
16 | dict, [GenericType(str, []), GenericType(list, [GenericType(str, [])])]
17 | ),
18 | ),
19 | (
20 | "Record>>",
21 | GenericType(
22 | dict,
23 | [
24 | GenericType(str, []),
25 | GenericType(list, [GenericType("C", ["T", "A"])]),
26 | ],
27 | ),
28 | ),
29 | ]
30 | type_symbols_for_failure = ["decimal", "unknown", "duration"]
31 | dir = Path(__file__).parent
32 | typespecs = [(dir / "namespace1.tsp", tests.idl.namespace1.expected_namespace)]
33 |
34 |
35 | @pytest.mark.parametrize("type_symb,expected", type_symbols)
36 | def test_type_t(type_symb: str, expected: type):
37 | assert (expected, "") == type_symbol(type_symb)
38 |
39 |
40 | @pytest.mark.parametrize("path,expected", typespecs)
41 | def test_namespace(path: Path, expected: Namespace):
42 | namespace = Namespace.from_spec_file(path)
43 | assert namespace.stubs() == expected.stubs()
44 |
45 | # Write stubs to file.
46 | # This file will be subject to linting.
47 | # Also a change in this file can indicate an unexpectedly breaking change.
48 | namespace.write_stubs(Path(__file__).parent / "stubs_output.py")
49 |
50 |
51 | @pytest.mark.parametrize("type_symb", type_symbols_for_failure)
52 | def test_parser_fail(type_symb: str):
53 | with pytest.raises(TierkreisError):
54 | type_symbol(type_symb)
55 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/templates/function.jinja:
--------------------------------------------------------------------------------
1 | {% from "macros/breadcrumbs.html" import breadcrumbs_macro with context %}
2 | {% from "macros/common_head.html" import common_head_macro with context %}
3 | {% from "macros/header.html" import header_macro with context %}
4 |
5 |
6 | Tierkreis visualization: node
7 | {{ common_head_macro() }}
8 |
9 |
10 | {{ header_macro() }}
11 |
12 | {{ breadcrumbs_macro(breadcrumbs) }}
13 |
14 |
20 | {{ definition.function_name }}
21 |
22 | {% if data.has_error %}
23 | Error
24 | {{ data.error_message}}
25 |
26 | {% endif %}
27 |
28 | -
29 | Inputs
30 |
31 | {% for input,path in definition.inputs.items() %}
32 | -
33 | {{ input }}
36 | ({{ path }})
37 |
38 | {% endfor %}
39 |
40 |
41 |
42 | -
43 | Outputs
44 |
45 | {% for output,path in definition.outputs.items() %}
46 | -
47 | {{ output }}
50 | ({{ path }})
51 |
52 | {% endfor %}
53 |
54 |
55 | - logs
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/infra/slurm_local/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | if [ "$1" = "slurmdbd" ]
5 | then
6 | echo "---> Starting the MUNGE Authentication service (munged) ..."
7 | gosu munge /usr/sbin/munged
8 |
9 | echo "---> Starting the Slurm Database Daemon (slurmdbd) ..."
10 |
11 | {
12 | . /etc/slurm/slurmdbd.conf
13 | until echo "SELECT 1" | mysql -h $StorageHost -u$StorageUser -p$StoragePass 2>&1 > /dev/null
14 | do
15 | echo "-- Waiting for database to become active ..."
16 | sleep 2
17 | done
18 | }
19 | echo "-- Database is now active ..."
20 |
21 | exec gosu slurm /usr/sbin/slurmdbd -Dvvv
22 | fi
23 |
24 | if [ "$1" = "slurmctld" ]
25 | then
26 | echo "---> Starting the MUNGE Authentication service (munged) ..."
27 | gosu munge /usr/sbin/munged
28 |
29 | echo "---> Waiting for slurmdbd to become active before starting slurmctld ..."
30 |
31 | until 2>/dev/null >/dev/tcp/slurmdbd/6819
32 | do
33 | echo "-- slurmdbd is not available. Sleeping ..."
34 | sleep 2
35 | done
36 | echo "-- slurmdbd is now active ..."
37 |
38 | echo "---> Starting the Slurm Controller Daemon (slurmctld) ..."
39 | if /usr/sbin/slurmctld -V | grep -q '17.02' ; then
40 | exec gosu slurm /usr/sbin/slurmctld -Dvvv
41 | else
42 | exec gosu slurm /usr/sbin/slurmctld -i -Dvvv
43 | fi
44 | fi
45 |
46 | if [ "$1" = "slurmd" ]
47 | then
48 | echo "---> Starting the MUNGE Authentication service (munged) ..."
49 | gosu munge /usr/sbin/munged
50 |
51 | echo "---> Waiting for slurmctld to become active before starting slurmd..."
52 |
53 | until 2>/dev/null >/dev/tcp/slurmctld/6817
54 | do
55 | echo "-- slurmctld is not available. Sleeping ..."
56 | sleep 2
57 | done
58 | echo "-- slurmctld is now active ..."
59 |
60 | echo "---> Starting the Slurm Node Daemon (slurmd) ..."
61 | exec /usr/sbin/slurmd -Dvvv
62 | fi
63 |
64 | exec "$@"
65 |
--------------------------------------------------------------------------------
/tierkreis_workers/qulacs_worker/main.py:
--------------------------------------------------------------------------------
1 | from sys import argv
2 | from typing import Any
3 |
4 | from tierkreis import Worker
5 | from pytket._tket.circuit import Circuit
6 | from pytket.backends.backend import Backend
7 | from pytket.backends.backendresult import BackendResult
8 | from pytket.extensions.qulacs.backends.qulacs_backend import QulacsBackend
9 |
10 | worker = Worker("qulacs_worker")
11 |
12 |
13 | def get_backend(result_type: str = "state_vector", gpu_sim: bool = False) -> Backend:
14 | if gpu_sim:
15 | from pytket.extensions.qulacs.backends.qulacs_backend import QulacsGPUBackend
16 |
17 | return QulacsGPUBackend()
18 | else:
19 | return QulacsBackend(result_type)
20 |
21 |
22 | def get_config(seed: int | None = None) -> dict[str, Any]:
23 | return {} if seed is None else {"seed": seed}
24 |
25 |
26 | @worker.task()
27 | def get_compiled_circuit(
28 | circuit: Circuit,
29 | optimisation_level: int = 2,
30 | result_type: str = "state_vector",
31 | gpu_sim: bool = False,
32 | ) -> Circuit:
33 | backend = get_backend(result_type, gpu_sim)
34 | return backend.get_compiled_circuit(circuit, optimisation_level)
35 |
36 |
37 | @worker.task()
38 | def run_circuit(
39 | circuit: Circuit,
40 | n_shots: int,
41 | result_type: str = "state_vector",
42 | gpu_sim: bool = False,
43 | seed: int | None = None,
44 | ) -> BackendResult:
45 | backend = get_backend(result_type, gpu_sim)
46 | config = get_config(seed)
47 | return backend.run_circuit(circuit, n_shots, **config)
48 |
49 |
50 | @worker.task()
51 | def run_circuits(
52 | circuits: list[Circuit],
53 | n_shots: list[int],
54 | result_type: str = "state_vector",
55 | gpu_sim: bool = False,
56 | seed: int | None = None,
57 | ) -> list[BackendResult]:
58 | backend = get_backend(result_type, gpu_sim)
59 | config = get_config(seed)
60 | return backend.run_circuits(circuits, n_shots, **config)
61 |
62 |
63 | if __name__ == "__main__":
64 | worker.app(argv)
65 |
--------------------------------------------------------------------------------
/tierkreis/tests/cli/test_run_workflow.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import json
3 | import logging
4 | from pathlib import Path
5 | from uuid import UUID
6 | from unittest import mock
7 |
8 |
9 | from tierkreis.controller.data.graph import GraphData
10 | from tierkreis.cli.run_workflow import run_workflow
11 | from tests.controller.sample_graphdata import simple_eval
12 | from tierkreis.controller.data.types import ptype_from_bytes
13 |
14 | logger = logging.getLogger(__name__)
15 |
16 |
17 | @pytest.fixture()
18 | def graph() -> GraphData:
19 | return simple_eval()
20 |
21 |
22 | def test_run_workflow(graph: GraphData) -> None:
23 | inputs = {}
24 | run_workflow(inputs=inputs, graph=graph, run_id=31415)
25 | with open(
26 | Path.home()
27 | / ".tierkreis"
28 | / "checkpoints"
29 | / str(UUID(int=31415))
30 | / "-/outputs/simple_eval_output",
31 | "rb",
32 | ) as fh:
33 | c = ptype_from_bytes(fh.read())
34 |
35 | assert c == 12
36 |
37 |
38 | def test_run_workflow_with_output(graph: GraphData, capfd) -> None:
39 | inputs = {}
40 | run_workflow(inputs=inputs, graph=graph, run_id=31415, print_output=True)
41 | out, _ = capfd.readouterr()
42 | assert "simple_eval_output: b'12'\n" in out
43 |
44 |
45 | @mock.patch("uuid.uuid4", return_value=UUID(int=31415))
46 | def test_run_workflow_default_run_id(_, graph: GraphData) -> None:
47 | inputs = {}
48 | run_workflow(inputs=inputs, graph=graph)
49 | with open(
50 | Path.home()
51 | / ".tierkreis"
52 | / "checkpoints"
53 | / str(UUID(int=31415))
54 | / "-/outputs/simple_eval_output",
55 | "rb",
56 | ) as fh:
57 | c = json.loads(fh.read())
58 | assert c == 12
59 |
60 |
61 | def test_run_workflow_uv_executor(graph: GraphData) -> None:
62 | inputs = {}
63 | run_workflow(
64 | inputs=inputs,
65 | graph=graph,
66 | run_id=31415,
67 | use_uv_worker=True,
68 | registry_path=Path("."),
69 | )
70 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # React Flow starter (Vite + TS)
4 |
5 | We've put together this template to serve as a starting point for folks
6 | interested in React Flow. You can use this both as a base for your own React
7 | Flow applications, or for small experiments or bug reports.
8 |
9 | **TypeScript not your thing?** We also have a vanilla JavaScript starter template,
10 | just for you!
11 |
12 | ## Getting up and running
13 |
14 | You can get this template without forking/cloning the repo using `degit`:
15 |
16 | ```bash
17 | npx degit xyflow/vite-react-flow-template your-app-name
18 | ```
19 |
20 | The template contains mostly the minimum dependencies to get up and running, but
21 | also includes eslint and some additional rules to help you write React code that
22 | is less likely to run into issues:
23 |
24 | ```bash
25 | npm install # or `pnpm install` or `yarn install`
26 | ```
27 |
28 | Vite is a great development server and build tool that we recommend our users to
29 | use. You can start a development server with:
30 |
31 | ```bash
32 | npm run dev
33 | ```
34 |
35 | While the development server is running, changes you make to the code will be
36 | automatically reflected in the browser!
37 |
38 | ## Things to try:
39 |
40 | - Create a new custom node inside `src/nodes/` (don't forget to export it from `src/nodes/index.ts`).
41 | - Change how things look by [overriding some of the built-in classes](https://reactflow.dev/learn/customization/theming#overriding-built-in-classes).
42 | - Add a layouting library to [position your nodes automatically](https://reactflow.dev/learn/layouting/layouting)
43 |
44 | ## Resources
45 |
46 | Links:
47 |
48 | - [React Flow - Docs](https://reactflow.dev)
49 | - [React Flow - Discord](https://discord.com/invite/Bqt6xrs)
50 |
51 | Learn:
52 |
53 | - [React Flow – Custom Nodes](https://reactflow.dev/learn/customization/custom-nodes)
54 | - [React Flow – Layouting](https://reactflow.dev/learn/layouting/layouting)
55 |
--------------------------------------------------------------------------------
/tierkreis/tests/idl/stubs_output.py:
--------------------------------------------------------------------------------
1 | """Code generated from TestNamespace namespace. Please do not edit."""
2 |
3 | from typing import NamedTuple, Protocol
4 | from tierkreis.controller.data.models import TKR
5 | from tierkreis.controller.data.types import PType, Struct
6 |
7 |
8 | class A(NamedTuple):
9 | age: TKR[int] # noqa: F821 # fmt: skip
10 | name: TKR[dict[str, str]] # noqa: F821 # fmt: skip
11 |
12 |
13 | class B(Struct, Protocol):
14 | age: int # noqa: F821 # fmt: skip
15 | name: dict[str, str] # noqa: F821 # fmt: skip
16 |
17 |
18 | class C[T: PType](Struct, Protocol):
19 | a: list[int] # noqa: F821 # fmt: skip
20 | b: "B" # noqa: F821 # fmt: skip
21 | included: "IncludedType" # noqa: F821 # fmt: skip
22 | ol: "list[ListItem]" # noqa: F821 # fmt: skip
23 | t: "T" # noqa: F821 # fmt: skip
24 |
25 |
26 | class IncludedType(Struct, Protocol):
27 | nested: "NestedType" # noqa: F821 # fmt: skip
28 |
29 |
30 | class ListItem(Struct, Protocol):
31 | i: int # noqa: F821 # fmt: skip
32 |
33 |
34 | class NestedType(Struct, Protocol):
35 | city: str # noqa: F821 # fmt: skip
36 |
37 |
38 | class foo(NamedTuple):
39 | a: TKR[int] # noqa: F821 # fmt: skip
40 | b: TKR[str] # noqa: F821 # fmt: skip
41 |
42 | @staticmethod
43 | def out() -> type[A]: # noqa: F821 # fmt: skip
44 | return A # noqa: F821 # fmt: skip
45 |
46 | @property
47 | def namespace(self) -> str:
48 | return "TestNamespace"
49 |
50 |
51 | class bar(NamedTuple):
52 | @staticmethod
53 | def out() -> type[TKR[B]]: # noqa: F821 # fmt: skip
54 | return TKR[B] # noqa: F821 # fmt: skip
55 |
56 | @property
57 | def namespace(self) -> str:
58 | return "TestNamespace"
59 |
60 |
61 | class z[T: PType](NamedTuple):
62 | c: TKR[C[T]] # noqa: F821 # fmt: skip
63 |
64 | @staticmethod
65 | def out() -> type[TKR[C[T]]]: # noqa: F821 # fmt: skip
66 | return TKR[C[T]] # noqa: F821 # fmt: skip
67 |
68 | @property
69 | def namespace(self) -> str:
70 | return "TestNamespace"
71 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/controller/executor/in_memory_executor.py:
--------------------------------------------------------------------------------
1 | import json
2 | import logging
3 | import importlib.util
4 | from pathlib import Path
5 |
6 | from tierkreis.controller.data.location import WorkerCallArgs
7 | from tierkreis.controller.storage.in_memory import ControllerInMemoryStorage
8 | from tierkreis.worker.storage.in_memory import InMemoryWorkerStorage
9 | from tierkreis.exceptions import TierkreisError
10 |
11 |
12 | logger = logging.getLogger(__name__)
13 |
14 |
15 | class InMemoryExecutor:
16 | """Executes workers in the same process as the controller.
17 |
18 | Implements: :py:class:`tierkreis.controller.executor.protocol.ControllerExecutor`
19 | """
20 |
21 | def __init__(self, registry_path: Path, storage: ControllerInMemoryStorage) -> None:
22 | self.registry_path = registry_path
23 | self.storage = storage
24 |
25 | def run(
26 | self,
27 | launcher_name: str,
28 | worker_call_args_path: Path,
29 | ) -> None:
30 | logging.basicConfig(
31 | format="%(asctime)s: %(message)s",
32 | datefmt="%Y-%m-%dT%H:%M:%S%z",
33 | filemode="a",
34 | level=logging.INFO,
35 | )
36 | logger.info("START %s %s", launcher_name, worker_call_args_path)
37 | call_args = WorkerCallArgs(
38 | **json.loads(self.storage.read(worker_call_args_path))
39 | )
40 |
41 | spec = importlib.util.spec_from_file_location(
42 | "in_memory", self.registry_path / launcher_name / "main.py"
43 | )
44 | if spec is None or spec.loader is None:
45 | raise TierkreisError(
46 | f"Couldn't load main.py in {self.registry_path / launcher_name}"
47 | )
48 | module = importlib.util.module_from_spec(spec)
49 | spec.loader.exec_module(module)
50 | worker_storage = InMemoryWorkerStorage(self.storage)
51 | module.worker.storage = worker_storage
52 | module.worker.functions[call_args.function_name](call_args)
53 | self.storage.touch(call_args.done_path)
54 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/templates/fallback.jinja:
--------------------------------------------------------------------------------
1 | {% from "macros/breadcrumbs.html" import breadcrumbs_macro with context %}
2 | {% from "macros/common_head.html" import common_head_macro with context %}
3 | {% from "macros/header.html" import header_macro with context %}
4 |
5 |
6 | Tierkreis visualization: node
7 | {{ common_head_macro() }}
8 |
9 |
10 | {{ header_macro() }}
11 |
12 | {{ breadcrumbs_macro(breadcrumbs) }}
13 |
14 |
23 | {{ node.type }}
24 |
25 |
26 | -
27 | Inputs
32 |
33 |
34 | {% for k, (loc, port) in inputs.items() %}
35 | -
36 | {{ k }}
37 | ({{ loc }}, {{ port }})
38 |
39 | {% endfor %}
40 |
41 |
42 |
43 | -
44 | Outputs
49 |
50 | {% for k, (loc, port) in outputs.items() %}
51 | -
52 | {{ k }}
53 | ({{ loc }}, {{ port }})
54 |
55 | {% endfor %}
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tierkreis
2 |
3 | Quantum-classical hybrid workflow orchestration tool.
4 | This is the top level repository containing the python packages.
5 |
6 | ## Packages
7 | - **Tierkreis**: the core functionality `pip install tierkreis`
8 | - **Tierkreis Visualization**: A simple web visualization tool for debugging workflows `pip install tierkreis-visualization`
9 | - **Tierkreis Workers**: A collection of commonly used workers `pip install tkr--worker`
10 | - Qiskit Aer
11 | - Quantinuum Nexus
12 | - Quantinuum PyTKet
13 |
14 |
15 | ## CLI
16 |
17 | Tierkreis comes with a command line interface for running workflows.
18 | To see all available options use `tkr --help`.
19 | To run the hello world example from the cli
20 | ```
21 | uv run tkr -g examples/hello_world/hello_world_graph.py:hello_graph -i data.json --uv --registry-path examples/hello_world/ -o
22 | ```
23 | Explanation:
24 | * `-g` specifies the graph to run by specifying the location and function to run.
25 | * `-i` specifies the input for the graph function. In this case it loads a json file from the project root with the contents `{"value": "world!"}`
26 | * `--uv` enables the use of the UV executor.
27 | * `--registry-path` specifies the location of the registry to use for the UV executor.
28 | * `-o` enables output printing.
29 |
30 |
31 |
32 | ## Development
33 | Requirements
34 | - `uv` >= 0.4
35 |
36 |
37 | See the justfile (requires `just`) for common development commands. For example:
38 |
39 | ```sh
40 | just test
41 | ```
42 |
43 | ## Documentation
44 |
45 | To build the documentation locally, run the command in the top level justfile.
46 |
47 | ```sh
48 | just docs
49 | ```
50 |
51 | Documentation will be built in HTML format by default and will be available in the `docs/build/html` folder.
52 |
53 | For documentation we use the [MyST](https://myst-parser.readthedocs.io/en/latest/index.html) parser with markdown files.
54 |
55 |
56 | ## License
57 |
58 | This project is licensed under Apache License, Version 2.0 ([LICENSE][] or http://www.apache.org/licenses/LICENSE-2.0).
59 |
60 |
61 | [LICENSE]: https://github.com/CQCL/tierkreis/blob/main/LICENCE
62 |
--------------------------------------------------------------------------------
/docs/source/worker/index.md:
--------------------------------------------------------------------------------
1 | # Workers
2 |
3 | A worker implements _atomic_ functionalities that will not be broken further by the controller.
4 | Tierkreis workers come in three flavors:
5 |
6 | - Builtin workers, provided by Tierkreis itself
7 | - User defined workers, by using the `@worker.task()` annotation or writing external ones
8 | - Prepackaged workers from the Tierkreis developers
9 |
10 | ```{toctree}
11 | :maxdepth: 2
12 | complex_types.md
13 | external_workers.md
14 | hello_world.md
15 | native_workers/index
16 | ```
17 |
18 | ## Prepackaged workers
19 |
20 | The following outlines the functionality of the prepackaged workers.
21 | Currently the following workers are provided as separate packages on pypi:
22 |
23 | - IBMQ
24 | - Qiskit Aer
25 | - Pytket
26 | - Quantinuum Nexus
27 | - Quantinuum Backend
28 |
29 | ### Qiskit Aer
30 |
31 | Compile and run quantum circuits locally with Qiskit Aer.
32 | More detailed docs [here](worker/native_workers/aer_worker.md).
33 |
34 | ### IBMQ
35 |
36 | Compile and run quantum circuits locally with IBMQ.
37 | More detailed docs [here](worker/native_workers/ibmq_worker.md).
38 |
39 | ### Pytket
40 |
41 | The pytket compiler suite to optimize circuits.
42 | The worker only contains a subset of common operations.
43 | For a custom compilation pipeline it is advised to build your own worker.
44 |
45 | More detailed docs [here](worker/native_workers/pytket_worker.md).
46 |
47 | **Installation**
48 |
49 | ```sh
50 | pip install tkr-pytket-worker
51 | ```
52 |
53 | will install an executable Python script `tkr_pytket_worker` into your virtual environment.
54 |
55 | **Example**
56 |
57 | See the example `hamiltonian_graph.py`.
58 |
59 | ### Quantinuum Nexus
60 |
61 | Interface to the Quantinuum Nexus platform.
62 | More detailed docs [here](worker/native_workers/nexus_worker.md).
63 |
64 | ### Quantinuum Backend
65 |
66 | Compile and run quantum circuits locally with Quantinuum backends.
67 | More detailed docs [here](worker/native_workers/quantinuum_worker.md).
68 |
69 | ### Qulacs
70 |
71 | Compile and run quantum circuits locally with Qulacs.
72 | More detailed docs [here](worker/native_workers/qulacs_worker.md).
73 |
--------------------------------------------------------------------------------
/tierkreis/tests/executor/test_hpc_executor.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from uuid import UUID
3 | import pytest
4 | from tierkreis.builder import GraphBuilder
5 | from tierkreis.controller import run_graph
6 | from tierkreis.controller.data.graph import GraphData
7 | from tierkreis.controller.data.models import TKR
8 | from tierkreis.controller.executor.hpc.job_spec import (
9 | JobSpec,
10 | MpiSpec,
11 | ResourceSpec,
12 | )
13 | from tierkreis.controller.executor.hpc.slurm import SLURMExecutor
14 | from tierkreis.controller.storage.filestorage import ControllerFileStorage
15 |
16 | from tests.executor.stubs import mpi_rank_info
17 | from tierkreis.storage import read_outputs
18 |
19 |
20 | def mpi_graph() -> GraphData:
21 | builder = GraphBuilder(outputs_type=TKR[str | None])
22 | mpi_result = builder.task(mpi_rank_info())
23 | builder.outputs(mpi_result)
24 | return builder.data
25 |
26 |
27 | def job_spec() -> JobSpec:
28 | return JobSpec(
29 | job_name="test_job",
30 | account="test_usr",
31 | command="--allow-run-as-root /root/.local/bin/uv run /slurm_mpi_worker/main.py ",
32 | resource=ResourceSpec(nodes=2, memory_gb=None),
33 | walltime="00:15:00",
34 | mpi=MpiSpec(max_proc_per_node=1),
35 | extra_scheduler_args={"--open-mode=append": None},
36 | output_path=Path("./logs.log"),
37 | error_path=Path("./errors.log"),
38 | )
39 |
40 |
41 | @pytest.mark.skip(reason="Needs SLURM setup.")
42 | def test_slurm_with_mpi() -> None:
43 | g = mpi_graph()
44 | storage = ControllerFileStorage(
45 | UUID(int=22),
46 | name="mpi_graph",
47 | do_cleanup=True,
48 | )
49 | sbatch = str(
50 | Path(__file__).parent.parent.parent.parent / "infra/slurm_local/sbatch"
51 | )
52 | executor = SLURMExecutor(
53 | spec=job_spec(),
54 | registry_path=None,
55 | logs_path=storage.logs_path,
56 | command=sbatch,
57 | )
58 | run_graph(storage, executor, g, {})
59 |
60 | output = read_outputs(g, storage)
61 |
62 | assert output is not None
63 | assert output == "Rank 0 out of 2 on c1.\nRank 1 out of 2 on c2."
64 |
--------------------------------------------------------------------------------
/tierkreis_visualization/frontend/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | function TooltipProvider({
9 | delayDuration = 0,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
18 | )
19 | }
20 |
21 | function Tooltip({
22 | ...props
23 | }: React.ComponentProps) {
24 | return (
25 |
26 |
27 |
28 | )
29 | }
30 |
31 | function TooltipTrigger({
32 | ...props
33 | }: React.ComponentProps) {
34 | return
35 | }
36 |
37 | function TooltipContent({
38 | className,
39 | sideOffset = 0,
40 | children,
41 | ...props
42 | }: React.ComponentProps) {
43 | return (
44 |
45 |
54 | {children}
55 |
56 |
57 |
58 | )
59 | }
60 |
61 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
62 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/cli/run_workflow.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import uuid
3 | import logging
4 |
5 | from tierkreis.controller import run_graph
6 | from tierkreis.controller.data.graph import GraphData
7 | from tierkreis.controller.data.location import Loc
8 | from tierkreis.controller.data.types import PType
9 | from tierkreis.controller.storage.filestorage import ControllerFileStorage
10 | from tierkreis.controller.executor.shell_executor import ShellExecutor
11 | from tierkreis.controller.executor.uv_executor import UvExecutor
12 |
13 | logger = logging.getLogger(__name__)
14 |
15 |
16 | def run_workflow(
17 | graph: GraphData,
18 | inputs: dict[str, PType],
19 | name: str | None = None,
20 | run_id: int | None = None,
21 | log_level: int | str = logging.INFO,
22 | registry_path: Path | None = None,
23 | print_output: bool = False,
24 | use_uv_worker: bool = False,
25 | n_iterations: int = 10000,
26 | polling_interval_seconds: float = 0.1,
27 | ) -> None:
28 | """Run a workflow."""
29 | logging.basicConfig(
30 | format="%(asctime)s: %(message)s",
31 | datefmt="%Y-%m-%dT%H:%M:%S%z",
32 | level=log_level,
33 | )
34 |
35 | if run_id is None:
36 | workflow_id = uuid.uuid4()
37 | else:
38 | workflow_id = uuid.UUID(int=run_id)
39 | logging.info("Workflow ID is %s", workflow_id)
40 | storage = ControllerFileStorage(workflow_id, name=name, do_cleanup=True)
41 | if registry_path is None:
42 | registry_path = Path(__file__).parent
43 | if use_uv_worker:
44 | executor = UvExecutor(registry_path=registry_path, logs_path=storage.logs_path)
45 | else:
46 | executor = ShellExecutor(registry_path, storage.workflow_dir)
47 |
48 | logging.info("Starting workflow at location: %s", storage.logs_path)
49 |
50 | run_graph(
51 | storage,
52 | executor,
53 | graph,
54 | inputs,
55 | n_iterations,
56 | polling_interval_seconds,
57 | )
58 | if print_output:
59 | all_outputs = graph.nodes[graph.output_idx()].inputs
60 | for output in all_outputs:
61 | print(f"{output}: {storage.read_output(Loc(), output)}")
62 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/controller/storage/in_memory.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from uuid import UUID
3 | from time import time
4 |
5 |
6 | from tierkreis.controller.storage.protocol import (
7 | StorageEntryMetadata,
8 | ControllerStorage,
9 | )
10 |
11 |
12 | class InMemoryFileData:
13 | value: bytes
14 | stats: StorageEntryMetadata
15 |
16 | def __init__(self, value: bytes) -> None:
17 | self.value = value
18 | self.stats = StorageEntryMetadata(time())
19 |
20 |
21 | class ControllerInMemoryStorage(ControllerStorage):
22 | def __init__(
23 | self,
24 | workflow_id: UUID,
25 | name: str | None = None,
26 | tierkreis_directory: Path = Path(),
27 | ) -> None:
28 | self.tkr_dir = tierkreis_directory
29 | self.workflow_id = workflow_id
30 | self.name = name
31 |
32 | self.files: dict[Path, InMemoryFileData] = {}
33 |
34 | def delete(self, path: Path) -> None:
35 | self.files = {}
36 |
37 | def exists(self, path: Path) -> bool:
38 | return path in list(self.files.keys())
39 |
40 | def list_subpaths(self, path: Path) -> list[Path]:
41 | if path == self.workflow_dir:
42 | nodes = set(
43 | [
44 | Path("/".join(str(x).split("/")[:2]))
45 | for x in self.files.keys()
46 | if str(x).startswith(str(path) + "/")
47 | ]
48 | )
49 | return list(nodes)
50 | return [x for x in self.files.keys() if str(x).startswith(str(path) + "/")]
51 |
52 | def link(self, src: Path, dst: Path) -> None:
53 | self.files[dst] = self.files[src]
54 |
55 | def mkdir(self, path: Path) -> None:
56 | return
57 |
58 | def read(self, path: Path) -> bytes:
59 | return self.files[path].value
60 |
61 | def touch(self, path: Path, is_dir: bool = False) -> None:
62 | self.files[path] = InMemoryFileData(b"")
63 |
64 | def stat(self, path: Path) -> StorageEntryMetadata:
65 | return self.files[path].stats
66 |
67 | def write(self, path: Path, value: bytes) -> None:
68 | self.files[path] = InMemoryFileData(value)
69 |
--------------------------------------------------------------------------------
/tierkreis/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 |
4 | ## [0.5.0] (2024-02-15)
5 |
6 |
7 | ### Features
8 |
9 | * New `OpaqueModel` which derives from pydantic `BaseModel` to defer Tierkreis
10 | serialization to pydantic.
11 | * New `TierkreisType.register_alternative` method to register
12 | Tierkreis-compatible alternatives to types to use for
13 | serialization/deserialization for arbitrary types.
14 | * `UnpackRow` base dataclass for defining rows that should be unpacked in Tierkreis
15 | (e.g. multiple outputs in a worker function).
16 |
17 | ### Breaking changes
18 |
19 | * Graph, Signature and Worker protos updated to v1alpha1 to add `FunctionNode`
20 | message to graph proto, with optional `retry_secs` field. Graphs serialized
21 | with v1alpha would deserialize as containing functions with empty names.
22 | * `ServerRuntime` simplified to only offer `run_graph` for execution, all "task"
23 | related methods removed. `runtime.proto` updated to v1alpha1 to match.
24 | * `TierkreisValue.from_python` uses python type to guide conversion - takes an
25 | optional annotation or uses `type(value)` as default.
26 | Subclasses of supported types will no longer converted as the supported super type.
27 |
28 |
29 | ## [0.4.1] (2024-02-07)
30 |
31 |
32 | ### Bug Fixes
33 | * Fix bug in `val_known_tk_type` (assumed dataclass for all structures).
34 | * Fix protobuf versioning
35 | * Fix shebang in pytket worker
36 |
37 |
38 | ## [0.4.0] (2024-02-01)
39 |
40 |
41 | ### Features
42 |
43 | * Support Pydantic `BaseModel` automated conversion to
44 | `StructType`/`StructValue`.
45 | * `UnpackRow` base dataclass for defining rows that should be unpacked in Tierkreis
46 | (e.g. multiple outputs in a worker function).
47 |
48 | ### Breaking changes
49 |
50 | * `TierkreisStruct` and `register_struct_convertible` are removed. Now automated
51 | conversion will be attempted on any dataclass or Pydantic `BaseModel`.
52 | * `ServerRuntime` simplified to only offer `run_graph` for execution, all "task"
53 | related methods removed. `runtime.proto` updated to v1alpha1 to match.
54 |
55 | ### Minor changes
56 |
57 | * `CircStruct` simplified to just contain the JSON string of a Pytket Circuit
58 | (essentially acts as an opaque newtype).
--------------------------------------------------------------------------------
/tierkreis/tests/controller/test_read_loop_trace.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from typing import Any, Type
3 | from uuid import UUID
4 |
5 | import pytest
6 | from tests.controller.loop_graphdata import loop_multiple_acc, loop_multiple_acc_untyped
7 | from tierkreis.controller import run_graph
8 | from tierkreis.controller.executor.in_memory_executor import InMemoryExecutor
9 | from tierkreis.controller.executor.shell_executor import ShellExecutor
10 | from tierkreis.controller.storage.filestorage import ControllerFileStorage
11 | from tierkreis.controller.storage.in_memory import ControllerInMemoryStorage
12 | from tierkreis.controller.data.graph import GraphData
13 | from tierkreis.storage import read_loop_trace
14 |
15 |
16 | return_value = [
17 | {"acc1": x, "acc2": y, "acc3": z}
18 | for x, y, z in zip(range(1, 7), range(2, 13, 2), range(3, 19, 3))
19 | ]
20 |
21 | params: list[tuple[GraphData, Any, str, int]] = [
22 | (
23 | loop_multiple_acc_untyped(),
24 | return_value,
25 | "multi_acc",
26 | 9,
27 | ),
28 | (
29 | loop_multiple_acc().get_data(),
30 | return_value,
31 | "multi_acc",
32 | 9,
33 | ),
34 | ]
35 | ids = [
36 | "loop_multiple_acc_untyped",
37 | "loop_multiple_acc",
38 | ]
39 |
40 | storage_classes = [ControllerFileStorage, ControllerInMemoryStorage]
41 | storage_ids = ["FileStorage", "In-memory"]
42 |
43 |
44 | @pytest.mark.parametrize("storage_class", storage_classes, ids=storage_ids)
45 | @pytest.mark.parametrize("graph,output,name,id", params, ids=ids)
46 | def test_read_loop_trace(
47 | storage_class: Type[ControllerFileStorage | ControllerInMemoryStorage],
48 | graph: GraphData,
49 | output: Any,
50 | name: str,
51 | id: int,
52 | ):
53 | g = graph
54 | storage = storage_class(UUID(int=id), name=name)
55 | executor = ShellExecutor(Path("./python/examples/launchers"), Path(""))
56 | if isinstance(storage, ControllerInMemoryStorage):
57 | executor = InMemoryExecutor(Path("./tierkreis/tierkreis"), storage=storage)
58 | storage.clean_graph_files()
59 | run_graph(storage, executor, g, {})
60 |
61 | actual_output = read_loop_trace(g, storage, "my_loop")
62 | assert actual_output == output
63 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/controller/storage/filestorage.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 | from pathlib import Path
4 | from time import time_ns
5 | from uuid import UUID
6 |
7 | from tierkreis.controller.storage.protocol import (
8 | StorageEntryMetadata,
9 | ControllerStorage,
10 | )
11 |
12 |
13 | class ControllerFileStorage(ControllerStorage):
14 | def __init__(
15 | self,
16 | workflow_id: UUID,
17 | name: str | None = None,
18 | tierkreis_directory: Path = Path.home() / ".tierkreis" / "checkpoints",
19 | do_cleanup: bool = False,
20 | ) -> None:
21 | self.tkr_dir = tierkreis_directory
22 | self.workflow_id = workflow_id
23 | self.name = name
24 | if do_cleanup:
25 | self.delete(self.workflow_dir)
26 |
27 | def delete(self, path: Path) -> None:
28 | uid = os.getuid()
29 | tmp_dir = Path(f"/tmp/{uid}/tierkreis/archive/{self.workflow_id}/{time_ns()}")
30 | tmp_dir.mkdir(parents=True, exist_ok=True)
31 | if self.exists(path):
32 | shutil.move(path, tmp_dir)
33 |
34 | def exists(self, path: Path) -> bool:
35 | return path.exists()
36 |
37 | def list_subpaths(self, path: Path) -> list[Path]:
38 | return [sub_path for sub_path in path.iterdir()]
39 |
40 | def link(self, src: Path, dst: Path) -> None:
41 | dst.parent.mkdir(parents=True, exist_ok=True)
42 | os.link(src, dst)
43 |
44 | def mkdir(self, path: Path) -> None:
45 | return path.mkdir(parents=True, exist_ok=True)
46 |
47 | def read(self, path: Path) -> bytes:
48 | with open(path, "rb") as fh:
49 | return fh.read()
50 |
51 | def touch(self, path: Path, is_dir: bool = False) -> None:
52 | if is_dir:
53 | path.mkdir(parents=True, exist_ok=True)
54 | return
55 |
56 | path.parent.mkdir(parents=True, exist_ok=True)
57 | path.touch()
58 |
59 | def stat(self, path: Path) -> StorageEntryMetadata:
60 | return StorageEntryMetadata(path.stat().st_mtime)
61 |
62 | def write(self, path: Path, value: bytes) -> None:
63 | path.parent.mkdir(parents=True, exist_ok=True)
64 | with open(path, "wb+") as fh:
65 | fh.write(value)
66 |
--------------------------------------------------------------------------------
/tierkreis/tierkreis/storage.py:
--------------------------------------------------------------------------------
1 | from tierkreis.builder import GraphBuilder
2 | from tierkreis.controller.data.graph import GraphData
3 | from tierkreis.controller.data.location import Loc
4 | from tierkreis.controller.data.types import PType, ptype_from_bytes
5 | from tierkreis.controller.storage.protocol import ControllerStorage
6 | from tierkreis.controller.storage.filestorage import (
7 | ControllerFileStorage as FileStorage,
8 | )
9 | from tierkreis.controller.storage.in_memory import (
10 | ControllerInMemoryStorage as InMemoryStorage,
11 | )
12 | from tierkreis.exceptions import TierkreisError
13 |
14 | __all__ = ["FileStorage", "InMemoryStorage"]
15 |
16 |
17 | def read_outputs(
18 | g: GraphData | GraphBuilder, storage: ControllerStorage
19 | ) -> dict[str, PType] | PType:
20 | if isinstance(g, GraphBuilder):
21 | g = g.get_data()
22 |
23 | out_ports = list(g.nodes[g.output_idx()].inputs.keys())
24 | if len(out_ports) == 1 and "value" in out_ports:
25 | return ptype_from_bytes(storage.read_output(Loc(), "value"))
26 | return {k: ptype_from_bytes(storage.read_output(Loc(), k)) for k in out_ports}
27 |
28 |
29 | def read_loop_trace(
30 | g: GraphData | GraphBuilder,
31 | storage: ControllerStorage,
32 | node_name: str,
33 | output_name: str | None = None,
34 | ) -> list[PType | dict[str, list[PType]]]:
35 | """Reads the trace of a loop from storage."""
36 | if isinstance(g, GraphBuilder):
37 | g = g.get_data()
38 | loc = storage.loc_from_node_name(node_name)
39 | if loc is None:
40 | raise TierkreisError(f"Loop name {node_name} not found in debug data.")
41 | output_names = storage.read_output_ports(loc)
42 | if output_name is None:
43 | traces = {
44 | name: [ptype_from_bytes(r) for r in storage.read_loop_trace(loc, name)]
45 | for name in output_names
46 | if name != "should_continue"
47 | }
48 | return [dict(zip(traces.keys(), vals)) for vals in zip(*traces.values())]
49 |
50 | if output_name not in output_names:
51 | raise TierkreisError(f"Output name {output_name} not found in loop node output")
52 | results = storage.read_loop_trace(loc, output_name)
53 | return [ptype_from_bytes(r) for r in results]
54 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/templates/eval.jinja:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% from "macros/breadcrumbs.html" import breadcrumbs_macro with context %}
4 | {% from "macros/common_head.html" import common_head_macro with context %}
5 | {% from "macros/header.html" import header_macro with context %}
6 | {% from "macros/json_editor.html" import json_editor_macro with context %}
7 |
8 |
9 | Tierkreis visualization: EVAL node
10 | {{ common_head_macro() }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{ header_macro() }}
19 |
20 | {{ breadcrumbs_macro(breadcrumbs, true) }}
21 |
22 |
23 |
26 |
27 | {{ json_editor_macro(nodes|tojson, edges|tojson) }}
28 |
29 |
30 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/examples/pytket_graph.py:
--------------------------------------------------------------------------------
1 | # /// script
2 | # requires-python = ">=3.12"
3 | # dependencies = ["tierkreis", "pytket"]
4 | #
5 | # [tool.uv.sources]
6 | # tierkreis = { path = "../tierkreis", editable = true }
7 | # ///
8 | from uuid import UUID
9 | from pathlib import Path
10 | from typing import NamedTuple
11 |
12 | from pytket._tket.circuit import Circuit
13 | from pytket.backends.backendresult import BackendResult
14 |
15 | from tierkreis.builder import GraphBuilder
16 |
17 | from tierkreis.controller.data.models import TKR, OpaqueType
18 | from tierkreis.controller import run_graph
19 | from tierkreis.storage import read_outputs, InMemoryStorage
20 | from tierkreis.controller.executor.in_memory_executor import InMemoryExecutor
21 | from tierkreis.ibmq_worker import compile_circuit_ibmq
22 | from tierkreis.aer_worker import submit_single
23 |
24 |
25 | def ghz() -> Circuit:
26 | circ1 = Circuit(2)
27 | circ1.H(0)
28 | circ1.CX(0, 1)
29 | circ1.measure_all()
30 | return circ1
31 |
32 |
33 | class IBMInput(NamedTuple):
34 | circuit: TKR[OpaqueType["pytket._tket.circuit.Circuit"]] # noqa: F821
35 | n_shots: TKR[int]
36 | backend: TKR[str]
37 |
38 |
39 | def compile_run_single():
40 | g = GraphBuilder(
41 | IBMInput, TKR[OpaqueType["pytket.backends.backendresult.BackendResult"]]
42 | )
43 |
44 | compiled_circuit = g.task(
45 | compile_circuit_ibmq(
46 | circuit=g.inputs.circuit,
47 | device_name=g.inputs.backend,
48 | optimisation_level=g.const(2),
49 | )
50 | )
51 | res = g.task(submit_single(compiled_circuit, g.inputs.n_shots))
52 | g.outputs(res)
53 | return g
54 |
55 |
56 | def main():
57 | g = compile_run_single()
58 | storage = InMemoryStorage(UUID(int=109))
59 | executor = InMemoryExecutor(
60 | Path(__file__).parent.parent / "tierkreis_workers", storage
61 | )
62 | n_shots = 30
63 | run_graph(
64 | storage,
65 | executor,
66 | g,
67 | {
68 | "circuit": ghz(),
69 | "n_shots": n_shots,
70 | },
71 | polling_interval_seconds=0.1,
72 | )
73 | res = read_outputs(g, storage)
74 | assert isinstance(res, BackendResult)
75 | assert len(res.get_shots()) == n_shots
76 | print(res)
77 |
78 |
79 | if __name__ == "__main__":
80 | main()
81 |
--------------------------------------------------------------------------------
/tierkreis_visualization/tierkreis_visualization/data/loop.py:
--------------------------------------------------------------------------------
1 | import json
2 | from pydantic import BaseModel
3 | from tierkreis.controller.data.location import Loc
4 | from tierkreis.controller.storage.protocol import ControllerStorage
5 |
6 |
7 | from tierkreis_visualization.data.eval import check_error
8 | from tierkreis_visualization.data.models import PyNode, PyEdge
9 |
10 |
11 | class LoopNodeData(BaseModel):
12 | nodes: list[PyNode]
13 | edges: list[PyEdge]
14 |
15 |
16 | def get_loop_node(
17 | storage: ControllerStorage, node_location: Loc, errored_nodes: list[Loc]
18 | ) -> LoopNodeData:
19 | i = 0
20 | while storage.is_node_started(node_location.L(i + 1)):
21 | i += 1
22 | new_location = node_location.L(i)
23 |
24 | nodes = [
25 | PyNode(
26 | id=n,
27 | status="Finished",
28 | function_name=f"L{n}",
29 | node_location=node_location.L(n),
30 | started_time=storage.read_started_time(node_location.L(n)) or "",
31 | finished_time=storage.read_finished_time(node_location.L(n)) or "",
32 | )
33 | for n in range(i)
34 | ]
35 |
36 | if check_error(node_location, errored_nodes):
37 | last_status = "Error"
38 | elif storage.is_node_finished(new_location):
39 | last_status = "Finished"
40 | else:
41 | last_status = "Started"
42 | nodes.append(
43 | PyNode(
44 | id=i,
45 | status=last_status,
46 | function_name=f"L{i}",
47 | node_location=new_location,
48 | started_time=storage.read_started_time(new_location) or "",
49 | finished_time=storage.read_finished_time(new_location) or "",
50 | )
51 | )
52 | edges = []
53 | for port_name in storage.read_node_def(node_location.L(0)).outputs:
54 | edges.extend(
55 | [
56 | PyEdge(
57 | from_node=n,
58 | from_port=port_name,
59 | to_node=n + 1,
60 | to_port=port_name,
61 | value=json.loads(
62 | storage.read_output(node_location.L(n), port_name)
63 | ),
64 | )
65 | for n in range(i)
66 | ]
67 | )
68 | return LoopNodeData(nodes=nodes, edges=edges)
69 |
--------------------------------------------------------------------------------
/docs/source/tutorial/eval.md:
--------------------------------------------------------------------------------
1 | ---
2 | file_format: mystnb
3 | kernelspec:
4 | name: python3
5 | ---
6 |
7 | # Nested graphs using Eval
8 |
9 | To create this graph we need only to install the `tierkreis` package:
10 |
11 | ```
12 | pip install tierkreis
13 | ```
14 |
15 | ## Graph
16 |
17 | We can run graphs from within other graphs by using `GraphBuilder.eval`.
18 | Recall the `fib_step` graph that we wrote last time:
19 |
20 | ```{code-cell} ipython3
21 | from typing import NamedTuple
22 |
23 | from tierkreis.builder import GraphBuilder
24 | from tierkreis.builtins.stubs import iadd
25 | from tierkreis.models import TKR
26 |
27 |
28 | class FibData(NamedTuple):
29 | a: TKR[int]
30 | b: TKR[int]
31 |
32 |
33 | fib_step = GraphBuilder(FibData, FibData)
34 | sum = fib_step.task(iadd(fib_step.inputs.a, fib_step.inputs.b))
35 | fib_step.outputs(FibData(fib_step.inputs.b, sum))
36 | ```
37 |
38 | We create a graph `fib4` that calls `fib_step` three times.
39 | The graph will have no inputs and gives a single integer as output:
40 |
41 | ```{code-cell} ipython3
42 | from tierkreis.models import EmptyModel
43 |
44 | fib4 = GraphBuilder(EmptyModel, TKR[int])
45 | ```
46 |
47 | The `GraphBuilder.eval` method takes a `GraphBuilder` object as its first argument
48 | and the appropriately typed input data as the second object.
49 |
50 | ```{code-cell} ipython3
51 | second = fib4.eval(fib_step, FibData(a=fib4.const(0), b=fib4.const(1)))
52 | ```
53 |
54 | We can iterate manually as follows:
55 |
56 | ```{code-cell} ipython3
57 | third = fib4.eval(fib_step, second)
58 | fourth = fib4.eval(fib_step, third)
59 | fib4.outputs(fourth.b)
60 | ```
61 |
62 | In the [next tutorial](./loop.md) we will see how to iterate programmatically.
63 |
64 | # Execution
65 |
66 | Since we still only use built-in functions, we execute the graph in the same way as before.
67 |
68 | ```{code-cell} ipython3
69 | from uuid import UUID
70 | from pathlib import Path
71 |
72 | from tierkreis import run_graph
73 | from tierkreis.storage import FileStorage, read_outputs
74 | from tierkreis.executor import ShellExecutor
75 |
76 | storage = FileStorage(UUID(int=99), name="Nested graphs using Eval")
77 | executor = ShellExecutor(Path("."), workflow_dir=storage.workflow_dir)
78 |
79 | storage.clean_graph_files()
80 | run_graph(storage, executor, fib4.get_data(), {})
81 | print(read_outputs(fib4, storage))
82 | ```
83 |
--------------------------------------------------------------------------------