From 90b4786d5da0e482b2dfd7ad0af23bd429c616e7 Mon Sep 17 00:00:00 2001 From: marcosf2 Date: Fri, 8 May 2026 11:07:29 -0500 Subject: [PATCH 1/6] First pass at real version --- .gitignore | 2 + CONTEXT.md | 74 ++++++++ docs/_static/{hardware => polarimeter}/BS.gif | Bin .../{hardware => polarimeter}/Magnet+Base.gif | Bin .../{hardware => polarimeter}/Mirror.gif | Bin .../{hardware => polarimeter}/Post+Base.gif | Bin .../{hardware => polarimeter}/film-mount.gif | Bin .../{hardware => polarimeter}/laser.gif | Bin .../{hardware => polarimeter}/lens-mount.gif | Bin .../mount-to-post.gif | Bin docs/backend/architecture.md | 76 -------- docs/backend/configuration.md | 108 ----------- docs/backend/index.md | 15 -- docs/backend/installation.md | 40 ---- docs/backend/protocols.md | 51 ------ docs/backend/running.md | 54 ------ docs/conf.py | 14 +- docs/contributing/index.md | 12 +- docs/deployment/index.md | 13 ++ docs/frontend/architecture.md | 55 ------ docs/frontend/configuration.md | 25 --- docs/frontend/index.md | 14 -- docs/frontend/installation.md | 36 ---- docs/frontend/running.md | 38 ---- docs/index.md | 57 ++---- docs/overview/index.md | 40 ++-- docs/overview/software-map.md | 173 ++++++++++++++++++ docs/physical-devices/index.md | 13 ++ .../polarimeter}/assembly.md | 16 +- .../polarimeter}/components.md | 0 .../polarimeter}/index.md | 6 +- docs/pqn-gui/index.md | 9 + docs/pqn-hardware/index.md | 17 ++ docs/pqn-node/index.md | 14 ++ pyproject.toml | 1 + uv.lock | 16 ++ 36 files changed, 395 insertions(+), 594 deletions(-) create mode 100644 CONTEXT.md rename docs/_static/{hardware => polarimeter}/BS.gif (100%) rename docs/_static/{hardware => polarimeter}/Magnet+Base.gif (100%) rename docs/_static/{hardware => polarimeter}/Mirror.gif (100%) rename docs/_static/{hardware => polarimeter}/Post+Base.gif (100%) rename docs/_static/{hardware => polarimeter}/film-mount.gif (100%) rename docs/_static/{hardware => polarimeter}/laser.gif (100%) rename docs/_static/{hardware => polarimeter}/lens-mount.gif (100%) rename docs/_static/{hardware => polarimeter}/mount-to-post.gif (100%) delete mode 100644 docs/backend/architecture.md delete mode 100644 docs/backend/configuration.md delete mode 100644 docs/backend/index.md delete mode 100644 docs/backend/installation.md delete mode 100644 docs/backend/protocols.md delete mode 100644 docs/backend/running.md create mode 100644 docs/deployment/index.md delete mode 100644 docs/frontend/architecture.md delete mode 100644 docs/frontend/configuration.md delete mode 100644 docs/frontend/index.md delete mode 100644 docs/frontend/installation.md delete mode 100644 docs/frontend/running.md create mode 100644 docs/overview/software-map.md create mode 100644 docs/physical-devices/index.md rename docs/{hardware => physical-devices/polarimeter}/assembly.md (81%) rename docs/{hardware => physical-devices/polarimeter}/components.md (100%) rename docs/{hardware => physical-devices/polarimeter}/index.md (65%) create mode 100644 docs/pqn-gui/index.md create mode 100644 docs/pqn-hardware/index.md create mode 100644 docs/pqn-node/index.md diff --git a/.gitignore b/.gitignore index a31a0c7..6ee907d 100644 --- a/.gitignore +++ b/.gitignore @@ -206,3 +206,5 @@ cython_debug/ marimo/_static/ marimo/_lsp/ __marimo__/ + +.DS_Store diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..b492eb8 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,74 @@ +# PQN + +The Public Quantum Network: a federation of physical sites (**Nodes**) that expose real quantum experiments to the public through a web interface. + +This file is the canonical glossary for terms used across `pqn-docs`, `pqn-node`, `pqn-gui`, and `pqn-hardware`. When the same concept has multiple candidate names, the bolded entry is the one to use; aliases are listed as `_Avoid_`. + +## Language + +**Node**: +A PQN deployment at a single physical site — comprises a Node API, a Router, one or more Hardware Providers, instrument drivers, and the physical instruments themselves. The GUI is _not_ part of a Node. +_Avoid_: site, instance, station + +**Node API**: +The FastAPI service inside a Node. The only Node component reachable from outside the local intranet; handles requests from the GUI and from peer Nodes. Sometimes informally called "the Node" when zoomed in (see Flagged ambiguities). +_Avoid_: backend, server + +**Router**: +ZMQ message broker inside a Node. Routes messages between the Node API, Hardware Providers, and developer clients. + +**Hardware Provider**: +Process inside a Node that hosts physical instruments and exposes them to the rest of the Node via ProxyInstruments. +_Avoid_: Instrument Provider (older name; still appears in some code paths) + +**ProxyInstrument**: +Client-side handle for an instrument hosted by a Hardware Provider. Lets the Node API call the instrument without knowing where it physically runs. + +**Instrument**: +Software abstraction for one piece of physical hardware — TimeTagger, Polarimeter, Rotator, etc. Concrete implementations are called drivers and live in `pqn-hardware`. +_Avoid_: device (reserved for the physical thing) + +**Driver**: +A concrete Instrument implementation for a specific piece of hardware (e.g. Thorlabs rotator driver). + +**Protocol**: +The quantum-measurement logic for a single experiment type — CHSH, QKD, Tomography, Visibility. Lives in `pqn-hardware`. Distinct from Experiment. + +**Experiment**: +A user-facing activity in the GUI (CHSH Bell Test, Quantum Fortune, QKD, SSM, Tomography, Visibility). Implemented on top of one or more Protocols. + +**GUI**: +`pqn-gui`, the Next.js public-facing web interface. Talks to a single Node API, typically over localhost. Optional — every Experiment can be run programmatically without it. +_Avoid_: frontend, web app, UI (when precision matters) + +**PQN Network**: +A set of Nodes that can communicate with each other to run multi-Node Experiments (e.g. two-Node CHSH). + +**PQN Clone**: +An institution-run instance of the PQN — i.e. an organisation that stands up its own Node(s) and optionally federates with others. + +## Relationships + +- A **PQN Network** is composed of one or more **Nodes**. +- A **Node** contains one **Node API**, one **Router**, and one or more **Hardware Providers**. +- A **Hardware Provider** hosts one or more **Instruments**; each Instrument is implemented by a **Driver**. +- The **Node API** reaches Instruments via **ProxyInstruments** routed through the **Router**. +- An **Experiment** in the **GUI** invokes one or more **Protocols** on the **Node API**. +- A **Protocol** orchestrates Instruments to perform a single quantum measurement task. + +## Example dialogue + +> **New contributor:** "When the user clicks 'Run CHSH' in the GUI, what happens?" +> **Maintainer:** "The **GUI** calls the **Node API**. The Node API kicks off the CHSH **Protocol**, which talks to the local **Hardware Provider** through **ProxyInstruments** to drive the **Instruments** — rotators, the TimeTagger, etc. For two-**Node** CHSH the Node API also coordinates with the peer **Node**'s Node API." +> +> **New contributor:** "And the GUI is part of the Node?" +> **Maintainer:** "No — the GUI is just a client. The **Node** is the backend stack at one site. You can run every **Experiment** without the GUI." + +## Flagged ambiguities + +- **"Node"** is polysemous by design: + - At the **network** zoom level, "Node" means the whole site-level deployment. + - When working **inside** a Node, "Node" is sometimes used as shorthand for the **Node API** (the brain of the Node). + Resolution: prefer **"Node API"** in writing whenever you mean the FastAPI service. Reserve unqualified "Node" for the site-level meaning. +- **"Hardware"** has two unrelated meanings — the Python library `pqn-hardware` (drivers + Router + Provider + Protocols) and the physical devices themselves. In docs prose, write `pqn-hardware` (in code font) for the package and **Physical Device** / **Instrument** for the physical thing. +- **"Instrument Provider"** vs **"Hardware Provider"** — same concept, two names in code. Prefer **Hardware Provider** in docs. diff --git a/docs/_static/hardware/BS.gif b/docs/_static/polarimeter/BS.gif similarity index 100% rename from docs/_static/hardware/BS.gif rename to docs/_static/polarimeter/BS.gif diff --git a/docs/_static/hardware/Magnet+Base.gif b/docs/_static/polarimeter/Magnet+Base.gif similarity index 100% rename from docs/_static/hardware/Magnet+Base.gif rename to docs/_static/polarimeter/Magnet+Base.gif diff --git a/docs/_static/hardware/Mirror.gif b/docs/_static/polarimeter/Mirror.gif similarity index 100% rename from docs/_static/hardware/Mirror.gif rename to docs/_static/polarimeter/Mirror.gif diff --git a/docs/_static/hardware/Post+Base.gif b/docs/_static/polarimeter/Post+Base.gif similarity index 100% rename from docs/_static/hardware/Post+Base.gif rename to docs/_static/polarimeter/Post+Base.gif diff --git a/docs/_static/hardware/film-mount.gif b/docs/_static/polarimeter/film-mount.gif similarity index 100% rename from docs/_static/hardware/film-mount.gif rename to docs/_static/polarimeter/film-mount.gif diff --git a/docs/_static/hardware/laser.gif b/docs/_static/polarimeter/laser.gif similarity index 100% rename from docs/_static/hardware/laser.gif rename to docs/_static/polarimeter/laser.gif diff --git a/docs/_static/hardware/lens-mount.gif b/docs/_static/polarimeter/lens-mount.gif similarity index 100% rename from docs/_static/hardware/lens-mount.gif rename to docs/_static/polarimeter/lens-mount.gif diff --git a/docs/_static/hardware/mount-to-post.gif b/docs/_static/polarimeter/mount-to-post.gif similarity index 100% rename from docs/_static/hardware/mount-to-post.gif rename to docs/_static/polarimeter/mount-to-post.gif diff --git a/docs/backend/architecture.md b/docs/backend/architecture.md deleted file mode 100644 index e341feb..0000000 --- a/docs/backend/architecture.md +++ /dev/null @@ -1,76 +0,0 @@ -# Architecture - -A PQN node is composed of four main components. All components except the Node API reside in an internal intranet with no external network access. - -``` - ┌─────────────────────────────────┐ - │ PQN Node │ - │ │ - Web UI / Other Nodes ─┤──► Node API (FastAPI) │ - │ │ │ - │ ▼ │ - │ Router (ZMQ) │ - │ │ │ - │ ▼ │ - │ Hardware Provider │ - │ │ │ - │ ▼ │ - │ Quantum Hardware │ - └─────────────────────────────────┘ -``` - -## Node API - -**File**: `src/pqnstack/app/main.py` - -A FastAPI application that is the only component with external network access. It handles: -- Communication with the web frontend (`pqn-gui`) -- Node-to-node communication (e.g., for two-node CHSH experiments) -- Exposing experiment endpoints - -Interactive API docs are available at `http://127.0.0.1:8000/docs` when the server is running. - -## Router - -**File**: `src/pqnstack/network/router.py` - -Routes messages between Hardware Providers, PQN developers, and Node APIs using ZMQ sockets. Required on the first computer in a node; optional for additional computers joining the same node. - -## Hardware Provider - -**File**: `src/pqnstack/network/instrument_provider.py` - -Hosts quantum hardware resources and makes them available to other components via ProxyInstruments. Each instrument is loaded from a driver class specified in the config file. - -## Drivers - -**Directory**: `src/pqnstack/pqn/drivers/` - -Hardware drivers abstract physical devices: - -| Driver | Description | -|---|---| -| `TimeTagger` | Photon detection timing | -| `Polarimeter` | Photon polarization measurement | -| `Rotator` / `RotaryEncoder` | Polarization basis rotation (half-wave plates) | -| `QKD Driver` | Quantum key distribution hardware | -| `CHSH Driver` | Bell test measurement | -| `DummyInstrument` | Software-only testing without physical hardware | - -## Protocols - -**Directory**: `src/pqnstack/pqn/protocols/` - -Experiment protocols implement the quantum measurement logic: - -| Protocol | File | Description | -|---|---|---| -| CHSH | `chsh.py` | Bell inequality test | -| QKD | `qkd.py` | Quantum key distribution | -| Tomography | `tomography.py` | Quantum state tomography | -| Visibility | `visibility.py` | Interference visibility measurement | -| Measurement | `measurement.py` | Base measurement functionality | - -## CLI - -The `pqn` CLI (implemented with [Typer](https://typer.tiangolo.com/)) provides commands to start each component. See {doc}`running` for usage. diff --git a/docs/backend/configuration.md b/docs/backend/configuration.md deleted file mode 100644 index c5455ae..0000000 --- a/docs/backend/configuration.md +++ /dev/null @@ -1,108 +0,0 @@ -# Configuration - -The backend uses TOML configuration files. Two separate config files are needed: one for the Node API and one for the Router and Hardware Provider. - -## Node API Configuration (`config.toml`) - -Copy the example config and rename it: - -```bash -cp configs/config_app_example.toml config.toml -``` - -> **Important**: The file **must** be named `config.toml` and placed at the root of the repository. - -Edit `config.toml` with your actual settings: - -```toml -node_name = "example_node" - -# Router configuration -router_name = "router1" -router_address = "xx.xx.xx.xx" # Replace with actual IP address -router_port = 5555 - -# Rotary encoder serial address -rotary_encoder_address = "/dev/tty.usbmodem1101" - -# Timetagger configuration (provider_name, instrument_name) -timetagger = ["provider", "tagger"] - -# Bell state (default: Phi_plus = 0) -bell_state = 0 - -# CHSH experiment settings -[chsh_settings] -hwp = ["provider", "instrument_hwp"] -request_hwp = ["provider", "instrument_hwp"] -expectation_signs = [-1, 1, 1, 1] # HV + VH basis - -[chsh_settings.measurement_config] -integration_time_s = 5 -binwidth = 500 -channel1 = 1 -channel2 = 2 -dark_count = 0 - -# QKD experiment settings -[qkd_settings] -hwp = ["provider", "instrument_hwp"] -request_hwp = ["provider", "instrument_hwp"] -bitstring_length = 4 -discriminating_threshold = 10 - -[qkd_settings.measurement_config] -integration_time_s = 5 -binwidth = 500 -channel1 = 1 -channel2 = 2 -dark_count = 0 - -# Daily CHSH report (optional, for automated Slack reporting) -[daily_report] -slack_webhook_url = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL" -api_url = "http://localhost:8000" -timetagger_address = "127.0.0.1:8000" -follower_node_address = "192.168.1.100:9000" -basis = [0, 22.5] -``` - -## Router and Hardware Provider Configuration - -For the first computer in a PQN node, both a Router and Hardware Provider are needed. Additional computers in the same node only need a Hardware Provider. - -Copy and edit the messaging config: - -```bash -cp configs/config_messaging_example.toml my_messaging.toml -``` - -Example `config_messaging_example.toml`: - -```toml -[router] -name = "pqnstack-router" -host = "localhost" -port = 5556 - -[provider] -name = "pqnstack-provider" -router_name = "pqnstack-router" -host = "localhost" -port = 5556 -beat_period = 2000 - -[[provider.instruments]] -name = "dummy1" -import = "pqnstack.pqn.drivers.dummies.DummyInstrument" -desc = "Dummy instrument for testing" -hw_address = "1234" - -[[provider.instruments]] -name = "dummy2" -import = "pqnstack.pqn.drivers.dummies.DummyInstrument" -desc = "Dummy instrument for testing" -hw_address = "1234" -``` - -Each `[[provider.instruments]]` entry specifies a hardware driver to load. Replace the dummy drivers with real hardware drivers for production use (see {doc}`architecture`). diff --git a/docs/backend/index.md b/docs/backend/index.md deleted file mode 100644 index a363634..0000000 --- a/docs/backend/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# Backend - -The `pqn-stack` repository contains the Python backend for PQN nodes. It manages quantum hardware, implements experiment protocols, and exposes a FastAPI server for the frontend and inter-node communication. - -**Repository**: [github.com/PublicQuantumNetwork/pqn-stack](https://github.com/PublicQuantumNetwork/pqn-stack) - -```{toctree} -:maxdepth: 2 - -installation -configuration -architecture -running -protocols -``` diff --git a/docs/backend/installation.md b/docs/backend/installation.md deleted file mode 100644 index abb8b59..0000000 --- a/docs/backend/installation.md +++ /dev/null @@ -1,40 +0,0 @@ -# Installation - -## Prerequisites - -- Python 3.12 or higher -- [uv](https://docs.astral.sh/uv/) package manager -- Quantum hardware components (TimeTagger, rotators, etc.) — see {doc}`../overview/index` - -## Install uv - -If you don't have `uv` installed: - -```bash -curl -LsSf https://astral.sh/uv/install.sh | sh -``` - -## Clone and Install - -```bash -git clone https://github.com/PublicQuantumNetwork/pqn-stack.git -cd pqn-stack -``` - -Install dependencies including the FastAPI web server: - -```bash -uv sync --extra webapp -``` - -This installs all required packages into an isolated virtual environment managed by `uv`. - -## Verify Installation - -Check that the `pqn` CLI is available: - -```bash -uv run pqn --help -``` - -You should see a list of available commands. diff --git a/docs/backend/protocols.md b/docs/backend/protocols.md deleted file mode 100644 index e77a215..0000000 --- a/docs/backend/protocols.md +++ /dev/null @@ -1,51 +0,0 @@ -# Experiment Protocols - -Protocols are implemented in `src/pqnstack/pqn/protocols/` and exposed via the Node API. - -## CHSH Bell Test - -**File**: `src/pqnstack/pqn/protocols/chsh.py` - -Tests Bell inequalities to verify quantum entanglement between two nodes. Each node measures photon polarization in two bases; the correlations between results either satisfy or violate the classical Bell bound (S ≤ 2). - -A violation (S > 2) confirms quantum entanglement. The PQN uses the CHSH form of the Bell inequality. - -**Configuration** (`chsh_settings` in `config.toml`): -- `hwp`: Half-wave plate instrument for local basis rotation -- `request_hwp`: Half-wave plate on the follower node -- `expectation_signs`: Sign pattern for the correlation matrix (depends on the Bell state prepared) -- `measurement_config.integration_time_s`: How long to collect photon counts per measurement - -## Quantum Key Distribution (QKD) - -**File**: `src/pqnstack/pqn/protocols/qkd.py` - -Implements the BB84-like protocol to generate a shared secret key between two parties using quantum mechanics. Eavesdropping disturbs the quantum states and is detectable. - -**Configuration** (`qkd_settings` in `config.toml`): -- `bitstring_length`: Number of key bits to generate -- `discriminating_threshold`: Minimum coincidence count to classify a measurement as valid - -## Quantum Fortune - -Generates random bits using quantum measurement outcomes. Because quantum measurement results are fundamentally random, the output is certified random by the laws of physics. - -## Secret Message Sharing (SSM) - -Combines QKD key generation with classical encryption to allow two users to exchange a quantum-secured secret message through the web interface. - -## Tomography - -**File**: `src/pqnstack/pqn/protocols/tomography.py` - -Performs quantum state tomography by measuring in multiple bases to reconstruct the density matrix of the quantum state being produced. - -## Visibility - -**File**: `src/pqnstack/pqn/protocols/visibility.py` - -Measures the visibility of quantum interference — a key metric for assessing the quality of the quantum source and optical alignment. - -## API Reference - -All protocols are accessible via the Node API. With the server running, visit `http://127.0.0.1:8000/docs` for the full interactive OpenAPI documentation. diff --git a/docs/backend/running.md b/docs/backend/running.md deleted file mode 100644 index 6ea9bcb..0000000 --- a/docs/backend/running.md +++ /dev/null @@ -1,54 +0,0 @@ -# Running a Node - -A full PQN node requires starting up to four processes. Start them in this order. - -## 1. Start the Router - -Using a config file: - -```bash -uv run pqn start-router --config configs/config_messaging_example.toml -``` - -Or using CLI flags directly: - -```bash -uv run pqn start-router --name router1 --host localhost --port 5555 -``` - -The Router is required on the first machine in a node. Additional machines joining the same node do not need their own Router. - -## 2. Start the Hardware Provider - -Using a config file: - -```bash -uv run pqn start-provider --config configs/config_messaging_example.toml -``` - -Or using CLI flags: - -```bash -uv run pqn start-provider \ - --name provider1 \ - --router-name router1 \ - --instruments '{"dummy1": {"import": "pqnstack.pqn.drivers.dummies.DummyInstrument", "desc": "Test", "hw_address": "1234"}}' -``` - -## 3. Start the Node API - -```bash -uv run fastapi run src/pqnstack/app/main.py -``` - -The API will be available at `http://127.0.0.1:8000`. Interactive API documentation is at `http://127.0.0.1:8000/docs`. - -## 4. Start the Web Frontend (optional) - -See the {doc}`../frontend/running` page for frontend setup instructions. - -## Two-Node Setup - -For two-node experiments (CHSH, QKD), each node runs its own full stack. Configure the `follower_node_address` in `config.toml` to point to the second node's API address. - -The frontend also needs to be configured with the second node's address — see {doc}`../frontend/running`. diff --git a/docs/conf.py b/docs/conf.py index 5eacb25..c505600 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,8 +1,8 @@ project = 'Public Quantum Network' -copyright = '2026, Benjamin Nussbaum, Marcos Frenkel, Soroush Hoseini' -author = 'Benjamin Nussbaum, Marcos Frenkel, Soroush Hoseini' +copyright = '2026, Public Quantum Network' +author = 'Public Quantum Network Team' -extensions = ['myst_parser'] +extensions = ['myst_parser', 'sphinxcontrib.mermaid'] templates_path = ['_templates'] exclude_patterns = ['build'] @@ -19,13 +19,17 @@ }, "external_links": [ { - "url": "https://github.com/PublicQuantumNetwork/pqn-stack", - "name": "pqn-stack", + "url": "https://github.com/PublicQuantumNetwork/pqn-node", + "name": "pqn-node", }, { "url": "https://github.com/PublicQuantumNetwork/pqn-gui", "name": "pqn-gui", }, + { + "url": "https://github.com/PublicQuantumNetwork/pqn-hardware", + "name": "pqn-hardware", + }, ], "icon_links": [ { diff --git a/docs/contributing/index.md b/docs/contributing/index.md index e968a53..71a96a8 100644 --- a/docs/contributing/index.md +++ b/docs/contributing/index.md @@ -6,8 +6,9 @@ Thank you for your interest in contributing to the Public Quantum Network! | Repository | Description | |---|---| -| [pqn-stack](https://github.com/PublicQuantumNetwork/pqn-stack) | Python backend stack | -| [pqn-gui](https://github.com/PublicQuantumNetwork/pqn-gui) | Next.js frontend | +| [pqn-node](https://github.com/PublicQuantumNetwork/pqn-node) | FastAPI Node service | +| [pqn-gui](https://github.com/PublicQuantumNetwork/pqn-gui) | Next.js public web interface | +| [pqn-hardware](https://github.com/PublicQuantumNetwork/pqn-hardware) | Instrument drivers, ZMQ messaging, and quantum protocols | | [pqn-docs](https://github.com/PublicQuantumNetwork/pqn-docs) | This documentation site | ## Contributing to the Docs @@ -43,10 +44,13 @@ Open `docs/build/html/index.html` in your browser to preview the result. - Keep technical jargon to a minimum or define it when first used - Use tables for configuration references and command comparisons -## Contributing to the Backend or Frontend +## Contributing to a Package Please refer to the `CONTRIBUTING.md` file in each repository: -- [pqn-stack/CONTRIBUTING.md](https://github.com/PublicQuantumNetwork/pqn-stack/blob/master/CONTRIBUTING.md) + +- [pqn-node](https://github.com/PublicQuantumNetwork/pqn-node) +- [pqn-gui](https://github.com/PublicQuantumNetwork/pqn-gui) +- [pqn-hardware/CONTRIBUTING.md](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/CONTRIBUTING.md) ## Contact diff --git a/docs/deployment/index.md b/docs/deployment/index.md new file mode 100644 index 0000000..42bdde6 --- /dev/null +++ b/docs/deployment/index.md @@ -0,0 +1,13 @@ +# Deployment + +How to stand up a complete PQN Node from scratch and, optionally, federate it with other Nodes to form a PQN Network. + +This section is the operator's runbook: it walks through installing `pqn-node`, pulling in `pqn-hardware`, configuring instruments, starting the Router and Hardware Providers, launching the Node API, deploying the GUI, and pointing everything at the right addresses. It also covers the network-side configuration needed for two-Node Experiments. + +> _Placeholder._ Detailed deployment instructions are still being written. For now, the per-package READMEs are the most current reference: +> +> - [`pqn-node` README](https://github.com/PublicQuantumNetwork/pqn-node) +> - [`pqn-hardware` README](https://github.com/PublicQuantumNetwork/pqn-hardware) +> - [`pqn-gui` README](https://github.com/PublicQuantumNetwork/pqn-gui) + +For a high-level mental model of what you are deploying, see {doc}`../overview/software-map`. diff --git a/docs/frontend/architecture.md b/docs/frontend/architecture.md deleted file mode 100644 index 2e3f443..0000000 --- a/docs/frontend/architecture.md +++ /dev/null @@ -1,55 +0,0 @@ -# Architecture - -The frontend is a [Next.js](https://nextjs.org/) 16 application using the App Router with React 19, TypeScript, and Material-UI. - -## Directory Structure - -``` -src/app/ -├── layout.tsx # Root layout: MUI theme, navigation bar -├── page.tsx # Home page: experiment selection -├── calls.tsx # Centralized API client (fetch wrappers for the Node API) -├── contexts/ # React contexts -│ └── PageRedirectContext.tsx -├── hooks/ # Custom React hooks -│ └── useWebSocket.ts # WebSocket hook for real-time experiment updates -├── components/ # Shared UI components -│ ├── Whobit.tsx # Animated character that guides users -│ ├── HeaderBar.tsx # Top navigation bar -│ └── ... -├── chsh/ # CHSH Bell test experiment (pages 1–3) -├── qf/ # Quantum Fortune (pages 1–2) -├── qkd/ # Quantum Key Distribution -├── ssm/ # Secret Message Sharing (pages 1–4) -├── survey/ # Post-experiment user feedback -└── about/ # Project information page -``` - -## Design Patterns - -### Page Layout - -Each experiment page follows a consistent two-column layout: -- **Left**: Whobit character with step-by-step instructions -- **Right**: Interactive content (buttons, results, visualizations) - -### API Communication - -All calls to the Node API are centralized in `calls.tsx`. This file exports typed functions for each endpoint, keeping API logic out of UI components. - -### Real-Time Updates - -Experiment results are streamed from the backend via WebSocket. The `useWebSocket` hook in `hooks/` manages the connection lifecycle and delivers updates to components. - -### Multi-Page Experiments - -Multi-step experiments (e.g., CHSH has 3 pages, SSM has 4 pages) use `PageRedirectContext` to track progress and navigate between steps while preserving experiment state. - -## Key Technologies - -| Technology | Version | Purpose | -|---|---|---| -| Next.js | 16 | React framework with App Router | -| React | 19 | UI rendering | -| TypeScript | — | Type safety | -| Material-UI | — | Component library and theming | diff --git a/docs/frontend/configuration.md b/docs/frontend/configuration.md deleted file mode 100644 index 56655af..0000000 --- a/docs/frontend/configuration.md +++ /dev/null @@ -1,25 +0,0 @@ -# Configuration - -## Environment Variables - -All frontend configuration is done through environment variables in a `.env.local` file at the project root. See {doc}`installation` for the full variable reference. - -## Multi-Node Setup - -The PQN is designed for two-node quantum experiments. To run two frontend instances simultaneously (one per node): - -1. Start the first instance on the default port: - ```bash - npm run dev - # Available at http://localhost:3000 - ``` - -2. Start the second instance on port 3001: - ```bash - PORT=3001 npm run dev - # Available at http://localhost:3001 - ``` - -Each frontend instance should point to its corresponding Node API via `NEXT_PUBLIC_API_ADDRESS`. Use separate `.env.local` files (e.g., `.env.locala` and `.env.localb`) and load them manually, or configure the addresses at startup. - -> **Note**: The PQN currently supports exactly two nodes. Three or more nodes are not yet supported. diff --git a/docs/frontend/index.md b/docs/frontend/index.md deleted file mode 100644 index 8bbb8c0..0000000 --- a/docs/frontend/index.md +++ /dev/null @@ -1,14 +0,0 @@ -# Frontend - -The `pqn-gui` repository contains the Next.js web interface for PQN nodes. It provides a public-facing UI that guides users through quantum experiments step by step. - -**Repository**: [github.com/PublicQuantumNetwork/pqn-gui](https://github.com/PublicQuantumNetwork/pqn-gui) - -```{toctree} -:maxdepth: 2 - -installation -configuration -running -architecture -``` diff --git a/docs/frontend/installation.md b/docs/frontend/installation.md deleted file mode 100644 index 3845cb8..0000000 --- a/docs/frontend/installation.md +++ /dev/null @@ -1,36 +0,0 @@ -# Installation - -## Prerequisites - -- Node.js 18 or higher and npm ([installation guide](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)) -- A running PQN Node API (see {doc}`../backend/running`) - -## Clone and Install - -Clone the repository **outside** the `pqn-stack` directory: - -```bash -git clone https://github.com/PublicQuantumNetwork/pqn-gui.git -cd pqn-gui -npm install -``` - -## Environment Configuration - -Create a `.env.local` file at the root of the project: - -```bash -NEXT_PUBLIC_API_ADDRESS=127.0.0.1:8000 -NEXT_PUBLIC_TIMETAGGER_ADDRESS=127.0.0.1:8000 -NEXT_PUBLIC_FOLLOWER_NODE_ADDRESS=127.0.0.1:9000 -NEXT_PUBLIC_SURVEY_FORM_URL=https://surveys.illinois.edu/sec/1160990162 -``` - -| Variable | Description | -|---|---| -| `NEXT_PUBLIC_API_ADDRESS` | Address of the local PQN Node API | -| `NEXT_PUBLIC_TIMETAGGER_ADDRESS` | Address of the node with the TimeTagger device | -| `NEXT_PUBLIC_FOLLOWER_NODE_ADDRESS` | Address of the second node (for two-node experiments) | -| `NEXT_PUBLIC_SURVEY_FORM_URL` | URL of the post-experiment survey (optional) | - -Replace addresses with your actual Node API endpoints if they differ from the defaults. diff --git a/docs/frontend/running.md b/docs/frontend/running.md deleted file mode 100644 index 1b0be40..0000000 --- a/docs/frontend/running.md +++ /dev/null @@ -1,38 +0,0 @@ -# Running the Frontend - -## Prerequisites - -Ensure the PQN Node API is running before starting the frontend. See {doc}`../backend/running`. - -## Development Mode - -```bash -npm run dev -``` - -Opens at [http://localhost:3000](http://localhost:3000). Includes hot reload for code changes. - -## Production Mode - -```bash -npm run build -npm run start -``` - -Production mode is faster than development mode and recommended for public deployments. - -## Two-Node Operation - -For two-node experiments (CHSH, QKD), run two frontend instances and two backend stacks simultaneously: - -``` -Node A: - - pqn-stack API on port 8000 - - pqn-gui on port 3000 → NEXT_PUBLIC_API_ADDRESS=127.0.0.1:8000 - -Node B: - - pqn-stack API on port 9000 - - pqn-gui on port 3001 → NEXT_PUBLIC_API_ADDRESS=127.0.0.1:9000 -``` - -Set `NEXT_PUBLIC_FOLLOWER_NODE_ADDRESS` on each node to point to the other node's API. diff --git a/docs/index.md b/docs/index.md index 1042abb..5888025 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,58 +6,25 @@ myst: # Public Quantum Network -Welcome to the documentation for the **Public Quantum Network (PQN)** — a distributed quantum network system designed to make quantum computing and quantum experiments accessible to the general public. +Welcome to the documentation for the **Public Quantum Network (PQN)** — a federation of physical sites that lets the general public run real quantum experiments through a web interface. -The PQN allows users to interact with real quantum hardware through a web interface, running experiments such as Bell tests, quantum key distribution, and quantum random number generation. +The codebase is split across three packages: -> **Early Development**: This project is in early stages of development. APIs, installation procedures, and distribution methods are subject to change. +- **[pqn-node](https://github.com/PublicQuantumNetwork/pqn-node)** — the FastAPI service that runs at each site +- **[pqn-gui](https://github.com/PublicQuantumNetwork/pqn-gui)** — the public-facing web interface +- **[pqn-hardware](https://github.com/PublicQuantumNetwork/pqn-hardware)** — instrument drivers, ZMQ messaging, and quantum protocols -## Overview - -Project description, supported experiments, and acknowledgements. +> **Early Development**: This project is in early stages. APIs, installation procedures, and distribution methods are subject to change. ```{toctree} :maxdepth: 2 +:hidden: overview/index -``` - -## Backend - -Installation, configuration, and architecture of the Python backend stack (`pqn-stack`). - -```{toctree} -:maxdepth: 2 - -backend/index -``` - -## Frontend - -Installation, configuration, and architecture of the Next.js web interface (`pqn-gui`). - -```{toctree} -:maxdepth: 2 - -frontend/index -``` - -## Hardware - -3D-printable polarimeter components and assembly instructions for building a PQN node instrument. - -```{toctree} -:maxdepth: 2 - -hardware/index -``` - -## Contributing - -How to contribute to the PQN project. - -```{toctree} -:maxdepth: 2 - +deployment/index +pqn-node/index +pqn-gui/index +pqn-hardware/index +physical-devices/index contributing/index ``` diff --git a/docs/overview/index.md b/docs/overview/index.md index 1fb6cbc..7947864 100644 --- a/docs/overview/index.md +++ b/docs/overview/index.md @@ -1,40 +1,46 @@ # Overview +```{toctree} +:hidden: + +software-map +``` + ## What is the PQN? -The Public Quantum Network (PQN) is a distributed quantum network system that enables the general public to interact with real quantum hardware through a lightweight web interface. Visitors can run quantum experiments — including entanglement verification, quantum key distribution, and quantum random number generation — without any prior knowledge of quantum mechanics. +The Public Quantum Network (PQN) is a federation of physical sites — called **Nodes** — that expose real quantum experiments to the general public through a lightweight web interface. A visitor with no background in quantum mechanics can walk up to a Node, click through an experiment in the browser, and watch real photons being measured in real time. -The network is built around a **node-based architecture**: each PQN node consists of a backend software stack (`pqn-stack`) managing hardware and protocols, and a frontend web interface (`pqn-gui`) for public interaction. +Each Node is a self-contained backend stack running on a small intranet. Nodes can talk to each other over the public internet to run multi-Node experiments such as the CHSH Bell test. The web interface (the GUI) is an optional client — every Experiment can also be run programmatically. + +For the canonical glossary of terms used throughout the docs (Node, Node API, Hardware Provider, Protocol, Experiment, …), see {doc}`software-map` and the project's `CONTEXT.md`. ## Supported Experiments | Experiment | Description | |---|---| -| **CHSH Bell Test** | Verifies quantum entanglement by testing Bell inequalities between two nodes | +| **CHSH Bell Test** | Verifies quantum entanglement by testing Bell inequalities between two Nodes | | **Quantum Key Distribution (QKD)** | Generates a shared secret key between two parties using quantum mechanics | | **Quantum Fortune** | Generates random numbers using quantum randomness | -| **Secret Message Sharing (SSM)** | Sends a secret message encoded with quantum-generated keys | -| **Tomography** | Characterizes quantum states via state tomography | +| **Secret Message Sharing (SSM)** | Sends a secret message encoded with a quantum-generated key | +| **Tomography** | Characterises quantum states via state tomography | | **Visibility** | Measures the visibility of quantum interference fringes | -## Architecture - -The PQN uses a two-repo architecture: +## How the software fits together -- **[pqn-stack](https://github.com/PublicQuantumNetwork/pqn-stack)**: Python backend that manages quantum hardware, protocols, and node-to-node communication via a FastAPI server. -- **[pqn-gui](https://github.com/PublicQuantumNetwork/pqn-gui)**: Next.js frontend providing the public-facing web interface. +The system is split across three packages plus a set of physical devices: -See the {doc}`../backend/architecture` and {doc}`../frontend/architecture` pages for details. +- **`pqn-node`** — the FastAPI service that runs at each Node. The Node API; the brain of the Node. +- **`pqn-gui`** — the Next.js web interface. Talks to a single Node API, typically over localhost. +- **`pqn-hardware`** — the Python library used by `pqn-node` to drive instruments, route ZMQ messages, and execute quantum protocols. +- **Physical devices** — the polarimeters, time taggers, rotators, and lasers that physically perform the measurements. -## Hardware Requirements +For the full mental model — diagrams of the network, a single Node, and the request flow through the system — see the {doc}`software-map`. -Running the full PQN stack currently requires physical quantum hardware components: +## Hardware requirements -- **TimeTagger**: Photon detection timing device -- **Rotators / Rotary Encoders**: Polarization basis rotation control (e.g., half-wave plates) -- **Polarimeter**: Photon polarization measurement +Running a Node currently requires real quantum-optics hardware: a TimeTagger, polarisation rotators, and a polarimeter. Dummy drivers exist for software-only development, but full Experiment functionality requires physical instruments. -Dummy instrument drivers are available for software-only testing, but full experiment functionality requires real hardware. +See {doc}`../physical-devices/index` for build guides for the physical components. ## Acknowledgements diff --git a/docs/overview/software-map.md b/docs/overview/software-map.md new file mode 100644 index 0000000..b01a3c5 --- /dev/null +++ b/docs/overview/software-map.md @@ -0,0 +1,173 @@ +# Software Map + +This page is the top-level mental map of the Public Quantum Network: how the whole system hangs together, how a single Node is structured internally, where each package fits, and how a real request flows through the stack. It is intended for new contributors and for maintainers of PQN clones who need to internalise the architecture quickly. + +For a full glossary of the terms used here — Node, Node API, Hardware Provider, ProxyInstrument, Protocol, Experiment, GUI — see `CONTEXT.md` at the repository root. + +Deeper, package-specific architecture details live under {doc}`../pqn-node/index`, {doc}`../pqn-gui/index`, and {doc}`../pqn-hardware/index`. Operator-focused setup details live under {doc}`../deployment/index`. + +## 1. The PQN Network + +The PQN is a federation of independent **Nodes**. Each Node is a self-contained backend deployment at one physical site. Nodes talk to each other over the public internet to run multi-Node Experiments. Public visitors interact with a Node through the **GUI**, which is just a client of the Node API — it sits outside the Node itself and can run anywhere that has network access to the Node. + +```{mermaid} +flowchart LR + subgraph NodeA["Node A (site)"] + APIA[Node API] + end + subgraph NodeB["Node B (site)"] + APIB[Node API] + end + GUIA[GUI / browser] + GUIB[GUI / browser] + GUIA -.HTTP / WebSocket.-> APIA + GUIB -.HTTP / WebSocket.-> APIB + APIA <-.peer-to-peer.-> APIB +``` + +The Node API is the only component of a Node that is reachable from outside the local intranet. Everything else — Router, Hardware Providers, drivers, instruments — sits on a private network and is only reachable through the Node API. + +## 2. A single Node + +Zoom into one Node. It contains four logical layers: + +```{mermaid} +flowchart TB + GUI[GUI / browser]:::ext + Peer[Peer Node API]:::ext + + subgraph Node["Node (site intranet)"] + API[Node API
pqn-node] + Router[Router
pqn-hardware] + Provider1[Hardware Provider
pqn-hardware] + Provider2[Hardware Provider
pqn-hardware] + Driver1[Driver] + Driver2[Driver] + Driver3[Driver] + Instr1[(Physical
Instrument)] + Instr2[(Physical
Instrument)] + Instr3[(Physical
Instrument)] + end + + GUI -.HTTP / WebSocket.-> API + Peer -.HTTP.-> API + API -- ZMQ --> Router + Router -- ZMQ --> Provider1 + Router -- ZMQ --> Provider2 + Provider1 --> Driver1 + Provider1 --> Driver2 + Provider2 --> Driver3 + Driver1 --> Instr1 + Driver2 --> Instr2 + Driver3 --> Instr3 + + classDef ext fill:#eee,stroke:#888,stroke-dasharray: 4 2; +``` + +- **Node API** (in `pqn-node`) — FastAPI service. The only component reachable from outside. Handles GUI requests and peer-Node coordination. Owns the orchestration logic for each Experiment. +- **Router** (in `pqn-hardware`) — ZMQ message broker. All in-Node messaging passes through it. +- **Hardware Provider** (in `pqn-hardware`) — process that hosts physical instruments. A Node can run more than one Provider; for example, one machine per optics table or per piece of expensive hardware. +- **Driver** (in `pqn-hardware`) — concrete instrument implementation. Talks to one piece of physical hardware (Thorlabs rotator, TimeTagger, etc.). + +The Node API never talks to a Driver directly. It calls a **ProxyInstrument** — a client-side handle whose method calls are serialised onto the Router and dispatched to whichever Hardware Provider hosts the real instrument. This means the Node API code does not need to know which machine an instrument is plugged into. + +## 3. The three packages + +The same picture, redrawn around the package boundary instead of the runtime topology: + +```{mermaid} +flowchart LR + GUI["pqn-gui
Next.js · TypeScript
public web UI"]:::ts + Node["pqn-node
FastAPI · Python
Node API service"]:::py + HW["pqn-hardware
library · Python
drivers, Router, Provider, Protocols"]:::py + + GUI -- HTTP / WebSocket --> Node + Node -- imports --> HW + Node -- launches --> RouterProc["Router process
pqn-hw start-router"] + Node -- launches --> ProviderProc["Hardware Provider process
pqn-hw start-provider"] + RouterProc -. provided by .- HW + ProviderProc -. provided by .- HW + + classDef py fill:#e6f0ff,stroke:#3b6cb3; + classDef ts fill:#fff3e0,stroke:#b36b1f; +``` + +- **`pqn-node`** is the only package that actually runs the Node API service. It depends on `pqn-hardware` as a git-pinned library and orchestrates everything: HTTP routing, peer-Node coordination, Experiment lifecycle, WebSocket streaming. +- **`pqn-gui`** is a Next.js application. It has no Python; it knows the Node API only through its HTTP/WebSocket surface. +- **`pqn-hardware`** is a Python library *and* a CLI (`pqn-hw`). The library is consumed in-process by `pqn-node` for ProxyInstrument calls and Protocol logic. The CLI runs the Router and Hardware Provider as separate long-lived processes, started during deployment. + +The split between `pqn-node` and `pqn-hardware` exists so that hardware-driver work (which moves at the pace of new instruments and ZMQ infrastructure changes) can evolve independently of the FastAPI service (which moves at the pace of new Experiments and UI features). + +## 4. Walkthrough: a single-Node experiment + +What happens when a visitor clicks **Run** on the Quantum Fortune page in the GUI: + +```{mermaid} +sequenceDiagram + actor User + participant GUI as GUI
(pqn-gui) + participant API as Node API
(pqn-node) + participant Proto as Protocol
(pqn-hardware) + participant Router as Router
(pqn-hardware) + participant Provider as Hardware Provider
(pqn-hardware) + participant Instr as Physical Instrument + + User->>GUI: click "Run" + GUI->>API: POST /experiments/quantum-fortune + API->>Proto: invoke Protocol + Proto->>Router: ProxyInstrument call + Router->>Provider: routed ZMQ message + Provider->>Instr: driver call + Instr-->>Provider: measurement + Provider-->>Router: result + Router-->>Proto: result + Proto-->>API: result + API-->>GUI: WebSocket update + GUI-->>User: render result +``` + +Two things to notice: + +1. **ProxyInstruments hide the network.** From the Protocol's point of view, calling an instrument looks like a local method call — but the call is actually marshalled across ZMQ to wherever the Hardware Provider is running. +2. **Results stream back over WebSocket.** Long-running experiments emit progress updates as they go, so the GUI can render live counts and partial results rather than waiting for a single response at the end. + +## 5. Walkthrough: a two-Node CHSH experiment + +The CHSH Bell test is the canonical multi-Node Experiment. Two Nodes — call them Alice and Bob — each measure one half of an entangled pair on independently chosen polarisation bases, and the Node API on the initiating side aggregates the results. + +```{mermaid} +sequenceDiagram + actor User + participant GUI as GUI
(at Alice) + participant Alice as Node API
Alice + participant Bob as Node API
Bob + participant InstrA as Alice's
instruments + participant InstrB as Bob's
instruments + + User->>GUI: click "Run CHSH" + GUI->>Alice: POST /experiments/chsh
(peer = Bob) + Alice->>Bob: announce run, exchange config + par measurement on each side + Alice->>InstrA: rotate, measure + InstrA-->>Alice: counts + and + Bob->>InstrB: rotate, measure + InstrB-->>Bob: counts + end + Bob-->>Alice: counts for Bob's bases + Alice->>Alice: compute CHSH inequality value + Alice-->>GUI: WebSocket result + GUI-->>User: render Bell-violation plot +``` + +Key points: + +- The **GUI only talks to one Node API** (Alice's). The peer-Node coordination is entirely between Node APIs over HTTP — the GUI never connects to Bob. +- Each Node drives its own instruments through its own Router and Hardware Providers; neither side touches the other side's hardware. +- The initiating Node (Alice) is responsible for aggregating the joint statistics and computing the Bell inequality value. + +## Where to go next + +- {doc}`../deployment/index` — how to actually stand up a Node and join a network. +- {doc}`../pqn-node/index`, {doc}`../pqn-gui/index`, {doc}`../pqn-hardware/index` — package-specific internals. +- {doc}`../physical-devices/index` — build guides for the physical instruments referenced throughout this page. diff --git a/docs/physical-devices/index.md b/docs/physical-devices/index.md new file mode 100644 index 0000000..e0d300b --- /dev/null +++ b/docs/physical-devices/index.md @@ -0,0 +1,13 @@ +# Physical Devices + +Build and setup guides for the physical instruments used at a PQN Node. + +A Node's software (`pqn-node`, `pqn-hardware`) drives a stack of real optical and electronic hardware: polarimeters, time taggers, polarisation rotators, lasers, and the optomechanics that hold them in place. This section collects the hands-on guides needed to build, wire, and prepare those devices for use. + +The first device documented here is the **polarimeter** — the tactile centrepiece of a PQN Node, designed to be 3D-printed cheaply enough that any institution can build one. Other devices (TimeTagger setup, rotator wiring, alignment procedures) will be added as standalone subsections over time. + +```{toctree} +:maxdepth: 2 + +polarimeter/index +``` diff --git a/docs/hardware/assembly.md b/docs/physical-devices/polarimeter/assembly.md similarity index 81% rename from docs/hardware/assembly.md rename to docs/physical-devices/polarimeter/assembly.md index 2f23d95..1bf4928 100644 --- a/docs/hardware/assembly.md +++ b/docs/physical-devices/polarimeter/assembly.md @@ -4,7 +4,7 @@ Step-by-step guide for assembling the PQN polarimeter optomechanics. ## 1. Attach magnet to base -```{image} ../_static/hardware/Magnet+Base.gif +```{image} ../../_static/polarimeter/Magnet+Base.gif :alt: Attaching adhesive magnet to base :width: 250px :align: center @@ -16,7 +16,7 @@ Apply an adhesive magnet to the bottom of the base. ## 2. Insert post into base -```{image} ../_static/hardware/Post+Base.gif +```{image} ../../_static/polarimeter/Post+Base.gif :alt: Merging post with base :width: 250px :align: center @@ -28,7 +28,7 @@ Apply gentle force to press the post into the base until it friction-fits secure ## 3. Load polarizer or retardation film into mount -```{image} ../_static/hardware/film-mount.gif +```{image} ../../_static/polarimeter/film-mount.gif :alt: Loading film into mount :width: 250px :align: center @@ -42,7 +42,7 @@ Cut the film to a 1-inch diameter. Slide it into the slot on the mount — the c ## 4. Load Fresnel lens into mount -```{image} ../_static/hardware/lens-mount.gif +```{image} ../../_static/polarimeter/lens-mount.gif :alt: Loading lens into mount :width: 250px :align: center @@ -54,7 +54,7 @@ Open the mount slot and gently slide the lens in. The clamping filament holds th ## 5. Attach mirror to mount -```{image} ../_static/hardware/Mirror.gif +```{image} ../../_static/polarimeter/Mirror.gif :alt: Attaching mirror to mount :width: 250px :align: center @@ -66,7 +66,7 @@ Press the adhesive side of the mirror against one face of the mount, then peel o ## 6. Insert laser into mount -```{image} ../_static/hardware/laser.gif +```{image} ../../_static/polarimeter/laser.gif :alt: Inserting laser into mount :width: 250px :align: center @@ -80,7 +80,7 @@ Push the laser (11 mm diameter) into the mount hole. ## 7. Attach beam splitter to mount -```{image} ../_static/hardware/BS.gif +```{image} ../../_static/polarimeter/BS.gif :alt: Inserting beam splitter into mount :width: 250px :align: center @@ -92,7 +92,7 @@ Slide the 1 mm beam splitter into the dedicated mount slot. ## 8. Attach any mount to a post -```{image} ../_static/hardware/mount-to-post.gif +```{image} ../../_static/polarimeter/mount-to-post.gif :alt: Attaching mount to post :width: 250px :align: center diff --git a/docs/hardware/components.md b/docs/physical-devices/polarimeter/components.md similarity index 100% rename from docs/hardware/components.md rename to docs/physical-devices/polarimeter/components.md diff --git a/docs/hardware/index.md b/docs/physical-devices/polarimeter/index.md similarity index 65% rename from docs/hardware/index.md rename to docs/physical-devices/polarimeter/index.md index 31df309..26fa3f0 100644 --- a/docs/hardware/index.md +++ b/docs/physical-devices/polarimeter/index.md @@ -1,10 +1,10 @@ -# Hardware +# Polarimeter -The tactile interface for the Public Quantum Network is a polarimeter setup that lets users observe and interact with the polarization of light — a direct demonstration of quantum superposition. +The tactile interface for a PQN Node is a polarimeter setup that lets users observe and interact with the polarisation of light — a direct demonstration of quantum superposition. Lab-grade polarimeter systems cost several hundred to several thousand dollars, creating a significant barrier to public engagement. To address this, the PQN provides an inexpensive, modular alternative built from **3D-printed components and affordable optics** that replicates the same visual effects at a fraction of the cost. -The design files (STL and CAD) are available in the [PQN hardware repository](https://github.com/PublicQuantumNetwork/hardware) so nodes can print the parts directly or adapt the dimensions to their needs. +The design files (STL and CAD) are available in the [PQN hardware repository](https://github.com/PublicQuantumNetwork/hardware) so Nodes can print the parts directly or adapt the dimensions to their needs. ```{toctree} :maxdepth: 2 diff --git a/docs/pqn-gui/index.md b/docs/pqn-gui/index.md new file mode 100644 index 0000000..4183c37 --- /dev/null +++ b/docs/pqn-gui/index.md @@ -0,0 +1,9 @@ +# pqn-gui + +`pqn-gui` is the public-facing web interface for the PQN. It is a Next.js / React / TypeScript application that talks to a single Node API over HTTP and WebSocket. It is intended for non-technical visitors — the experience is built around guided, multi-step Experiment flows narrated by an animated character. + +**Repository:** [github.com/PublicQuantumNetwork/pqn-gui](https://github.com/PublicQuantumNetwork/pqn-gui) + +`pqn-gui` is **optional**: every Experiment can be invoked directly against the Node API without the GUI in the loop. The GUI exists to make the system approachable to the public, not to gate access to it. + +> _Placeholder._ Detailed pages on `pqn-gui`'s structure, page conventions, API client layer, WebSocket handling, and theming will be added here. Until then, see the [README](https://github.com/PublicQuantumNetwork/pqn-gui) and the {doc}`../overview/software-map` for context. diff --git a/docs/pqn-hardware/index.md b/docs/pqn-hardware/index.md new file mode 100644 index 0000000..e9c2996 --- /dev/null +++ b/docs/pqn-hardware/index.md @@ -0,0 +1,17 @@ +# pqn-hardware + +`pqn-hardware` is the Python library that everything below the Node API runs on. It provides the instrument abstractions, ZMQ-based messaging fabric, concrete drivers, and quantum protocol implementations that `pqn-node` consumes. It also ships a CLI (`pqn-hw`) used to launch the Router and Hardware Provider processes during deployment. + +**Repository:** [github.com/PublicQuantumNetwork/pqn-hardware](https://github.com/PublicQuantumNetwork/pqn-hardware) + +What lives in here: + +- **Instrument base classes and ProxyInstrument plumbing** — the common abstractions over physical hardware +- **Drivers** — concrete instruments: Thorlabs rotators, TimeTagger, polarimeter, QKD hardware, plus dummy drivers for software-only development +- **Network layer** — Router (ZMQ broker) and Hardware Provider, plus the client side used by `pqn-node` +- **Protocols** — quantum-measurement logic for CHSH, QKD, Tomography, Visibility +- **`pqn-hw` CLI** — `start-router`, `start-provider` + +`pqn-hardware` was split out from `pqn-node` (originally `pqn-stack`) so hardware-driver work and Node-service work can evolve independently. `pqn-node` pins it as a git dependency. + +> _Placeholder._ Detailed pages on the driver model, ProxyInstrument lifecycle, Router/Provider topology, Protocol authoring, and the `pqn-hw` CLI will be added here. Until then, see the [README](https://github.com/PublicQuantumNetwork/pqn-hardware) and the {doc}`../overview/software-map` for context. diff --git a/docs/pqn-node/index.md b/docs/pqn-node/index.md new file mode 100644 index 0000000..b7d4580 --- /dev/null +++ b/docs/pqn-node/index.md @@ -0,0 +1,14 @@ +# pqn-node + +`pqn-node` is the FastAPI service that runs at each PQN site. It is the **Node API** — the only externally reachable component of a Node — and the orchestration brain for every Experiment. It depends on `pqn-hardware` as a git-pinned Python library for instrument access and Protocol logic. + +**Repository:** [github.com/PublicQuantumNetwork/pqn-node](https://github.com/PublicQuantumNetwork/pqn-node) + +Responsibilities: + +- Expose HTTP and WebSocket endpoints to the GUI and to peer Nodes +- Coordinate multi-Node Experiments (e.g. two-Node CHSH) +- Drive instruments through ProxyInstruments backed by the Router +- Manage Experiment lifecycle: launch, progress streaming, cancellation + +> _Placeholder._ Detailed pages on `pqn-node`'s internal architecture, request flow, configuration, and HTTP API will be added here. Until then, see the [README](https://github.com/PublicQuantumNetwork/pqn-node) and the {doc}`../overview/software-map` for context. diff --git a/pyproject.toml b/pyproject.toml index 762515d..8514be6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,4 +6,5 @@ dependencies = [ "sphinx", "pydata-sphinx-theme", "myst-parser", + "sphinxcontrib-mermaid", ] diff --git a/uv.lock b/uv.lock index 714518a..93ee462 100644 --- a/uv.lock +++ b/uv.lock @@ -278,6 +278,7 @@ dependencies = [ { name = "myst-parser" }, { name = "pydata-sphinx-theme" }, { name = "sphinx" }, + { name = "sphinxcontrib-mermaid" }, ] [package.metadata] @@ -285,6 +286,7 @@ requires-dist = [ { name = "myst-parser" }, { name = "pydata-sphinx-theme" }, { name = "sphinx" }, + { name = "sphinxcontrib-mermaid" }, ] [[package]] @@ -456,6 +458,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, ] +[[package]] +name = "sphinxcontrib-mermaid" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/75/3a1cc926da8c563c58ddc124a7b3fe5ccadcae96c96e3a6f8ac3653a210a/sphinxcontrib_mermaid-2.0.2.tar.gz", hash = "sha256:f09576c78ca93fa0e3034fd9c45aaffa7c44ab449de9c43b8b8d262afe52bc66", size = 19265, upload-time = "2026-05-05T13:59:02.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/8d/93be7e0f7fa915a576859b3bfac7a7baa3303181c44d7db7eefbd3e8a69f/sphinxcontrib_mermaid-2.0.2-py3-none-any.whl", hash = "sha256:d862e514991279fb4816302c5cfe167d2557bf3ce7125ae0cb47dac80a0f46ce", size = 14094, upload-time = "2026-05-05T13:59:01.585Z" }, +] + [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" From 57cfe1e44e2e6f6f4171609db843fb690bdd7373 Mon Sep 17 00:00:00 2001 From: marcosf2 Date: Thu, 28 May 2026 16:28:47 -0500 Subject: [PATCH 2/6] Refine language and structure in documentation for clarity and consistency --- CONTEXT.md | 18 +++--- docs/contributing/index.md | 2 +- docs/index.md | 8 +-- docs/overview/index.md | 34 ++++------- docs/overview/software-map.md | 57 +++++++++++-------- docs/physical-devices/index.md | 2 +- docs/physical-devices/polarimeter/assembly.md | 2 +- docs/physical-devices/polarimeter/index.md | 2 +- docs/pqn-gui/index.md | 2 +- docs/pqn-hardware/index.md | 10 ++-- docs/pqn-node/index.md | 2 +- 11 files changed, 68 insertions(+), 71 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index b492eb8..c04e92c 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,7 +7,7 @@ This file is the canonical glossary for terms used across `pqn-docs`, `pqn-node` ## Language **Node**: -A PQN deployment at a single physical site — comprises a Node API, a Router, one or more Hardware Providers, instrument drivers, and the physical instruments themselves. The GUI is _not_ part of a Node. +A PQN deployment at a single physical site. Comprises a Node API, a Router, one or more Hardware Providers, instrument drivers, and the physical instruments themselves. The GUI is _not_ part of a Node. _Avoid_: site, instance, station **Node API**: @@ -25,27 +25,27 @@ _Avoid_: Instrument Provider (older name; still appears in some code paths) Client-side handle for an instrument hosted by a Hardware Provider. Lets the Node API call the instrument without knowing where it physically runs. **Instrument**: -Software abstraction for one piece of physical hardware — TimeTagger, Polarimeter, Rotator, etc. Concrete implementations are called drivers and live in `pqn-hardware`. +Software abstraction for one piece of physical hardware (TimeTagger, Polarimeter, Rotator, etc.). Concrete implementations are called drivers and live in `pqn-hardware`. _Avoid_: device (reserved for the physical thing) **Driver**: A concrete Instrument implementation for a specific piece of hardware (e.g. Thorlabs rotator driver). **Protocol**: -The quantum-measurement logic for a single experiment type — CHSH, QKD, Tomography, Visibility. Lives in `pqn-hardware`. Distinct from Experiment. +The quantum-measurement logic for a single experiment type (CHSH, QKD, Tomography, Visibility). Lives in `pqn-hardware`. Distinct from Experiment. **Experiment**: A user-facing activity in the GUI (CHSH Bell Test, Quantum Fortune, QKD, SSM, Tomography, Visibility). Implemented on top of one or more Protocols. **GUI**: -`pqn-gui`, the Next.js public-facing web interface. Talks to a single Node API, typically over localhost. Optional — every Experiment can be run programmatically without it. +`pqn-gui`, the Next.js public-facing web interface. Talks to a single Node API, typically over localhost. Every Experiment can also be run programmatically without it. _Avoid_: frontend, web app, UI (when precision matters) **PQN Network**: A set of Nodes that can communicate with each other to run multi-Node Experiments (e.g. two-Node CHSH). **PQN Clone**: -An institution-run instance of the PQN — i.e. an organisation that stands up its own Node(s) and optionally federates with others. +An institution-run instance of the PQN. An organisation that stands up its own Node(s) and optionally federates with others. ## Relationships @@ -59,10 +59,10 @@ An institution-run instance of the PQN — i.e. an organisation that stands up i ## Example dialogue > **New contributor:** "When the user clicks 'Run CHSH' in the GUI, what happens?" -> **Maintainer:** "The **GUI** calls the **Node API**. The Node API kicks off the CHSH **Protocol**, which talks to the local **Hardware Provider** through **ProxyInstruments** to drive the **Instruments** — rotators, the TimeTagger, etc. For two-**Node** CHSH the Node API also coordinates with the peer **Node**'s Node API." +> **Maintainer:** "The **GUI** calls the **Node API**. The Node API kicks off the CHSH **Protocol**, which talks to the local **Hardware Provider** through **ProxyInstruments** to drive the **Instruments** (rotators, the TimeTagger, etc.) For two-**Node** CHSH the Node API also coordinates with the peer **Node**'s Node API." > > **New contributor:** "And the GUI is part of the Node?" -> **Maintainer:** "No — the GUI is just a client. The **Node** is the backend stack at one site. You can run every **Experiment** without the GUI." +> **Maintainer:** "No, the GUI is just a client. The **Node** is the backend stack at one site. You can run every **Experiment** without the GUI." ## Flagged ambiguities @@ -70,5 +70,5 @@ An institution-run instance of the PQN — i.e. an organisation that stands up i - At the **network** zoom level, "Node" means the whole site-level deployment. - When working **inside** a Node, "Node" is sometimes used as shorthand for the **Node API** (the brain of the Node). Resolution: prefer **"Node API"** in writing whenever you mean the FastAPI service. Reserve unqualified "Node" for the site-level meaning. -- **"Hardware"** has two unrelated meanings — the Python library `pqn-hardware` (drivers + Router + Provider + Protocols) and the physical devices themselves. In docs prose, write `pqn-hardware` (in code font) for the package and **Physical Device** / **Instrument** for the physical thing. -- **"Instrument Provider"** vs **"Hardware Provider"** — same concept, two names in code. Prefer **Hardware Provider** in docs. +- **"Hardware"** has two unrelated meanings: the Python library `pqn-hardware` (drivers + Router + Provider + Protocols) and the physical devices themselves. In docs prose, write `pqn-hardware` (in code font) for the package and **Physical Device** / **Instrument** for the physical thing. +- **"Instrument Provider"** vs **"Hardware Provider"**: same concept, two names in code. Prefer **Hardware Provider** in docs. diff --git a/docs/contributing/index.md b/docs/contributing/index.md index 71a96a8..47a57a8 100644 --- a/docs/contributing/index.md +++ b/docs/contributing/index.md @@ -40,7 +40,7 @@ Open `docs/build/html/index.html` in your browser to preview the result. ### Writing Style -- Use plain, accessible language — remember that many readers are members of the general public interacting with quantum experiments for the first time +- Use plain, accessible language. Many readers are members of the general public interacting with quantum experiments for the first time. - Keep technical jargon to a minimum or define it when first used - Use tables for configuration references and command comparisons diff --git a/docs/index.md b/docs/index.md index 5888025..46e1bc1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,13 +6,13 @@ myst: # Public Quantum Network -Welcome to the documentation for the **Public Quantum Network (PQN)** — a federation of physical sites that lets the general public run real quantum experiments through a web interface. +Welcome to the documentation for the **Public Quantum Network (PQN)**. The PQN lets the general public run real quantum experiments from a web browser. Each PQN site runs a small backend that drives real optical hardware, and a web interface presents the experiments as a series of interactive games. The codebase is split across three packages: -- **[pqn-node](https://github.com/PublicQuantumNetwork/pqn-node)** — the FastAPI service that runs at each site -- **[pqn-gui](https://github.com/PublicQuantumNetwork/pqn-gui)** — the public-facing web interface -- **[pqn-hardware](https://github.com/PublicQuantumNetwork/pqn-hardware)** — instrument drivers, ZMQ messaging, and quantum protocols +- **[pqn-node](https://github.com/PublicQuantumNetwork/pqn-node)**: the backend service that runs at each site. +- **[pqn-gui](https://github.com/PublicQuantumNetwork/pqn-gui)**: the public-facing web interface. +- **[pqn-hardware](https://github.com/PublicQuantumNetwork/pqn-hardware)**: instrument drivers, messaging, and quantum protocols. > **Early Development**: This project is in early stages. APIs, installation procedures, and distribution methods are subject to change. diff --git a/docs/overview/index.md b/docs/overview/index.md index 7947864..82f4622 100644 --- a/docs/overview/index.md +++ b/docs/overview/index.md @@ -8,39 +8,25 @@ software-map ## What is the PQN? -The Public Quantum Network (PQN) is a federation of physical sites — called **Nodes** — that expose real quantum experiments to the general public through a lightweight web interface. A visitor with no background in quantum mechanics can walk up to a Node, click through an experiment in the browser, and watch real photons being measured in real time. +The Public Quantum Network (PQN) lets the general public run real quantum experiments from a web browser. A visitor with no background in quantum mechanics can walk up to a PQN station, click through an experiment, and watch real photons being measured in real time. -Each Node is a self-contained backend stack running on a small intranet. Nodes can talk to each other over the public internet to run multi-Node experiments such as the CHSH Bell test. The web interface (the GUI) is an optional client — every Experiment can also be run programmatically. +A PQN site (a Node) bundles three things: a backend service that runs the experiments, a web interface that presents them as a series of interactive games, and a library that drives the physical hardware. The web interface is how the public actually meets the PQN. Each game is a guided walkthrough that wraps a quantum experiment, with the experiment itself acting as the game's backend. -For the canonical glossary of terms used throughout the docs (Node, Node API, Hardware Provider, Protocol, Experiment, …), see {doc}`software-map` and the project's `CONTEXT.md`. - -## Supported Experiments - -| Experiment | Description | -|---|---| -| **CHSH Bell Test** | Verifies quantum entanglement by testing Bell inequalities between two Nodes | -| **Quantum Key Distribution (QKD)** | Generates a shared secret key between two parties using quantum mechanics | -| **Quantum Fortune** | Generates random numbers using quantum randomness | -| **Secret Message Sharing (SSM)** | Sends a secret message encoded with a quantum-generated key | -| **Tomography** | Characterises quantum states via state tomography | -| **Visibility** | Measures the visibility of quantum interference fringes | +Two Nodes can also talk to each other so they can run experiments that need both sides, such as the CHSH Bell test. The classical communication between Nodes runs over a VPN that connects all the participating sites, and the quantum communication runs through an optical fibre between the two labs. Nothing in the PQN is exposed to the public internet. ## How the software fits together -The system is split across three packages plus a set of physical devices: - -- **`pqn-node`** — the FastAPI service that runs at each Node. The Node API; the brain of the Node. -- **`pqn-gui`** — the Next.js web interface. Talks to a single Node API, typically over localhost. -- **`pqn-hardware`** — the Python library used by `pqn-node` to drive instruments, route ZMQ messages, and execute quantum protocols. -- **Physical devices** — the polarimeters, time taggers, rotators, and lasers that physically perform the measurements. +The PQN is built from three packages: -For the full mental model — diagrams of the network, a single Node, and the request flow through the system — see the {doc}`software-map`. +- **`pqn-node`**: the backend service that runs at each Node. Handles requests from the web interface and from peer Nodes, and orchestrates each experiment. +- **`pqn-gui`**: the web interface. A Next.js app whose pages are organised as games, each one wrapping a quantum experiment. +- **`pqn-hardware`**: the library that drives the physical devices, routes messages between processes, and runs the quantum protocols. The polarimeters, time taggers, rotators, and lasers it controls are part of this layer. -## Hardware requirements +For diagrams of how these fit together, see the {doc}`software-map`. -Running a Node currently requires real quantum-optics hardware: a TimeTagger, polarisation rotators, and a polarimeter. Dummy drivers exist for software-only development, but full Experiment functionality requires physical instruments. +## Hardware -See {doc}`../physical-devices/index` for build guides for the physical components. +A PQN Node currently runs on real quantum-optics hardware. Detailed requirements and setup notes are still being written, we will get back to them. For the parts that are already documented, see {doc}`../physical-devices/index`. ## Acknowledgements diff --git a/docs/overview/software-map.md b/docs/overview/software-map.md index b01a3c5..9b3484e 100644 --- a/docs/overview/software-map.md +++ b/docs/overview/software-map.md @@ -1,31 +1,42 @@ # Software Map -This page is the top-level mental map of the Public Quantum Network: how the whole system hangs together, how a single Node is structured internally, where each package fits, and how a real request flows through the stack. It is intended for new contributors and for maintainers of PQN clones who need to internalise the architecture quickly. +This page is the top-level mental map of the Public Quantum Network: how the whole system fits together, how a single Node is structured, where each package lives, and how a real request flows through everything. -For a full glossary of the terms used here — Node, Node API, Hardware Provider, ProxyInstrument, Protocol, Experiment, GUI — see `CONTEXT.md` at the repository root. +```{note} +For a glossary of the terms used here (Node, Node API, Hardware Provider, ProxyInstrument, Protocol, Experiment, GUI), see `CONTEXT.md` at the repository root. -Deeper, package-specific architecture details live under {doc}`../pqn-node/index`, {doc}`../pqn-gui/index`, and {doc}`../pqn-hardware/index`. Operator-focused setup details live under {doc}`../deployment/index`. +Deeper, package-specific details live under {doc}`../pqn-node/index`, {doc}`../pqn-gui/index`, and {doc}`../pqn-hardware/index`. Operator-focused setup lives under {doc}`../deployment/index`. +``` ## 1. The PQN Network -The PQN is a federation of independent **Nodes**. Each Node is a self-contained backend deployment at one physical site. Nodes talk to each other over the public internet to run multi-Node Experiments. Public visitors interact with a Node through the **GUI**, which is just a client of the Node API — it sits outside the Node itself and can run anywhere that has network access to the Node. +At the network level the PQN is a small group of optical labs scattered across a few institutions. Each lab is a **Node**: it can run quantum experiments end to end on its own, and any pair of Nodes that have been physically connected can team up on experiments that need two sites at once, like the CHSH Bell test. + +Connecting two Nodes takes two channels. Classically, like normal computers, every Node sits on a shared VPN that links all the participating sites. Quantumly, the two labs are physically joined by an optical fibre that lets them exchange entangled photons. The fibre carries the quantum state of the experiment; the VPN carries the classical bookkeeping the two Nodes need to agree on what they measured. + +A visitor meets a Node through its **GUI**, a small web app that runs alongside the Node API. The GUI is the part of the PQN that the public actually sees. ```{mermaid} flowchart LR - subgraph NodeA["Node A (site)"] + GUIA[GUI]:::client + GUIB[GUI]:::client + subgraph NodeA["Node A"] APIA[Node API] + InstrA[(Optical
hardware)] end - subgraph NodeB["Node B (site)"] + subgraph NodeB["Node B"] APIB[Node API] + InstrB[(Optical
hardware)] end - GUIA[GUI / browser] - GUIB[GUI / browser] - GUIA -.HTTP / WebSocket.-> APIA - GUIB -.HTTP / WebSocket.-> APIB - APIA <-.peer-to-peer.-> APIB + GUIA -.HTTP.-> APIA + GUIB -.HTTP.-> APIB + APIA <==>|"classical (VPN)"| APIB + InstrA <==>|"quantum (optical fibre)"| InstrB + + classDef client fill:#fff3e0,stroke:#b36b1f; ``` -The Node API is the only component of a Node that is reachable from outside the local intranet. Everything else — Router, Hardware Providers, drivers, instruments — sits on a private network and is only reachable through the Node API. +Inside a Node, the Node API is the only component that ever talks to anything outside the Node. Everything else (Router, Hardware Providers, drivers, instruments) lives on the Node's local network and is reachable only through the Node API. Nothing in the PQN is exposed to the public internet. ## 2. A single Node @@ -64,12 +75,12 @@ flowchart TB classDef ext fill:#eee,stroke:#888,stroke-dasharray: 4 2; ``` -- **Node API** (in `pqn-node`) — FastAPI service. The only component reachable from outside. Handles GUI requests and peer-Node coordination. Owns the orchestration logic for each Experiment. -- **Router** (in `pqn-hardware`) — ZMQ message broker. All in-Node messaging passes through it. -- **Hardware Provider** (in `pqn-hardware`) — process that hosts physical instruments. A Node can run more than one Provider; for example, one machine per optics table or per piece of expensive hardware. -- **Driver** (in `pqn-hardware`) — concrete instrument implementation. Talks to one piece of physical hardware (Thorlabs rotator, TimeTagger, etc.). +- **Node API** (in `pqn-node`): FastAPI service. The only component reachable from outside. Handles GUI requests and peer-Node coordination. Owns the orchestration logic for each Experiment. +- **Router** (in `pqn-hardware`): ZMQ message broker. All in-Node messaging passes through it. +- **Hardware Provider** (in `pqn-hardware`): process that hosts physical instruments. A Node can run more than one Provider; for example, one machine per optics table or per piece of expensive hardware. +- **Driver** (in `pqn-hardware`): concrete instrument implementation. Talks to one piece of physical hardware (Thorlabs rotator, TimeTagger, etc.). -The Node API never talks to a Driver directly. It calls a **ProxyInstrument** — a client-side handle whose method calls are serialised onto the Router and dispatched to whichever Hardware Provider hosts the real instrument. This means the Node API code does not need to know which machine an instrument is plugged into. +The Node API never talks to a Driver directly. It calls a **ProxyInstrument**, a client-side handle whose method calls are serialised onto the Router and dispatched to whichever Hardware Provider hosts the real instrument. This means the Node API code does not need to know which machine an instrument is plugged into. ## 3. The three packages @@ -128,12 +139,12 @@ sequenceDiagram Two things to notice: -1. **ProxyInstruments hide the network.** From the Protocol's point of view, calling an instrument looks like a local method call — but the call is actually marshalled across ZMQ to wherever the Hardware Provider is running. +1. **ProxyInstruments hide the network.** From the Protocol's point of view, calling an instrument looks like a local method call, but the call is actually marshalled across ZMQ to wherever the Hardware Provider is running. 2. **Results stream back over WebSocket.** Long-running experiments emit progress updates as they go, so the GUI can render live counts and partial results rather than waiting for a single response at the end. ## 5. Walkthrough: a two-Node CHSH experiment -The CHSH Bell test is the canonical multi-Node Experiment. Two Nodes — call them Alice and Bob — each measure one half of an entangled pair on independently chosen polarisation bases, and the Node API on the initiating side aggregates the results. +The CHSH Bell test is the canonical multi-Node Experiment. Two Nodes (call them Alice and Bob) each measure one half of an entangled pair on independently chosen polarisation bases, and the Node API on the initiating side aggregates the results. ```{mermaid} sequenceDiagram @@ -162,12 +173,12 @@ sequenceDiagram Key points: -- The **GUI only talks to one Node API** (Alice's). The peer-Node coordination is entirely between Node APIs over HTTP — the GUI never connects to Bob. +- The **GUI only talks to one Node API** (Alice's). The peer-Node coordination is entirely between Node APIs over HTTP. The GUI never connects to Bob. - Each Node drives its own instruments through its own Router and Hardware Providers; neither side touches the other side's hardware. - The initiating Node (Alice) is responsible for aggregating the joint statistics and computing the Bell inequality value. ## Where to go next -- {doc}`../deployment/index` — how to actually stand up a Node and join a network. -- {doc}`../pqn-node/index`, {doc}`../pqn-gui/index`, {doc}`../pqn-hardware/index` — package-specific internals. -- {doc}`../physical-devices/index` — build guides for the physical instruments referenced throughout this page. +- {doc}`../deployment/index`: how to actually stand up a Node and join a network. +- {doc}`../pqn-node/index`, {doc}`../pqn-gui/index`, {doc}`../pqn-hardware/index`: package-specific internals. +- {doc}`../physical-devices/index`: build guides for the physical instruments referenced throughout this page. diff --git a/docs/physical-devices/index.md b/docs/physical-devices/index.md index e0d300b..0cfb7b2 100644 --- a/docs/physical-devices/index.md +++ b/docs/physical-devices/index.md @@ -4,7 +4,7 @@ Build and setup guides for the physical instruments used at a PQN Node. A Node's software (`pqn-node`, `pqn-hardware`) drives a stack of real optical and electronic hardware: polarimeters, time taggers, polarisation rotators, lasers, and the optomechanics that hold them in place. This section collects the hands-on guides needed to build, wire, and prepare those devices for use. -The first device documented here is the **polarimeter** — the tactile centrepiece of a PQN Node, designed to be 3D-printed cheaply enough that any institution can build one. Other devices (TimeTagger setup, rotator wiring, alignment procedures) will be added as standalone subsections over time. +The first device documented here is the **polarimeter**, the tactile centrepiece of a PQN Node, designed to be 3D-printed cheaply enough that any institution can build one. Other devices (TimeTagger setup, rotator wiring, alignment procedures) will be added as standalone subsections over time. ```{toctree} :maxdepth: 2 diff --git a/docs/physical-devices/polarimeter/assembly.md b/docs/physical-devices/polarimeter/assembly.md index 1bf4928..3d18d0f 100644 --- a/docs/physical-devices/polarimeter/assembly.md +++ b/docs/physical-devices/polarimeter/assembly.md @@ -34,7 +34,7 @@ Apply gentle force to press the post into the base until it friction-fits secure :align: center ``` -Cut the film to a 1-inch diameter. Slide it into the slot on the mount — the crack is sized to accept the film snugly. +Cut the film to a 1-inch diameter. Slide it into the slot on the mount. The crack is sized to accept the film snugly. > **Note:** Characterize the optics to the desired polarization basis before mounting. diff --git a/docs/physical-devices/polarimeter/index.md b/docs/physical-devices/polarimeter/index.md index 26fa3f0..5a2f5e4 100644 --- a/docs/physical-devices/polarimeter/index.md +++ b/docs/physical-devices/polarimeter/index.md @@ -1,6 +1,6 @@ # Polarimeter -The tactile interface for a PQN Node is a polarimeter setup that lets users observe and interact with the polarisation of light — a direct demonstration of quantum superposition. +The tactile interface for a PQN Node is a polarimeter setup that lets users observe and interact with the polarisation of light, a direct demonstration of quantum superposition. Lab-grade polarimeter systems cost several hundred to several thousand dollars, creating a significant barrier to public engagement. To address this, the PQN provides an inexpensive, modular alternative built from **3D-printed components and affordable optics** that replicates the same visual effects at a fraction of the cost. diff --git a/docs/pqn-gui/index.md b/docs/pqn-gui/index.md index 4183c37..ce935fe 100644 --- a/docs/pqn-gui/index.md +++ b/docs/pqn-gui/index.md @@ -1,6 +1,6 @@ # pqn-gui -`pqn-gui` is the public-facing web interface for the PQN. It is a Next.js / React / TypeScript application that talks to a single Node API over HTTP and WebSocket. It is intended for non-technical visitors — the experience is built around guided, multi-step Experiment flows narrated by an animated character. +`pqn-gui` is the public-facing web interface for the PQN. It is a Next.js / React / TypeScript application that talks to a single Node API over HTTP and WebSocket. It is intended for non-technical visitors, with the experience built around guided, multi-step Experiment flows narrated by an animated character. **Repository:** [github.com/PublicQuantumNetwork/pqn-gui](https://github.com/PublicQuantumNetwork/pqn-gui) diff --git a/docs/pqn-hardware/index.md b/docs/pqn-hardware/index.md index e9c2996..8196342 100644 --- a/docs/pqn-hardware/index.md +++ b/docs/pqn-hardware/index.md @@ -6,11 +6,11 @@ What lives in here: -- **Instrument base classes and ProxyInstrument plumbing** — the common abstractions over physical hardware -- **Drivers** — concrete instruments: Thorlabs rotators, TimeTagger, polarimeter, QKD hardware, plus dummy drivers for software-only development -- **Network layer** — Router (ZMQ broker) and Hardware Provider, plus the client side used by `pqn-node` -- **Protocols** — quantum-measurement logic for CHSH, QKD, Tomography, Visibility -- **`pqn-hw` CLI** — `start-router`, `start-provider` +- **Instrument base classes and ProxyInstrument plumbing**: the common abstractions over physical hardware. +- **Drivers**: concrete instruments such as Thorlabs rotators, TimeTagger, polarimeter, and QKD hardware, plus dummy drivers for software-only development. +- **Network layer**: Router (ZMQ broker) and Hardware Provider, plus the client side used by `pqn-node`. +- **Protocols**: quantum-measurement logic for CHSH, QKD, Tomography, Visibility. +- **`pqn-hw` CLI**: `start-router`, `start-provider`. `pqn-hardware` was split out from `pqn-node` (originally `pqn-stack`) so hardware-driver work and Node-service work can evolve independently. `pqn-node` pins it as a git dependency. diff --git a/docs/pqn-node/index.md b/docs/pqn-node/index.md index b7d4580..155a60e 100644 --- a/docs/pqn-node/index.md +++ b/docs/pqn-node/index.md @@ -1,6 +1,6 @@ # pqn-node -`pqn-node` is the FastAPI service that runs at each PQN site. It is the **Node API** — the only externally reachable component of a Node — and the orchestration brain for every Experiment. It depends on `pqn-hardware` as a git-pinned Python library for instrument access and Protocol logic. +`pqn-node` is the FastAPI service that runs at each PQN site. It is the **Node API** (the only externally reachable component of a Node) and the orchestration brain for every Experiment. It depends on `pqn-hardware` as a git-pinned Python library for instrument access and Protocol logic. **Repository:** [github.com/PublicQuantumNetwork/pqn-node](https://github.com/PublicQuantumNetwork/pqn-node) From 09eeabb163b4998fb1961acd2c3c64e4548e29d6 Mon Sep 17 00:00:00 2001 From: marcosf2 Date: Fri, 29 May 2026 14:04:24 -0500 Subject: [PATCH 3/6] Refine documentation to align terminology (`Instrument Provider`), add placeholders for detailed content, and improve clarity in structure and language. --- CONTEXT.md | 29 +++++++++------- docs/conf.py | 2 +- docs/overview/software-map.md | 26 +++++++------- docs/pqn-hardware/cli.md | 5 +++ docs/pqn-hardware/index.md | 36 +++++++++++++++----- docs/pqn-hardware/instruments-and-drivers.md | 7 ++++ docs/pqn-hardware/network.md | 7 ++++ 7 files changed, 77 insertions(+), 35 deletions(-) create mode 100644 docs/pqn-hardware/cli.md create mode 100644 docs/pqn-hardware/instruments-and-drivers.md create mode 100644 docs/pqn-hardware/network.md diff --git a/CONTEXT.md b/CONTEXT.md index c04e92c..3d6ba57 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,7 +7,7 @@ This file is the canonical glossary for terms used across `pqn-docs`, `pqn-node` ## Language **Node**: -A PQN deployment at a single physical site. Comprises a Node API, a Router, one or more Hardware Providers, instrument drivers, and the physical instruments themselves. The GUI is _not_ part of a Node. +A PQN deployment at a single physical site. Comprises a Node API, a Router, one or more Instrument Providers, instrument drivers, and the physical instruments themselves. The GUI is _not_ part of a Node. _Avoid_: site, instance, station **Node API**: @@ -17,22 +17,26 @@ _Avoid_: backend, server **Router**: ZMQ message broker inside a Node. Routes messages between the Node API, Hardware Providers, and developer clients. -**Hardware Provider**: -Process inside a Node that hosts physical instruments and exposes them to the rest of the Node via ProxyInstruments. -_Avoid_: Instrument Provider (older name; still appears in some code paths) +**Instrument Provider** / `InstrumentProvider`: +Process inside a Node that hosts physical instruments and exposes them to the rest of the Node via ProxyInstruments. The class is `InstrumentProvider` (`pqn_hardware.network.instrument_provider`). +_Avoid_: Hardware Provider (appears in some prose, but no such name exists in the code) **ProxyInstrument**: -Client-side handle for an instrument hosted by a Hardware Provider. Lets the Node API call the instrument without knowing where it physically runs. +Client-side handle for an instrument hosted by an Instrument Provider. Lets the Node API call the instrument without knowing where it physically runs. **Instrument**: Software abstraction for one piece of physical hardware (TimeTagger, Polarimeter, Rotator, etc.). Concrete implementations are called drivers and live in `pqn-hardware`. _Avoid_: device (reserved for the physical thing) **Driver**: -A concrete Instrument implementation for a specific piece of hardware (e.g. Thorlabs rotator driver). +A concrete Instrument implementation for a specific piece of hardware (e.g. Thorlabs rotator driver). One Instrument type can have many Drivers (e.g. `RotatorInstrument` → `APTRotator` / `SerialRotator` / `EllxRotator`). + +**Physical Device**: +The physical apparatus a Driver controls — the actual hardware on the bench. In prose, refer to it as "the device" or "the hardware"; reserve **Instrument** for the software abstraction. Driver docstrings and logs already use "device" this way. +_Avoid_: instrument (reserved for the software abstraction) **Protocol**: -The quantum-measurement logic for a single experiment type (CHSH, QKD, Tomography, Visibility). Lives in `pqn-hardware`. Distinct from Experiment. +The quantum-measurement logic for a single experiment type (CHSH, QKD, Tomography, Visibility). A **Node-level** concept that lives in `pqn-node`, *not* in `pqn-hardware` (see `docs/adr/0001-pqn-hardware-node-split.md`). Distinct from Experiment. **Experiment**: A user-facing activity in the GUI (CHSH Bell Test, Quantum Fortune, QKD, SSM, Tomography, Visibility). Implemented on top of one or more Protocols. @@ -50,8 +54,8 @@ An institution-run instance of the PQN. An organisation that stands up its own N ## Relationships - A **PQN Network** is composed of one or more **Nodes**. -- A **Node** contains one **Node API**, one **Router**, and one or more **Hardware Providers**. -- A **Hardware Provider** hosts one or more **Instruments**; each Instrument is implemented by a **Driver**. +- A **Node** contains one **Node API**, one **Router**, and one or more **Instrument Providers**. +- An **Instrument Provider** hosts one or more **Instruments**; each Instrument is implemented by a **Driver**. - The **Node API** reaches Instruments via **ProxyInstruments** routed through the **Router**. - An **Experiment** in the **GUI** invokes one or more **Protocols** on the **Node API**. - A **Protocol** orchestrates Instruments to perform a single quantum measurement task. @@ -59,7 +63,7 @@ An institution-run instance of the PQN. An organisation that stands up its own N ## Example dialogue > **New contributor:** "When the user clicks 'Run CHSH' in the GUI, what happens?" -> **Maintainer:** "The **GUI** calls the **Node API**. The Node API kicks off the CHSH **Protocol**, which talks to the local **Hardware Provider** through **ProxyInstruments** to drive the **Instruments** (rotators, the TimeTagger, etc.) For two-**Node** CHSH the Node API also coordinates with the peer **Node**'s Node API." +> **Maintainer:** "The **GUI** calls the **Node API**. The Node API kicks off the CHSH **Protocol**, which talks to the local **Instrument Provider** through **ProxyInstruments** to drive the **Instruments** (rotators, the TimeTagger, etc.) For two-**Node** CHSH the Node API also coordinates with the peer **Node**'s Node API." > > **New contributor:** "And the GUI is part of the Node?" > **Maintainer:** "No, the GUI is just a client. The **Node** is the backend stack at one site. You can run every **Experiment** without the GUI." @@ -70,5 +74,6 @@ An institution-run instance of the PQN. An organisation that stands up its own N - At the **network** zoom level, "Node" means the whole site-level deployment. - When working **inside** a Node, "Node" is sometimes used as shorthand for the **Node API** (the brain of the Node). Resolution: prefer **"Node API"** in writing whenever you mean the FastAPI service. Reserve unqualified "Node" for the site-level meaning. -- **"Hardware"** has two unrelated meanings: the Python library `pqn-hardware` (drivers + Router + Provider + Protocols) and the physical devices themselves. In docs prose, write `pqn-hardware` (in code font) for the package and **Physical Device** / **Instrument** for the physical thing. -- **"Instrument Provider"** vs **"Hardware Provider"**: same concept, two names in code. Prefer **Hardware Provider** in docs. +- **"Hardware"** has two unrelated meanings: the Python library `pqn-hardware` (drivers + Router + Instrument Provider + client) and the physical devices themselves. In docs prose, write `pqn-hardware` (in code font) for the package and **Physical Device** / **Instrument** for the physical thing. +- **"Instrument Provider"** vs **"Hardware Provider"**: same concept. The code uses `InstrumentProvider` everywhere; "Hardware Provider" exists only in older prose. Prefer **Instrument Provider** in docs. +- **"device" in the client API**: the client-side method and message names say "device" for a *hosted Instrument* — `get_device`, `get_available_devices`, `GET_DEVICE_STRUCTURE`, `device_name`. This is a historical naming quirk; the thing returned is a **ProxyInstrument** (an Instrument), not a Physical Device. In prose always write **Instrument**, and note the method names say "device" for historical reasons. diff --git a/docs/conf.py b/docs/conf.py index c505600..32494de 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,7 +5,7 @@ extensions = ['myst_parser', 'sphinxcontrib.mermaid'] templates_path = ['_templates'] -exclude_patterns = ['build'] +exclude_patterns = ['build', 'adr'] language = "en" diff --git a/docs/overview/software-map.md b/docs/overview/software-map.md index 9b3484e..ce5399d 100644 --- a/docs/overview/software-map.md +++ b/docs/overview/software-map.md @@ -3,7 +3,7 @@ This page is the top-level mental map of the Public Quantum Network: how the whole system fits together, how a single Node is structured, where each package lives, and how a real request flows through everything. ```{note} -For a glossary of the terms used here (Node, Node API, Hardware Provider, ProxyInstrument, Protocol, Experiment, GUI), see `CONTEXT.md` at the repository root. +For a glossary of the terms used here (Node, Node API, Instrument Provider, ProxyInstrument, Protocol, Experiment, GUI), see `CONTEXT.md` at the repository root. Deeper, package-specific details live under {doc}`../pqn-node/index`, {doc}`../pqn-gui/index`, and {doc}`../pqn-hardware/index`. Operator-focused setup lives under {doc}`../deployment/index`. ``` @@ -36,7 +36,7 @@ flowchart LR classDef client fill:#fff3e0,stroke:#b36b1f; ``` -Inside a Node, the Node API is the only component that ever talks to anything outside the Node. Everything else (Router, Hardware Providers, drivers, instruments) lives on the Node's local network and is reachable only through the Node API. Nothing in the PQN is exposed to the public internet. +Inside a Node, the Node API is the only component that ever talks to anything outside the Node. Everything else (Router, Instrument Providers, drivers, instruments) lives on the Node's local network and is reachable only through the Node API. Nothing in the PQN is exposed to the public internet. ## 2. A single Node @@ -50,8 +50,8 @@ flowchart TB subgraph Node["Node (site intranet)"] API[Node API
pqn-node] Router[Router
pqn-hardware] - Provider1[Hardware Provider
pqn-hardware] - Provider2[Hardware Provider
pqn-hardware] + Provider1[Instrument Provider
pqn-hardware] + Provider2[Instrument Provider
pqn-hardware] Driver1[Driver] Driver2[Driver] Driver3[Driver] @@ -77,10 +77,10 @@ flowchart TB - **Node API** (in `pqn-node`): FastAPI service. The only component reachable from outside. Handles GUI requests and peer-Node coordination. Owns the orchestration logic for each Experiment. - **Router** (in `pqn-hardware`): ZMQ message broker. All in-Node messaging passes through it. -- **Hardware Provider** (in `pqn-hardware`): process that hosts physical instruments. A Node can run more than one Provider; for example, one machine per optics table or per piece of expensive hardware. +- **Instrument Provider** (in `pqn-hardware`): process that hosts physical instruments. A Node can run more than one Provider; for example, one machine per optics table or per piece of expensive hardware. - **Driver** (in `pqn-hardware`): concrete instrument implementation. Talks to one piece of physical hardware (Thorlabs rotator, TimeTagger, etc.). -The Node API never talks to a Driver directly. It calls a **ProxyInstrument**, a client-side handle whose method calls are serialised onto the Router and dispatched to whichever Hardware Provider hosts the real instrument. This means the Node API code does not need to know which machine an instrument is plugged into. +The Node API never talks to a Driver directly. It calls a **ProxyInstrument**, a client-side handle whose method calls are serialised onto the Router and dispatched to whichever Instrument Provider hosts the real instrument. This means the Node API code does not need to know which machine an instrument is plugged into. ## 3. The three packages @@ -90,12 +90,12 @@ The same picture, redrawn around the package boundary instead of the runtime top flowchart LR GUI["pqn-gui
Next.js · TypeScript
public web UI"]:::ts Node["pqn-node
FastAPI · Python
Node API service"]:::py - HW["pqn-hardware
library · Python
drivers, Router, Provider, Protocols"]:::py + HW["pqn-hardware
library · Python
drivers, Router, Provider"]:::py GUI -- HTTP / WebSocket --> Node Node -- imports --> HW Node -- launches --> RouterProc["Router process
pqn-hw start-router"] - Node -- launches --> ProviderProc["Hardware Provider process
pqn-hw start-provider"] + Node -- launches --> ProviderProc["Instrument Provider process
pqn-hw start-provider"] RouterProc -. provided by .- HW ProviderProc -. provided by .- HW @@ -103,9 +103,9 @@ flowchart LR classDef ts fill:#fff3e0,stroke:#b36b1f; ``` -- **`pqn-node`** is the only package that actually runs the Node API service. It depends on `pqn-hardware` as a git-pinned library and orchestrates everything: HTTP routing, peer-Node coordination, Experiment lifecycle, WebSocket streaming. +- **`pqn-node`** is the only package that actually runs the Node API service. It depends on `pqn-hardware` as a git-pinned library and orchestrates everything: HTTP routing, peer-Node coordination, Protocol logic, Experiment lifecycle, WebSocket streaming. - **`pqn-gui`** is a Next.js application. It has no Python; it knows the Node API only through its HTTP/WebSocket surface. -- **`pqn-hardware`** is a Python library *and* a CLI (`pqn-hw`). The library is consumed in-process by `pqn-node` for ProxyInstrument calls and Protocol logic. The CLI runs the Router and Hardware Provider as separate long-lived processes, started during deployment. +- **`pqn-hardware`** is a Python library *and* a CLI (`pqn-hw`). The library is consumed in-process by `pqn-node` for ProxyInstrument calls. The CLI runs the Router and Instrument Provider as separate long-lived processes, started during deployment. The split between `pqn-node` and `pqn-hardware` exists so that hardware-driver work (which moves at the pace of new instruments and ZMQ infrastructure changes) can evolve independently of the FastAPI service (which moves at the pace of new Experiments and UI features). @@ -118,9 +118,9 @@ sequenceDiagram actor User participant GUI as GUI
(pqn-gui) participant API as Node API
(pqn-node) - participant Proto as Protocol
(pqn-hardware) + participant Proto as Protocol
(pqn-node) participant Router as Router
(pqn-hardware) - participant Provider as Hardware Provider
(pqn-hardware) + participant Provider as Instrument Provider
(pqn-hardware) participant Instr as Physical Instrument User->>GUI: click "Run" @@ -174,7 +174,7 @@ sequenceDiagram Key points: - The **GUI only talks to one Node API** (Alice's). The peer-Node coordination is entirely between Node APIs over HTTP. The GUI never connects to Bob. -- Each Node drives its own instruments through its own Router and Hardware Providers; neither side touches the other side's hardware. +- Each Node drives its own instruments through its own Router and Instrument Providers; neither side touches the other side's hardware. - The initiating Node (Alice) is responsible for aggregating the joint statistics and computing the Bell inequality value. ## Where to go next diff --git a/docs/pqn-hardware/cli.md b/docs/pqn-hardware/cli.md new file mode 100644 index 0000000..7c6540b --- /dev/null +++ b/docs/pqn-hardware/cli.md @@ -0,0 +1,5 @@ +# The `pqn-hw` CLI + +> _Placeholder — to be drafted in Phase 5._ This page will document `start-router` +> and `start-provider`, the TOML config shape and every flag, with the corrected +> `config_example.toml` lifted as the worked example. diff --git a/docs/pqn-hardware/index.md b/docs/pqn-hardware/index.md index 8196342..5de68ec 100644 --- a/docs/pqn-hardware/index.md +++ b/docs/pqn-hardware/index.md @@ -1,17 +1,35 @@ # pqn-hardware -`pqn-hardware` is the Python library that everything below the Node API runs on. It provides the instrument abstractions, ZMQ-based messaging fabric, concrete drivers, and quantum protocol implementations that `pqn-node` consumes. It also ships a CLI (`pqn-hw`) used to launch the Router and Hardware Provider processes during deployment. +`pqn-hardware` is the Python library that talks to the individual pieces of hardware around a Node. It provides the software abstraction for an instrument, the concrete drivers behind real devices, and the ZMQ messaging fabric that carries instrument calls across a Node's intranet. It also ships a CLI, `pqn-hw`, that launches the Router and Instrument Provider processes a Node runs. `pqn-node` consumes it as a git-pinned dependency. **Repository:** [github.com/PublicQuantumNetwork/pqn-hardware](https://github.com/PublicQuantumNetwork/pqn-hardware) -What lives in here: +## The three layers -- **Instrument base classes and ProxyInstrument plumbing**: the common abstractions over physical hardware. -- **Drivers**: concrete instruments such as Thorlabs rotators, TimeTagger, polarimeter, and QKD hardware, plus dummy drivers for software-only development. -- **Network layer**: Router (ZMQ broker) and Hardware Provider, plus the client side used by `pqn-node`. -- **Protocols**: quantum-measurement logic for CHSH, QKD, Tomography, Visibility. -- **`pqn-hw` CLI**: `start-router`, `start-provider`. +The library is organised into three layers, each documented on its own page: -`pqn-hardware` was split out from `pqn-node` (originally `pqn-stack`) so hardware-driver work and Node-service work can evolve independently. `pqn-node` pins it as a git dependency. +- **Instruments & drivers** — the `Instrument` abstraction (a `typing.Protocol`), the typed sub-protocols for time taggers, rotators, and polarimeters, the concrete drivers that back real devices, and the `ProxyInstrument` that lets code drive a remote instrument as if it were local. See {doc}`instruments-and-drivers`. +- **Network transport** — the ZMQ star that carries instrument calls: the Router (broker), the Instrument Providers that host instruments, and the Client side that calls them. In practice this is a single-router network. See {doc}`network`. +- **The `pqn-hw` CLI** — the commands that stand up those processes during deployment: `start-router` and `start-provider`, and the TOML config that wires providers to a router. See {doc}`cli`. -> _Placeholder._ Detailed pages on the driver model, ProxyInstrument lifecycle, Router/Provider topology, Protocol authoring, and the `pqn-hw` CLI will be added here. Until then, see the [README](https://github.com/PublicQuantumNetwork/pqn-hardware) and the {doc}`../overview/software-map` for context. +## How `pqn-node` uses it + +`pqn-node` pins `pqn-hardware` as a git dependency and consumes it two ways. In-process, the Node API drives instruments by calling **ProxyInstruments** — client-side handles whose method calls are marshalled across the Router to whichever Instrument Provider hosts the real device, so the Node API never needs to know which machine an instrument is plugged into. Out-of-process, a deployment uses the `pqn-hw` CLI to run the Router and Instrument Provider as long-lived processes. For the system-wide picture of how this fits with the Node API and the GUI, see {doc}`../overview/software-map`. + +```{note} +This layer is **internal to a Node**. The Router, Instrument Providers, and the instruments behind them live on the Node's local network and are not reachable from outside it. Other Nodes never touch a peer's `pqn-hardware` resources directly — all cross-Node interaction goes through the **Node API**, the only externally reachable component of a Node. +``` + +## Where to go next + +- {doc}`../overview/software-map` — how `pqn-hardware`, `pqn-node`, and `pqn-gui` fit together, with request walkthroughs. +- {doc}`../pqn-node/index` — the Node API service that consumes this library. +- {doc}`../physical-devices/index` — build and wiring guides for the physical devices the drivers control. + +```{toctree} +:maxdepth: 2 + +instruments-and-drivers +network +cli +``` diff --git a/docs/pqn-hardware/instruments-and-drivers.md b/docs/pqn-hardware/instruments-and-drivers.md new file mode 100644 index 0000000..be9569a --- /dev/null +++ b/docs/pqn-hardware/instruments-and-drivers.md @@ -0,0 +1,7 @@ +# Instruments & drivers + +> _Placeholder — to be drafted in Phase 3._ This page will cover the `Instrument` +> `typing.Protocol` (`parameters` vs `operations`, `InstrumentInfo`, the +> `log_operation` / `log_parameter` decorators, the typed sub-protocols), writing a +> Driver (worked example on `DummyInstrument` plus a tour of the real drivers), and +> the ProxyInstrument lifecycle. diff --git a/docs/pqn-hardware/network.md b/docs/pqn-hardware/network.md new file mode 100644 index 0000000..36903fb --- /dev/null +++ b/docs/pqn-hardware/network.md @@ -0,0 +1,7 @@ +# Network transport: Router, Instrument Provider, Client + +> _Placeholder — to be drafted in Phase 4._ This page will cover the ZMQ-star +> messaging fabric (Router / Instrument Provider / Client, socket types, +> registration + heartbeat, packet intents, the CONTROL request format, and the +> single-router reality) and the operator runbook (first-time bring-up, recovery, +> health check). From b988afdacf683d040be833f1e7e132e165d5f3f9 Mon Sep 17 00:00:00 2001 From: marcosf2 Date: Fri, 29 May 2026 15:23:28 -0500 Subject: [PATCH 4/6] Anatomy of a driver section --- CONTEXT.md | 2 +- docs/pqn-hardware/instruments-and-drivers.md | 187 ++++++++++++++++++- 2 files changed, 183 insertions(+), 6 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 3d6ba57..45338d6 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -15,7 +15,7 @@ The FastAPI service inside a Node. The only Node component reachable from outsid _Avoid_: backend, server **Router**: -ZMQ message broker inside a Node. Routes messages between the Node API, Hardware Providers, and developer clients. +ZMQ message broker inside a Node. Routes messages between the Node API, Instrument Providers, and developer clients. **Instrument Provider** / `InstrumentProvider`: Process inside a Node that hosts physical instruments and exposes them to the rest of the Node via ProxyInstruments. The class is `InstrumentProvider` (`pqn_hardware.network.instrument_provider`). diff --git a/docs/pqn-hardware/instruments-and-drivers.md b/docs/pqn-hardware/instruments-and-drivers.md index be9569a..e060162 100644 --- a/docs/pqn-hardware/instruments-and-drivers.md +++ b/docs/pqn-hardware/instruments-and-drivers.md @@ -1,7 +1,184 @@ # Instruments & drivers -> _Placeholder — to be drafted in Phase 3._ This page will cover the `Instrument` -> `typing.Protocol` (`parameters` vs `operations`, `InstrumentInfo`, the -> `log_operation` / `log_parameter` decorators, the typed sub-protocols), writing a -> Driver (worked example on `DummyInstrument` plus a tour of the real drivers), and -> the ProxyInstrument lifecycle. +A Node is surrounded by real equipment — rotators, time taggers, polarimeters — and +something has to turn "rotate this waveplate to 22.5°" into the actual bytes that make +the motor move. That's the job of this part of `pqn-hardware`: it gives every piece of +hardware a uniform shape in software, so the rest of the system can drive it without +knowing the messy details of any particular device. + +Two ideas sit at the centre of that, and it helps to keep them apart from the start. + +The first is the **Instrument** — the *software abstraction* for a piece of hardware. +It's a small contract that says what any instrument looks like: it has a name, it can be +started and closed, and it exposes some values you can read or write and some actions +you can call. The rest of the system only ever talks to this abstraction. + +The second is the **Driver** — a *concrete implementation* of that contract for one +specific piece of hardware, like a Thorlabs rotator or a Swabian time tagger. The same +kind of instrument can have several Drivers: a rotator is still a rotator whether it +speaks the Thorlabs APT protocol, a plain serial protocol, or the ELLx protocol, so all +three are Drivers behind the one rotator Instrument. + +So you *write* Drivers, and you *program against* Instruments. And when a piece of +hardware lives on another machine, you still program against an Instrument — a +`ProxyInstrument` that quietly forwards your calls across the Node. The rest of this +page works through each of those in turn: the Instrument model first, then writing a +Driver, then using one from afar through a `ProxyInstrument`. + +## The Instrument model + +Every instrument in `pqn-hardware`, real or simulated, is built on a single base type +called `Instrument`, defined in +[`src/pqn_hardware/instrument.py`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/instrument.py). +It's written as a +`typing.Protocol` — Python's way of describing a *shape* that other classes are expected +to match — but it's also a `dataclass`, so a Driver doesn't just match it from a +distance: it subclasses `Instrument` directly and fills in the pieces. In other words, +`Instrument` does double duty. It's the contract that describes what every instrument +exposes to the rest of the system, and it's the base class you start from when you write +a new one. + +The contract itself is small, so it's worth building up a piece at a time. + +At its core, an Instrument is an identity plus two collections of capabilities: + +```python +@runtime_checkable +@dataclass(slots=True) +class Instrument(Protocol): + name: str + desc: str + hw_address: str + parameters: set[str] = field(default_factory=set) + operations: dict[str, Callable[..., Any]] = field(default_factory=dict) +``` +*Source: [`instrument.py:26-42`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/instrument.py#L26-L42)* + +The identity fields are straightforward: a `name` the instrument is addressed by, a +human-readable `desc`, and an `hw_address` (a serial port, a USB id, whatever the +hardware is reached through). + +The two capability collections are the heart of the model: + +- **`parameters`** is a set of attribute *names* — values you **read and write**. A + rotator's `degrees`, a dummy's `param_int`: state you query or set. +- **`operations`** is a dict mapping *names* to callables — *actions* you **invoke**. A + rotator's `move_to`, a time tagger's `count_singles`: things the instrument *does*. + +This split — values you get/set versus actions you call — is what makes an instrument +addressable from a distance. When code drives a remote instrument, the proxy uses these +two collections to decide whether an attribute access should read or write a value or +call a method (see *Using a ProxyInstrument* below, and the wire format on +{doc}`network`). + +Because the two collections are keyed by name, there's one rule on names: + +```{note} +An instrument's `name` may not contain a `:` character. The `:` separates the parts of +a request when talking to a remote instrument, so a colon in the name would be +ambiguous ([`instrument.py:33`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/instrument.py#L33)). +``` + +The `@runtime_checkable` decorator means an ordinary `isinstance(x, Instrument)` check +works at runtime, so code can confirm something really is an instrument before driving +it. + +### Anatomy of a Driver + +#### Lifecycle + +An Instrument has a start/stop lifecycle: + +```python + def start(self) -> None: ... + def close(self) -> None: ... +``` +*Source: [`instrument.py:47-48`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/instrument.py#L47-L48)* + +These two look trivial, but they carry the whole relationship with the physical device, +and it's worth keeping them cleanly separated when you write a Driver. + +The rule: **constructing the Instrument object should do nothing to the hardware; the +device should only be touched once `start()` is called.** +Building the object should only record where the device lives — its `name`, `desc`, and +`hw_address`, plus any driver-specific settings — and set sensible defaults. It should +not open a port, contact a controller, or move anything. Save all of that for `start()`: +opening the serial or USB connection, handshaking with the controller, homing a motor to +a known reference angle. + +The reason is in how Instruments come to life. An Instrument Provider builds each one +straight from its config — `class_(name=..., desc=..., hw_address=..., **settings)`, +purely from the strings in a TOML file — and only *then* calls `start()` +([`instrument_provider.py:113`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/network/instrument_provider.py#L113)). +At construction time it has no idea whether the device is plugged in, powered, or even +reachable. If your constructor tried to open the port, a missing or busy device would +make the object impossible to *create* — and you would lose the clean line between an +instrument that is *configured* and one that is *connected*. Deferring every side effect +to `start()` keeps construction cheap and predictable, so an instrument can be created +freely and brought online deliberately. + +`close()` is the other half of that bargain. Most of these devices hold an *exclusive* +resource — a serial port, a USB handle — that nothing else can open while it's in use. +If a process walks away without calling `close()`, the port can stay locked until the +hardware is power-cycled, and the device may be left in an undefined state. So a Driver +does its real connect-and-initialise in `start()` and is responsible for releasing +everything cleanly in `close()`. + +#### Reporting state: `info` + +An Instrument should be able to report its complete current state on demand, and that's +what `info` is for. It hands back a single read-only snapshot that captures everything +worth knowing about the instrument — its identity and every piece of live state — so a +caller can inspect it without having to read parameters one by one: + +```python + @property + def info(self) -> InstrumentInfo: ... +``` +*Source: [`instrument.py:50-51`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/instrument.py#L50-L51)* + +That snapshot is an `InstrumentInfo`. The base class only holds the fields every +instrument has — its identity, plus a free-form `hw_status` for anything else worth +reporting: + +```python +@dataclass(frozen=True, slots=True) +class InstrumentInfo: + name: str = "" + desc: str = "" + hw_address: str = "" + hw_status: dict[str, Any] = field(default_factory=dict) +``` +*Source: [`instrument.py:18-23`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/instrument.py#L18-L23)* + +You won't usually return a bare `InstrumentInfo`, though. When you write a Driver, you +define your own info class that extends `InstrumentInfo` with the state specific to your +device, and return *that* from `info`. The dummy driver's `DummyInfo` adds its three +parameter values; a rotator's `RotatorInfo` adds the current angle and offset. A caller +then gets back the full, typed state of whatever instrument it happens to be holding. + +#### Instrument categories + +The base `Instrument` describes any instrument at all. On top of it, the library defines +a more specific Instrument for each broad category of hardware: + +- **`TimeTaggerInstrument`** +- **`RotatorInstrument`** +- **`PolarimeterInstrument`** + +Each of these builds on `Instrument` — it still *is* an Instrument — and pins down a +**standard interface** for its category: the exact parameters and operations every +rotator, time tagger, or polarimeter is expected to expose. That shared interface is the +whole point. Because every rotator presents the same `degrees`, `move_to`, and `move_by`, +the rest of the system can be written against "a rotator" without caring whether the +physical device is a Thorlabs APT unit, a serial unit, or an ELLx unit. What each +category adds, and how you build a Driver on top of one, is covered in *Writing a Driver* +below. + +## Writing a Driver + +> _Placeholder — drafted in sub-phase 3.2 (grill pending)._ + +## Using a ProxyInstrument + +> _Placeholder — drafted in sub-phase 3.3 (grill pending)._ From a1cb5ba19499953aa9b0c3fbc997c85597970a89 Mon Sep 17 00:00:00 2001 From: marcosf2 Date: Fri, 29 May 2026 15:48:10 -0500 Subject: [PATCH 5/6] Building a driver --- docs/pqn-hardware/instruments-and-drivers.md | 217 ++++++++++++++++++- 1 file changed, 212 insertions(+), 5 deletions(-) diff --git a/docs/pqn-hardware/instruments-and-drivers.md b/docs/pqn-hardware/instruments-and-drivers.md index e060162..af1e5a3 100644 --- a/docs/pqn-hardware/instruments-and-drivers.md +++ b/docs/pqn-hardware/instruments-and-drivers.md @@ -40,7 +40,7 @@ a new one. The contract itself is small, so it's worth building up a piece at a time. -At its core, an Instrument is an identity plus two collections of capabilities: +At its core, an Instrument is an identity plus two groups of capabilities: ```python @runtime_checkable @@ -58,7 +58,7 @@ The identity fields are straightforward: a `name` the instrument is addressed by human-readable `desc`, and an `hw_address` (a serial port, a USB id, whatever the hardware is reached through). -The two capability collections are the heart of the model: +These two groups are the heart of the model: - **`parameters`** is a set of attribute *names* — values you **read and write**. A rotator's `degrees`, a dummy's `param_int`: state you query or set. @@ -67,11 +67,11 @@ The two capability collections are the heart of the model: This split — values you get/set versus actions you call — is what makes an instrument addressable from a distance. When code drives a remote instrument, the proxy uses these -two collections to decide whether an attribute access should read or write a value or +two groups to decide whether an attribute access should read or write a value or call a method (see *Using a ProxyInstrument* below, and the wire format on {doc}`network`). -Because the two collections are keyed by name, there's one rule on names: +Because both groups are keyed by name, there's one rule on names: ```{note} An instrument's `name` may not contain a `:` character. The `:` separates the parts of @@ -177,7 +177,214 @@ below. ## Writing a Driver -> _Placeholder — drafted in sub-phase 3.2 (grill pending)._ +Writing a Driver means giving real behaviour to the lifecycle, parameters, operations, +and `info` from the previous section, for one specific piece of hardware. The clearest +way to see how is to build one up from scratch — and the repository already ships the +perfect specimen for that: `DummyInstrument`, a fake instrument with no hardware behind +it, used in tests and as a worked example. We'll assemble it piece by piece, then look at +how a real Driver differs. + +### Assembling a Driver, step by step + +A Driver is a `dataclass` that subclasses `Instrument`. Start with the class itself and +the state it keeps: + +```python +@dataclass(slots=True) +class DummyInstrument(Instrument): + _param_int: int = 2 + _param_str: str = "hello" + _param_bool: bool = True + connected: bool = False +``` +*Source: [`dummies.py:17-22`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L17-L22)* + +The fields prefixed with `_` are the dummy's private state — the values a real Driver +would read off the hardware. (`name`, `desc`, and `hw_address` come from `Instrument` +itself, so they don't need repeating here.) + +Next, declare what the instrument can do. Recall that an Instrument advertises its +capabilities in two places, `parameters` and `operations`; a Driver fills those in +inside `__post_init__`: + +```python + def __post_init__(self) -> None: + self.parameters = {"param_int", "param_str", "param_bool"} + self.operations = { + "double_int": self.double_int, + "lowercase_str": self.lowercase_str, + "uppercase_str": self.uppercase_str, + "toggle_bool": self.toggle_bool, + "set_half_input_int": self.set_half_input_int, + } +``` +*Source: [`dummies.py:24-32`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L24-L32)* + +`parameters` is just the set of attribute names a caller may read and write; +`operations` maps each callable name to the bound method that implements it. Everything a +caller is allowed to do remotely has to be registered in one of these two. + +Now the lifecycle. The dummy has nothing to connect to, so `start` and `close` only flip +a flag — but this is exactly where a real Driver would open its serial port and release +it again: + +```python + def start(self) -> None: + self.connected = True + + def close(self) -> None: + self.connected = False +``` +*Source: [`dummies.py:45-49`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L45-L49)* + +A parameter is implemented as an ordinary property pair — a getter and a setter over the +private field: + +```python + @property + @log_parameter + def param_int(self) -> int: + return self._param_int + + @param_int.setter + @log_parameter + def param_int(self, value: int) -> None: + self._param_int = value +``` +*Source: [`dummies.py:51-59`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L51-L59)* + +The `@log_parameter` and `@log_operation` decorators that appear here and below are +optional helpers from `instrument.py`: wrap a parameter accessor or an operation and +every read, write, and call is logged with timing. They aren't required for a working +Driver, but they're cheap observability, so the real drivers use them throughout. + +An operation is just a method, registered in `operations` above: + +```python + @log_operation + def double_int(self) -> int: + self._param_int *= 2 + return self._param_int +``` +*Source: [`dummies.py:81-84`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L81-L84)* + +Finally, `info`. As described earlier, a Driver returns its *own* info class carrying its +complete state — for the dummy that's `DummyInfo`, which extends `InstrumentInfo` with +the three parameter values: + +```python +@dataclass(frozen=True, slots=True) +class DummyInfo(InstrumentInfo): + param_int: int = 0 + param_str: str = "" + param_bool: bool = False +``` +*Source: [`dummies.py:10-14`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L10-L14)* + +```python + @property + def info(self) -> DummyInfo: + return DummyInfo( + name=self.name, + desc=self.desc, + hw_address=self.hw_address, + param_int=self.param_int, + param_str=self.param_str, + param_bool=self.param_bool, + ) +``` +*Source: [`dummies.py:34-43`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L34-L43)* + +That's a complete Driver: state, capabilities, lifecycle, accessors, and a snapshot. +Swap the flag-flipping for real serial I/O and you have the shape of every real Driver in +the package. + +### Building on an Instrument category + +Subclassing the base `Instrument` means wiring up everything by hand, as the dummy does. +When your hardware fits one of the Instrument categories, you *can* subclass that instead +and let it do some of the wiring for you. `RotatorInstrument`, for example, already +registers the standard rotator operations and parameter in its own `__post_init__`: + +```python + def __post_init__(self) -> None: + self.operations["move_to"] = self.move_to + self.operations["move_by"] = self.move_by + self.parameters.add("degrees") +``` +*Source: [`instrument.py:180-184`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/instrument.py#L180-L184)* + +It even implements `move_to` and `move_by` for you, in terms of `degrees`: + +```python + def move_to(self, angle: float) -> None: + self.degrees = angle + + def move_by(self, angle: float) -> None: + self.degrees += angle +``` +*Source: [`instrument.py:194-200`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/instrument.py#L194-L200)* + +So a rotator Driver built on `RotatorInstrument` only has to supply the parts that are +genuinely hardware-specific: how to `start` and `close` the connection, how to read and +write `degrees`, and its `info`. `SerialRotator` is about as small as a real Driver gets: + +```python +@dataclass(slots=True) +class SerialRotator(RotatorInstrument): + _degrees: float = 0.0 # The hardware doesn't support position tracking + _conn: serial.Serial = field(init=False, repr=False) + + def start(self) -> None: + self._conn = serial.Serial(self.hw_address, baudrate=115200, timeout=1) + self._conn.write(b"open_channel") + self._conn.read(100) + self._conn.write(b"motor_ready") + self._conn.read(100) + self.degrees = self.offset_degrees + + def close(self) -> None: + self.degrees = 0 + self._conn.close() + + @property + def degrees(self) -> float: + return self._degrees + + @degrees.setter + def degrees(self, degrees: float) -> None: + self._conn.write(f"SRA {degrees}".encode()) + self._degrees = degrees + _ = self._conn.readline().decode() +``` +*Source: [`rotator.py:95-132`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/rotator.py#L95-L132)* + +It never defines `move_to`, `move_by`, or `__post_init__` — all three come from +`RotatorInstrument`. Notice the payoff of the lifecycle rule from earlier: opening the +serial port lives entirely in `start()`, so constructing a `SerialRotator` from config +touches no hardware. + +```{note} +Because construction does no I/O, an operation could be called before `start()` has run. +If that would misbehave on your hardware, guard against it: `APTRotator` raises +`DeviceNotStartedError` if you try to move it before its device handle exists +([`rotator.py:67`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/rotator.py#L67)). +``` + +### The drivers that ship today + +The package ships these Drivers, grouped by the Instrument category each one implements: + +| Category | Drivers | Hardware | +|---|---|---| +| `TimeTaggerInstrument` | [`SwabianTimeTagger`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/timetagger.py#L19) | Swabian Instruments time tagger | +| `RotatorInstrument` | [`APTRotator`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/rotator.py#L24), [`SerialRotator`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/rotator.py#L96), [`EllxRotator`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/rotator.py#L136) | Thorlabs APT mounts; a plain serial rotator; Thorlabs ELLx mounts | +| `PolarimeterInstrument` | [`ArduinoPolarimeter`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/polarimeter.py#L95) | Arduino-based polarimeter | +| `Instrument` (base) | [`DummyInstrument`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L18) | none — test and example fixture | + +The rotator row is the point made back in the orientation: one category, several Drivers. +A `RotatorInstrument` is a rotator whatever protocol it speaks, and the rest of the +system drives all three through the identical `degrees` / `move_to` / `move_by` interface. ## Using a ProxyInstrument From 73b36ce87c4b1cb186fe730672d0df41a2e89251 Mon Sep 17 00:00:00 2001 From: marcosf2 Date: Fri, 29 May 2026 17:14:58 -0500 Subject: [PATCH 6/6] How to use a driver --- docs/pqn-hardware/instruments-and-drivers.md | 143 +++++++++++++++---- 1 file changed, 117 insertions(+), 26 deletions(-) diff --git a/docs/pqn-hardware/instruments-and-drivers.md b/docs/pqn-hardware/instruments-and-drivers.md index af1e5a3..5bebb7d 100644 --- a/docs/pqn-hardware/instruments-and-drivers.md +++ b/docs/pqn-hardware/instruments-and-drivers.md @@ -1,6 +1,6 @@ # Instruments & drivers -A Node is surrounded by real equipment — rotators, time taggers, polarimeters — and +A Node is surrounded by real equipment (rotators, time taggers, polarimeters), and something has to turn "rotate this waveplate to 22.5°" into the actual bytes that make the motor move. That's the job of this part of `pqn-hardware`: it gives every piece of hardware a uniform shape in software, so the rest of the system can drive it without @@ -8,19 +8,19 @@ knowing the messy details of any particular device. Two ideas sit at the centre of that, and it helps to keep them apart from the start. -The first is the **Instrument** — the *software abstraction* for a piece of hardware. +The first is the **Instrument**, the *software abstraction* for a piece of hardware. It's a small contract that says what any instrument looks like: it has a name, it can be started and closed, and it exposes some values you can read or write and some actions you can call. The rest of the system only ever talks to this abstraction. -The second is the **Driver** — a *concrete implementation* of that contract for one +The second is the **Driver**, a *concrete implementation* of that contract for one specific piece of hardware, like a Thorlabs rotator or a Swabian time tagger. The same kind of instrument can have several Drivers: a rotator is still a rotator whether it speaks the Thorlabs APT protocol, a plain serial protocol, or the ELLx protocol, so all three are Drivers behind the one rotator Instrument. So you *write* Drivers, and you *program against* Instruments. And when a piece of -hardware lives on another machine, you still program against an Instrument — a +hardware lives on another machine, you still program against an Instrument, a `ProxyInstrument` that quietly forwards your calls across the Node. The rest of this page works through each of those in turn: the Instrument model first, then writing a Driver, then using one from afar through a `ProxyInstrument`. @@ -31,8 +31,8 @@ Every instrument in `pqn-hardware`, real or simulated, is built on a single base called `Instrument`, defined in [`src/pqn_hardware/instrument.py`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/instrument.py). It's written as a -`typing.Protocol` — Python's way of describing a *shape* that other classes are expected -to match — but it's also a `dataclass`, so a Driver doesn't just match it from a +`typing.Protocol` (Python's way of describing a *shape* that other classes are expected +to match), but it's also a `dataclass`, so a Driver doesn't just match it from a distance: it subclasses `Instrument` directly and fills in the pieces. In other words, `Instrument` does double duty. It's the contract that describes what every instrument exposes to the rest of the system, and it's the base class you start from when you write @@ -60,12 +60,12 @@ hardware is reached through). These two groups are the heart of the model: -- **`parameters`** is a set of attribute *names* — values you **read and write**. A +- **`parameters`** is a set of attribute *names*: values you **read and write**. A rotator's `degrees`, a dummy's `param_int`: state you query or set. -- **`operations`** is a dict mapping *names* to callables — *actions* you **invoke**. A +- **`operations`** is a dict mapping *names* to callables: *actions* you **invoke**. A rotator's `move_to`, a time tagger's `count_singles`: things the instrument *does*. -This split — values you get/set versus actions you call — is what makes an instrument +This split, values you get/set versus actions you call, is what makes an instrument addressable from a distance. When code drives a remote instrument, the proxy uses these two groups to decide whether an attribute access should read or write a value or call a method (see *Using a ProxyInstrument* below, and the wire format on @@ -100,25 +100,25 @@ and it's worth keeping them cleanly separated when you write a Driver. The rule: **constructing the Instrument object should do nothing to the hardware; the device should only be touched once `start()` is called.** -Building the object should only record where the device lives — its `name`, `desc`, and -`hw_address`, plus any driver-specific settings — and set sensible defaults. It should +Building the object should only record where the device lives (its `name`, `desc`, and +`hw_address`, plus any driver-specific settings) and set sensible defaults. It should not open a port, contact a controller, or move anything. Save all of that for `start()`: opening the serial or USB connection, handshaking with the controller, homing a motor to a known reference angle. The reason is in how Instruments come to life. An Instrument Provider builds each one -straight from its config — `class_(name=..., desc=..., hw_address=..., **settings)`, -purely from the strings in a TOML file — and only *then* calls `start()` +straight from its config (`class_(name=..., desc=..., hw_address=..., **settings)`, +purely from the strings in a TOML file) and only *then* calls `start()` ([`instrument_provider.py:113`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/network/instrument_provider.py#L113)). At construction time it has no idea whether the device is plugged in, powered, or even reachable. If your constructor tried to open the port, a missing or busy device would -make the object impossible to *create* — and you would lose the clean line between an +make the object impossible to *create*, and you would lose the clean line between an instrument that is *configured* and one that is *connected*. Deferring every side effect to `start()` keeps construction cheap and predictable, so an instrument can be created freely and brought online deliberately. `close()` is the other half of that bargain. Most of these devices hold an *exclusive* -resource — a serial port, a USB handle — that nothing else can open while it's in use. +resource (a serial port, a USB handle) that nothing else can open while it's in use. If a process walks away without calling `close()`, the port can stay locked until the hardware is power-cycled, and the device may be left in an undefined state. So a Driver does its real connect-and-initialise in `start()` and is responsible for releasing @@ -128,7 +128,7 @@ everything cleanly in `close()`. An Instrument should be able to report its complete current state on demand, and that's what `info` is for. It hands back a single read-only snapshot that captures everything -worth knowing about the instrument — its identity and every piece of live state — so a +worth knowing about the instrument, its identity and every piece of live state, so a caller can inspect it without having to read parameters one by one: ```python @@ -138,7 +138,7 @@ caller can inspect it without having to read parameters one by one: *Source: [`instrument.py:50-51`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/instrument.py#L50-L51)* That snapshot is an `InstrumentInfo`. The base class only holds the fields every -instrument has — its identity, plus a free-form `hw_status` for anything else worth +instrument has: its identity, plus a free-form `hw_status` for anything else worth reporting: ```python @@ -166,7 +166,7 @@ a more specific Instrument for each broad category of hardware: - **`RotatorInstrument`** - **`PolarimeterInstrument`** -Each of these builds on `Instrument` — it still *is* an Instrument — and pins down a +Each of these builds on `Instrument` (it still *is* an Instrument) and pins down a **standard interface** for its category: the exact parameters and operations every rotator, time tagger, or polarimeter is expected to expose. That shared interface is the whole point. Because every rotator presents the same `degrees`, `move_to`, and `move_by`, @@ -179,7 +179,7 @@ below. Writing a Driver means giving real behaviour to the lifecycle, parameters, operations, and `info` from the previous section, for one specific piece of hardware. The clearest -way to see how is to build one up from scratch — and the repository already ships the +way to see how is to build one up from scratch, and the repository already ships the perfect specimen for that: `DummyInstrument`, a fake instrument with no hardware behind it, used in tests and as a worked example. We'll assemble it piece by piece, then look at how a real Driver differs. @@ -199,7 +199,7 @@ class DummyInstrument(Instrument): ``` *Source: [`dummies.py:17-22`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L17-L22)* -The fields prefixed with `_` are the dummy's private state — the values a real Driver +The fields prefixed with `_` are the dummy's private state, the values a real Driver would read off the hardware. (`name`, `desc`, and `hw_address` come from `Instrument` itself, so they don't need repeating here.) @@ -225,7 +225,7 @@ inside `__post_init__`: caller is allowed to do remotely has to be registered in one of these two. Now the lifecycle. The dummy has nothing to connect to, so `start` and `close` only flip -a flag — but this is exactly where a real Driver would open its serial port and release +a flag, but this is exactly where a real Driver would open its serial port and release it again: ```python @@ -237,7 +237,7 @@ it again: ``` *Source: [`dummies.py:45-49`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L45-L49)* -A parameter is implemented as an ordinary property pair — a getter and a setter over the +A parameter is implemented as an ordinary property pair: a getter and a setter over the private field: ```python @@ -269,7 +269,7 @@ An operation is just a method, registered in `operations` above: *Source: [`dummies.py:81-84`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L81-L84)* Finally, `info`. As described earlier, a Driver returns its *own* info class carrying its -complete state — for the dummy that's `DummyInfo`, which extends `InstrumentInfo` with +complete state. For the dummy that's `DummyInfo`, which extends `InstrumentInfo` with the three parameter values: ```python @@ -359,7 +359,7 @@ class SerialRotator(RotatorInstrument): ``` *Source: [`rotator.py:95-132`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/rotator.py#L95-L132)* -It never defines `move_to`, `move_by`, or `__post_init__` — all three come from +It never defines `move_to`, `move_by`, or `__post_init__`; all three come from `RotatorInstrument`. Notice the payoff of the lifecycle rule from earlier: opening the serial port lives entirely in `start()`, so constructing a `SerialRotator` from config touches no hardware. @@ -380,7 +380,7 @@ The package ships these Drivers, grouped by the Instrument category each one imp | `TimeTaggerInstrument` | [`SwabianTimeTagger`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/timetagger.py#L19) | Swabian Instruments time tagger | | `RotatorInstrument` | [`APTRotator`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/rotator.py#L24), [`SerialRotator`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/rotator.py#L96), [`EllxRotator`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/rotator.py#L136) | Thorlabs APT mounts; a plain serial rotator; Thorlabs ELLx mounts | | `PolarimeterInstrument` | [`ArduinoPolarimeter`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/polarimeter.py#L95) | Arduino-based polarimeter | -| `Instrument` (base) | [`DummyInstrument`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L18) | none — test and example fixture | +| `Instrument` (base) | [`DummyInstrument`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/drivers/dummies.py#L18) | none; test and example fixture | The rotator row is the point made back in the orientation: one category, several Drivers. A `RotatorInstrument` is a rotator whatever protocol it speaks, and the rest of the @@ -388,4 +388,95 @@ system drives all three through the identical `degrees` / `move_to` / `move_by` ## Using a ProxyInstrument -> _Placeholder — drafted in sub-phase 3.3 (grill pending)._ +Everything so far has involved holding a Driver directly. In a running Node you rarely +do that. The real instruments are created and owned by an Instrument Provider, often on a +different machine, and your code reaches them over the Node's internal network through +the Router (how that fabric works is the subject of {doc}`network`). + +What your code holds instead is a **ProxyInstrument**: a local stand-in that forwards +every call across the network to the real instrument. It satisfies the same Instrument +contract as any Driver, so your code behaves exactly as it would a local instrument, +with no idea the hardware is somewhere else. + +```{mermaid} +flowchart LR + A[Your code] --> B[ProxyInstrument] + B -. across the Node .-> C[Router] + C --> D[Instrument Provider] + D --> E[Instrument + hardware] +``` + +The `Router` and `InstrumentProvider` hops in the middle are exactly what {doc}`network` +covers; here we only care about the two ends: your code, and the instrument it drives. + +### Getting a ProxyInstrument + +You obtain a proxy through a `Client`. Connect one, ask a provider what it hosts, then +request a device by name: + +```python +from pqn_hardware.network.client import Client + +client = Client() # connect to the Router +devices = client.get_available_devices("provider1") # what does provider1 host? +instrument = client.get_device("provider1", "dummy1") # a ProxyInstrument for "dummy1" +``` +*Source: [`tests/messaging/client.py:10-20`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/tests/messaging/client.py#L10-L20)* + +`get_available_devices` returns a dict that maps each instrument the provider hosts to +its Driver class. Run against a provider hosting a single dummy, it gives: + +```text +{'dummy1': } +``` + +`get_device` then hands back a `ProxyInstrument` for one of those names. Ask for a name +the provider doesn't host and it raises a `PacketError` carrying the provider's reply +(`Instrument 'does_not_exist' not found.`), so it's worth checking `get_available_devices` +first when a name might be wrong. + +### Using it + +From here you use `instrument` exactly as if it were local. Call its operations, with or +without arguments: + +```python +instrument.double_int() # operation, no arguments +instrument.set_half_input_int(10) # positional argument +instrument.set_half_input_int(value=36) # keyword argument +``` +*Source: [`tests/messaging/client.py:25-39`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/tests/messaging/client.py#L25-L39)* + +Read and write its parameters as ordinary attributes: + +```python +current = instrument.param_int # read a parameter +instrument.param_int = 42 # write a parameter +``` +*Source: [`tests/messaging/client.py:42-49`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/tests/messaging/client.py#L42-L49)* + +And ask for its state snapshot through `instrument.info`, just like a local Instrument. +Each of these quietly becomes a round-trip to the provider and back, but your code never +has to deal with that. + +### What you can't do + +A proxy only exposes what the instrument actually registered as a parameter or an +operation. Assigning anything else is rejected, rather than silently creating a local +attribute that would never reach the hardware: + +```python +instrument.new_attr = 348 # raises AttributeError +``` +*Source: [`tests/messaging/client.py:53-56`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/tests/messaging/client.py#L53-L56)* + +### Closing + +When you're finished, close the proxy. For a `ProxyInstrument`, `close()` tears down the +underlying network connection rather than touching any hardware; the real instrument +keeps running under its Instrument Provider: + +```python +instrument.close() +``` +*Source: [`client.py:266-267`](https://github.com/PublicQuantumNetwork/pqn-hardware/blob/master/src/pqn_hardware/network/client.py#L266-L267)*