pyqrack-cpu-complex128 2.22.3__tar.gz → 2.23.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 (30) hide show
  1. {pyqrack_cpu_complex128-2.22.3/pyqrack_cpu_complex128.egg-info → pyqrack_cpu_complex128-2.23.0}/PKG-INFO +1 -1
  2. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/qrack_ace_backend.py +371 -21
  3. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/qrack_simulator.py +6 -0
  4. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0/pyqrack_cpu_complex128.egg-info}/PKG-INFO +1 -1
  5. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/setup.py +1 -1
  6. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/LICENSE +0 -0
  7. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/MANIFEST.in +0 -0
  8. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/Makefile +0 -0
  9. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/README.md +0 -0
  10. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyproject.toml +0 -0
  11. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/__init__.py +0 -0
  12. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/neuron_activation_fn.py +0 -0
  13. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/pauli.py +0 -0
  14. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/qrack_circuit.py +0 -0
  15. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/qrack_near_clifford_qec_backend.py +0 -0
  16. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/qrack_neuron.py +0 -0
  17. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/qrack_neuron_torch_layer.py +0 -0
  18. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/qrack_stabilizer.py +0 -0
  19. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/qrack_system/__init__.py +0 -0
  20. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/qrack_system/qrack_system.py +0 -0
  21. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/quimb_circuit_type.py +0 -0
  22. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/stats/__init__.py +0 -0
  23. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/stats/load_quantized_data.py +0 -0
  24. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack/stats/quantize_by_range.py +0 -0
  25. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack_cpu_complex128.egg-info/SOURCES.txt +0 -0
  26. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack_cpu_complex128.egg-info/dependency_links.txt +0 -0
  27. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack_cpu_complex128.egg-info/not-zip-safe +0 -0
  28. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack_cpu_complex128.egg-info/requires.txt +0 -0
  29. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/pyqrack_cpu_complex128.egg-info/top_level.txt +0 -0
  30. {pyqrack_cpu_complex128-2.22.3 → pyqrack_cpu_complex128-2.23.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.22.3
3
+ Version: 2.23.0
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -18,6 +18,17 @@ try:
18
18
  from qiskit.circuit.quantumcircuit import QuantumCircuit
19
19
  from qiskit.compiler import transpile
20
20
  from qiskit.quantum_info.operators.symplectic.clifford import Clifford
21
+ from qiskit.transpiler import Target, InstructionProperties
22
+ from qiskit.circuit import Parameter
23
+ from qiskit.circuit.library import (
24
+ IGate, UGate, U3Gate, U2Gate, U1Gate,
25
+ XGate, YGate, ZGate, HGate,
26
+ RXGate, RYGate, RZGate,
27
+ SGate, SdgGate, TGate, TdgGate,
28
+ CXGate, CYGate, CZGate, SwapGate, iSwapGate,
29
+ CCXGate, CCZGate, CSwapGate,
30
+ Measure, Reset,
31
+ )
21
32
  except ImportError:
22
33
  _IS_QISKIT_AVAILABLE = False
23
34
 
@@ -803,7 +814,6 @@ class QrackAceBackend:
803
814
 
804
815
  sim.mtrx([m00, m01, m10, m11], q)
805
816
 
806
- @staticmethod
807
817
  def _get_lhv_bloch_angles(sim):
808
818
  z = sim.bloch[2]
809
819
  x = sim.bloch[0]
@@ -812,7 +822,6 @@ class QrackAceBackend:
812
822
  azimuth = math.atan2(y, x)
813
823
  return azimuth, inclination
814
824
 
815
- @staticmethod
816
825
  def _rotate_lhv_to_bloch(sim, delta_azimuth, delta_inclination):
817
826
  cosA = math.cos(delta_azimuth)
818
827
  sinA = math.sin(delta_azimuth)
@@ -1713,16 +1722,68 @@ class QrackAceBackend:
1713
1722
  else:
1714
1723
  bits = [self.force_m(a, False) for a in ancs]
1715
1724
 
1716
- is_flipped = False
1717
- best_p = -1.0
1725
+ # Map each participant to the SPECIFIC physical replica
1726
+ # of hq1 its own syndrome actually covers -- anc1 is
1727
+ # patch A (hq1[0]), anc1c is always the crossbar
1728
+ # (hq1[2], by construction), and anc1b is whichever of
1729
+ # hq1's replicas lives in hq2's own patch (found by sim
1730
+ # id rather than assumed to be a fixed index, since
1731
+ # corner qubits have patch replicas at indices 1, 3, and
1732
+ # 4). This can legitimately be absent: hq1 and hq2 may
1733
+ # share no common patch at all for an arbitrary (non-
1734
+ # adjacent) logical coupling -- confirmed directly
1735
+ # (hq1=[(2,1),(3,0),(4,2)] coupled to hq2=[(1,1)], no
1736
+ # overlap) -- in which case there is no single physical
1737
+ # replica to target for that participant specifically.
1738
+ replicas = [hq1[0]]
1739
+ if anc1b is not None:
1740
+ replicas.append(next((r for r in hq1 if r[0] == hq2[0][0]), None))
1741
+ if anc1c is not None:
1742
+ replicas.append(hq1[2])
1743
+
1744
+ # Majority vote decides whether the LOGICAL value itself
1745
+ # genuinely flipped (updating the LHV shadow, same as the
1746
+ # transversal self.x(lq1) used to do unconditionally);
1747
+ # any minority participant is still individually
1748
+ # corrected below regardless, since ITS replica is wrong
1749
+ # either way -- it just isn't treated as evidence the
1750
+ # logical value moved, the same way a repetition code's
1751
+ # decoded value doesn't change just because one physical
1752
+ # bit needed fixing.
1753
+ n_flipped = sum(1 for b in bits if b)
1754
+ if (n_flipped << 1) > len(ancs):
1755
+ is_flipped = True
1756
+ lhv = self._lhv.get(lq1)
1757
+ if lhv is not None:
1758
+ lhv.x()
1759
+
1718
1760
  for i, a in enumerate(ancs):
1719
1761
  if bits[i]:
1720
1762
  self.x(a)
1721
- if probs[i] > best_p:
1722
- best_p = probs[i]
1723
- if best_p >= 0.0:
1724
- self.x(lq1)
1725
- is_flipped = True
1763
+ # Targeted, single-replica correction rather than
1764
+ # a transversal self.x(lq1): these replicas are a
1765
+ # pseudo-repetition code for lq1, and a
1766
+ # participant flipping means THAT PHYSICAL COPY
1767
+ # is the one in error, not the logical value as a
1768
+ # whole. Flipping every replica transversally is
1769
+ # only ever correct by coincidence when all
1770
+ # participants agree -- on a genuine minority
1771
+ # disagreement it would "fix" the actual error
1772
+ # while simultaneously breaking the (correct)
1773
+ # majority replicas that were never wrong. Fixing
1774
+ # per-replica is correct in the agreeing case too
1775
+ # (each flipped participant's own replica still
1776
+ # gets corrected), so this applies unconditionally,
1777
+ # not just when a disagreement is detected. Falls
1778
+ # back to the transversal correction only when no
1779
+ # single hq1 replica corresponds to this
1780
+ # participant at all (see the no-overlap case
1781
+ # above).
1782
+ b = replicas[i]
1783
+ if b is not None:
1784
+ self.sim[b[0]].x(b[1])
1785
+ else:
1786
+ self.x(lq1)
1726
1787
  else:
1727
1788
  anc_sim, anc_idx = self._qubits[anc1][0]
1728
1789
  p = self.sim[anc_sim].prob(anc_idx)
@@ -1918,46 +1979,174 @@ class QrackAceBackend:
1918
1979
  self._cpauli(lq1, lq2, True, Pauli.PauliZ)
1919
1980
 
1920
1981
  def mcx(self, lq1, lq2):
1921
- if len(lq1) > 1:
1982
+ if len(lq1) == 1:
1983
+ self.cx(lq1[0], lq2)
1984
+ return
1985
+
1986
+ hq = self._unpack(lq2)
1987
+ if len(hq) > 1:
1922
1988
  raise RuntimeError(
1923
1989
  "QrackAceBackend.mcx() is provided for syntax convenience and only supports 1 control qubit!"
1924
1990
  )
1925
- self.cx(lq1[0], lq2)
1991
+ s = hq[0][0]
1992
+ t = hq[0][1]
1993
+ c = []
1994
+ for l in lq1:
1995
+ hq = self._unpack(l)
1996
+ if (len(hq) > 1) or (s != hq[0][0]):
1997
+ raise RuntimeError(
1998
+ "QrackAceBackend.mcx() is provided for syntax convenience and only supports 1 control qubit!"
1999
+ )
2000
+ c.append(hq[0][1])
2001
+
2002
+ # We happen to be able to accommodate this:
2003
+ self.sim[s].mcx(c, t)
1926
2004
 
1927
2005
  def mcy(self, lq1, lq2):
1928
- if len(lq1) > 1:
2006
+ if len(lq1) == 1:
2007
+ self.cy(lq1[0], lq2)
2008
+ return
2009
+
2010
+ hq = self._unpack(lq2)
2011
+ if len(hq) > 1:
1929
2012
  raise RuntimeError(
1930
2013
  "QrackAceBackend.mcy() is provided for syntax convenience and only supports 1 control qubit!"
1931
2014
  )
1932
- self.cy(lq1[0], lq2)
2015
+ s = hq[0][0]
2016
+ t = hq[0][1]
2017
+ c = []
2018
+ for l in lq1:
2019
+ hq = self._unpack(l)
2020
+ if (len(hq) > 1) or (s != hq[0][0]):
2021
+ raise RuntimeError(
2022
+ "QrackAceBackend.mcy() is provided for syntax convenience and only supports 1 control qubit!"
2023
+ )
2024
+ c.append(hq[0][1])
2025
+
2026
+ # We happen to be able to accommodate this:
2027
+ self.sim[s].mcy(c, t)
1933
2028
 
1934
2029
  def mcz(self, lq1, lq2):
1935
- if len(lq1) > 1:
2030
+ if len(lq1) == 1:
2031
+ self.cz(lq1[0], lq2)
2032
+ return
2033
+
2034
+ hq = self._unpack(lq2)
2035
+ if len(hq) > 1:
1936
2036
  raise RuntimeError(
1937
2037
  "QrackAceBackend.mcz() is provided for syntax convenience and only supports 1 control qubit!"
1938
2038
  )
1939
- self.cz(lq1[0], lq2)
2039
+ s = hq[0][0]
2040
+ t = hq[0][1]
2041
+ c = []
2042
+ for l in lq1:
2043
+ hq = self._unpack(l)
2044
+ if (len(hq) > 1) or (s != hq[0][0]):
2045
+ raise RuntimeError(
2046
+ "QrackAceBackend.mcz() is provided for syntax convenience and only supports 1 control qubit!"
2047
+ )
2048
+ c.append(hq[0][1])
2049
+
2050
+ # We happen to be able to accommodate this:
2051
+ self.sim[s].mcz(c, t)
1940
2052
 
1941
2053
  def macx(self, lq1, lq2):
1942
- if len(lq1) > 1:
2054
+ if len(lq1) == 1:
2055
+ self.acx(lq1[0], lq2)
2056
+ return
2057
+
2058
+ hq = self._unpack(lq2)
2059
+ if len(hq) > 1:
1943
2060
  raise RuntimeError(
1944
2061
  "QrackAceBackend.macx() is provided for syntax convenience and only supports 1 control qubit!"
1945
2062
  )
1946
- self.acx(lq1[0], lq2)
2063
+ s = hq[0][0]
2064
+ t = hq[0][1]
2065
+ c = []
2066
+ for l in lq1:
2067
+ hq = self._unpack(l)
2068
+ if (len(hq) > 1) or (s != hq[0][0]):
2069
+ raise RuntimeError(
2070
+ "QrackAceBackend.macx() is provided for syntax convenience and only supports 1 control qubit!"
2071
+ )
2072
+ c.append(hq[0][1])
2073
+
2074
+ # We happen to be able to accommodate this:
2075
+ self.sim[s].macx(c, t)
1947
2076
 
1948
2077
  def macy(self, lq1, lq2):
1949
- if len(lq1) > 1:
2078
+ if len(lq1) == 1:
2079
+ self.acy(lq1[0], lq2)
2080
+ return
2081
+
2082
+ hq = self._unpack(lq2)
2083
+ if len(hq) > 1:
1950
2084
  raise RuntimeError(
1951
2085
  "QrackAceBackend.macy() is provided for syntax convenience and only supports 1 control qubit!"
1952
2086
  )
1953
- self.acy(lq1[0], lq2)
2087
+ s = hq[0][0]
2088
+ t = hq[0][1]
2089
+ c = []
2090
+ for l in lq1:
2091
+ hq = self._unpack(l)
2092
+ if (len(hq) > 1) or (s != hq[0][0]):
2093
+ raise RuntimeError(
2094
+ "QrackAceBackend.macy() is provided for syntax convenience and only supports 1 control qubit!"
2095
+ )
2096
+ c.append(hq[0][1])
2097
+
2098
+ # We happen to be able to accommodate this:
2099
+ self.sim[s].macy(c, t)
1954
2100
 
1955
2101
  def macz(self, lq1, lq2):
1956
- if len(lq1) > 1:
2102
+ if len(lq1) == 1:
2103
+ self.acz(lq1[0], lq2)
2104
+ return
2105
+
2106
+ hq = self._unpack(lq2)
2107
+ if len(hq) > 1:
1957
2108
  raise RuntimeError(
1958
2109
  "QrackAceBackend.macz() is provided for syntax convenience and only supports 1 control qubit!"
1959
2110
  )
1960
- self.acz(lq1[0], lq2)
2111
+ s = hq[0][0]
2112
+ t = hq[0][1]
2113
+ c = []
2114
+ for l in lq1:
2115
+ hq = self._unpack(l)
2116
+ if (len(hq) > 1) or (s != hq[0][0]):
2117
+ raise RuntimeError(
2118
+ "QrackAceBackend.macz() is provided for syntax convenience and only supports 1 control qubit!"
2119
+ )
2120
+ c.append(hq[0][1])
2121
+
2122
+ # We happen to be able to accommodate this:
2123
+ self.sim[s].macz(c, t)
2124
+
2125
+ def cswap(self, lq1, lq2, lq3):
2126
+ hq = self._unpack(lq2)
2127
+ if len(hq) > 1:
2128
+ raise RuntimeError(
2129
+ "QrackAceBackend.cswap() is provided for syntax convenience and only supports intra-patch!"
2130
+ )
2131
+ s = hq[0][0]
2132
+ t1 = hq[0][1]
2133
+ hq = self._unpack(lq3)
2134
+ if (len(hq) > 1) or (s != hq[0][0]):
2135
+ raise RuntimeError(
2136
+ "QrackAceBackend.cswap() is provided for syntax convenience and only supports intra-patch!"
2137
+ )
2138
+ t2 = hq[0][1]
2139
+ c = []
2140
+ for l in lq1:
2141
+ hq = self._unpack(l)
2142
+ if (len(hq) > 1) or (s != hq[0][0]):
2143
+ raise RuntimeError(
2144
+ "QrackAceBackend.cswap() is provided for syntax convenience and only supports intra-patch!"
2145
+ )
2146
+ c.append(hq[0][1])
2147
+
2148
+ # We happen to be able to accommodate this:
2149
+ self.sim[s].cswap(c, t1, t2)
1961
2150
 
1962
2151
  def swap(self, lq1, lq2):
1963
2152
  hq1 = self._unpack(lq1)
@@ -2383,11 +2572,23 @@ class QrackAceBackend:
2383
2572
  self._sim.cy(operation.qubits[0]._index, operation.qubits[1]._index)
2384
2573
  elif name == "cz":
2385
2574
  self._sim.cz(operation.qubits[0]._index, operation.qubits[1]._index)
2575
+ elif name == "ccx":
2576
+ self._sim.mcx([operation.qubits[0]._index, operation.qubits[1]._index], operation.qubits[2]._index)
2577
+ elif name == "ccz":
2578
+ self._sim.mcz([operation.qubits[0]._index, operation.qubits[1]._index], operation.qubits[2]._index)
2579
+ elif name == "mcx":
2580
+ self._sim.mcx([q._index for q in operation.qubits[:-1]], operation.qubits[-1]._index)
2581
+ elif name == "mcy":
2582
+ self._sim.mcy([q._index for q in operation.qubits[:-1]], operation.qubits[-1]._index)
2583
+ elif name == "mcz":
2584
+ self._sim.mcz([q._index for q in operation.qubits[:-1]], operation.qubits[-1]._index)
2386
2585
  elif name == "dcx":
2387
2586
  self._sim.mcx(operation.qubits[0]._index, operation.qubits[1]._index)
2388
2587
  self._sim.mcx(operation.qubits[1]._index, operation.qubits[0]._index)
2389
2588
  elif name == "swap":
2390
2589
  self._sim.swap(operation.qubits[0]._index, operation.qubits[1]._index)
2590
+ elif name == "cswap":
2591
+ self._sim.cswap([q._index for q in operation.qubits[:-2]], operation.qubits[-2]._index, operation.qubits[-1]._index)
2391
2592
  elif name == "iswap":
2392
2593
  self._sim.iswap(operation.qubits[0]._index, operation.qubits[1]._index)
2393
2594
  elif name == "iswap_dg":
@@ -2627,7 +2828,13 @@ class QrackAceBackend:
2627
2828
  "cx",
2628
2829
  "cy",
2629
2830
  "cz",
2831
+ "ccx",
2832
+ "ccz",
2833
+ "mcx",
2834
+ "mcy",
2835
+ "mcz",
2630
2836
  "swap",
2837
+ "cswap",
2631
2838
  "iswap",
2632
2839
  "reset",
2633
2840
  "measure",
@@ -2670,6 +2877,149 @@ class QrackAceBackend:
2670
2877
 
2671
2878
  return self._coupling_map
2672
2879
 
2880
+ # Moved here from AceQasmSimulator (qiskit-qrack-provider), so a
2881
+ # QrackAceBackend can be transpiled against directly with plain Qiskit,
2882
+ # without needing the separate Qiskit BackendV2 wrapper at all. The
2883
+ # Qiskit backend now calls this instead of duplicating the logic.
2884
+ #
2885
+ # One correction made in the move: the original 1-qubit "boundary
2886
+ # penalty hint" set was approximated from get_logical_coupling_map()
2887
+ # (any qubit appearing in ANY coupling pair) -- but that map includes
2888
+ # ordinary bulk-bulk pairs within the same patch too, so it actually
2889
+ # flagged nearly every qubit as "boundary." Here, with direct access to
2890
+ # self._unpack(), the real criterion (len(self._unpack(q)) > 1) is used
2891
+ # instead.
2892
+ def get_target(self, description=None, error_2q=0.5):
2893
+ """Build a qiskit Target describing this backend's gate set and
2894
+ connectivity, so it can be used with qiskit.transpile() directly --
2895
+ with or without the separate Qiskit BackendV2 wrapper.
2896
+
2897
+ IMPORTANT, verified directly rather than assumed: qiskit's
2898
+ transpile() does NOT reliably enforce the qubit-tuple-level
2899
+ InstructionProperties restrictions declared here for 3+-qubit gates
2900
+ (ccx/ccz/cswap), at any optimization_level that doesn't outright
2901
+ crash. Confirmed: the default optimization_level raises
2902
+ TranspilerError("target contains multi-qubit operations") on ANY
2903
+ Target containing 3+-qubit gates, valid placement or not (Qiskit's
2904
+ default SabreLayout routing pass doesn't support them at all).
2905
+ optimization_level=0 or 1 avoid that crash, but ALSO transpiled a
2906
+ deliberately-invalid (boundary-qubit) ccx placement successfully in
2907
+ testing -- i.e., they don't validate 3-qubit placements against the
2908
+ target either, they just don't crash on them.
2909
+ The Target's 3-qubit entries are still meaningful as a description
2910
+ of what's actually valid (useful for introspection, or for a
2911
+ transpiler pass that does check placements properly), but they are
2912
+ NOT currently a transpile-time safety guarantee the way the 2-qubit
2913
+ coupling map is. The actual, reliable enforcement is the runtime
2914
+ RuntimeError in mcx()/mcy()/mcz()/cswap() themselves, which fire
2915
+ correctly regardless of what the transpiler allowed through.
2916
+ """
2917
+ if not _IS_QISKIT_AVAILABLE:
2918
+ raise RuntimeError(
2919
+ "QrackAceBackend.get_target() requires Qiskit to be installed!"
2920
+ )
2921
+
2922
+ n = self.num_qubits()
2923
+ tgt = Target(num_qubits=n, description=description)
2924
+
2925
+ # Parameters for parameterised gates
2926
+ theta = Parameter("theta")
2927
+ phi = Parameter("phi")
2928
+ lam = Parameter("lam")
2929
+
2930
+ # --- single-qubit gates: all qubits ---
2931
+ boundary = {q for q in range(n) if len(self._unpack(q)) > 1}
2932
+
2933
+ def _1q_props():
2934
+ return {
2935
+ (q,): InstructionProperties()
2936
+ for q in range(n)
2937
+ }
2938
+
2939
+ tgt.add_instruction(IGate(), _1q_props())
2940
+ tgt.add_instruction(UGate(theta, phi, lam), _1q_props())
2941
+ tgt.add_instruction(U3Gate(theta, phi, lam), _1q_props())
2942
+ tgt.add_instruction(U2Gate(phi, lam), _1q_props())
2943
+ tgt.add_instruction(U1Gate(lam), _1q_props())
2944
+ tgt.add_instruction(RXGate(theta), _1q_props())
2945
+ tgt.add_instruction(RYGate(theta), _1q_props())
2946
+ tgt.add_instruction(RZGate(theta), _1q_props())
2947
+ tgt.add_instruction(HGate(), _1q_props())
2948
+ tgt.add_instruction(XGate(), _1q_props())
2949
+ tgt.add_instruction(YGate(), _1q_props())
2950
+ tgt.add_instruction(ZGate(), _1q_props())
2951
+ tgt.add_instruction(SGate(), _1q_props())
2952
+ tgt.add_instruction(SdgGate(), _1q_props())
2953
+ tgt.add_instruction(TGate(), _1q_props())
2954
+ tgt.add_instruction(TdgGate(), _1q_props())
2955
+
2956
+ # --- two-qubit gates: coupled pairs (includes boundary-crossing
2957
+ # pairs -- those are supported, via the noisy-coupler mechanism) ---
2958
+ bulk = []
2959
+ for q in range(n):
2960
+ hq = self._unpack(q)
2961
+ if len(hq) == 1:
2962
+ bulk.append(q)
2963
+ coupling_map = self.get_logical_coupling_map()
2964
+ if coupling_map:
2965
+ pair_props = {
2966
+ (a, b): InstructionProperties() if (a in bulk) and (b in bulk) else InstructionProperties(error=error_2q) for a, b in coupling_map
2967
+ }
2968
+ else:
2969
+ pair_props = {
2970
+ (a, b): InstructionProperties()
2971
+ for a in range(n)
2972
+ for b in range(n)
2973
+ if a != b
2974
+ }
2975
+
2976
+ tgt.add_instruction(CXGate(), pair_props)
2977
+ tgt.add_instruction(CYGate(), pair_props)
2978
+ tgt.add_instruction(CZGate(), pair_props)
2979
+ tgt.add_instruction(SwapGate(), pair_props)
2980
+ tgt.add_instruction(iSwapGate(), pair_props)
2981
+
2982
+ # --- 3-qubit gates: intra-patch, all-bulk triples only ---
2983
+ # ccx/ccz/cswap (and, by extension, mcx/mcy/mcz beyond a single
2984
+ # control) are only valid when every involved qubit is bulk and
2985
+ # shares the same patch -- see mcx()/cswap() above, which raise
2986
+ # otherwise.
2987
+ #
2988
+ # CCXGate/CCZGate and CSwapGate do NOT share a qubit-ordering
2989
+ # convention (verified directly against their gate definitions):
2990
+ # CCXGate puts its target on qubit index 2, with 0-1 as the two
2991
+ # (interchangeable) controls; CSwapGate puts its control on qubit
2992
+ # index 0, with 1-2 as the (interchangeable) swapped pair. They
2993
+ # need separately-ordered InstructionProperties dicts.
2994
+ # bulk_by_patch = {}
2995
+ # for q in range(n):
2996
+ # hq = self._unpack(q)
2997
+ # if len(hq) == 1:
2998
+ # bulk_by_patch.setdefault(hq[0][0], []).append(q)
2999
+ #
3000
+ # ccx_props = {}
3001
+ # cswap_props = {}
3002
+ # for qubits_here in bulk_by_patch.values():
3003
+ # for pivot in qubits_here:
3004
+ # others = [q for q in qubits_here if q != pivot]
3005
+ # for i in range(len(others)):
3006
+ # for j in range(len(others)):
3007
+ # if i == j:
3008
+ # continue
3009
+ # ccx_props[(others[i], others[j], pivot)] = InstructionProperties()
3010
+ # cswap_props[(pivot, others[i], others[j])] = InstructionProperties()
3011
+ # if ccx_props:
3012
+ # tgt.add_instruction(CCXGate(), ccx_props)
3013
+ # tgt.add_instruction(CCZGate(), ccx_props)
3014
+ # if cswap_props:
3015
+ # tgt.add_instruction(CSwapGate(), cswap_props)
3016
+
3017
+ # --- measure / reset: all qubits ---
3018
+ tgt.add_instruction(Measure(), {(q,): InstructionProperties() for q in range(n)})
3019
+ tgt.add_instruction(Reset(), {(q,): InstructionProperties() for q in range(n)})
3020
+
3021
+ return tgt
3022
+
2673
3023
  # Designed by Dan, Elara (ChatGPT), and (Anthropic) Claude:
2674
3024
  def create_noise_model(self, x=0.5, y=0.5):
2675
3025
  if not _IS_QISKIT_AER_AVAILABLE:
@@ -4311,6 +4311,8 @@ class QrackSimulator:
4311
4311
  self._sim.mcz([q._index for q in operation.qubits[0:-1]], operation.qubits[-1]._index)
4312
4312
  elif name == "swap":
4313
4313
  self._sim.swap(operation.qubits[0]._index, operation.qubits[1]._index)
4314
+ elif name == "cswap":
4315
+ self._sim.cswap([q._index for q in operation.qubits[:-2]], operation.qubits[-2]._index, operation.qubits[-1]._index)
4314
4316
  elif name == "iswap":
4315
4317
  self._sim.iswap(operation.qubits[0]._index, operation.qubits[1]._index)
4316
4318
  elif name == "iswap_dg":
@@ -4568,7 +4570,11 @@ class QrackSimulator:
4568
4570
  "csx",
4569
4571
  "ccx",
4570
4572
  "ccz",
4573
+ "mcx",
4574
+ "mcy",
4575
+ "mcz",
4571
4576
  "swap",
4577
+ "cswap",
4572
4578
  "iswap",
4573
4579
  "cswap",
4574
4580
  "reset",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqrack-cpu-complex128
3
- Version: 2.22.3
3
+ Version: 2.23.0
4
4
  Summary: pyqrack - Pure Python vm6502q/qrack Wrapper
5
5
  Home-page: https://github.com/vm6502q/pyqrack
6
6
  Author: Daniel Strano
@@ -7,7 +7,7 @@ from setuptools import setup
7
7
  from setuptools.command.build_py import build_py
8
8
 
9
9
 
10
- VERSION = "2.22.3"
10
+ VERSION = "2.23.0"
11
11
 
12
12
  # Read long description from README.
13
13
  README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.md')