quprep 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. quprep-0.3.0/.github/workflows/release.yml +41 -0
  2. {quprep-0.2.0 → quprep-0.3.0}/CHANGELOG.md +54 -1
  3. {quprep-0.2.0 → quprep-0.3.0}/PKG-INFO +31 -1
  4. {quprep-0.2.0 → quprep-0.3.0}/README.md +30 -0
  5. {quprep-0.2.0 → quprep-0.3.0}/docs/api/index.md +9 -1
  6. quprep-0.3.0/docs/guides/qubo.md +257 -0
  7. {quprep-0.2.0 → quprep-0.3.0}/docs/index.md +5 -1
  8. {quprep-0.2.0 → quprep-0.3.0}/examples/05_recommend.py +1 -1
  9. {quprep-0.2.0 → quprep-0.3.0}/examples/06_visualization.py +1 -1
  10. quprep-0.3.0/examples/07_qubo.py +94 -0
  11. {quprep-0.2.0 → quprep-0.3.0}/examples/README.md +1 -0
  12. {quprep-0.2.0 → quprep-0.3.0}/pyproject.toml +1 -1
  13. {quprep-0.2.0 → quprep-0.3.0}/quprep/__init__.py +1 -1
  14. quprep-0.3.0/quprep/cli.py +427 -0
  15. quprep-0.3.0/quprep/qubo/__init__.py +42 -0
  16. quprep-0.3.0/quprep/qubo/constraints.py +157 -0
  17. quprep-0.3.0/quprep/qubo/converter.py +237 -0
  18. quprep-0.3.0/quprep/qubo/ising.py +143 -0
  19. quprep-0.3.0/quprep/qubo/problems/__init__.py +23 -0
  20. quprep-0.3.0/quprep/qubo/problems/graph_color.py +101 -0
  21. quprep-0.3.0/quprep/qubo/problems/knapsack.py +82 -0
  22. quprep-0.3.0/quprep/qubo/problems/maxcut.py +59 -0
  23. quprep-0.3.0/quprep/qubo/problems/number_partition.py +101 -0
  24. quprep-0.3.0/quprep/qubo/problems/portfolio.py +87 -0
  25. quprep-0.3.0/quprep/qubo/problems/scheduling.py +107 -0
  26. quprep-0.3.0/quprep/qubo/problems/tsp.py +109 -0
  27. quprep-0.3.0/quprep/qubo/qaoa.py +130 -0
  28. quprep-0.3.0/quprep/qubo/solver.py +179 -0
  29. quprep-0.3.0/quprep/qubo/utils.py +59 -0
  30. quprep-0.3.0/quprep/qubo/visualize.py +178 -0
  31. {quprep-0.2.0 → quprep-0.3.0}/tests/test_pipeline.py +1 -1
  32. quprep-0.3.0/tests/test_qubo.py +833 -0
  33. {quprep-0.2.0 → quprep-0.3.0}/uv.lock +1 -1
  34. quprep-0.2.0/docs/guides/qubo.md +0 -47
  35. quprep-0.2.0/quprep/cli.py +0 -169
  36. quprep-0.2.0/quprep/qubo/__init__.py +0 -1
  37. quprep-0.2.0/quprep/qubo/constraints.py +0 -30
  38. quprep-0.2.0/quprep/qubo/converter.py +0 -64
  39. quprep-0.2.0/quprep/qubo/ising.py +0 -41
  40. quprep-0.2.0/quprep/qubo/problems/__init__.py +0 -12
  41. {quprep-0.2.0 → quprep-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  42. {quprep-0.2.0 → quprep-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  43. {quprep-0.2.0 → quprep-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  44. {quprep-0.2.0 → quprep-0.3.0}/.github/workflows/ci.yml +0 -0
  45. {quprep-0.2.0 → quprep-0.3.0}/.gitignore +0 -0
  46. {quprep-0.2.0 → quprep-0.3.0}/.readthedocs.yaml +0 -0
  47. {quprep-0.2.0 → quprep-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  48. {quprep-0.2.0 → quprep-0.3.0}/CONTRIBUTING.md +0 -0
  49. {quprep-0.2.0 → quprep-0.3.0}/LICENSE +0 -0
  50. {quprep-0.2.0 → quprep-0.3.0}/docs/api/encoders.md +0 -0
  51. {quprep-0.2.0 → quprep-0.3.0}/docs/api/exporters.md +0 -0
  52. {quprep-0.2.0 → quprep-0.3.0}/docs/api/pipeline.md +0 -0
  53. {quprep-0.2.0 → quprep-0.3.0}/docs/contributing.md +0 -0
  54. {quprep-0.2.0 → quprep-0.3.0}/docs/getting-started/installation.md +0 -0
  55. {quprep-0.2.0 → quprep-0.3.0}/docs/getting-started/quickstart.md +0 -0
  56. {quprep-0.2.0 → quprep-0.3.0}/docs/guides/encodings.md +0 -0
  57. {quprep-0.2.0 → quprep-0.3.0}/docs/guides/export.md +0 -0
  58. {quprep-0.2.0 → quprep-0.3.0}/docs/guides/reduction.md +0 -0
  59. {quprep-0.2.0 → quprep-0.3.0}/examples/01_quickstart.ipynb +0 -0
  60. {quprep-0.2.0 → quprep-0.3.0}/examples/01_quickstart.py +0 -0
  61. {quprep-0.2.0 → quprep-0.3.0}/examples/02_pipeline.ipynb +0 -0
  62. {quprep-0.2.0 → quprep-0.3.0}/examples/02_pipeline.py +0 -0
  63. {quprep-0.2.0 → quprep-0.3.0}/examples/03_encoders.ipynb +0 -0
  64. {quprep-0.2.0 → quprep-0.3.0}/examples/03_encoders.py +0 -0
  65. {quprep-0.2.0 → quprep-0.3.0}/examples/04_export.py +0 -0
  66. {quprep-0.2.0 → quprep-0.3.0}/mkdocs.yml +0 -0
  67. {quprep-0.2.0 → quprep-0.3.0}/quprep/clean/__init__.py +0 -0
  68. {quprep-0.2.0 → quprep-0.3.0}/quprep/clean/categorical.py +0 -0
  69. {quprep-0.2.0 → quprep-0.3.0}/quprep/clean/imputer.py +0 -0
  70. {quprep-0.2.0 → quprep-0.3.0}/quprep/clean/outlier.py +0 -0
  71. {quprep-0.2.0 → quprep-0.3.0}/quprep/clean/selector.py +0 -0
  72. {quprep-0.2.0 → quprep-0.3.0}/quprep/core/__init__.py +0 -0
  73. {quprep-0.2.0 → quprep-0.3.0}/quprep/core/dataset.py +0 -0
  74. {quprep-0.2.0 → quprep-0.3.0}/quprep/core/pipeline.py +0 -0
  75. {quprep-0.2.0 → quprep-0.3.0}/quprep/core/recommender.py +0 -0
  76. {quprep-0.2.0 → quprep-0.3.0}/quprep/encode/__init__.py +0 -0
  77. {quprep-0.2.0 → quprep-0.3.0}/quprep/encode/amplitude.py +0 -0
  78. {quprep-0.2.0 → quprep-0.3.0}/quprep/encode/angle.py +0 -0
  79. {quprep-0.2.0 → quprep-0.3.0}/quprep/encode/base.py +0 -0
  80. {quprep-0.2.0 → quprep-0.3.0}/quprep/encode/basis.py +0 -0
  81. {quprep-0.2.0 → quprep-0.3.0}/quprep/encode/entangled_angle.py +0 -0
  82. {quprep-0.2.0 → quprep-0.3.0}/quprep/encode/hamiltonian.py +0 -0
  83. {quprep-0.2.0 → quprep-0.3.0}/quprep/encode/iqp.py +0 -0
  84. {quprep-0.2.0 → quprep-0.3.0}/quprep/encode/reupload.py +0 -0
  85. {quprep-0.2.0 → quprep-0.3.0}/quprep/export/__init__.py +0 -0
  86. {quprep-0.2.0 → quprep-0.3.0}/quprep/export/cirq_export.py +0 -0
  87. {quprep-0.2.0 → quprep-0.3.0}/quprep/export/pennylane_export.py +0 -0
  88. {quprep-0.2.0 → quprep-0.3.0}/quprep/export/qasm_export.py +0 -0
  89. {quprep-0.2.0 → quprep-0.3.0}/quprep/export/qiskit_export.py +0 -0
  90. {quprep-0.2.0 → quprep-0.3.0}/quprep/export/tket_export.py +0 -0
  91. {quprep-0.2.0 → quprep-0.3.0}/quprep/export/visualize.py +0 -0
  92. {quprep-0.2.0 → quprep-0.3.0}/quprep/ingest/__init__.py +0 -0
  93. {quprep-0.2.0 → quprep-0.3.0}/quprep/ingest/csv_ingester.py +0 -0
  94. {quprep-0.2.0 → quprep-0.3.0}/quprep/ingest/numpy_ingester.py +0 -0
  95. {quprep-0.2.0 → quprep-0.3.0}/quprep/ingest/profiler.py +0 -0
  96. {quprep-0.2.0 → quprep-0.3.0}/quprep/normalize/__init__.py +0 -0
  97. {quprep-0.2.0 → quprep-0.3.0}/quprep/normalize/scalers.py +0 -0
  98. {quprep-0.2.0 → quprep-0.3.0}/quprep/reduce/__init__.py +0 -0
  99. {quprep-0.2.0 → quprep-0.3.0}/quprep/reduce/hardware_aware.py +0 -0
  100. {quprep-0.2.0 → quprep-0.3.0}/quprep/reduce/lda.py +0 -0
  101. {quprep-0.2.0 → quprep-0.3.0}/quprep/reduce/pca.py +0 -0
  102. {quprep-0.2.0 → quprep-0.3.0}/quprep/reduce/spectral.py +0 -0
  103. {quprep-0.2.0 → quprep-0.3.0}/tests/__init__.py +0 -0
  104. {quprep-0.2.0 → quprep-0.3.0}/tests/test_clean.py +0 -0
  105. {quprep-0.2.0 → quprep-0.3.0}/tests/test_cli.py +0 -0
  106. {quprep-0.2.0 → quprep-0.3.0}/tests/test_encode.py +0 -0
  107. {quprep-0.2.0 → quprep-0.3.0}/tests/test_export.py +0 -0
  108. {quprep-0.2.0 → quprep-0.3.0}/tests/test_ingest.py +0 -0
  109. {quprep-0.2.0 → quprep-0.3.0}/tests/test_normalize.py +0 -0
  110. {quprep-0.2.0 → quprep-0.3.0}/tests/test_recommender.py +0 -0
  111. {quprep-0.2.0 → quprep-0.3.0}/tests/test_reduce.py +0 -0
  112. {quprep-0.2.0 → quprep-0.3.0}/tests/test_visualize.py +0 -0
@@ -0,0 +1,41 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ release:
10
+ name: Create GitHub Release
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: write
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Extract changelog for this version
19
+ id: changelog
20
+ run: |
21
+ VERSION="${GITHUB_REF_NAME#v}"
22
+ BODY=$(python3 - <<EOF
23
+ import re, sys
24
+ content = open("CHANGELOG.md").read()
25
+ version = "$VERSION"
26
+ pattern = rf"## \[{re.escape(version)}\][^\n]*\n(.*?)(?=\n## \[|\Z)"
27
+ m = re.search(pattern, content, re.DOTALL)
28
+ print(m.group(1).strip() if m else "")
29
+ EOF
30
+ )
31
+ echo "body<<ENDOFBODY" >> $GITHUB_OUTPUT
32
+ echo "$BODY" >> $GITHUB_OUTPUT
33
+ echo "ENDOFBODY" >> $GITHUB_OUTPUT
34
+
35
+ - name: Create GitHub Release
36
+ uses: softprops/action-gh-release@v2
37
+ with:
38
+ name: ${{ github.ref_name }}
39
+ body: ${{ steps.changelog.outputs.body }}
40
+ draft: false
41
+ prerelease: false
@@ -5,12 +5,61 @@ All notable changes to QuPrep will be documented in this file.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
  QuPrep uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+
8
9
  ---
9
10
 
10
11
  ## [Unreleased]
11
12
 
12
13
  ---
13
14
 
15
+ ## [0.3.0] — 2026-03-21
16
+
17
+ ### Added
18
+
19
+ **QUBO / Ising conversion** (`quprep.qubo`)
20
+ - `to_qubo(cost_matrix, constraints, penalty)` — converts any square cost matrix to upper-triangular QUBO form; supports equality and inequality constraints via Lagrangian penalty
21
+ - `QUBOResult` — holds Q matrix, offset, variable map, n_original; `.to_ising()`, `.evaluate(x)`, `.to_dwave()`, `.to_dict()` / `.from_dict()` methods
22
+ - `IsingResult` — holds h, J, offset; `.to_qubo()` round-trip conversion
23
+ - `qubo_to_ising(qubo)` — QUBO → Ising transformation (s = 2x − 1); energy-consistent for all binary inputs
24
+ - `ising_to_qubo(ising)` — Ising → QUBO inverse transformation; completes the bidirectional round-trip
25
+ - `equality_penalty(A, b, penalty)` — encodes Ax = b as a QUBO penalty matrix
26
+ - `inequality_penalty(A, b, penalty)` — encodes Ax ≤ b via binary slack variables; augments Q from (n,n) to (n+K,n+K)
27
+ - `add_qubo(q1, q2, weight)` — combines two same-size QUBOs; useful for multi-objective problems
28
+
29
+ **Problem library** (`quprep.qubo.problems`) — 7 NP-hard combinatorial problems
30
+ - `max_cut(adjacency)` — Max-Cut graph partitioning
31
+ - `knapsack(weights, values, capacity, penalty)` — 0/1 Knapsack
32
+ - `tsp(distance_matrix, penalty)` — Travelling Salesman Problem (n² binary variables)
33
+ - `portfolio(returns, covariance, budget, risk_penalty, budget_penalty)` — Markowitz portfolio optimization
34
+ - `graph_color(adjacency, n_colors, penalty)` — Graph Colouring (n×K binary variables)
35
+ - `scheduling(processing_times, n_machines, penalty)` — Job scheduling / load balancing
36
+ - `number_partition(values, penalty)` — Number Partitioning
37
+
38
+ **Solvers** (`quprep.qubo.solver`)
39
+ - `solve_brute(qubo, max_n=20)` — exact exhaustive solver; evaluates all 2^n states; practical up to n=20
40
+ - `solve_sa(qubo, n_steps, T_start, T_end, seed, restarts)` — simulated annealing heuristic; O(n) incremental energy update with geometric cooling; scales to n ~ 500+
41
+
42
+ **QAOA circuit generator** (`quprep.qubo.qaoa`)
43
+ - `qaoa_circuit(qubo, p, gamma, beta)` — generates a p-layer QAOA ansatz as OpenQASM 3.0; converts QUBO → Ising internally; compatible with Qiskit, Cirq, and any QASM backend
44
+
45
+ **Visualization** (`quprep.qubo.visualize`) — requires `pip install quprep[viz]`
46
+ - `draw_qubo(qubo, title, cmap, ax)` — heatmap of Q matrix with symmetric colour scale; annotates cells for n ≤ 10
47
+ - `draw_ising(ising, title, ax)` — circular graph layout; node colour = h_i bias; edge colour/width = J_ij coupling strength
48
+
49
+ **CLI** (`quprep qubo`)
50
+ - `quprep qubo maxcut --adjacency ... [--solve]`
51
+ - `quprep qubo knapsack --weights ... --values ... --capacity ... [--solve]`
52
+ - `quprep qubo tsp --distances ... [--solve]`
53
+ - `quprep qubo schedule --times ... --machines ... [--solve]`
54
+ - `quprep qubo partition --values ... [--solve]`
55
+ - `quprep qubo portfolio --returns ... --covariance ... --budget ... [--solve]`
56
+ - `quprep qubo graphcolor --adjacency ... --colors ... [--solve]`
57
+ - `quprep qubo qaoa <problem> ... [--p N] [--gamma ...] [--beta ...] [--output file]`
58
+ - `quprep qubo export <problem> ... [--format json|npy] [--output file]`
59
+ - `--solve` auto-switches from exact to simulated annealing for n > 20
60
+
61
+ ---
62
+
14
63
  ## [0.2.0] — 2026-03-21
15
64
 
16
65
  ### Added
@@ -49,6 +98,9 @@ QuPrep uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
49
98
  - `Pipeline` auto-normalizes IQP/re-upload → `minmax_pm_pi`, Hamiltonian → `zscore`
50
99
  - `prepare()` accepts `encoding='iqp'`, `'reupload'`, `'hamiltonian'` with matching kwargs
51
100
 
101
+ ### Fixed
102
+ - `HamiltonianEncoder` via `prepare()` and `Pipeline` was broken — `_encoding_key()` returned `"zscore"` (a strategy name) instead of `"hamiltonian"`, causing `auto_normalizer()` to raise `ValueError`
103
+
52
104
  ---
53
105
 
54
106
  ## [0.1.0] — 2026-03-19
@@ -100,6 +152,7 @@ First public release. Covers the full ingest → clean → normalize → encode
100
152
 
101
153
  ---
102
154
 
103
- [Unreleased]: https://github.com/quprep/quprep/compare/v0.2.0...HEAD
155
+ [Unreleased]: https://github.com/quprep/quprep/compare/v0.3.0...HEAD
156
+ [0.3.0]: https://github.com/quprep/quprep/compare/v0.2.0...v0.3.0
104
157
  [0.2.0]: https://github.com/quprep/quprep/compare/v0.1.0...v0.2.0
105
158
  [0.1.0]: https://github.com/quprep/quprep/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quprep
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Quantum data preparation — the missing preprocessing layer between classical datasets and quantum computing frameworks
5
5
  Project-URL: Homepage, https://quprep.org
6
6
  Project-URL: Documentation, https://quprep.readthedocs.io
@@ -282,6 +282,9 @@ CSV / DataFrame / NumPy → QuPrep → circuit-ready output for your framewo
282
282
  - Recommend the best encoding for your dataset and task
283
283
  - Export circuits to OpenQASM 3.0, Qiskit, PennyLane, Cirq, and TKET
284
284
  - Visualize circuits as ASCII diagrams or matplotlib figures
285
+ - Formulate combinatorial optimization problems as QUBO / Ising models (Max-Cut, TSP, Knapsack, Portfolio, Graph Colouring, Scheduling, Number Partitioning)
286
+ - Solve with exact brute-force (n ≤ 20) or simulated annealing (any n)
287
+ - Generate QAOA circuits and export to D-Wave Ocean SDK format
285
288
 
286
289
  ## What QuPrep does NOT do
287
290
 
@@ -356,12 +359,38 @@ print(quprep.draw_ascii(result.encoded[0]))
356
359
  quprep.draw_matplotlib(result.encoded[0], filename="circuit.png")
357
360
  ```
358
361
 
362
+ ### QUBO / combinatorial optimization
363
+
364
+ ```python
365
+ from quprep.qubo import max_cut, knapsack, solve_brute, solve_sa, qaoa_circuit
366
+ import numpy as np
367
+
368
+ # Max-Cut on a weighted graph
369
+ adj = np.array([[0,1,1],[1,0,1],[1,1,0]], dtype=float)
370
+ q = max_cut(adj)
371
+ print(q.evaluate(np.array([0., 1., 1.]))) # -2.0
372
+
373
+ # Brute-force (n ≤ 20) or simulated annealing (any n)
374
+ sol = solve_brute(q) # exact
375
+ sol = solve_sa(q, seed=42) # heuristic, scales to n ~ 500+
376
+
377
+ # Generate a QAOA circuit
378
+ qasm = qaoa_circuit(q, p=2)
379
+
380
+ # D-Wave Ocean SDK export
381
+ bqm_dict = q.to_dwave() # {(i, j): coeff}
382
+ ```
383
+
359
384
  ### CLI
360
385
 
361
386
  ```bash
362
387
  quprep convert data.csv --encoding angle --framework qasm
363
388
  quprep convert data.csv --encoding iqp --framework pennylane
364
389
  quprep recommend data.csv --task classification --qubits 8
390
+
391
+ quprep qubo maxcut --adjacency "0,1,1;1,0,1;1,1,0" --solve
392
+ quprep qubo knapsack --weights "2,3,4" --values "3,4,5" --capacity 5
393
+ quprep qubo qaoa maxcut --adjacency "0,1,1;1,0,1;1,1,0" --p 2 --output circuit.qasm
365
394
  ```
366
395
 
367
396
  ---
@@ -413,6 +442,7 @@ See the [`examples/`](examples/) directory:
413
442
  | 04 | Framework export — QASM, Qiskit, PennyLane, Cirq, TKET |
414
443
  | 05 | Encoding recommendation |
415
444
  | 06 | Circuit visualization |
445
+ | 07 | QUBO / Ising — Max-Cut, Knapsack, solvers, D-Wave export, QAOA |
416
446
 
417
447
  ---
418
448
 
@@ -28,6 +28,9 @@ CSV / DataFrame / NumPy → QuPrep → circuit-ready output for your framewo
28
28
  - Recommend the best encoding for your dataset and task
29
29
  - Export circuits to OpenQASM 3.0, Qiskit, PennyLane, Cirq, and TKET
30
30
  - Visualize circuits as ASCII diagrams or matplotlib figures
31
+ - Formulate combinatorial optimization problems as QUBO / Ising models (Max-Cut, TSP, Knapsack, Portfolio, Graph Colouring, Scheduling, Number Partitioning)
32
+ - Solve with exact brute-force (n ≤ 20) or simulated annealing (any n)
33
+ - Generate QAOA circuits and export to D-Wave Ocean SDK format
31
34
 
32
35
  ## What QuPrep does NOT do
33
36
 
@@ -102,12 +105,38 @@ print(quprep.draw_ascii(result.encoded[0]))
102
105
  quprep.draw_matplotlib(result.encoded[0], filename="circuit.png")
103
106
  ```
104
107
 
108
+ ### QUBO / combinatorial optimization
109
+
110
+ ```python
111
+ from quprep.qubo import max_cut, knapsack, solve_brute, solve_sa, qaoa_circuit
112
+ import numpy as np
113
+
114
+ # Max-Cut on a weighted graph
115
+ adj = np.array([[0,1,1],[1,0,1],[1,1,0]], dtype=float)
116
+ q = max_cut(adj)
117
+ print(q.evaluate(np.array([0., 1., 1.]))) # -2.0
118
+
119
+ # Brute-force (n ≤ 20) or simulated annealing (any n)
120
+ sol = solve_brute(q) # exact
121
+ sol = solve_sa(q, seed=42) # heuristic, scales to n ~ 500+
122
+
123
+ # Generate a QAOA circuit
124
+ qasm = qaoa_circuit(q, p=2)
125
+
126
+ # D-Wave Ocean SDK export
127
+ bqm_dict = q.to_dwave() # {(i, j): coeff}
128
+ ```
129
+
105
130
  ### CLI
106
131
 
107
132
  ```bash
108
133
  quprep convert data.csv --encoding angle --framework qasm
109
134
  quprep convert data.csv --encoding iqp --framework pennylane
110
135
  quprep recommend data.csv --task classification --qubits 8
136
+
137
+ quprep qubo maxcut --adjacency "0,1,1;1,0,1;1,1,0" --solve
138
+ quprep qubo knapsack --weights "2,3,4" --values "3,4,5" --capacity 5
139
+ quprep qubo qaoa maxcut --adjacency "0,1,1;1,0,1;1,1,0" --p 2 --output circuit.qasm
111
140
  ```
112
141
 
113
142
  ---
@@ -159,6 +188,7 @@ See the [`examples/`](examples/) directory:
159
188
  | 04 | Framework export — QASM, Qiskit, PennyLane, Cirq, TKET |
160
189
  | 05 | Encoding recommendation |
161
190
  | 06 | Circuit visualization |
191
+ | 07 | QUBO / Ising — Max-Cut, Knapsack, solvers, D-Wave export, QAOA |
162
192
 
163
193
  ---
164
194
 
@@ -17,7 +17,7 @@ Everything else is accessed via submodules.
17
17
  ```python
18
18
  import quprep
19
19
 
20
- quprep.__version__ # "0.2.0"
20
+ quprep.__version__ # "0.3.0"
21
21
  quprep.prepare(...)
22
22
  quprep.Pipeline(...)
23
23
  quprep.recommend(...)
@@ -54,6 +54,14 @@ quprep.draw_matplotlib(...)
54
54
  | `quprep.ingest.numpy_ingester` | `NumpyIngester` |
55
55
  | `quprep.ingest.profiler` | `profile`, `DatasetProfile` |
56
56
  | `quprep.core.dataset` | `Dataset` |
57
+ | `quprep.qubo` | `to_qubo`, `QUBOResult`, `qubo_to_ising`, `ising_to_qubo`, `IsingResult` |
58
+ | `quprep.qubo.problems` | `max_cut`, `knapsack`, `tsp`, `portfolio`, `graph_color`, `scheduling`, `number_partition` |
59
+ | `quprep.qubo.solver` | `solve_brute`, `solve_sa`, `SolveResult` |
60
+ | `quprep.qubo.qaoa` | `qaoa_circuit` |
61
+ | `quprep.qubo.constraints` | `equality_penalty`, `inequality_penalty` |
62
+ | `quprep.qubo.ising` | `qubo_to_ising`, `ising_to_qubo` |
63
+ | `quprep.qubo.utils` | `add_qubo` |
64
+ | `quprep.qubo.visualize` | `draw_qubo`, `draw_ising` |
57
65
 
58
66
  ---
59
67
 
@@ -0,0 +1,257 @@
1
+ # QUBO / Ising
2
+
3
+ QUBO (Quadratic Unconstrained Binary Optimization) is the standard input format for quantum annealers (D-Wave) and QAOA circuits. QuPrep handles the full pipeline: problem formulation → QUBO matrix → Ising model → QAOA circuit or annealer export.
4
+
5
+ ---
6
+
7
+ ## Quick example
8
+
9
+ ```python
10
+ import numpy as np
11
+ from quprep.qubo import max_cut, solve_brute, qaoa_circuit
12
+
13
+ # Max-Cut on a triangle graph
14
+ adj = np.array([[0,1,1],[1,0,1],[1,1,0]], dtype=float)
15
+ q = max_cut(adj)
16
+
17
+ print(q) # QUBOResult(n_variables=3, offset=0.0)
18
+ print(q.Q) # 3×3 upper-triangular Q matrix
19
+
20
+ # Brute-force solve (exact, n ≤ 20)
21
+ sol = solve_brute(q)
22
+ print(sol.energy) # -2.0
23
+
24
+ # Generate a QAOA circuit
25
+ qasm = qaoa_circuit(q, p=2)
26
+ print(qasm[:60]) # OPENQASM 3.0; ...
27
+ ```
28
+
29
+ ---
30
+
31
+ ## QUBOResult
32
+
33
+ All problem functions return a `QUBOResult`:
34
+
35
+ ```python
36
+ from quprep.qubo import max_cut
37
+ import numpy as np
38
+
39
+ q = max_cut(np.array([[0,1],[1,0]], dtype=float))
40
+
41
+ q.Q # np.ndarray (n, n) — upper-triangular Q matrix
42
+ q.offset # float — constant energy offset
43
+ q.variable_map # dict — name → index mapping
44
+ q.n_original # int — original variable count (before slack)
45
+
46
+ # Evaluate a solution
47
+ q.evaluate(np.array([0.0, 1.0])) # x^T Q x + offset
48
+
49
+ # Convert to Ising
50
+ ising = q.to_ising()
51
+ ising.h # bias vector
52
+ ising.J # coupling matrix (upper-triangular)
53
+
54
+ # Serialize
55
+ d = q.to_dict() # JSON-compatible dict
56
+ q2 = QUBOResult.from_dict(d)
57
+
58
+ # D-Wave Ocean SDK export
59
+ bqm_dict = q.to_dwave() # {(i, j): coeff} i <= j
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Problem library
65
+
66
+ Seven NP-hard combinatorial problems, ready to formulate as QUBO:
67
+
68
+ ```python
69
+ import numpy as np
70
+ from quprep.qubo import (
71
+ max_cut, knapsack, tsp, portfolio,
72
+ graph_color, scheduling, number_partition,
73
+ )
74
+
75
+ # Max-Cut
76
+ adj = np.array([[0,1,1],[1,0,1],[1,1,0]], dtype=float)
77
+ q = max_cut(adj)
78
+
79
+ # 0/1 Knapsack
80
+ q = knapsack(
81
+ weights=np.array([2.0, 3.0, 4.0]),
82
+ values=np.array([3.0, 4.0, 5.0]),
83
+ capacity=5.0,
84
+ )
85
+
86
+ # Travelling Salesman Problem (n² variables)
87
+ D = np.array([[0,1,2],[1,0,1],[2,1,0]], dtype=float)
88
+ q = tsp(D)
89
+
90
+ # Markowitz Portfolio Optimization
91
+ mu = np.array([0.1, 0.2, 0.15, 0.05])
92
+ Sigma = np.eye(4) * 0.01
93
+ q = portfolio(mu, Sigma, budget=2)
94
+
95
+ # Graph Colouring
96
+ q = graph_color(adj, n_colors=3)
97
+
98
+ # Job Scheduling (load balancing)
99
+ q = scheduling(processing_times=np.array([3.0,1.0,4.0,2.0]), n_machines=2)
100
+
101
+ # Number Partitioning
102
+ q = number_partition(values=np.array([3.0,1.0,1.0,2.0,2.0,1.0]))
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Solvers
108
+
109
+ ```python
110
+ from quprep.qubo import solve_brute, solve_sa, max_cut
111
+ import numpy as np
112
+
113
+ adj = np.array([[0,1,1],[1,0,1],[1,1,0]], dtype=float)
114
+ q = max_cut(adj)
115
+
116
+ # Exact — exhaustive 2^n search (n ≤ 20)
117
+ sol = solve_brute(q)
118
+ print(sol.x) # optimal binary vector
119
+ print(sol.energy) # optimal energy
120
+
121
+ # Simulated annealing — scales to n ~ 500+
122
+ sol = solve_sa(q, n_steps=10_000, restarts=3, seed=42)
123
+ print(sol.energy)
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Constraints
129
+
130
+ ```python
131
+ from quprep.qubo import to_qubo
132
+ from quprep.qubo.constraints import equality_penalty, inequality_penalty
133
+ import numpy as np
134
+
135
+ # Equality constraint: x0 + x1 + x2 = 1
136
+ Q_pen, offset = equality_penalty(
137
+ A=np.array([[1.0, 1.0, 1.0]]),
138
+ b=np.array([1.0]),
139
+ penalty=10.0,
140
+ )
141
+
142
+ # Inequality constraint: x0 + x1 ≤ 1 (adds slack variables)
143
+ Q_pen, offset, n_slack = inequality_penalty(
144
+ A=np.array([[1.0, 1.0]]),
145
+ b=np.array([1.0]),
146
+ penalty=10.0,
147
+ )
148
+
149
+ # Or pass constraints directly to to_qubo()
150
+ cost = np.diag([-1.0, -2.0, -3.0])
151
+ q = to_qubo(cost, constraints=[
152
+ {"type": "eq", "A": np.ones((1, 3)), "b": np.array([1.0]), "penalty": 10.0},
153
+ {"type": "ineq", "A": np.array([[1.0, 1.0, 0.0]]), "b": np.array([1.0])},
154
+ ])
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Ising conversion
160
+
161
+ ```python
162
+ from quprep.qubo import qubo_to_ising, ising_to_qubo, max_cut
163
+ import numpy as np
164
+
165
+ q = max_cut(np.array([[0,1,1],[1,0,1],[1,1,0]], dtype=float))
166
+
167
+ # QUBO → Ising
168
+ ising = qubo_to_ising(q) # or: q.to_ising()
169
+ print(ising.h) # bias vector h_i
170
+ print(ising.J) # coupling matrix J_ij
171
+
172
+ # Ising → QUBO (round-trip)
173
+ q2 = ising_to_qubo(ising) # or: ising.to_qubo()
174
+ ```
175
+
176
+ ---
177
+
178
+ ## QAOA circuit generation
179
+
180
+ ```python
181
+ from quprep.qubo import qaoa_circuit, max_cut
182
+ import numpy as np
183
+
184
+ adj = np.array([[0,1,1],[1,0,1],[1,1,0]], dtype=float)
185
+ q = max_cut(adj)
186
+
187
+ # Generate p-layer QAOA circuit (OpenQASM 3.0)
188
+ qasm = qaoa_circuit(q, p=2)
189
+
190
+ # Custom parameters
191
+ qasm = qaoa_circuit(q, p=2, gamma=[0.5, 0.3], beta=[0.2, 0.1])
192
+
193
+ # Write to file
194
+ from pathlib import Path
195
+ Path("qaoa_maxcut.qasm").write_text(qasm)
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Combining QUBOs
201
+
202
+ ```python
203
+ from quprep.qubo import add_qubo, max_cut
204
+ from quprep.qubo.constraints import equality_penalty
205
+ from quprep.qubo.converter import QUBOResult
206
+ import numpy as np
207
+
208
+ q_obj = max_cut(np.array([[0,1],[1,0]], dtype=float))
209
+ Q_pen, off = equality_penalty(np.array([[1.0, 1.0]]), np.array([1.0]), 5.0)
210
+ q_pen = QUBOResult(Q=Q_pen, offset=off)
211
+
212
+ q_combined = add_qubo(q_obj, q_pen, weight=2.0)
213
+ ```
214
+
215
+ ---
216
+
217
+ ## Visualization
218
+
219
+ Requires `pip install quprep[viz]`.
220
+
221
+ ```python
222
+ from quprep.qubo import draw_qubo, draw_ising, max_cut
223
+ import numpy as np
224
+
225
+ adj = np.array([[0,1,1],[1,0,1],[1,1,0]], dtype=float)
226
+ q = max_cut(adj)
227
+
228
+ # Heatmap of Q matrix
229
+ ax = draw_qubo(q, title="Max-Cut QUBO")
230
+
231
+ # Circular Ising graph
232
+ ax = draw_ising(q.to_ising(), title="Max-Cut Ising")
233
+ ```
234
+
235
+ ---
236
+
237
+ ## CLI
238
+
239
+ ```bash
240
+ # Problem formulation
241
+ quprep qubo maxcut --adjacency "0,1,1;1,0,1;1,1,0" --solve
242
+ quprep qubo knapsack --weights "2,3,4" --values "3,4,5" --capacity 5 --solve
243
+ quprep qubo tsp --distances "0,1,2;1,0,1;2,1,0"
244
+ quprep qubo schedule --times "3,1,4,2" --machines 2
245
+ quprep qubo partition --values "3,1,1,2,2,1" --solve
246
+ quprep qubo portfolio --returns "0.5,0.3,0.2" --covariance "0.1,0.02,0.01;0.02,0.05,0.01;0.01,0.01,0.08" --budget 2
247
+ quprep qubo graphcolor --adjacency "0,1,1;1,0,1;1,1,0" --colors 3
248
+
249
+ # QAOA circuit
250
+ quprep qubo qaoa maxcut --adjacency "0,1,1;1,0,1;1,1,0" --p 2 --output circuit.qasm
251
+
252
+ # Export Q matrix
253
+ quprep qubo export maxcut --adjacency "0,1,1;1,0,1;1,1,0" --format json --output q.json
254
+ quprep qubo export knapsack --weights "2,3,4" --values "3,4,5" --capacity 5 --format npy
255
+ ```
256
+
257
+ `--solve` automatically uses exact brute-force for n ≤ 20 and simulated annealing for larger problems.
@@ -75,7 +75,7 @@ qnode = result.circuit # callable qml.QNode
75
75
  | **Encode+** | ✅ v0.2.0 | IQP, Entangled Angle, Data re-uploading, Hamiltonian |
76
76
  | **Export+** | ✅ v0.2.0 | PennyLane, Cirq, TKET, ASCII + matplotlib visualization |
77
77
  | **Recommend** | ✅ v0.2.0 | Automatic encoding selection for your dataset and task |
78
- | **QUBO** | 🔲 v0.3.0 | QUBO/Ising conversion, problem library |
78
+ | **QUBO** | v0.3.0 | QUBO/Ising conversion, 7 problem formulations, solvers, QAOA, D-Wave export |
79
79
 
80
80
  ---
81
81
 
@@ -100,4 +100,8 @@ quprep convert data.csv --encoding angle --framework qasm
100
100
  quprep convert data.csv --encoding iqp --framework pennylane
101
101
  quprep convert data.csv --encoding basis --samples 10 --output circuits.qasm
102
102
  quprep recommend data.csv --task classification --qubits 8
103
+
104
+ quprep qubo maxcut --adjacency "0,1,1;1,0,1;1,1,0" --solve
105
+ quprep qubo knapsack --weights "2,3,4" --values "3,4,5" --capacity 5
106
+ quprep qubo qaoa maxcut --adjacency "0,1,1;1,0,1;1,1,0" --p 2 --output circuit.qasm
103
107
  ```
@@ -67,4 +67,4 @@ print("=" * 55)
67
67
 
68
68
  rec2 = quprep.recommend(X, task="classification", qubits=8)
69
69
  for i, alt in enumerate(rec2.alternatives):
70
- print(f" #{i + 1}: {alt['method']:20s} score={alt['score']:.1f}")
70
+ print(f" #{i + 1}: {alt.method:20s} score={alt.score:.1f}")
@@ -74,7 +74,7 @@ else:
74
74
  ]
75
75
 
76
76
  for name, enc in encodings:
77
- path = f"circuit_{name}.png"
77
+ path = f"/tmp/circuit_{name}.png"
78
78
  draw_matplotlib(enc, filename=path)
79
79
  print(f" Saved: {path}")
80
80
 
@@ -0,0 +1,94 @@
1
+ """
2
+ Example 07 — QUBO / Ising
3
+
4
+ Demonstrates:
5
+ - Problem formulation (Max-Cut, Knapsack)
6
+ - Brute-force and simulated-annealing solvers
7
+ - QUBO ↔ Ising round-trip
8
+ - D-Wave export
9
+ - QAOA circuit generation
10
+ """
11
+
12
+ import numpy as np
13
+
14
+ from quprep.qubo import (
15
+ ising_to_qubo,
16
+ knapsack,
17
+ max_cut,
18
+ qaoa_circuit,
19
+ qubo_to_ising,
20
+ solve_brute,
21
+ solve_sa,
22
+ )
23
+
24
+ # ── Max-Cut ───────────────────────────────────────────────────────────────────
25
+ print("── Max-Cut ──────────────────────────────────────────────────────────────")
26
+
27
+ adj = np.array([[0, 1, 1, 0],
28
+ [1, 0, 1, 1],
29
+ [1, 1, 0, 1],
30
+ [0, 1, 1, 0]], dtype=float)
31
+
32
+ q = max_cut(adj)
33
+ print(f"Variables : {q.Q.shape[0]}")
34
+ print(f"Offset : {q.offset}")
35
+
36
+ sol = solve_brute(q)
37
+ bits = "".join(str(int(b)) for b in sol.x)
38
+ print(f"Exact solution : x={bits} energy={sol.energy:.4f}")
39
+
40
+ # Evaluate a specific assignment manually
41
+ x_test = np.array([0.0, 1.0, 0.0, 1.0])
42
+ print(f"evaluate([0,1,0,1]) = {q.evaluate(x_test):.4f}")
43
+
44
+ # ── Ising round-trip ──────────────────────────────────────────────────────────
45
+ print("\n── Ising round-trip ─────────────────────────────────────────────────────")
46
+
47
+ ising = q.to_ising()
48
+ print(f"Ising h : {ising.h}")
49
+ print(f"Ising J matrix:\n{ising.J}")
50
+
51
+ q2 = ising.to_qubo()
52
+ print(f"Round-trip Q matches: {np.allclose(q.Q, q2.Q)}")
53
+
54
+ # ── D-Wave export ─────────────────────────────────────────────────────────────
55
+ print("\n── D-Wave export ────────────────────────────────────────────────────────")
56
+
57
+ dwave = q.to_dwave()
58
+ print(f"D-Wave BQM dict ({len(dwave)} entries):")
59
+ for (i, j), v in list(dwave.items())[:5]:
60
+ print(f" ({i},{j}): {v:.4f}")
61
+
62
+ # Standalone ising_to_qubo
63
+ q3 = ising_to_qubo(qubo_to_ising(q))
64
+ print(f"ising_to_qubo() round-trip matches: {np.allclose(q.Q, q3.Q)}")
65
+
66
+ # ── Knapsack with simulated annealing ─────────────────────────────────────────
67
+ print("\n── Knapsack (SA solver) ─────────────────────────────────────────────────")
68
+
69
+ weights = np.array([2.0, 3.0, 4.0, 5.0, 6.0])
70
+ values = np.array([3.0, 4.0, 5.0, 6.0, 7.0])
71
+ kq = knapsack(weights, values, capacity=10.0)
72
+ print(f"Variables : {kq.Q.shape[0]}")
73
+
74
+ sol_sa = solve_sa(kq, n_steps=20_000, restarts=3, seed=42)
75
+ bits_sa = "".join(str(int(b)) for b in sol_sa.x[:len(weights)])
76
+ print(f"SA solution : x={bits_sa} energy={sol_sa.energy:.4f}")
77
+
78
+ sol_exact = solve_brute(kq)
79
+ bits_exact = "".join(str(int(b)) for b in sol_exact.x[:len(weights)])
80
+ print(f"Exact : x={bits_exact} energy={sol_exact.energy:.4f}")
81
+
82
+ # ── QAOA circuit ─────────────────────────────────────────────────────────────
83
+ print("\n── QAOA circuit ─────────────────────────────────────────────────────────")
84
+
85
+ small_adj = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]], dtype=float)
86
+ qsmall = max_cut(small_adj)
87
+ qasm = qaoa_circuit(qsmall, p=2)
88
+ lines = qasm.strip().splitlines()
89
+ print(f"QAOA circuit ({len(lines)} lines):")
90
+ for line in lines[:6]:
91
+ print(f" {line}")
92
+ print(" ...")
93
+
94
+ print("\nDone.")
@@ -10,6 +10,7 @@ Each example is a runnable Python script.
10
10
  | 04 | Framework export — QASM, Qiskit, PennyLane, Cirq, TKET | `04_export.py` |
11
11
  | 05 | Encoding recommendation — `recommend()` | `05_recommend.py` |
12
12
  | 06 | Circuit visualization — ASCII + matplotlib | `06_visualization.py` |
13
+ | 07 | QUBO / Ising — Max-Cut, Knapsack, solvers, D-Wave export, QAOA | `07_qubo.py` |
13
14
 
14
15
  ## Run a script
15
16
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "quprep"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Quantum data preparation — the missing preprocessing layer between classical datasets and quantum computing frameworks"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -20,7 +20,7 @@ The missing preprocessing layer between classical datasets and quantum computing
20
20
  rec = quprep.recommend(df, task="classification", qubits=8)
21
21
  """
22
22
 
23
- __version__ = "0.2.0"
23
+ __version__ = "0.3.0"
24
24
  __author__ = "Hasarindu Perera"
25
25
  __license__ = "Apache-2.0"
26
26