pauliengine 0.1.2__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 (41) hide show
  1. pauliengine-0.1.2/.git_archival.txt +3 -0
  2. pauliengine-0.1.2/CMakeLists.txt +114 -0
  3. pauliengine-0.1.2/CMakeUserPresets.json +9 -0
  4. pauliengine-0.1.2/LICENSE +21 -0
  5. pauliengine-0.1.2/PKG-INFO +332 -0
  6. pauliengine-0.1.2/README.md +282 -0
  7. pauliengine-0.1.2/THIRD_PARTY_LICENSES.txt +15 -0
  8. pauliengine-0.1.2/cmake/compiler_flags/CXXFlags.cmake +79 -0
  9. pauliengine-0.1.2/cmake/compiler_flags/Clang.CXX.cmake +22 -0
  10. pauliengine-0.1.2/cmake/compiler_flags/GNU.CXX.cmake +9 -0
  11. pauliengine-0.1.2/cmake/compiler_flags/Intel.CXX.cmake +6 -0
  12. pauliengine-0.1.2/cmake/compiler_flags/MSVC.CXX.cmake +6 -0
  13. pauliengine-0.1.2/conan_profile +4 -0
  14. pauliengine-0.1.2/conanfile.txt +6 -0
  15. pauliengine-0.1.2/include/pauliengine/Info.h.in +29 -0
  16. pauliengine-0.1.2/include/pauliengine/PauliString.h +712 -0
  17. pauliengine-0.1.2/include/pauliengine/QubitHamiltonian.h +811 -0
  18. pauliengine-0.1.2/pyproject.toml +321 -0
  19. pauliengine-0.1.2/src/CMakeLists.txt +59 -0
  20. pauliengine-0.1.2/src/PauliString_test.cc +78 -0
  21. pauliengine-0.1.2/src/QubitHamiltonian.cpp +1 -0
  22. pauliengine-0.1.2/src/bindings/CMakeLists.txt +42 -0
  23. pauliengine-0.1.2/src/bindings/bindings.cpp +298 -0
  24. pauliengine-0.1.2/src/example.cpp +20 -0
  25. pauliengine-0.1.2/src/pauliengine/__init__.py +24 -0
  26. pauliengine-0.1.2/src/pauliengine/_version.py +24 -0
  27. pauliengine-0.1.2/src/pauliengine/pauli_string.py +96 -0
  28. pauliengine-0.1.2/src/pauliengine/qubit_hamiltonian.py +192 -0
  29. pauliengine-0.1.2/tests/test_package.py +9 -0
  30. pauliengine-0.1.2/tests/test_pauli_string.py +672 -0
  31. pauliengine-0.1.2/tests/test_qubit_hamiltonian.py +1101 -0
  32. pauliengine-0.1.2/tools/benchmarks/README.md +95 -0
  33. pauliengine-0.1.2/tools/benchmarks/__init__.py +1 -0
  34. pauliengine-0.1.2/tools/benchmarks/benchmark.py +167 -0
  35. pauliengine-0.1.2/tools/benchmarks/compare.py +208 -0
  36. pauliengine-0.1.2/tools/benchmarks/generate.py +77 -0
  37. pauliengine-0.1.2/tools/benchmarks/hardware.py +130 -0
  38. pauliengine-0.1.2/tools/benchmarks/plot.py +100 -0
  39. pauliengine-0.1.2/tools/benchmarks/results/.gitkeep +0 -0
  40. pauliengine-0.1.2/tools/benchmarks/run.py +136 -0
  41. pauliengine-0.1.2/tools/install-deps.sh +81 -0
