sia-script 1.0.3__tar.gz → 1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sia-script
3
- Version: 1.0.3
3
+ Version: 1.3.0
4
4
  Summary: SIA — Simplified Integrated Architecture: SiaNTT, SIA-ELS (ABAP interpreter), the HDQS server client, and hdqs_engine (offline statevector quantum simulator — no server or API token required)
5
5
  Author-email: Sia Software Innovations Private Limited <info@siasoftwareinnovations.com>
6
6
  License: # PROPRIETARY SOFTWARE LICENSE
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["setuptools>=61.0", "wheel", "pybind11>=2.11"]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sia-script"
7
- version = "1.0.3"
7
+ version = "1.3.0"
8
8
  description = "SIA — Simplified Integrated Architecture: SiaNTT, SIA-ELS (ABAP interpreter), the HDQS server client, and hdqs_engine (offline statevector quantum simulator — no server or API token required)"
9
9
  authors = [
10
10
  { name = "Sia Software Innovations Private Limited", email = "info@siasoftwareinnovations.com" }
@@ -62,7 +62,8 @@ client = []
62
62
  # numpy is now a base dependency; kept as an empty extra for backward
63
63
  # compatibility with anyone depending on sia-script[engine].
64
64
  engine = []
65
- # sia.hdqs_engine.torch_layer — trainable QuantumLayer nn.Module.
65
+ # sia.hdqs_engine.torch_layer — trainable QuantumLayer/BackpropQuantumLayer/
66
+ # MidCircuitQuantumLayer/QASMQuantumLayer (nn.Module).
66
67
  torch = [
67
68
  "torch>=2.0",
68
69
  ]
@@ -80,4 +81,4 @@ full = [
80
81
  packages = ["sia"]
81
82
 
82
83
  [tool.setuptools.package-data]
83
- "sia" = ["*.py"]
84
+ "sia" = ["*.py"]
@@ -36,13 +36,8 @@ Client-side package. Loads:
36
36
  aliases, register/slice broadcasting, reset/barrier/end,
37
37
  and if/else and switch branching on measured bits.
38
38
  Requires the `openqasm3[parser]` extra.
39
- Optional native (C++) acceleration for the statevector
40
- gate-application hot path, with hand-vectorized AVX2/
41
- AVX-512 kernels where available and automatic fallback to
42
- pure NumPy on platforms without a compiled extension for
43
- this build (see `sia.hdqs_engine._NATIVE_AVAILABLE`) --
44
- a pure performance path, validated to produce identical
45
- results to the NumPy path.
39
+ Pure Python (NumPy + optional PyTorch) — no compiled
40
+ extension, no build step, works on any platform.
46
41
  MIT-licensed (see the MIT_LICENSE string in sia/hdqs_engine.py),
47
42
  distinct from the proprietary license covering the rest of
48
43
  this package.
@@ -53,7 +48,7 @@ this client package. They live in the server deployment only. What you get
53
48
  here for local, offline quantum simulation is sia.hdqs_engine.
54
49
  """
55
50
 
56
- __version__ = "1.0.0"
51
+ __version__ = "1.3.0"
57
52
  __author__ = "Sia Software Innovations Private Limited"
58
53
  # NOTE: hardcoding a bare IP couples every released client to one host. If
59
54
  # the server ever moves, every installed copy breaks. Prefer a DNS name
@@ -194,6 +189,7 @@ try:
194
189
  from .hdqs_engine import (
195
190
  # Core class (renamed from StatevectorEngine)
196
191
  HDQS,
192
+ PI,
197
193
  GATES,
198
194
  controlled,
199
195
  random_state,
@@ -265,6 +261,7 @@ except ImportError as _engine_import_error:
265
261
  _warnings.warn(f"sia.hdqs_engine unavailable: {_engine_import_error}",
266
262
  ImportWarning)
267
263
  HDQS = None
264
+ PI = None
268
265
  StatevectorEngine = None
269
266
  GATES = None
270
267
  controlled = None
@@ -371,6 +368,7 @@ __all__ = [
371
368
  # HDQS-Engine (offline, no server/token required)
372
369
  # Core class (renamed for cleaner API)
373
370
  "HDQS",
371
+ "PI",
374
372
  "StatevectorEngine", # Legacy alias for backward compatibility
375
373
  "GATES",
376
374
  "controlled",
@@ -2,13 +2,9 @@
2
2
  sia.hdqs_engine — offline quantum simulator with automatic GPU support
3
3
  ================================================================================
4
4
 
5
- Version 1.0.0 — adds an optional native (C++) acceleration core for the
6
- statevector hot path (h/x/y/z/rx/ry/rz/p/u3/cnot/cz/cy/cp/crx/cry/crz/swap,
7
- plus a general small-k kernel covering ccx/mcx/cswap and gate fusion
8
- flushes), hand-vectorized with AVX2 and AVX-512 (FMA-based complex
9
- multiply) where available, with automatic fallback to portable NumPy if
10
- the compiled extension isn't present for a given platform -- see
11
- ``_NATIVE_AVAILABLE``. Also adds OpenQASM 3 import/export
5
+ Version 1.1.0 — pure Python (NumPy + optional PyTorch) implementation.
6
+ No compiled extension, no build step -- `pip install`/`import` and it
7
+ works, on any platform. Adds OpenQASM 3 import/export
12
8
  (``HDQS.from_qasm3``/``to_qasm3``, tier 1+2: constants, for-loops, custom
13
9
  gate inlining, gate modifiers, switch/case, aliasing), a
14
10
  mid-circuit-measurement-aware batched trainable layer
@@ -33,13 +29,6 @@ Conventions
33
29
  unavailable); ``use_gpu=False`` forces the NumPy backend. ``device=``
34
30
  overrides for power users (e.g. device='cpu' runs the PyTorch backend on
35
31
  CPU — same code path as CUDA, useful for testing).
36
- * Native acceleration (optional): when the compiled ``hdqs_core`` extension
37
- is importable, statevector gate application routes through it instead of
38
- NumPy's einsum-based path. This is a pure performance path -- results
39
- are validated identical to the NumPy path across hundreds of randomized
40
- circuits per release, covering every gate family including asymmetric
41
- multi-qubit matrices (cswap). No behavior changes based on whether the
42
- extension is present; only speed does.
43
32
 
44
33
  Changelog v0.4.0 (from v0.3.0)
45
34
  ------------------------------
@@ -85,7 +74,9 @@ from typing import (Any, Callable, Dict, Iterable, List, Optional, Sequence,
85
74
 
86
75
  import numpy as np
87
76
 
88
- __version__ = "1.0.0"
77
+ __version__ = "1.3.0"
78
+
79
+ PI = np.pi # convenience alias — HDQS(1).rx(0, PI/2) instead of np.pi/2
89
80
 
90
81
  # ==========================================================================
91
82
 
@@ -175,28 +166,6 @@ except ImportError:
175
166
  pass
176
167
 
177
168
 
178
- # --------------------------------------------------------------------------
179
- # Native (C++) kernel detection. Optional: this module works fully on pure
180
- # NumPy without it. When present, the statevector gate-application and
181
- # single-qubit-Kraus-channel hot paths route through compiled kernels
182
- # instead of NumPy's einsum/moveaxis dispatch (which dominates per-gate
183
- # overhead — profiling showed ~70% of time in bookkeeping, not arithmetic).
184
- # Measured: ~5-25x faster on statevector gates, ~4-12x faster on Kraus
185
- # channels, scaling up with qubit count, single-threaded.
186
- # --------------------------------------------------------------------------
187
- _NATIVE_AVAILABLE = False
188
- _hdqs_native = None
189
- try:
190
- from . import hdqs_core as _hdqs_native # noqa: F401
191
- _NATIVE_AVAILABLE = True
192
- except ImportError:
193
- try:
194
- import hdqs_core as _hdqs_native # noqa: F401 (standalone/dev use)
195
- _NATIVE_AVAILABLE = True
196
- except ImportError:
197
- pass
198
-
199
-
200
169
  def is_tensor(arr: Any) -> bool:
201
170
  """True iff *arr* is a PyTorch tensor. Safe when torch is not installed
202
171
  (v0.3.0 raised NameError here, making the module unusable without torch)."""
@@ -613,6 +582,15 @@ class HDQS:
613
582
  "readout_error": readout_error_channel,
614
583
  }
615
584
 
585
+ @classmethod
586
+ def dm(cls, num_qubits: int, **kwargs) -> "HDQS":
587
+ """Alternative constructor for density-matrix mode: behaviorally
588
+ identical to HDQS(num_qubits, use_density_matrix=True, **kwargs),
589
+ including every other constructor argument (seed, use_gpu,
590
+ device). The use_density_matrix=True keyword form remains fully
591
+ supported -- this is an additional spelling, not a replacement."""
592
+ return cls(num_qubits, use_density_matrix=True, **kwargs)
593
+
616
594
  # -- backend helpers ------------------------------------------------------
617
595
 
618
596
  def _to_backend(self, arr):
@@ -802,27 +780,6 @@ class HDQS:
802
780
  raise ValueError(
803
781
  f"unitary shape {U.shape} does not match {k} target(s)")
804
782
  n = self.num_qubits
805
- if (not self._use_density_matrix and _NATIVE_AVAILABLE
806
- and not self._backend_torch):
807
- bitpos = [n - 1 - t for t in targets]
808
- if k == 1:
809
- sv = np.ascontiguousarray(self._sv, dtype=complex)
810
- _hdqs_native.apply_1q(sv, bitpos[0],
811
- U[0, 0], U[0, 1], U[1, 0], U[1, 1])
812
- self._sv = sv
813
- return self
814
- if k == 2 and np.array_equal(U, _SWAP_MATRIX):
815
- sv = np.ascontiguousarray(self._sv, dtype=complex)
816
- _hdqs_native.apply_swap(sv, bitpos[0], bitpos[1])
817
- self._sv = sv
818
- return self
819
- sv = np.ascontiguousarray(self._sv, dtype=complex)
820
- if k <= 4:
821
- _hdqs_native.apply_gate_fast(sv, bitpos, U)
822
- else:
823
- _hdqs_native.apply_gate(sv, bitpos, U, [])
824
- self._sv = sv
825
- return self
826
783
  Ub = self._to_backend(U)
827
784
  xp = self._xp
828
785
  if not self._use_density_matrix:
@@ -843,37 +800,6 @@ class HDQS:
843
800
  target: int) -> "HDQS":
844
801
  """Apply single-qubit U to *target* conditioned on all controls = 1."""
845
802
  controls = list(controls)
846
- if (not self._use_density_matrix and _NATIVE_AVAILABLE
847
- and not self._backend_torch):
848
- U = np.asarray(U, dtype=complex)
849
- # The native kernel takes an explicit control list, so a
850
- # multi-controlled gate (mcx with many controls, crz, ...)
851
- # never needs the 2^(k+1)x2^(k+1) padded-identity matrix the
852
- # NumPy path below has to materialize -- this is an
853
- # algorithmic win, not just a constant-factor one.
854
- n = self.num_qubits
855
- bit_target = [n - 1 - target]
856
- bit_controls = [n - 1 - c for c in controls]
857
- if len(controls) == 1 and np.array_equal(U, _X):
858
- # pure index-swap, no arithmetic -- fastest path for the
859
- # single most common 2-qubit gate
860
- sv = np.ascontiguousarray(self._sv, dtype=complex)
861
- _hdqs_native.apply_cnot_fast(sv, bit_controls[0], bit_target[0])
862
- self._sv = sv
863
- return self
864
- if len(controls) == 1:
865
- # covers cy/cz/cp/crx/cry/crz -- any single-control,
866
- # single-target gate with an arbitrary 2x2 U
867
- sv = np.ascontiguousarray(self._sv, dtype=complex)
868
- _hdqs_native.apply_controlled_1q(
869
- sv, bit_controls[0], bit_target[0],
870
- U[0, 0], U[0, 1], U[1, 0], U[1, 1])
871
- self._sv = sv
872
- return self
873
- sv = np.ascontiguousarray(self._sv, dtype=complex)
874
- _hdqs_native.apply_gate(sv, bit_target, U, bit_controls)
875
- self._sv = sv
876
- return self
877
803
  full = np.eye(2 ** (len(controls) + 1), dtype=complex)
878
804
  full[-2:, -2:] = np.asarray(U, dtype=complex)
879
805
  # target=LSB, controls=upper bits (see _apply_matrix_on_axes's
@@ -904,13 +830,6 @@ class HDQS:
904
830
  f"{name}: Kraus dimension {ks[0].shape[0]} does not match "
905
831
  f"{k} target qubit(s)")
906
832
  n = self.num_qubits
907
- if k == 1 and _NATIVE_AVAILABLE and not self._backend_torch:
908
- bitpos = n - 1 - targets[0]
909
- rho = np.ascontiguousarray(self._rho, dtype=complex)
910
- _hdqs_native.apply_kraus_1q(rho, n, bitpos, ks)
911
- self._rho = _renormalize_rho(rho)
912
- self._is_pure = self._check_purity()
913
- return self
914
833
  xp = self._xp
915
834
  T = self._rho.reshape((2,) * (2 * n))
916
835
  col_axes = [n + t for t in targets]
@@ -938,8 +857,22 @@ class HDQS:
938
857
 
939
858
  # -- single-qubit gates ---------------------------------------------------
940
859
 
941
- def h(self, q: int) -> "HDQS":
942
- return self._apply_unitary(_H, [q])
860
+ def _resolve_optional_qubit(self, q: Optional[int]) -> int:
861
+ """Resolve an omitted qubit index for the zero-parameter single-qubit
862
+ gates (h/x/y/z/s/sdg/t/tdg) to the sole qubit of a 1-qubit register.
863
+ Deliberately not extended to angle-parameter gates (rx/ry/rz/p/u3):
864
+ making their qubit index optional would require either reordering
865
+ their argument list or making the angle keyword-only, either of
866
+ which breaks existing call sites. Ambiguity always raises rather
867
+ than silently defaulting."""
868
+ if q is not None:
869
+ return q
870
+ if self.num_qubits != 1:
871
+ raise ValueError(
872
+ "qubit index is required when the register has more than "
873
+ f"one qubit ({self.num_qubits} qubits here); only a "
874
+ "1-qubit register can omit it")
875
+ return 0
943
876
 
944
877
  # -- circuit logging --------------------------------------------------
945
878
 
@@ -955,42 +888,50 @@ class HDQS:
955
888
 
956
889
  # -- single-qubit gates -----------------------------------------------
957
890
 
958
- def h(self, q: int) -> "HDQS":
891
+ def h(self, q: Optional[int] = None) -> "HDQS":
892
+ q = self._resolve_optional_qubit(q)
959
893
  self._log(f"H({q})")
960
894
  self._ops.append(("h", [q], []))
961
895
  return self._apply_unitary(_H, [q])
962
896
 
963
- def x(self, q: int) -> "HDQS":
897
+ def x(self, q: Optional[int] = None) -> "HDQS":
898
+ q = self._resolve_optional_qubit(q)
964
899
  self._log(f"X({q})")
965
900
  self._ops.append(("x", [q], []))
966
901
  return self._apply_unitary(_X, [q])
967
902
 
968
- def y(self, q: int) -> "HDQS":
903
+ def y(self, q: Optional[int] = None) -> "HDQS":
904
+ q = self._resolve_optional_qubit(q)
969
905
  self._log(f"Y({q})")
970
906
  self._ops.append(("y", [q], []))
971
907
  return self._apply_unitary(_Y, [q])
972
908
 
973
- def z(self, q: int) -> "HDQS":
909
+ def z(self, q: Optional[int] = None) -> "HDQS":
910
+ q = self._resolve_optional_qubit(q)
974
911
  self._log(f"Z({q})")
975
912
  self._ops.append(("z", [q], []))
976
913
  return self._apply_unitary(_Z, [q])
977
914
 
978
- def s(self, q: int) -> "HDQS":
915
+ def s(self, q: Optional[int] = None) -> "HDQS":
916
+ q = self._resolve_optional_qubit(q)
979
917
  self._log(f"S({q})")
980
918
  self._ops.append(("s", [q], []))
981
919
  return self._apply_unitary(_S, [q])
982
920
 
983
- def sdg(self, q: int) -> "HDQS":
921
+ def sdg(self, q: Optional[int] = None) -> "HDQS":
922
+ q = self._resolve_optional_qubit(q)
984
923
  self._log(f"Sdg({q})")
985
924
  self._ops.append(("sdg", [q], []))
986
925
  return self._apply_unitary(GATES["SDG"], [q])
987
926
 
988
- def t(self, q: int) -> "HDQS":
927
+ def t(self, q: Optional[int] = None) -> "HDQS":
928
+ q = self._resolve_optional_qubit(q)
989
929
  self._log(f"T({q})")
990
930
  self._ops.append(("t", [q], []))
991
931
  return self._apply_unitary(_T, [q])
992
932
 
993
- def tdg(self, q: int) -> "HDQS":
933
+ def tdg(self, q: Optional[int] = None) -> "HDQS":
934
+ q = self._resolve_optional_qubit(q)
994
935
  self._log(f"Tdg({q})")
995
936
  self._ops.append(("tdg", [q], []))
996
937
  return self._apply_unitary(GATES["TDG"], [q])
@@ -1130,6 +1071,46 @@ class HDQS:
1130
1071
  q, generalized_amplitude_damping_channel(p, gamma),
1131
1072
  "generalized_amplitude_damping")
1132
1073
 
1074
+ _NOISE_CHANNEL_METHODS = frozenset({
1075
+ "bit_flip", "phase_flip", "depolarizing",
1076
+ "amplitude_damping", "phase_damping",
1077
+ })
1078
+
1079
+ def with_noise(self, channel: str, *params) -> "HDQS":
1080
+ """Return a NEW HDQS instance (this one is left untouched) that
1081
+ replays every gate applied so far, inserting `channel` on every
1082
+ qubit each gate touches, right after that gate. One-liner analog
1083
+ of Cirq's `circuit.with_noise(cirq.depolarize(0.01))`; scoped to
1084
+ the single-qubit channels already on this class (`channel` is one
1085
+ of "bit_flip"/"phase_flip"/"depolarizing"/"amplitude_damping"/
1086
+ "phase_damping", `*params` are that channel's own arguments, e.g.
1087
+ `with_noise("depolarizing", 0.01)`).
1088
+
1089
+ Because HDQS executes eagerly rather than building a circuit
1090
+ object first, "insert noise after the fact" means replaying the
1091
+ recorded gate history (self._ops) onto a fresh density-matrix
1092
+ instance rather than mutating a DAG in place -- the result is the
1093
+ same circuit, but noise-affected, and this instance's own state
1094
+ is unchanged. For gates that don't record every touched qubit
1095
+ symmetrically (e.g. mcx's controls), noise is applied to every
1096
+ qubit index the gate's own _ops entry lists, matching the common
1097
+ "apply a gate-error channel per qubit the gate acts on"
1098
+ convention used by most noisy-simulation tooling.
1099
+ """
1100
+ if channel not in self._NOISE_CHANNEL_METHODS:
1101
+ raise ValueError(
1102
+ f"with_noise: unknown channel {channel!r}; must be one "
1103
+ f"of {sorted(self._NOISE_CHANNEL_METHODS)}")
1104
+ noisy = HDQS(self.num_qubits, use_density_matrix=True)
1105
+ channel_method = getattr(noisy, channel)
1106
+ for name, qubits, gate_params in self._ops:
1107
+ if name in ("measure", "reset", "barrier"):
1108
+ continue # non-unitary/no-op entries, nothing to replay
1109
+ _qasm3_dispatch_gate(noisy, name, qubits, gate_params)
1110
+ for q in qubits:
1111
+ channel_method(q, *params)
1112
+ return noisy
1113
+
1133
1114
  def thermal_noise(self, q: int, p: float, T: float = 1.0) -> "HDQS":
1134
1115
  self._log(f"Thermal({q},p={p:.3g},T={T:.3g})")
1135
1116
  return self._apply_noise(q, thermal_noise_channel(p, T), "thermal")
@@ -1334,21 +1315,42 @@ class HDQS:
1334
1315
 
1335
1316
  # -- analysis -------------------------------------------------------------
1336
1317
 
1337
- def expectation(self, pauli_string: str) -> float:
1338
- """Expectation value of a Pauli string like 'ZZI' (qubit 0 leftmost).
1318
+ def expectation(self, pauli_string: str,
1319
+ on: Optional[Sequence[int]] = None) -> float:
1320
+ """Expectation value of a Pauli string.
1321
+
1322
+ Two forms:
1323
+ q.expectation("ZZI") # full string, one letter per qubit
1324
+ q.expectation("Z", on=1) # short form: Pauli letters at the
1325
+ q.expectation("ZZ", on=[0,2]) # given qubit(s), identity elsewhere
1326
+ `on` accepts a single int or a sequence the same length as
1327
+ `pauli_string`; every other qubit is treated as I. The short form
1328
+ exists so a Pauli acting on a handful of qubits out of a large
1329
+ register doesn't require hand-writing an identity-padded string
1330
+ the length of the whole register.
1339
1331
 
1340
1332
  Computed by axis-contraction in both modes and both backends —
1341
1333
  v0.3.0 built a dense 2^n x 2^n operator (and raised
1342
1334
  NotImplementedError on the GPU pure-state path)."""
1343
- s = pauli_string.upper()
1344
- if len(s) != self.num_qubits:
1335
+ n = self.num_qubits
1336
+ if on is not None:
1337
+ on = [on] if isinstance(on, int) else list(on)
1338
+ if len(on) != len(pauli_string):
1339
+ raise ValueError(
1340
+ f"pauli_string has {len(pauli_string)} letter(s) but "
1341
+ f"on= gives {len(on)} qubit(s) -- these must match")
1342
+ letters = dict(zip(on, pauli_string.upper()))
1343
+ s = "".join(letters.get(q, "I") for q in range(n))
1344
+ else:
1345
+ s = pauli_string.upper()
1346
+ if len(s) != n:
1345
1347
  raise ValueError(
1346
- f"pauli string must have length {self.num_qubits}")
1348
+ f"pauli string must have length {n} qubits "
1349
+ "(or pass on= to place a short string on specific qubits)")
1347
1350
  for ch in s:
1348
1351
  if ch not in "IXYZ":
1349
1352
  raise ValueError(f"invalid Pauli letter: {ch}")
1350
1353
  xp = self._xp
1351
- n = self.num_qubits
1352
1354
  if not self._use_density_matrix:
1353
1355
  T = self._sv.reshape((2,) * n)
1354
1356
  for q, ch in enumerate(s):
@@ -2092,6 +2094,8 @@ def _qasm3_dispatch_gate(q: "HDQS", name: str, idx: List[int],
2092
2094
  q.ccx(idx[0], idx[1], idx[2])
2093
2095
  elif name == "cswap":
2094
2096
  q.cswap(idx[0], idx[1], idx[2])
2097
+ elif name == "mcx":
2098
+ q.mcx(idx[:-1], idx[-1])
2095
2099
  else:
2096
2100
  raise NotImplementedError(f"from_qasm3: gate {name!r}")
2097
2101
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sia-script
3
- Version: 1.0.3
3
+ Version: 1.3.0
4
4
  Summary: SIA — Simplified Integrated Architecture: SiaNTT, SIA-ELS (ABAP interpreter), the HDQS server client, and hdqs_engine (offline statevector quantum simulator — no server or API token required)
5
5
  Author-email: Sia Software Innovations Private Limited <info@siasoftwareinnovations.com>
6
6
  License: # PROPRIETARY SOFTWARE LICENSE
@@ -1,8 +1,6 @@
1
1
  LICENSE.md
2
- MANIFEST.in
3
2
  README.md
4
3
  pyproject.toml
5
- setup.py
6
4
  sia/__init__.py
7
5
  sia/els.py
8
6
  sia/hdqs.py
@@ -11,7 +9,5 @@ sia/ntt.py
11
9
  sia_script.egg-info/PKG-INFO
12
10
  sia_script.egg-info/SOURCES.txt
13
11
  sia_script.egg-info/dependency_links.txt
14
- sia_script.egg-info/not-zip-safe
15
12
  sia_script.egg-info/requires.txt
16
- sia_script.egg-info/top_level.txt
17
- src/hdqs_core.cpp
13
+ sia_script.egg-info/top_level.txt
@@ -1,3 +0,0 @@
1
- include src/*.cpp
2
- include README.md LICENSE.md pyproject.toml setup.py
3
- recursive-include sia *.py
sia_script-1.0.3/setup.py DELETED
@@ -1,60 +0,0 @@
1
- """
2
- Build script for the sia-script native extension (hdqs_core).
3
-
4
- This compiles src/hdqs_core.cpp into sia/hdqs_core.<platform-tag>.so via
5
- pybind11 -- the same mechanism Qiskit Aer and PennyLane's lightning.qubit
6
- use (a thin Python package wrapping a compiled kernel).
7
-
8
- Local development build:
9
- pip install pybind11
10
- python setup.py build_ext --inplace
11
-
12
- This places the compiled module directly in sia/, where hdqs_engine.py's
13
- `from . import hdqs_core` picks it up automatically. If the build fails or
14
- is skipped, hdqs_engine.py still works -- it falls back to the pure NumPy
15
- path and sets _NATIVE_AVAILABLE = False.
16
- """
17
- import sys
18
- from setuptools import setup, Extension
19
- from pybind11.setup_helpers import Pybind11Extension, build_ext
20
-
21
- # -O3 for standard optimization. -march=native/-mavx2/-mavx512f are
22
- # deliberately NOT used here, and no longer need to be: hdqs_core.cpp
23
- # now does its own runtime CPU dispatch (GCC/Clang function
24
- # multiversioning + __builtin_cpu_supports, checked once at first call)
25
- # for the four hottest kernels. This one build, compiled with these
26
- # plain flags, contains scalar/AVX2/AVX-512 code paths simultaneously
27
- # and picks the fastest one the *running* CPU actually supports --
28
- # verified: compiled with zero SIMD flags, still dispatches to AVX-512
29
- # on an AVX-512-capable machine, at the same speed as an explicitly
30
- # -mavx512f-flagged build. No crash risk on older CPUs, no separate
31
- # "fast" build needed. (This applies to GCC/Clang on x86_64 -- MSVC/
32
- # Windows builds don't yet get automatic dispatch and fall back to the
33
- # single compile-time path, i.e. scalar unless /arch:AVX2 is passed
34
- # explicitly -- a known, stated gap, not a regression from before.)
35
- extra_compile_args = ["-O3", "-std=c++17"]
36
- extra_link_args = []
37
-
38
- if sys.platform == "win32":
39
- extra_compile_args = ["/O2", "/std:c++17", "/openmp"]
40
- else:
41
- extra_compile_args += ["-fopenmp"]
42
- extra_link_args += ["-fopenmp"]
43
-
44
- ext_modules = [
45
- Pybind11Extension(
46
- "sia.hdqs_core",
47
- ["src/hdqs_core.cpp"],
48
- cxx_std=17,
49
- extra_compile_args=extra_compile_args,
50
- extra_link_args=extra_link_args,
51
- ),
52
- ]
53
-
54
- setup(
55
- packages=["sia"],
56
- package_dir={"sia": "sia"},
57
- ext_modules=ext_modules,
58
- cmdclass={"build_ext": build_ext},
59
- zip_safe=False,
60
- )