Architecture Overview
Design Philosophy
FSharp.Azure.Quantum is a quantum-first optimization library with an opt-in classical path:
- Quantum solvers (Primary) - QAOA/VQE algorithms for optimization problems via quantum backends (LocalBackend or Azure Quantum)
- Classical solvers (Opt-in) - CPU heuristics used only when you go through
HybridSolverand the problem is below the advisor’s thresholds (50 variables by default) - Hybrid orchestration - HybridSolver routes based on problem size, whether you supplied a backend, and an optional cost budget
Philosophy: Quantum algorithms are the primary approach. The quantum solvers never fall back to classical silently; classical solving happens only where you ask for it.
Four-Layer Quantum-First Architecture
LAYER 1: User-Facing API
├─ Business Builders (SocialNetworkAnalyzer, ConstraintScheduler, CoverageOptimizer, etc.) → Domain-specific computation expressions
├─ High-Level Builders (GraphColoring, MaxCut, Knapsack, TSP, Portfolio, NetworkFlow, TaskScheduling) → Quantum computation expressions / createProblem helpers
└─ HybridSolver API (Optional) → Quantum/classical routing for TSP, Portfolio, MaxCut, Knapsack, Graph Coloring
LAYER 2: Problem Solvers
├─ Quantum Solvers (Primary, Solvers/Quantum)
│ ├─ QAOA-based (GraphColoring, MaxCut, Knapsack, TSP, Portfolio, NetworkFlow, VertexCover, Clique, SetCover, SAT, Matching, BinPacking, BinaryILP)
│ ├─ VQE (Quantum Chemistry)
│ ├─ QFT-based (Arithmetic, Shor's Factorization, Phase Estimation)
│ └─ Building blocks (Grover, Amplitude Amplification, QFT, QPE, HHL in Algorithms/)
│
└─ Classical Solvers (only via HybridSolver)
├─ TspSolver (Nearest Neighbor, 2-opt)
├─ PortfolioSolver (Greedy Ratio)
└─ Classical MaxCut / Knapsack / Graph Coloring heuristics inside the quantum solver modules
LAYER 3: Problem Decomposition
└─ ProblemDecomposition (Core/ProblemDecomposition.fs)
├─ Used by the VertexCover, Clique, SetCover, SAT, Matching, BinPacking and BinaryILP solvers
├─ Splits a problem that exceeds the backend's MaxQubits into sub-problems (e.g. connected components)
└─ Recombines the sub-solutions
LAYER 4: Execution Backends
├─ LocalBackend (CPU state-vector simulation, memory-derived width ≤30, default)
├─ Cloud Backends (CloudBackends.CloudBackendFactory)
│ ├─ RigettiCloudBackend (superconducting qubits)
│ ├─ IonQCloudBackend (trapped ions)
│ ├─ QuantinuumCloudBackend (trapped ions)
│ ├─ AtomComputingCloudBackend (neutral atoms)
│ └─ IqmCloudBackend (superconducting qubits)
├─ D-Wave annealers (DWaveBackend mock, RealDWaveBackend)
├─ Topological backend (separate FSharp.Azure.Quantum.Topological package)
├─ AWS Braket backend (separate FSharp.Azure.Quantum.Braket package)
└─ All backends implement IQuantumBackend (sync + Task-based async)
Key Architectural Principle: High-level builders go directly to quantum solvers. Classical solvers are only reached through HybridSolver.
Architecture Flow:
- High-level builders: Builder → Quantum solver → QaoaExecutionHelpers / QaoaCircuit → Backend
- Business builders: Business Builder → Quantum solver → ProblemDecomposition → QAOA → Backend
Key Concepts
Quantum-First Approach
Business Builders (SocialNetworkAnalyzer, ConstraintScheduler, CoverageOptimizer, ResourcePairing, PackingOptimizer):
- Domain-specific computation expression builders in the
Business/folder - Encode real-world business problems into the quantum optimization pipeline
- Internally delegate to the quantum solvers (clique, vertex cover, matching, set cover, SAT, bin packing)
- Flow: Business Builders → Solvers → ProblemDecomposition → Backend
High-Level Builders (GraphColoring, MaxCut, TSP, Portfolio, etc.):
- Use quantum algorithms directly (QAOA)
- Take an optional backend:
Nonemeans a new LocalBackend (simulation) - Recommended whenever the problem fits the backend
- Example:
GraphColoring.solveAsync problem 4 None cancellationToken→ LocalBackend (default)
Classical Solvers (TspSolver, PortfolioSolver, and the classical paths of the MaxCut/Knapsack/Graph Coloring solvers):
- Use CPU heuristics (Nearest Neighbor, Greedy, 2-opt)
- Only reached via HybridSolver - their solve functions are internal
- Used for problems below the advisor’s thresholds, or when forced with
Some HybridSolver.Classical - Example:
HybridSolver.solveTspAsync distances None None None cancellationToken→ Routes automatically
Why Both?
Direct Quantum (Recommended for quantum work):
- Consistent API across problem types
- Leverages quantum algorithms (QAOA/VQE/QFT)
- LocalBackend provides free simulation (width derived from available memory)
HybridSolver (Optional):
- Solves small problems with classical heuristics
- Saves quantum circuit overhead for problems too small to benefit
- Transparent routing with reasoning provided
- Use when problem sizes vary and small cases should stay classical
Builder Routing Architecture
High-Level Builders (GraphColoring, MaxCut, TSP, Portfolio, …) provide a business-friendly quantum API that encodes problems as QUBO/Ising models and solves them using QAOA. Business Builders (SocialNetworkAnalyzer, ConstraintScheduler, etc.) provide domain-specific APIs that delegate to the quantum solvers.
Direct Quantum Routing (Default):
User → GraphColoring.solveAsync problem numColors backend cancellationToken
↓
Encode as QUBO
↓
Build QAOA Circuit
↓
Execute on Backend (LocalBackend default)
↓
Decode Bitstring → Color Assignments
↓
Return Task<QuantumResult<ColoringSolution>>
Problems that need more qubits than the backend offers return an Error naming the qubit count; only the solvers behind the business builders decompose automatically.
HybridSolver Routing (Optional):
User → HybridSolver.solveGraphColoringAsync problem numColors budget timeout forceMethod cancellationToken
↓
forceMethod set? → run that method
↓
QuantumAdvisor: size < 50 (default thresholds)? → Classical
size ≥ 50 and a backend given? → Quantum
(estimated cost over budget → Classical)
↓
Execute chosen method
↓
Return Task<QuantumResult<HybridSolver.Solution<_>>> with Method + Reasoning
The plain solveXAsync functions pass no backend, so they only use quantum when forced; use the solveXWithBackendAsync variants to let the advisor route to quantum.
Benefits:
- ✅ Direct Builders: Simple quantum API, consistent across problem types
- ✅ HybridSolver: Classical answers for small problems, transparent reasoning
- ✅ Type-safe:
QuantumResult<'T>(=Result<'T, QuantumError>) for error handling - ✅ Backend abstraction: LocalBackend (simulation) or cloud backends (IonQ, Rigetti, Quantinuum, Atom Computing, IQM)
Example:
open System.Threading
open FSharp.Azure.Quantum
open FSharp.Azure.Quantum.GraphColoring
open FSharp.Azure.Quantum.Quantum
// Direct Quantum Approach (Recommended) - consistent API
let problem = graphColoring {
node "R1" ["R2"; "R3"]
node "R2" ["R1"; "R4"]
node "R3" ["R1"]
node "R4" ["R2"]
colors ["Red"; "Green"; "Blue"]
}
task {
match! GraphColoring.solveAsync problem 3 None CancellationToken.None with // None = LocalBackend
| Ok solution -> printfn "Colors Used: %d" solution.ColorsUsed
| Error err -> printfn "Error: %s" err.Message
}
// HybridSolver (Optional) takes the solver-level problem type
let hybridProblem: QuantumGraphColoringSolver.GraphColoringProblem =
{ Vertices = ["R1"; "R2"; "R3"; "R4"]
Edges =
[ GraphOptimization.edge "R1" "R2" 1.0
GraphOptimization.edge "R1" "R3" 1.0
GraphOptimization.edge "R2" "R4" 1.0 ]
NumColors = 3
FixedColors = Map.empty }
task {
match! HybridSolver.solveGraphColoringAsync hybridProblem 3 None None None CancellationToken.None with
| Ok solution ->
printfn "Method: %A" solution.Method // Shows Classical or Quantum
printfn "Reasoning: %s" solution.Reasoning
printfn "Colors Used: %d" solution.Result.ColorsUsed
| Error err -> printfn "Error: %s" err.Message
}
Folder Structure
src/FSharp.Azure.Quantum/
├── Core/ - Foundation (errors, auth, IQuantumBackend, QAOA circuit/optimizer, cost estimation)
│ └── ProblemDecomposition.fs - Backend-aware problem decomposition and partitioning
├── LocalSimulator/ - CPU-based state-vector simulation (StateVector, Gates, Measurement)
├── Backends/ - LocalBackend, cloud backends (Rigetti, IonQ, Quantinuum, AtomComputing, IQM), D-Wave
│ └── CloudBackends.fs - Cloud backends + CloudBackendFactory
├── Data/ - Molecule library, periodic table, chemistry and financial data providers
├── MachineLearning/ - Quantum ML (VQC, QuantumKernel, QuantumKernelSVM, HHL regression)
├── Solvers/
│ ├── Classical/ - High-level builders (GraphColoring, MaxCut, Knapsack, TSP, Portfolio, NetworkFlow)
│ │ plus the classical TspSolver/PortfolioSolver used by HybridSolver
│ ├── Quantum/ - Quantum solvers (QAOA, VQE, QFT-based) and the advanced builders
│ └── Hybrid/ - Optional routing logic (ProblemAnalysis, QuantumAdvisor, HybridSolver)
├── TaskScheduling/ - scheduledTask / resource / scheduling builders and solvers
├── Algorithms/ - Grover, Amplitude Amplification, QFT, QPE, Shor, HHL, Trotter-Suzuki, QRNG, ...
├── Builders/ - Circuit-level tooling (CircuitBuilder, transpiler, qubit routing, OpenQASM, QIR, noise models)
├── Business/ - Domain-specific computation expression builders
│ ├── SocialNetworkAnalyzer.fs - Community detection and influence optimization
│ ├── ConstraintScheduler.fs - Constraint-based scheduling optimization
│ ├── CoverageOptimizer.fs - Set cover and facility location problems
│ ├── ResourcePairing.fs - Bipartite matching and resource assignment
│ └── PackingOptimizer.fs - Bin packing and knapsack variants
├── ErrorMitigation/ - ZNE, PEC, REM (reduce quantum noise)
├── Visualization/ - ASCII and Mermaid renderers for circuits and solutions
└── Utilities/ - Performance benchmarking
The topological and AWS Braket backends live in separate projects, src/FSharp.Azure.Quantum.Topological and src/FSharp.Azure.Quantum.Braket.
Common Questions
Q: Should I use the high-level builders or HybridSolver?
A: Use high-level builders directly (GraphColoring.solveAsync, MaxCut.solveAsync, etc.) when you want quantum execution. They provide:
- Consistent quantum API across problem types
- LocalBackend simulation (free, memory-derived width)
Use HybridSolver if small problems should be solved classically (below 50 variables with the default thresholds) and quantum used only for large ones with a backend you supply.
Q: Can I access classical solvers directly?
A: Not their solve functions: those in TspSolver and PortfolioSolver are internal and reached via HybridSolver (pass Some HybridSolver.Classical to force the classical path). Helpers such as TspSolver.buildDistanceMatrix and TspSolver.calculateTourLength are public.
Q: What’s the difference between Algorithm, Solver, Builder, and Backend?
- Algorithm - Mathematical approach (QAOA, Grover, QFT, Shor)
- Solver - Problem-specific implementation (uses algorithms internally)
- ProblemDecomposition - Backend-aware partitioning layer that splits large problems into sub-problems matching backend constraints
- Builder - User-facing API with computation expressions (e.g.,
graphColoring { ... }) - Business Builder - Domain-specific builder for real-world problems (e.g.,
SocialNetworkAnalyzer,ConstraintScheduler) - Backend - Execution environment (LocalBackend, IonQCloudBackend, RigettiCloudBackend, …)
Design Patterns
Computation Expression Pattern: Fluent, type-safe problem construction
let coloringProblem = graphColoring {
node "R1" ["R2"; "R3"]
node "R2" ["R1"; "R4"]
node "R3" ["R1"]
node "R4" ["R2"]
colors ["Red"; "Blue"; "Green"]
}
Backend Abstraction: Unified interface for all quantum execution environments
open System.Threading
open FSharp.Azure.Quantum.Core
open FSharp.Azure.Quantum.Core.BackendAbstraction
open FSharp.Azure.Quantum.Backends
// A Bell-state circuit, wrapped as an ICircuit
let circuit =
CircuitBuilder.empty 2
|> CircuitBuilder.addGate (CircuitBuilder.Gate.H 0)
|> CircuitBuilder.addGate (CircuitBuilder.Gate.CNOT(0, 1))
|> CircuitAbstraction.wrapCircuit
// Synchronous execution
let run (backend: IQuantumBackend option) =
let actualBackend = backend |> Option.defaultValue (LocalBackendFactory.createUnified())
actualBackend.ExecuteToState circuit
// Async execution (Task-based, with CancellationToken)
let runAsync (backend: IQuantumBackend option) (ct: CancellationToken) =
task {
let actualBackend = backend |> Option.defaultValue (LocalBackendFactory.createUnified())
return! actualBackend.ExecuteToStateAsync circuit ct
}
Async API: All
IQuantumBackendimplementations provideExecuteToStateAsyncandApplyOperationAsyncmethods that returnTask<Result<QuantumState, QuantumError>>withCancellationTokensupport. The async variants suit cloud backends where I/O latency is significant. See API Reference for full signatures.
Result Type Pattern: Explicit error handling
task {
match! GraphColoring.solveAsync problem 3 None CancellationToken.None with
| Ok solution ->
// Process successful result
printfn "Colors used: %d" solution.ColorsUsed
| Error err ->
// Handle error gracefully
printfn "Error: %s" err.Message
}
Extending the Library
Add a High-Level Builder (Recommended):
- Add a quantum solver under
Solvers/Quantum/that encodes the problem as a QUBO/Ising model and runs QAOA (seeQuantumMaxCutSolver.fs) - Add the domain builder (computation expression or
createProblemhelpers andsolveAsync) next to the existing ones, e.g.Solvers/Classical/MaxCutBuilder.fs - Use
ProblemDecompositionif the problem can be split when it exceeds the backend’s qubits - Add to C# interop (
Builders/BuildersCSharpExtensions.fs) if needed
Add a Quantum Algorithm:
- Create
Algorithms/NewAlgorithm.fs - Express the algorithm as intent → plan → execute (see the Intent-First Algorithms ADR)
- Accept an
IQuantumBackendparameter -
Follow the Algorithms/QFT.fspattern, including its whole-circuit fallback: whenUnifiedBackend.isIncrementalUnsupportedreports that the backend (cloud hardware) refusesApplyOperation, submit the complete gate list from0…0⟩ with UnifiedBackend.submitAsCircuit, or wrap state-independent code inWholeCircuit.run
Add a Backend:
- Create
Backends/NewBackend.fs - Implement
IQuantumBackendinterface (both sync and async members):ExecuteToState/ExecuteToStateAsync(withCancellationToken)ApplyOperation/ApplyOperationAsync(withCancellationToken)InitializeState,SupportsOperation,Name,NativeStateType- Optionally
IQubitLimitedBackend.MaxQubitsso solvers can check capacity up front
- Handle provider-specific circuit format (or use OpenQASM); transpile to the provider’s gates first (
CloudBackendHelpers.transpileForTarget) - Add authentication and job submission logic. A backend that runs complete circuits only returns
OperationError("ApplyOperation", "... incremental ...")fromApplyOperation, so the algorithms switch to whole-circuit submission; it implementsIShotSamplingBackend(its shots per circuit) andIJobCountingBackend(aJobBudgetreserved before every submission) - Async methods should use
task { }computation expression (notasync { }) - See
Backends/CloudBackends.fsfor a reference cloud implementation
References
- Getting Started
- Local Simulation
- Backend Switching
- Error Mitigation - ZNE, PEC, REM strategies for NISQ hardware
- API Reference