@@ -0,0 +1,3 @@
1
+ node: $Format:%H$
2
+ node-date: $Format:%cI$
3
+ describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
@@ -0,0 +1,114 @@
1
+ cmake_minimum_required(VERSION 3.28...4.2.1)
2
+
3
+ if(SKBUILD)
4
+ # this value doesn't really matter, as it will be set by scikit-build-core
5
+ set(PROJECT_VERSION "0.1.0")
6
+ else()
7
+ include(FetchContent)
8
+ FetchContent_Declare(
9
+ CMakeExtraUtils
10
+ QUIET
11
+ URL
12
+ https://github.com/LecrisUT/CMakeExtraUtils/archive/refs/tags/v0.4.1.tar.gz
13
+ SOURCE_DIR
14
+ ${CMAKE_CURRENT_BINARY_DIR}/external/upstream/_srcs/CMakeExtraUtils
15
+ )
16
+ FetchContent_MakeAvailable(CMakeExtraUtils)
17
+
18
+ include(DynamicVersion)
19
+ dynamic_version(PROJECT_PREFIX pauliengine_)
20
+ endif()
21
+
22
+ project(pauliengine LANGUAGES CXX VERSION ${PROJECT_VERSION})
23
+
24
+ # if CMAKE_BUILD_TYPE undefined, we set it to Release
25
+ if(NOT CMAKE_BUILD_TYPE)
26
+ set(CMAKE_BUILD_TYPE "Release")
27
+ endif()
28
+
29
+
30
+ # Options handling utilities Macro for printing an option in a consistent manner
31
+ # Written by Lori A. Burns (@loriab) and Ryan M. Richard (@ryanmrichard) Syntax:
32
+ # print_option(<option to print> <was specified>)
33
+ macro(print_option variable default)
34
+ if(NOT DEFINED ${variable} OR "${${variable}}" STREQUAL "")
35
+ message(STATUS "Setting (unspecified) option ${variable}: ${default}")
36
+ else()
37
+ message(STATUS "Setting option ${variable}: ${${variable}}")
38
+ endif()
39
+ endmacro()
40
+
41
+ # Wraps an option with default ON/OFF. Adds nice messaging to option() Written
42
+ # by Lori A. Burns (@loriab) and Ryan M. Richard (@ryanmrichard) Syntax:
43
+ # option_with_print(<option name> <description> <default value>)
44
+ macro(option_with_print variable msge default)
45
+ print_option(${variable} ${default})
46
+ option(${variable} ${msge} ${default})
47
+ endmacro()
48
+
49
+ # Wraps an option with a default other than ON/OFF and prints it Written by Lori
50
+ # A. Burns (@loriab) and Ryan M. Richard (@ryanmrichard) NOTE: Can't combine
51
+ # with above b/c CMake handles ON/OFF options specially NOTE2: CMake variables
52
+ # are always defined so need to further check for if they are the NULL string.
53
+ # This is also why we need the force Syntax: option_with_default(<option name>
54
+ # <description> <default value>)
55
+ macro(option_with_default variable msge default)
56
+ print_option(${variable} "${default}")
57
+ if(NOT DEFINED ${variable} OR "${${variable}}" STREQUAL "")
58
+ set(${variable} "${default}" CACHE STRING ${msge} FORCE)
59
+ endif()
60
+ endmacro()
61
+
62
+ set(CMAKE_CXX_STANDARD 20 CACHE STRING "C++ standard")
63
+ set(CMAKE_CXX_STANDARD_REQUIRED ON CACHE BOOL "C++ standard required")
64
+ set(CMAKE_CXX_EXTENSIONS OFF CACHE BOOL "C++ extensions")
65
+
66
+ include(${PROJECT_SOURCE_DIR}/cmake/compiler_flags/CXXFlags.cmake)
67
+
68
+ message(STATUS "${PROJECT_NAME} version: ${PROJECT_VERSION_FULL}")
69
+
70
+ # report on compiler flags in use
71
+ message(STATUS "Configuring a ${CMAKE_BUILD_TYPE} build")
72
+ string(TOUPPER ${CMAKE_BUILD_TYPE} _cmake_build_type_upper)
73
+
74
+ message(STATUS "Compiler flags for ${CMAKE_CXX_COMPILER_ID}")
75
+ message(STATUS " From environment : ${CMAKE_CXX_FLAGS}")
76
+ set(
77
+ _cmake_build_type_specific_flags
78
+ "${CMAKE_CXX_FLAGS_${_cmake_build_type_upper}}"
79
+ )
80
+ message(
81
+ STATUS
82
+ " Build-type-specific : ${_cmake_build_type_specific_flags}"
83
+ )
84
+ message(STATUS " Vectorization flag : ${ARCH_FLAG}")
85
+ message(
86
+ STATUS
87
+ " Project defaults : ${CMAKE_CXX${CMAKE_CXX_STANDARD}_STANDARD_COMPILE_OPTION} ${pauliengine_CXX_FLAGS}"
88
+ )
89
+ message(STATUS " User-appended : ${EXTRA_CXXFLAGS}")
90
+
91
+ include(GNUInstallDirs)
92
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
93
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
94
+
95
+ # OpenMP is optional — code falls back to sequential loops when unavailable.
96
+ # On macOS with AppleClang you may need to `brew install libomp` and set
97
+ # OpenMP_ROOT to the Homebrew prefix before configuring.
98
+ find_package(OpenMP)
99
+ if(OpenMP_CXX_FOUND)
100
+ message(STATUS "OpenMP: enabled (spec ${OpenMP_CXX_SPEC_DATE}, ${OpenMP_CXX_FLAGS})")
101
+ else()
102
+ message(STATUS "OpenMP: not found — building without parallel loops")
103
+ endif()
104
+
105
+ configure_file(
106
+ ${PROJECT_SOURCE_DIR}/include/pauliengine/Info.h.in
107
+ ${PROJECT_BINARY_DIR}/include/pauliengine/Info.h
108
+ )
109
+
110
+ # Conan Toolchain einbinden (nach: conan install .. --install-folder=build --build=missing)
111
+ include(${CMAKE_BINARY_DIR}/conan_toolchain.cmake OPTIONAL RESULT_VARIABLE _found)
112
+
113
+ add_subdirectory(src)
114
+
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 4,
3
+ "vendor": {
4
+ "conan": {}
5
+ },
6
+ "include": [
7
+ "build/CMakePresets.json"
8
+ ]
9
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leon Müller
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,332 @@
1
+ Metadata-Version: 2.2
2
+ Name: pauliengine
3
+ Version: 0.1.2
4
+ Summary: Fast arithmetics for quantum operators (Pauli strings & Hamiltonians).
5
+ Keywords: quantum,quantum-computing,pauli,hamiltonian,symbolic,physics
6
+ Author-Email: Leon Müller <mueller.leon.home@gmail.com>, Jakob Kottmann <jakob.kottmann@gmail.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 Leon Müller
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Classifier: Development Status :: 4 - Beta
30
+ Classifier: Intended Audience :: Science/Research
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Classifier: Programming Language :: Python
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3 :: Only
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Topic :: Scientific/Engineering :: Physics
42
+ Classifier: Topic :: Scientific/Engineering
43
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
44
+ Classifier: Typing :: Typed
45
+ Project-URL: Homepage, https://github.com/tequilahub/pauliengine
46
+ Project-URL: Repository, https://github.com/tequilahub/pauliengine
47
+ Project-URL: Issues, https://github.com/tequilahub/pauliengine/issues
48
+ Requires-Python: <3.14,>=3.10
49
+ Description-Content-Type: text/markdown
50
+
51
+ # PauliEngine — Fast Arithmetic for Quantum Operators
52
+
53
+ A C++ core with a nanobind Python frontend for working with Pauli strings and
54
+ qubit Hamiltonians. Coefficients can be numeric (`std::complex<double>`) or
55
+ fully symbolic (SymEngine).
56
+
57
+ See [arXiv:2601.02233](https://arxiv.org/abs/2601.02233) for the algorithmic
58
+ background.
59
+
60
+ > **Status.** Functional core with a large test suite. The Python
61
+ > API surface is stable. Sphinx docs are not published yet.
62
+
63
+ ---
64
+
65
+ ## Highlights
66
+
67
+ - **Binary symplectic representation** — multiplication, commutators, and
68
+ hashing are O(n / 64) over the qubit count.
69
+ - **Two coefficient backends** — numeric (`complex<double>`) and symbolic
70
+ (`SymEngine::Expression`); cross-type arithmetic is handled automatically and
71
+ type-promotes the way you expect (any symbolic term → the whole Hamiltonian
72
+ becomes symbolic).
73
+ - **Compaction invariant** — every operation that returns a `QubitHamiltonian`
74
+ merges duplicate-operator terms and drops zero-coefficient terms. You never
75
+ need to call `simplify()` for correctness.
76
+ - **Tequila-compatible API** — `qubits`, `n_qubits`, `is_hermitian`,
77
+ `is_antihermitian`, `dagger`, `conjugate`, `transpose`, `simplify(threshold)`,
78
+ `split`, `map_qubits`, `power` / `__pow__`, `to_matrix`, `paulistrings`,
79
+ `count_measurements`, `is_all_z`.
80
+ - **OpenFermion bridge** — the `QubitHamiltonian` factory accepts an
81
+ `openfermion.QubitOperator` directly; `to_openfermion` and `from_openfermion`
82
+ helpers are available.
83
+
84
+ ---
85
+
86
+ ## Quickstart
87
+
88
+ ```python
89
+ import pauliengine as pe
90
+
91
+ # A single Pauli string: dict-of-operators style.
92
+ p1 = pe.PauliString(1.0, {0: "Z", 1: "X"})
93
+
94
+ # Symbolic coefficient — anything coercible to a SymEngine Expression.
95
+ p2 = pe.PauliString("a", {1: "X"})
96
+
97
+ # OpenFermion-style: PauliString((coeff, [(Pauli, qubit), ...]))
98
+ p3 = pe.PauliString((1.0, [("X", 0), ("Y", 2)]))
99
+
100
+ # Build a Hamiltonian from a list of PauliStrings (or (coeff, dict) tuples).
101
+ H = pe.QubitHamiltonian([p1, p2, p3])
102
+
103
+ print(H.qubits()) # [0, 1, 2]
104
+ print(H.is_hermitian()) # True
105
+ print(H.dagger()) # for Hermitian H this is just H
106
+ ```
107
+
108
+ Tests are in `tests/`:
109
+
110
+ ```bash
111
+ pytest tests/
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Construction
117
+
118
+ `pe.PauliString` accepts several input shapes:
119
+
120
+ ```python
121
+ pe.PauliString(1.0, {0: "Z", 1: "X"}) # dict input
122
+ pe.PauliString(1.0, "X0 Y1 Z2") # space-separated string input
123
+ pe.PauliString((1.0, [("X", 0), ("Y", 2)])) # OpenFermion-style (coeff, list)
124
+ pe.PauliString("a", {0: "X"}) # symbolic coefficient
125
+ ```
126
+
127
+ `pe.QubitHamiltonian` accepts:
128
+
129
+ ```python
130
+ pe.QubitHamiltonian([ps1, ps2, ...]) # list of PauliStrings
131
+ pe.QubitHamiltonian([(1.0, {0: "X"}), ("a", {1: "Z"})]) # list of tuples
132
+ pe.QubitHamiltonian(openfermion_qubit_operator) # see OpenFermion bridge
133
+ pe.QubitHamiltonian.zero() # empty Hamiltonian
134
+ pe.QubitHamiltonian.unit() # identity (single term, coeff 1, no ops)
135
+ ```
136
+
137
+ If any term in the list is symbolic, the resulting Hamiltonian is symbolic.
138
+
139
+ ---
140
+
141
+ ## Arithmetic
142
+
143
+ ```python
144
+ # PauliString * PauliString, with the right factors of i from Pauli algebra.
145
+ p4 = pe.PauliString(1.0, {0: "X"}) * pe.PauliString(1.0, {0: "Y"}) # -> 1j * Z(0)
146
+
147
+ # Scalar multiplication on both sides; +, -, unary -, and addition between
148
+ # PauliStrings (returns a QubitHamiltonian).
149
+ H1 = p1 + p2 - p3
150
+ H2 = 0.5 * H1 + (-H1) * 2j
151
+ H3 = H1 ** 3 # integer powers
152
+ c = H1.commutator(H2) # commutator (also available on PauliString)
153
+ ```
154
+
155
+ Cross-type multiplication (numeric × symbolic) is supported and promotes the
156
+ result to symbolic.
157
+
158
+ ---
159
+
160
+ ## Inspection and properties
161
+
162
+ ```python
163
+ H.size() # number of Pauli-string terms (also len(H))
164
+ H.qubits() # sorted list of qubits with non-identity operators
165
+ H.n_qubits() # len(H.qubits())
166
+ H.is_all_z()
167
+ H.is_hermitian() # True iff every coefficient is real
168
+ H.is_antihermitian() # True iff every coefficient is purely imaginary
169
+ H.count_measurements() # 1 if all-Z, else len(H)
170
+ ```
171
+
172
+ For a single `PauliString`:
173
+
174
+ ```python
175
+ ps.size() # number of non-identity Pauli ops (also len(ps))
176
+ ps.count_y() # number of Y operators (used by conjugate/transpose)
177
+ ps.naked() # same operator with coefficient 1
178
+ ps.key_openfermion() # OpenFermion-style key
179
+ ps.get_pauli_at_index(q) # "I" / "X" / "Y" / "Z"
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Transformations
185
+
186
+ ```python
187
+ H.dagger() # complex-conjugate each coefficient
188
+ H.conjugate() # complex conjugation (flips a sign per Y operator)
189
+ H.transpose() # transpose (flips a sign per Y operator, no conjugation)
190
+ H.simplify(1e-10) # drop terms with |coefficient| <= threshold
191
+ H.split() # -> (hermitian, anti_hermitian) pair (numeric coeffs only)
192
+ H.map_qubits({0: 5, 1: 2})
193
+ H.power(3) # also via H ** 3
194
+ ```
195
+
196
+ > `split()` and `to_matrix()` require coefficients that evaluate to a complex
197
+ > number — call `H.subs({...})` first on symbolic Hamiltonians.
198
+
199
+ ### Dense matrix
200
+
201
+ ```python
202
+ import numpy as np
203
+
204
+ M = np.array(H.to_matrix()) # 2**n x 2**n, ignores unused qubits
205
+ M_full = np.array(H.to_matrix(ignore_unused_qubits=False)) # absolute qubit indices
206
+ ```
207
+
208
+ ---
209
+
210
+
211
+ ## Symbolic coefficients
212
+
213
+ Any string coefficient (or `SymEngine::Expression` from C++) makes the term
214
+ symbolic. Symbolic PauliStrings and Hamiltonians support every arithmetic
215
+ operation plus:
216
+
217
+ ```python
218
+ H = pe.QubitHamiltonian([("a", {0: "X"}), ("b", {1: "Z"})])
219
+
220
+ dH = H.diff("a") # symbolic derivative
221
+ H2 = H.subs({"a": 2.0}) # substitute and evaluate
222
+ ```
223
+
224
+ `diff` is also available on `PauliString`. Mixing symbolic and numeric inputs
225
+ is fine: the factory scans every element and uses the symbolic builder if
226
+ needed.
227
+
228
+ ---
229
+
230
+ ## OpenFermion bridge
231
+
232
+ ```python
233
+ from openfermion import QubitOperator
234
+ qop = 1.5 * QubitOperator("X0 Y1") + 0.5j * QubitOperator("Z2")
235
+
236
+ # Factory accepts QubitOperator directly:
237
+ H = pe.QubitHamiltonian(qop)
238
+
239
+ # Or use the explicit helpers:
240
+ H = pe.from_openfermion(qop)
241
+ qop_back = pe.QubitHamiltonian.to_openfermion(H)
242
+ assert qop == qop_back
243
+ ```
244
+
245
+ `openfermion` is an optional dependency — `from_openfermion` /
246
+ `to_openfermion` import it lazily and raise `ImportError` with a helpful
247
+ message if it is missing.
248
+
249
+ ---
250
+
251
+ ## C++ usage
252
+
253
+ The library is a header-only template under `include/pauliengine/`. Both
254
+ `PauliString<Coeff>` and `QubitHamiltonian<Coeff>` work for
255
+ `Coeff = std::complex<double>` and `Coeff = SymEngine::Expression`. Every
256
+ operation exposed in Python is available in C++ with the same name.
257
+
258
+ ---
259
+
260
+ ## Installation
261
+
262
+ ### From PyPI
263
+
264
+ ```bash
265
+ pip install pauliengine
266
+ ```
267
+
268
+ Prebuilt wheels are available for Linux (x86_64/aarch64), macOS (arm64) and
269
+ Windows (x86_64/arm64) on Python 3.10–3.13. Installing from the source
270
+ distribution requires the build dependencies below.
271
+
272
+ ### Build dependencies
273
+
274
+ - A C++20 compiler (MSVC 19.3+, GCC 11+, or Clang 14+)
275
+ - CMake 3.20+
276
+ - Python 3.10–3.13
277
+ - [Conan 2](https://conan.io) (to pull in SymEngine)
278
+ - [nanobind](https://github.com/wjakob/nanobind) (build-time)
279
+
280
+ ### Install from source
281
+
282
+ ```bash
283
+ pip install conan
284
+ conan profile detect
285
+ conan install . --output-folder=build --build=missing
286
+ pip install .
287
+ ```
288
+
289
+ The CMake build picks up the Conan toolchain from `build/conan_toolchain.cmake`.
290
+ For an editable / development install use `pip install -e .` instead of
291
+ `pip install .`.
292
+
293
+ ### Windows notes
294
+
295
+ If you are on Windows and have not built SymEngine before, the Conan step will
296
+ build it from source on first install — that takes a few minutes. Subsequent
297
+ builds use the cached artifact.
298
+
299
+ > **Performance note.** PauliEngine can be built without SymEngine, but
300
+ > symbolic coefficients are unavailable in that mode and the runtime cost of
301
+ > certain numeric paths increases.
302
+
303
+ ### macOS prerequisite
304
+
305
+ Conan does not currently ship a prebuilt SymEngine binary for macOS. Build it
306
+ from source once before the main install step:
307
+
308
+ ```bash
309
+ conan install --build=symengine/0.14.0
310
+ ```
311
+
312
+ Subsequent installs pick up the cached artifact, so this only needs to be done
313
+ the first time.
314
+
315
+ ---
316
+
317
+
318
+
319
+ ## Testing
320
+
321
+ ```bash
322
+ pip install pytest
323
+ pytest tests/
324
+ ```
325
+
326
+
327
+ ---
328
+
329
+ ## Citation
330
+
331
+ If you use PauliEngine in academic work, please cite
332
+ [arXiv:2601.02233](https://arxiv.org/abs/2601.02233).