sia-script 1.0.2__tar.gz → 1.0.3__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.
- {sia_script-1.0.2/sia_script.egg-info → sia_script-1.0.3}/PKG-INFO +1 -1
- {sia_script-1.0.2 → sia_script-1.0.3}/pyproject.toml +1 -1
- sia_script-1.0.3/setup.py +60 -0
- {sia_script-1.0.2 → sia_script-1.0.3/sia_script.egg-info}/PKG-INFO +1 -1
- {sia_script-1.0.2 → sia_script-1.0.3}/src/hdqs_core.cpp +345 -235
- sia_script-1.0.2/setup.py +0 -48
- {sia_script-1.0.2 → sia_script-1.0.3}/LICENSE.md +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/MANIFEST.in +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/README.md +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/setup.cfg +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/sia/__init__.py +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/sia/els.py +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/sia/hdqs.py +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/sia/hdqs_engine.py +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/sia/ntt.py +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/sia_script.egg-info/SOURCES.txt +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/sia_script.egg-info/dependency_links.txt +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/sia_script.egg-info/not-zip-safe +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/sia_script.egg-info/requires.txt +0 -0
- {sia_script-1.0.2 → sia_script-1.0.3}/sia_script.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sia-script
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sia-script"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.3"
|
|
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" }
|
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sia-script
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
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,13 +1,32 @@
|
|
|
1
1
|
// hdqs_core.cpp — generalized native kernels for HDQS.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// Runtime CPU dispatch: the four hottest kernels (apply_1q,
|
|
4
|
+
// apply_cnot_fast, apply_controlled_1q, apply_swap) are each compiled
|
|
5
|
+
// in three ISA-specific variants -- scalar, AVX2+FMA, AVX-512F+FMA --
|
|
6
|
+
// inside a single binary, using per-function __attribute__((target(...)))
|
|
7
|
+
// (GCC/Clang function multiversioning). Which variant actually runs is
|
|
8
|
+
// decided once, at first call, by checking the *running* CPU's features
|
|
9
|
+
// via __builtin_cpu_supports -- not by which flags the file happened to
|
|
10
|
+
// be compiled with. This is what lets one published wheel be fast on a
|
|
11
|
+
// machine with AVX-512, fast (one tier down) on a machine with only
|
|
12
|
+
// AVX2, and still correct (just slower) on a machine with neither,
|
|
13
|
+
// without ever risking an "illegal instruction" crash from running
|
|
14
|
+
// AVX-512 code on a CPU that doesn't have it -- the dispatcher only
|
|
15
|
+
// picks a tier the CPU actually reports supporting.
|
|
8
16
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
17
|
+
// Platform note: this technique (__attribute__((target(...))) +
|
|
18
|
+
// __builtin_cpu_supports) is GCC/Clang-specific and validated on Linux
|
|
19
|
+
// x86_64 in this session. MSVC has no equivalent for mixing ISA levels
|
|
20
|
+
// within one translation unit the same way; on an MSVC build, these
|
|
21
|
+
// four kernels fall back to the single compile-time-selected path
|
|
22
|
+
// (scalar unless the build explicitly passes /arch:AVX2, exactly as
|
|
23
|
+
// before this change) -- see _DISPATCH_AVAILABLE below. This is a real,
|
|
24
|
+
// stated gap, not silently glossed over: Windows wheels do not yet get
|
|
25
|
+
// automatic runtime dispatch.
|
|
26
|
+
//
|
|
27
|
+
// apply_gate, apply_kraus_1q, and apply_gate_fast (the less-hot general
|
|
28
|
+
// fallback paths) are unchanged from the compile-time-flag scheme --
|
|
29
|
+
// runtime dispatch on those is a smaller win and out of scope here.
|
|
11
30
|
#include <pybind11/pybind11.h>
|
|
12
31
|
#include <pybind11/numpy.h>
|
|
13
32
|
#include <pybind11/stl.h>
|
|
@@ -16,7 +35,7 @@
|
|
|
16
35
|
#include <algorithm>
|
|
17
36
|
#include <cstdint>
|
|
18
37
|
#include <stdexcept>
|
|
19
|
-
#if defined(
|
|
38
|
+
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)
|
|
20
39
|
#include <immintrin.h>
|
|
21
40
|
#endif
|
|
22
41
|
#if defined(_OPENMP)
|
|
@@ -26,15 +45,15 @@
|
|
|
26
45
|
namespace py = pybind11;
|
|
27
46
|
using cplx = std::complex<double>;
|
|
28
47
|
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
48
|
+
// GCC and Clang both define __GNUC__ (Clang for compatibility) and both
|
|
49
|
+
// support __attribute__((target(...))) + __builtin_cpu_supports on x86
|
|
50
|
+
// with the same syntax -- this one check covers both compilers.
|
|
51
|
+
#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__))
|
|
52
|
+
#define _DISPATCH_AVAILABLE 1
|
|
53
|
+
#else
|
|
54
|
+
#define _DISPATCH_AVAILABLE 0
|
|
55
|
+
#endif
|
|
56
|
+
|
|
38
57
|
static inline bool _omp_worth_it() {
|
|
39
58
|
#if defined(_OPENMP)
|
|
40
59
|
static const bool worth_it = (omp_get_max_threads() > 1);
|
|
@@ -44,6 +63,24 @@ static inline bool _omp_worth_it() {
|
|
|
44
63
|
#endif
|
|
45
64
|
}
|
|
46
65
|
|
|
66
|
+
// 0 = scalar only, 1 = AVX2+FMA available, 2 = AVX-512F+FMA available.
|
|
67
|
+
// Computed once (the CPUID check itself has real cost -- cache it).
|
|
68
|
+
static inline int _cpu_tier() {
|
|
69
|
+
#if _DISPATCH_AVAILABLE
|
|
70
|
+
static const int tier = [] {
|
|
71
|
+
__builtin_cpu_init();
|
|
72
|
+
if (__builtin_cpu_supports("avx512f") && __builtin_cpu_supports("fma"))
|
|
73
|
+
return 2;
|
|
74
|
+
if (__builtin_cpu_supports("avx2") && __builtin_cpu_supports("fma"))
|
|
75
|
+
return 1;
|
|
76
|
+
return 0;
|
|
77
|
+
}();
|
|
78
|
+
return tier;
|
|
79
|
+
#else
|
|
80
|
+
return 0;
|
|
81
|
+
#endif
|
|
82
|
+
}
|
|
83
|
+
|
|
47
84
|
// Scatter the bits of `value` into `positions` (given LSB-first), leaving
|
|
48
85
|
// all other bits as given by `skeleton`.
|
|
49
86
|
static inline int64_t scatter_bits(int64_t skeleton,
|
|
@@ -56,27 +93,16 @@ static inline int64_t scatter_bits(int64_t skeleton,
|
|
|
56
93
|
return idx;
|
|
57
94
|
}
|
|
58
95
|
|
|
59
|
-
|
|
60
|
-
// --
|
|
61
|
-
// --
|
|
62
|
-
// --
|
|
63
|
-
|
|
64
|
-
// -- (measured single-threaded). apply_gate above remains the fallback --
|
|
65
|
-
// -- for every other gate shape (multi-qubit, multi-control). -----------
|
|
66
|
-
|
|
67
|
-
#if defined(__AVX2__)
|
|
68
|
-
// Complex multiplication via the standard "addsub" trick: packs 2
|
|
69
|
-
// complex doubles per __m256d and computes both products in one shot
|
|
70
|
-
// using shuffle + mul + addsub, operating directly on the interleaved
|
|
71
|
-
// std::complex<double> memory layout (no separate real/imag arrays).
|
|
72
|
-
// Measured ~2.9-3.6x faster than the scalar loop on realistic mixed-
|
|
73
|
-
// bit-position circuits (the auto-vectorizer could get partial credit
|
|
74
|
-
// on the block-iteration structure, per -fopt-info-vec, but never the
|
|
75
|
-
// complex multiply itself -- "no vectype for stmt" on the arithmetic).
|
|
96
|
+
#if _DISPATCH_AVAILABLE
|
|
97
|
+
// -- AVX2 helpers: compiled with AVX2 codegen regardless of the file's
|
|
98
|
+
// -- default -march setting, via the target attribute. Complex multiply
|
|
99
|
+
// -- via the "addsub" trick, as validated earlier this session.
|
|
100
|
+
__attribute__((target("avx2,fma")))
|
|
76
101
|
static inline __m256d _bcast_complex(cplx c) {
|
|
77
102
|
return _mm256_setr_pd(c.real(), c.imag(), c.real(), c.imag());
|
|
78
103
|
}
|
|
79
104
|
|
|
105
|
+
__attribute__((target("avx2,fma")))
|
|
80
106
|
static inline __m256d _complex_mul(__m256d a, __m256d b) {
|
|
81
107
|
__m256d b_re = _mm256_shuffle_pd(b, b, 0x0);
|
|
82
108
|
__m256d b_im = _mm256_shuffle_pd(b, b, 0xF);
|
|
@@ -85,23 +111,15 @@ static inline __m256d _complex_mul(__m256d a, __m256d b) {
|
|
|
85
111
|
__m256d t2 = _mm256_mul_pd(a_swap, b_im);
|
|
86
112
|
return _mm256_addsub_pd(t1, t2);
|
|
87
113
|
}
|
|
88
|
-
#endif
|
|
89
114
|
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
// port (mul + mask-blend) measured *slower* than AVX2 -- the extra
|
|
94
|
-
// blend instruction outweighed the wider register. Using fmaddsub
|
|
95
|
-
// instead (native on AVX-512, fuses the multiply and the alternating
|
|
96
|
-
// add/sub into one instruction) recovers the win: ~1.18-1.21x faster
|
|
97
|
-
// than AVX2 at n>=16, roughly tied below that. Pre-splitting each
|
|
98
|
-
// gate-matrix entry into separate re/im broadcast constants (outside
|
|
99
|
-
// the hot loop, once per gate call) avoids repeating that shuffle
|
|
100
|
-
// 4 times per inner-loop iteration.
|
|
115
|
+
// -- AVX-512 helpers: fmaddsub-based, as validated (naive mul+blend
|
|
116
|
+
// -- measured slower than AVX2, this recovers the win).
|
|
117
|
+
__attribute__((target("avx512f,fma")))
|
|
101
118
|
static inline __m512d _bcast_complex512(cplx c) {
|
|
102
119
|
return _mm512_set4_pd(c.imag(), c.real(), c.imag(), c.real());
|
|
103
120
|
}
|
|
104
121
|
|
|
122
|
+
__attribute__((target("avx512f,fma")))
|
|
105
123
|
static inline __m512d _complex_mul_fma512(__m512d a, __m512d b_re,
|
|
106
124
|
__m512d b_im, __m512d a_swap) {
|
|
107
125
|
__m512d t2 = _mm512_mul_pd(a_swap, b_im);
|
|
@@ -109,53 +127,67 @@ static inline __m512d _complex_mul_fma512(__m512d a, __m512d b_re,
|
|
|
109
127
|
}
|
|
110
128
|
#endif
|
|
111
129
|
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
int bitpos, cplx u00, cplx u01, cplx u10, cplx u11) {
|
|
119
|
-
auto buf = state.request();
|
|
120
|
-
cplx* sv = static_cast<cplx*>(buf.ptr);
|
|
121
|
-
int64_t dim = buf.shape[0];
|
|
122
|
-
int64_t block = int64_t(1) << bitpos;
|
|
130
|
+
// ---------------------------------------------------------------------
|
|
131
|
+
// apply_1q: single-qubit gate via contiguous-block iteration.
|
|
132
|
+
// ---------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
static void _apply_1q_scalar(cplx* sv, int64_t dim, int64_t block,
|
|
135
|
+
cplx u00, cplx u01, cplx u10, cplx u11) {
|
|
123
136
|
int64_t num_blocks = dim / (2 * block);
|
|
137
|
+
#pragma omp parallel for schedule(static) if(_omp_worth_it())
|
|
138
|
+
for (int64_t blk = 0; blk < num_blocks; blk++) {
|
|
139
|
+
int64_t base = blk * 2 * block;
|
|
140
|
+
cplx* p0 = sv + base;
|
|
141
|
+
cplx* p1 = sv + base + block;
|
|
142
|
+
for (int64_t j = 0; j < block; j++) {
|
|
143
|
+
cplx a0 = p0[j], a1 = p1[j];
|
|
144
|
+
p0[j] = u00 * a0 + u01 * a1;
|
|
145
|
+
p1[j] = u10 * a0 + u11 * a1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
124
149
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
150
|
+
#if _DISPATCH_AVAILABLE
|
|
151
|
+
__attribute__((target("avx2,fma")))
|
|
152
|
+
static void _apply_1q_avx2(cplx* sv, int64_t dim, int64_t block,
|
|
153
|
+
cplx u00, cplx u01, cplx u10, cplx u11) {
|
|
154
|
+
int64_t num_blocks = dim / (2 * block);
|
|
155
|
+
if (block < 4) { _apply_1q_scalar(sv, dim, block, u00, u01, u10, u11); return; }
|
|
156
|
+
__m256d U00 = _bcast_complex(u00), U01 = _bcast_complex(u01);
|
|
157
|
+
__m256d U10 = _bcast_complex(u10), U11 = _bcast_complex(u11);
|
|
158
|
+
#pragma omp parallel for schedule(static) if(_omp_worth_it())
|
|
159
|
+
for (int64_t blk = 0; blk < num_blocks; blk++) {
|
|
160
|
+
int64_t base = blk * 2 * block;
|
|
161
|
+
double* p0 = reinterpret_cast<double*>(sv + base);
|
|
162
|
+
double* p1 = reinterpret_cast<double*>(sv + base + block);
|
|
163
|
+
int64_t j = 0;
|
|
164
|
+
for (; j + 2 <= block; j += 2) {
|
|
165
|
+
__m256d a0 = _mm256_loadu_pd(p0 + 2 * j);
|
|
166
|
+
__m256d a1 = _mm256_loadu_pd(p1 + 2 * j);
|
|
167
|
+
__m256d new_p0 = _mm256_add_pd(_complex_mul(a0, U00), _complex_mul(a1, U01));
|
|
168
|
+
__m256d new_p1 = _mm256_add_pd(_complex_mul(a0, U10), _complex_mul(a1, U11));
|
|
169
|
+
_mm256_storeu_pd(p0 + 2 * j, new_p0);
|
|
170
|
+
_mm256_storeu_pd(p1 + 2 * j, new_p1);
|
|
171
|
+
}
|
|
172
|
+
for (; j < block; j++) {
|
|
173
|
+
cplx a0v = sv[base + j], a1v = sv[base + block + j];
|
|
174
|
+
sv[base + j] = u00 * a0v + u01 * a1v;
|
|
175
|
+
sv[base + block + j] = u10 * a0v + u11 * a1v;
|
|
147
176
|
}
|
|
148
|
-
return;
|
|
149
177
|
}
|
|
178
|
+
}
|
|
150
179
|
|
|
151
|
-
|
|
180
|
+
__attribute__((target("avx512f,fma")))
|
|
181
|
+
static void _apply_1q_avx512(cplx* sv, int64_t dim, int64_t block,
|
|
182
|
+
cplx u00, cplx u01, cplx u10, cplx u11) {
|
|
183
|
+
int64_t num_blocks = dim / (2 * block);
|
|
184
|
+
if (block < 4) { _apply_1q_scalar(sv, dim, block, u00, u01, u10, u11); return; }
|
|
152
185
|
__m512d U00 = _bcast_complex512(u00), U01 = _bcast_complex512(u01);
|
|
153
186
|
__m512d U10 = _bcast_complex512(u10), U11 = _bcast_complex512(u11);
|
|
154
187
|
__m512d U00re = _mm512_shuffle_pd(U00, U00, 0x00), U00im = _mm512_shuffle_pd(U00, U00, 0xFF);
|
|
155
188
|
__m512d U01re = _mm512_shuffle_pd(U01, U01, 0x00), U01im = _mm512_shuffle_pd(U01, U01, 0xFF);
|
|
156
189
|
__m512d U10re = _mm512_shuffle_pd(U10, U10, 0x00), U10im = _mm512_shuffle_pd(U10, U10, 0xFF);
|
|
157
190
|
__m512d U11re = _mm512_shuffle_pd(U11, U11, 0x00), U11im = _mm512_shuffle_pd(U11, U11, 0xFF);
|
|
158
|
-
|
|
159
191
|
#pragma omp parallel for schedule(static) if(_omp_worth_it())
|
|
160
192
|
for (int64_t blk = 0; blk < num_blocks; blk++) {
|
|
161
193
|
int64_t base = blk * 2 * block;
|
|
@@ -180,81 +212,58 @@ void apply_1q(py::array_t<cplx, py::array::c_style | py::array::forcecast> state
|
|
|
180
212
|
sv[base + block + j] = u10 * a0v + u11 * a1v;
|
|
181
213
|
}
|
|
182
214
|
}
|
|
183
|
-
#elif defined(__AVX2__)
|
|
184
|
-
__m256d U00 = _bcast_complex(u00), U01 = _bcast_complex(u01);
|
|
185
|
-
__m256d U10 = _bcast_complex(u10), U11 = _bcast_complex(u11);
|
|
186
|
-
|
|
187
|
-
#pragma omp parallel for schedule(static) if(_omp_worth_it())
|
|
188
|
-
for (int64_t blk = 0; blk < num_blocks; blk++) {
|
|
189
|
-
int64_t base = blk * 2 * block;
|
|
190
|
-
double* p0 = reinterpret_cast<double*>(sv + base);
|
|
191
|
-
double* p1 = reinterpret_cast<double*>(sv + base + block);
|
|
192
|
-
int64_t j = 0;
|
|
193
|
-
for (; j + 2 <= block; j += 2) {
|
|
194
|
-
__m256d a0 = _mm256_loadu_pd(p0 + 2 * j);
|
|
195
|
-
__m256d a1 = _mm256_loadu_pd(p1 + 2 * j);
|
|
196
|
-
__m256d new_p0 = _mm256_add_pd(_complex_mul(a0, U00), _complex_mul(a1, U01));
|
|
197
|
-
__m256d new_p1 = _mm256_add_pd(_complex_mul(a0, U10), _complex_mul(a1, U11));
|
|
198
|
-
_mm256_storeu_pd(p0 + 2 * j, new_p0);
|
|
199
|
-
_mm256_storeu_pd(p1 + 2 * j, new_p1);
|
|
200
|
-
}
|
|
201
|
-
for (; j < block; j++) {
|
|
202
|
-
cplx a0v = sv[base + j], a1v = sv[base + block + j];
|
|
203
|
-
sv[base + j] = u00 * a0v + u01 * a1v;
|
|
204
|
-
sv[base + block + j] = u10 * a0v + u11 * a1v;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
#else
|
|
208
|
-
#pragma omp parallel for schedule(static) if(_omp_worth_it())
|
|
209
|
-
for (int64_t blk = 0; blk < num_blocks; blk++) {
|
|
210
|
-
int64_t base = blk * 2 * block;
|
|
211
|
-
cplx* p0 = sv + base;
|
|
212
|
-
cplx* p1 = sv + base + block;
|
|
213
|
-
for (int64_t j = 0; j < block; j++) {
|
|
214
|
-
cplx a0 = p0[j], a1 = p1[j];
|
|
215
|
-
p0[j] = u00 * a0 + u01 * a1;
|
|
216
|
-
p1[j] = u10 * a0 + u11 * a1;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
#endif
|
|
220
215
|
}
|
|
216
|
+
#endif
|
|
221
217
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// target -- stays contiguous regardless of which of the two is larger.
|
|
225
|
-
// AVX2 path: swaps 2 amplitude pairs per iteration via wide load/store
|
|
226
|
-
// (pure data movement, no arithmetic, so this is close to free once
|
|
227
|
-
// vectorized -- the win is fewer, wider memory transactions).
|
|
228
|
-
void apply_cnot_fast(py::array_t<cplx, py::array::c_style | py::array::forcecast> state,
|
|
229
|
-
int control_bitpos, int target_bitpos) {
|
|
218
|
+
void apply_1q(py::array_t<cplx, py::array::c_style | py::array::forcecast> state,
|
|
219
|
+
int bitpos, cplx u00, cplx u01, cplx u10, cplx u11) {
|
|
230
220
|
auto buf = state.request();
|
|
231
221
|
cplx* sv = static_cast<cplx*>(buf.ptr);
|
|
232
222
|
int64_t dim = buf.shape[0];
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
223
|
+
int64_t block = int64_t(1) << bitpos;
|
|
224
|
+
|
|
225
|
+
#if _DISPATCH_AVAILABLE
|
|
226
|
+
int tier = _cpu_tier();
|
|
227
|
+
if (tier >= 2) { _apply_1q_avx512(sv, dim, block, u00, u01, u10, u11); return; }
|
|
228
|
+
if (tier >= 1) { _apply_1q_avx2(sv, dim, block, u00, u01, u10, u11); return; }
|
|
229
|
+
#endif
|
|
230
|
+
_apply_1q_scalar(sv, dim, block, u00, u01, u10, u11);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---------------------------------------------------------------------
|
|
234
|
+
// apply_cnot_fast: pure data movement, no arithmetic -- same dispatch.
|
|
235
|
+
// ---------------------------------------------------------------------
|
|
237
236
|
|
|
237
|
+
static void _apply_cnot_scalar(cplx* sv, int64_t dim, int lo, int hi,
|
|
238
|
+
int64_t lo_bit, int64_t cbit, int64_t tbit) {
|
|
238
239
|
int64_t n_outer = dim >> (hi + 1);
|
|
239
240
|
int64_t n_mid = int64_t(1) << (hi - lo - 1);
|
|
241
|
+
#pragma omp parallel for collapse(2) schedule(static) if(_omp_worth_it())
|
|
242
|
+
for (int64_t outer = 0; outer < n_outer; outer++) {
|
|
243
|
+
for (int64_t mid = 0; mid < n_mid; mid++) {
|
|
244
|
+
int64_t skeleton = (outer << (hi + 1)) | (mid << (lo + 1));
|
|
245
|
+
cplx* p0 = sv + skeleton + cbit;
|
|
246
|
+
cplx* p1 = p0 + tbit;
|
|
247
|
+
for (int64_t j = 0; j < lo_bit; j++) std::swap(p0[j], p1[j]);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
240
251
|
|
|
252
|
+
#if _DISPATCH_AVAILABLE
|
|
253
|
+
__attribute__((target("avx2,fma")))
|
|
254
|
+
static void _apply_cnot_avx2(cplx* sv, int64_t dim, int lo, int hi,
|
|
255
|
+
int64_t lo_bit, int64_t cbit, int64_t tbit) {
|
|
256
|
+
int64_t n_outer = dim >> (hi + 1);
|
|
257
|
+
int64_t n_mid = int64_t(1) << (hi - lo - 1);
|
|
241
258
|
#pragma omp parallel for collapse(2) schedule(static) if(_omp_worth_it())
|
|
242
259
|
for (int64_t outer = 0; outer < n_outer; outer++) {
|
|
243
260
|
for (int64_t mid = 0; mid < n_mid; mid++) {
|
|
244
261
|
int64_t skeleton = (outer << (hi + 1)) | (mid << (lo + 1));
|
|
245
|
-
cplx*
|
|
246
|
-
cplx*
|
|
247
|
-
cplx* p1 = base + tbit;
|
|
248
|
-
#if defined(__AVX512F__)
|
|
262
|
+
cplx* p0 = sv + skeleton + cbit;
|
|
263
|
+
cplx* p1 = p0 + tbit;
|
|
249
264
|
double* d0 = reinterpret_cast<double*>(p0);
|
|
250
265
|
double* d1 = reinterpret_cast<double*>(p1);
|
|
251
266
|
int64_t j = 0;
|
|
252
|
-
for (; j + 4 <= lo_bit; j += 4) {
|
|
253
|
-
__m512d a = _mm512_loadu_pd(d0 + 2 * j);
|
|
254
|
-
__m512d b = _mm512_loadu_pd(d1 + 2 * j);
|
|
255
|
-
_mm512_storeu_pd(d0 + 2 * j, b);
|
|
256
|
-
_mm512_storeu_pd(d1 + 2 * j, a);
|
|
257
|
-
}
|
|
258
267
|
for (; j + 2 <= lo_bit; j += 2) {
|
|
259
268
|
__m256d a = _mm256_loadu_pd(d0 + 2 * j);
|
|
260
269
|
__m256d b = _mm256_loadu_pd(d1 + 2 * j);
|
|
@@ -262,10 +271,30 @@ void apply_cnot_fast(py::array_t<cplx, py::array::c_style | py::array::forcecast
|
|
|
262
271
|
_mm256_storeu_pd(d1 + 2 * j, a);
|
|
263
272
|
}
|
|
264
273
|
for (; j < lo_bit; j++) std::swap(p0[j], p1[j]);
|
|
265
|
-
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
__attribute__((target("avx512f,fma")))
|
|
279
|
+
static void _apply_cnot_avx512(cplx* sv, int64_t dim, int lo, int hi,
|
|
280
|
+
int64_t lo_bit, int64_t cbit, int64_t tbit) {
|
|
281
|
+
int64_t n_outer = dim >> (hi + 1);
|
|
282
|
+
int64_t n_mid = int64_t(1) << (hi - lo - 1);
|
|
283
|
+
#pragma omp parallel for collapse(2) schedule(static) if(_omp_worth_it())
|
|
284
|
+
for (int64_t outer = 0; outer < n_outer; outer++) {
|
|
285
|
+
for (int64_t mid = 0; mid < n_mid; mid++) {
|
|
286
|
+
int64_t skeleton = (outer << (hi + 1)) | (mid << (lo + 1));
|
|
287
|
+
cplx* p0 = sv + skeleton + cbit;
|
|
288
|
+
cplx* p1 = p0 + tbit;
|
|
266
289
|
double* d0 = reinterpret_cast<double*>(p0);
|
|
267
290
|
double* d1 = reinterpret_cast<double*>(p1);
|
|
268
291
|
int64_t j = 0;
|
|
292
|
+
for (; j + 4 <= lo_bit; j += 4) {
|
|
293
|
+
__m512d a = _mm512_loadu_pd(d0 + 2 * j);
|
|
294
|
+
__m512d b = _mm512_loadu_pd(d1 + 2 * j);
|
|
295
|
+
_mm512_storeu_pd(d0 + 2 * j, b);
|
|
296
|
+
_mm512_storeu_pd(d1 + 2 * j, a);
|
|
297
|
+
}
|
|
269
298
|
for (; j + 2 <= lo_bit; j += 2) {
|
|
270
299
|
__m256d a = _mm256_loadu_pd(d0 + 2 * j);
|
|
271
300
|
__m256d b = _mm256_loadu_pd(d1 + 2 * j);
|
|
@@ -273,12 +302,28 @@ void apply_cnot_fast(py::array_t<cplx, py::array::c_style | py::array::forcecast
|
|
|
273
302
|
_mm256_storeu_pd(d1 + 2 * j, a);
|
|
274
303
|
}
|
|
275
304
|
for (; j < lo_bit; j++) std::swap(p0[j], p1[j]);
|
|
276
|
-
#else
|
|
277
|
-
for (int64_t j = 0; j < lo_bit; j++) std::swap(p0[j], p1[j]);
|
|
278
|
-
#endif
|
|
279
305
|
}
|
|
280
306
|
}
|
|
281
307
|
}
|
|
308
|
+
#endif
|
|
309
|
+
|
|
310
|
+
void apply_cnot_fast(py::array_t<cplx, py::array::c_style | py::array::forcecast> state,
|
|
311
|
+
int control_bitpos, int target_bitpos) {
|
|
312
|
+
auto buf = state.request();
|
|
313
|
+
cplx* sv = static_cast<cplx*>(buf.ptr);
|
|
314
|
+
int64_t dim = buf.shape[0];
|
|
315
|
+
int lo = std::min(control_bitpos, target_bitpos);
|
|
316
|
+
int hi = std::max(control_bitpos, target_bitpos);
|
|
317
|
+
int64_t lo_bit = int64_t(1) << lo;
|
|
318
|
+
int64_t cbit = int64_t(1) << control_bitpos, tbit = int64_t(1) << target_bitpos;
|
|
319
|
+
|
|
320
|
+
#if _DISPATCH_AVAILABLE
|
|
321
|
+
int tier = _cpu_tier();
|
|
322
|
+
if (tier >= 2) { _apply_cnot_avx512(sv, dim, lo, hi, lo_bit, cbit, tbit); return; }
|
|
323
|
+
if (tier >= 1) { _apply_cnot_avx2(sv, dim, lo, hi, lo_bit, cbit, tbit); return; }
|
|
324
|
+
#endif
|
|
325
|
+
_apply_cnot_scalar(sv, dim, lo, hi, lo_bit, cbit, tbit);
|
|
326
|
+
}
|
|
282
327
|
|
|
283
328
|
// General statevector gate application.
|
|
284
329
|
//
|
|
@@ -390,68 +435,92 @@ void apply_kraus_1q(py::array_t<cplx, py::array::c_style | py::array::forcecast>
|
|
|
390
435
|
}
|
|
391
436
|
for (int64_t i = 0; i < dim*dim; i++) R[i] = new_rho[i];
|
|
392
437
|
}
|
|
393
|
-
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
cplx u00, cplx u01, cplx u10, cplx u11) {
|
|
402
|
-
auto buf = state.request();
|
|
403
|
-
cplx* sv = static_cast<cplx*>(buf.ptr);
|
|
404
|
-
int64_t dim = buf.shape[0];
|
|
405
|
-
int lo = std::min(control_bitpos, target_bitpos);
|
|
406
|
-
int hi = std::max(control_bitpos, target_bitpos);
|
|
407
|
-
int64_t lo_bit = int64_t(1) << lo;
|
|
408
|
-
int64_t cbit = int64_t(1) << control_bitpos, tbit = int64_t(1) << target_bitpos;
|
|
409
|
-
|
|
438
|
+
// ---------------------------------------------------------------------
|
|
439
|
+
// apply_controlled_1q: single-control single-target gate (cx/cy/cz/cp/
|
|
440
|
+
// crx/cry/crz), same dispatch pattern as apply_1q.
|
|
441
|
+
// ---------------------------------------------------------------------
|
|
442
|
+
|
|
443
|
+
static void _apply_c1q_scalar(cplx* sv, int64_t dim, int lo, int hi,
|
|
444
|
+
int64_t lo_bit, int64_t cbit, int64_t tbit,
|
|
445
|
+
cplx u00, cplx u01, cplx u10, cplx u11) {
|
|
410
446
|
int64_t n_outer = dim >> (hi + 1);
|
|
411
447
|
int64_t n_mid = int64_t(1) << (hi - lo - 1);
|
|
448
|
+
#pragma omp parallel for collapse(2) schedule(static) if(_omp_worth_it())
|
|
449
|
+
for (int64_t outer = 0; outer < n_outer; outer++) {
|
|
450
|
+
for (int64_t mid = 0; mid < n_mid; mid++) {
|
|
451
|
+
int64_t skeleton = (outer << (hi + 1)) | (mid << (lo + 1));
|
|
452
|
+
cplx* p0 = sv + skeleton + cbit;
|
|
453
|
+
cplx* p1 = p0 + tbit;
|
|
454
|
+
for (int64_t j = 0; j < lo_bit; j++) {
|
|
455
|
+
cplx a0 = p0[j], a1 = p1[j];
|
|
456
|
+
p0[j] = u00 * a0 + u01 * a1;
|
|
457
|
+
p1[j] = u10 * a0 + u11 * a1;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
412
462
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
463
|
+
#if _DISPATCH_AVAILABLE
|
|
464
|
+
__attribute__((target("avx2,fma")))
|
|
465
|
+
static void _apply_c1q_avx2(cplx* sv, int64_t dim, int lo, int hi,
|
|
466
|
+
int64_t lo_bit, int64_t cbit, int64_t tbit,
|
|
467
|
+
cplx u00, cplx u01, cplx u10, cplx u11) {
|
|
417
468
|
if (lo_bit < 4) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
469
|
+
_apply_c1q_scalar(sv, dim, lo, hi, lo_bit, cbit, tbit, u00, u01, u10, u11);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
int64_t n_outer = dim >> (hi + 1);
|
|
473
|
+
int64_t n_mid = int64_t(1) << (hi - lo - 1);
|
|
474
|
+
__m256d U00 = _bcast_complex(u00), U01 = _bcast_complex(u01);
|
|
475
|
+
__m256d U10 = _bcast_complex(u10), U11 = _bcast_complex(u11);
|
|
476
|
+
#pragma omp parallel for collapse(2) schedule(static) if(_omp_worth_it())
|
|
477
|
+
for (int64_t outer = 0; outer < n_outer; outer++) {
|
|
478
|
+
for (int64_t mid = 0; mid < n_mid; mid++) {
|
|
479
|
+
int64_t skeleton = (outer << (hi + 1)) | (mid << (lo + 1));
|
|
480
|
+
cplx* p0 = sv + skeleton + cbit;
|
|
481
|
+
cplx* p1 = p0 + tbit;
|
|
482
|
+
double* d0 = reinterpret_cast<double*>(p0);
|
|
483
|
+
double* d1 = reinterpret_cast<double*>(p1);
|
|
484
|
+
int64_t j = 0;
|
|
485
|
+
for (; j + 2 <= lo_bit; j += 2) {
|
|
486
|
+
__m256d a0 = _mm256_loadu_pd(d0 + 2 * j);
|
|
487
|
+
__m256d a1 = _mm256_loadu_pd(d1 + 2 * j);
|
|
488
|
+
__m256d new_p0 = _mm256_add_pd(_complex_mul(a0, U00), _complex_mul(a1, U01));
|
|
489
|
+
__m256d new_p1 = _mm256_add_pd(_complex_mul(a0, U10), _complex_mul(a1, U11));
|
|
490
|
+
_mm256_storeu_pd(d0 + 2 * j, new_p0);
|
|
491
|
+
_mm256_storeu_pd(d1 + 2 * j, new_p1);
|
|
492
|
+
}
|
|
493
|
+
for (; j < lo_bit; j++) {
|
|
494
|
+
cplx a0v = p0[j], a1v = p1[j];
|
|
495
|
+
p0[j] = u00 * a0v + u01 * a1v;
|
|
496
|
+
p1[j] = u10 * a0v + u11 * a1v;
|
|
430
497
|
}
|
|
431
498
|
}
|
|
432
|
-
return;
|
|
433
499
|
}
|
|
500
|
+
}
|
|
434
501
|
|
|
435
|
-
|
|
502
|
+
__attribute__((target("avx512f,fma")))
|
|
503
|
+
static void _apply_c1q_avx512(cplx* sv, int64_t dim, int lo, int hi,
|
|
504
|
+
int64_t lo_bit, int64_t cbit, int64_t tbit,
|
|
505
|
+
cplx u00, cplx u01, cplx u10, cplx u11) {
|
|
506
|
+
if (lo_bit < 4) {
|
|
507
|
+
_apply_c1q_scalar(sv, dim, lo, hi, lo_bit, cbit, tbit, u00, u01, u10, u11);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
int64_t n_outer = dim >> (hi + 1);
|
|
511
|
+
int64_t n_mid = int64_t(1) << (hi - lo - 1);
|
|
436
512
|
__m512d U00 = _bcast_complex512(u00), U01 = _bcast_complex512(u01);
|
|
437
513
|
__m512d U10 = _bcast_complex512(u10), U11 = _bcast_complex512(u11);
|
|
438
514
|
__m512d U00re = _mm512_shuffle_pd(U00, U00, 0x00), U00im = _mm512_shuffle_pd(U00, U00, 0xFF);
|
|
439
515
|
__m512d U01re = _mm512_shuffle_pd(U01, U01, 0x00), U01im = _mm512_shuffle_pd(U01, U01, 0xFF);
|
|
440
516
|
__m512d U10re = _mm512_shuffle_pd(U10, U10, 0x00), U10im = _mm512_shuffle_pd(U10, U10, 0xFF);
|
|
441
517
|
__m512d U11re = _mm512_shuffle_pd(U11, U11, 0x00), U11im = _mm512_shuffle_pd(U11, U11, 0xFF);
|
|
442
|
-
#elif defined(__AVX2__)
|
|
443
|
-
__m256d U00 = _bcast_complex(u00), U01 = _bcast_complex(u01);
|
|
444
|
-
__m256d U10 = _bcast_complex(u10), U11 = _bcast_complex(u11);
|
|
445
|
-
#endif
|
|
446
|
-
|
|
447
518
|
#pragma omp parallel for collapse(2) schedule(static) if(_omp_worth_it())
|
|
448
519
|
for (int64_t outer = 0; outer < n_outer; outer++) {
|
|
449
520
|
for (int64_t mid = 0; mid < n_mid; mid++) {
|
|
450
521
|
int64_t skeleton = (outer << (hi + 1)) | (mid << (lo + 1));
|
|
451
|
-
cplx*
|
|
452
|
-
cplx*
|
|
453
|
-
cplx* p1 = base + tbit;
|
|
454
|
-
#if defined(__AVX512F__)
|
|
522
|
+
cplx* p0 = sv + skeleton + cbit;
|
|
523
|
+
cplx* p1 = p0 + tbit;
|
|
455
524
|
double* d0 = reinterpret_cast<double*>(p0);
|
|
456
525
|
double* d1 = reinterpret_cast<double*>(p1);
|
|
457
526
|
int64_t j = 0;
|
|
@@ -472,67 +541,65 @@ void apply_controlled_1q(py::array_t<cplx, py::array::c_style | py::array::force
|
|
|
472
541
|
p0[j] = u00 * a0v + u01 * a1v;
|
|
473
542
|
p1[j] = u10 * a0v + u11 * a1v;
|
|
474
543
|
}
|
|
475
|
-
#elif defined(__AVX2__)
|
|
476
|
-
double* d0 = reinterpret_cast<double*>(p0);
|
|
477
|
-
double* d1 = reinterpret_cast<double*>(p1);
|
|
478
|
-
int64_t j = 0;
|
|
479
|
-
for (; j + 2 <= lo_bit; j += 2) {
|
|
480
|
-
__m256d a0 = _mm256_loadu_pd(d0 + 2 * j);
|
|
481
|
-
__m256d a1 = _mm256_loadu_pd(d1 + 2 * j);
|
|
482
|
-
__m256d new_p0 = _mm256_add_pd(_complex_mul(a0, U00), _complex_mul(a1, U01));
|
|
483
|
-
__m256d new_p1 = _mm256_add_pd(_complex_mul(a0, U10), _complex_mul(a1, U11));
|
|
484
|
-
_mm256_storeu_pd(d0 + 2 * j, new_p0);
|
|
485
|
-
_mm256_storeu_pd(d1 + 2 * j, new_p1);
|
|
486
|
-
}
|
|
487
|
-
for (; j < lo_bit; j++) {
|
|
488
|
-
cplx a0v = p0[j], a1v = p1[j];
|
|
489
|
-
p0[j] = u00 * a0v + u01 * a1v;
|
|
490
|
-
p1[j] = u10 * a0v + u11 * a1v;
|
|
491
|
-
}
|
|
492
|
-
#else
|
|
493
|
-
for (int64_t j = 0; j < lo_bit; j++) {
|
|
494
|
-
cplx a0 = p0[j], a1 = p1[j];
|
|
495
|
-
p0[j] = u00 * a0 + u01 * a1;
|
|
496
|
-
p1[j] = u10 * a0 + u11 * a1;
|
|
497
|
-
}
|
|
498
|
-
#endif
|
|
499
544
|
}
|
|
500
545
|
}
|
|
501
546
|
}
|
|
547
|
+
#endif
|
|
502
548
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
// apply_cnot_fast.
|
|
507
|
-
void apply_swap(py::array_t<cplx, py::array::c_style | py::array::forcecast> state,
|
|
508
|
-
int bitpos_a, int bitpos_b) {
|
|
549
|
+
void apply_controlled_1q(py::array_t<cplx, py::array::c_style | py::array::forcecast> state,
|
|
550
|
+
int control_bitpos, int target_bitpos,
|
|
551
|
+
cplx u00, cplx u01, cplx u10, cplx u11) {
|
|
509
552
|
auto buf = state.request();
|
|
510
553
|
cplx* sv = static_cast<cplx*>(buf.ptr);
|
|
511
554
|
int64_t dim = buf.shape[0];
|
|
512
|
-
int lo = std::min(
|
|
513
|
-
int hi = std::max(
|
|
555
|
+
int lo = std::min(control_bitpos, target_bitpos);
|
|
556
|
+
int hi = std::max(control_bitpos, target_bitpos);
|
|
514
557
|
int64_t lo_bit = int64_t(1) << lo;
|
|
515
|
-
int64_t
|
|
558
|
+
int64_t cbit = int64_t(1) << control_bitpos, tbit = int64_t(1) << target_bitpos;
|
|
516
559
|
|
|
560
|
+
#if _DISPATCH_AVAILABLE
|
|
561
|
+
int tier = _cpu_tier();
|
|
562
|
+
if (tier >= 2) { _apply_c1q_avx512(sv, dim, lo, hi, lo_bit, cbit, tbit, u00, u01, u10, u11); return; }
|
|
563
|
+
if (tier >= 1) { _apply_c1q_avx2(sv, dim, lo, hi, lo_bit, cbit, tbit, u00, u01, u10, u11); return; }
|
|
564
|
+
#endif
|
|
565
|
+
_apply_c1q_scalar(sv, dim, lo, hi, lo_bit, cbit, tbit, u00, u01, u10, u11);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// ---------------------------------------------------------------------
|
|
569
|
+
// apply_swap: pure data movement -- same dispatch pattern as
|
|
570
|
+
// apply_cnot_fast.
|
|
571
|
+
// ---------------------------------------------------------------------
|
|
572
|
+
|
|
573
|
+
static void _apply_swap_scalar(cplx* sv, int64_t dim, int lo, int hi,
|
|
574
|
+
int64_t lo_bit, int64_t a_bit, int64_t b_bit) {
|
|
517
575
|
int64_t n_outer = dim >> (hi + 1);
|
|
518
576
|
int64_t n_mid = int64_t(1) << (hi - lo - 1);
|
|
577
|
+
#pragma omp parallel for collapse(2) schedule(static) if(_omp_worth_it())
|
|
578
|
+
for (int64_t outer = 0; outer < n_outer; outer++) {
|
|
579
|
+
for (int64_t mid = 0; mid < n_mid; mid++) {
|
|
580
|
+
int64_t skeleton = (outer << (hi + 1)) | (mid << (lo + 1));
|
|
581
|
+
cplx* p_ab = sv + skeleton + a_bit;
|
|
582
|
+
cplx* p_ba = sv + skeleton + b_bit;
|
|
583
|
+
for (int64_t j = 0; j < lo_bit; j++) std::swap(p_ab[j], p_ba[j]);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
519
587
|
|
|
588
|
+
#if _DISPATCH_AVAILABLE
|
|
589
|
+
__attribute__((target("avx2,fma")))
|
|
590
|
+
static void _apply_swap_avx2(cplx* sv, int64_t dim, int lo, int hi,
|
|
591
|
+
int64_t lo_bit, int64_t a_bit, int64_t b_bit) {
|
|
592
|
+
int64_t n_outer = dim >> (hi + 1);
|
|
593
|
+
int64_t n_mid = int64_t(1) << (hi - lo - 1);
|
|
520
594
|
#pragma omp parallel for collapse(2) schedule(static) if(_omp_worth_it())
|
|
521
595
|
for (int64_t outer = 0; outer < n_outer; outer++) {
|
|
522
596
|
for (int64_t mid = 0; mid < n_mid; mid++) {
|
|
523
597
|
int64_t skeleton = (outer << (hi + 1)) | (mid << (lo + 1));
|
|
524
598
|
cplx* p_ab = sv + skeleton + a_bit;
|
|
525
599
|
cplx* p_ba = sv + skeleton + b_bit;
|
|
526
|
-
#if defined(__AVX512F__)
|
|
527
600
|
double* d_ab = reinterpret_cast<double*>(p_ab);
|
|
528
601
|
double* d_ba = reinterpret_cast<double*>(p_ba);
|
|
529
602
|
int64_t j = 0;
|
|
530
|
-
for (; j + 4 <= lo_bit; j += 4) {
|
|
531
|
-
__m512d a = _mm512_loadu_pd(d_ab + 2 * j);
|
|
532
|
-
__m512d b = _mm512_loadu_pd(d_ba + 2 * j);
|
|
533
|
-
_mm512_storeu_pd(d_ab + 2 * j, b);
|
|
534
|
-
_mm512_storeu_pd(d_ba + 2 * j, a);
|
|
535
|
-
}
|
|
536
603
|
for (; j + 2 <= lo_bit; j += 2) {
|
|
537
604
|
__m256d a = _mm256_loadu_pd(d_ab + 2 * j);
|
|
538
605
|
__m256d b = _mm256_loadu_pd(d_ba + 2 * j);
|
|
@@ -540,10 +607,30 @@ void apply_swap(py::array_t<cplx, py::array::c_style | py::array::forcecast> sta
|
|
|
540
607
|
_mm256_storeu_pd(d_ba + 2 * j, a);
|
|
541
608
|
}
|
|
542
609
|
for (; j < lo_bit; j++) std::swap(p_ab[j], p_ba[j]);
|
|
543
|
-
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
__attribute__((target("avx512f,fma")))
|
|
615
|
+
static void _apply_swap_avx512(cplx* sv, int64_t dim, int lo, int hi,
|
|
616
|
+
int64_t lo_bit, int64_t a_bit, int64_t b_bit) {
|
|
617
|
+
int64_t n_outer = dim >> (hi + 1);
|
|
618
|
+
int64_t n_mid = int64_t(1) << (hi - lo - 1);
|
|
619
|
+
#pragma omp parallel for collapse(2) schedule(static) if(_omp_worth_it())
|
|
620
|
+
for (int64_t outer = 0; outer < n_outer; outer++) {
|
|
621
|
+
for (int64_t mid = 0; mid < n_mid; mid++) {
|
|
622
|
+
int64_t skeleton = (outer << (hi + 1)) | (mid << (lo + 1));
|
|
623
|
+
cplx* p_ab = sv + skeleton + a_bit;
|
|
624
|
+
cplx* p_ba = sv + skeleton + b_bit;
|
|
544
625
|
double* d_ab = reinterpret_cast<double*>(p_ab);
|
|
545
626
|
double* d_ba = reinterpret_cast<double*>(p_ba);
|
|
546
627
|
int64_t j = 0;
|
|
628
|
+
for (; j + 4 <= lo_bit; j += 4) {
|
|
629
|
+
__m512d a = _mm512_loadu_pd(d_ab + 2 * j);
|
|
630
|
+
__m512d b = _mm512_loadu_pd(d_ba + 2 * j);
|
|
631
|
+
_mm512_storeu_pd(d_ab + 2 * j, b);
|
|
632
|
+
_mm512_storeu_pd(d_ba + 2 * j, a);
|
|
633
|
+
}
|
|
547
634
|
for (; j + 2 <= lo_bit; j += 2) {
|
|
548
635
|
__m256d a = _mm256_loadu_pd(d_ab + 2 * j);
|
|
549
636
|
__m256d b = _mm256_loadu_pd(d_ba + 2 * j);
|
|
@@ -551,12 +638,28 @@ void apply_swap(py::array_t<cplx, py::array::c_style | py::array::forcecast> sta
|
|
|
551
638
|
_mm256_storeu_pd(d_ba + 2 * j, a);
|
|
552
639
|
}
|
|
553
640
|
for (; j < lo_bit; j++) std::swap(p_ab[j], p_ba[j]);
|
|
554
|
-
#else
|
|
555
|
-
for (int64_t j = 0; j < lo_bit; j++) std::swap(p_ab[j], p_ba[j]);
|
|
556
|
-
#endif
|
|
557
641
|
}
|
|
558
642
|
}
|
|
559
643
|
}
|
|
644
|
+
#endif
|
|
645
|
+
|
|
646
|
+
void apply_swap(py::array_t<cplx, py::array::c_style | py::array::forcecast> state,
|
|
647
|
+
int bitpos_a, int bitpos_b) {
|
|
648
|
+
auto buf = state.request();
|
|
649
|
+
cplx* sv = static_cast<cplx*>(buf.ptr);
|
|
650
|
+
int64_t dim = buf.shape[0];
|
|
651
|
+
int lo = std::min(bitpos_a, bitpos_b);
|
|
652
|
+
int hi = std::max(bitpos_a, bitpos_b);
|
|
653
|
+
int64_t lo_bit = int64_t(1) << lo;
|
|
654
|
+
int64_t a_bit = int64_t(1) << bitpos_a, b_bit = int64_t(1) << bitpos_b;
|
|
655
|
+
|
|
656
|
+
#if _DISPATCH_AVAILABLE
|
|
657
|
+
int tier = _cpu_tier();
|
|
658
|
+
if (tier >= 2) { _apply_swap_avx512(sv, dim, lo, hi, lo_bit, a_bit, b_bit); return; }
|
|
659
|
+
if (tier >= 1) { _apply_swap_avx2(sv, dim, lo, hi, lo_bit, a_bit, b_bit); return; }
|
|
660
|
+
#endif
|
|
661
|
+
_apply_swap_scalar(sv, dim, lo, hi, lo_bit, a_bit, b_bit);
|
|
662
|
+
}
|
|
560
663
|
|
|
561
664
|
// General small-k fast kernel: replaces the slow scatter_bits-based
|
|
562
665
|
// apply_gate for k<=4 target qubits (any control count already goes
|
|
@@ -652,7 +755,6 @@ void apply_gate_fast(py::array_t<cplx, py::array::c_style | py::array::forcecast
|
|
|
652
755
|
}
|
|
653
756
|
}
|
|
654
757
|
|
|
655
|
-
|
|
656
758
|
PYBIND11_MODULE(hdqs_core, m) {
|
|
657
759
|
m.doc() = "HDQS native statevector/density-matrix kernels";
|
|
658
760
|
m.def("apply_1q", &apply_1q,
|
|
@@ -673,4 +775,12 @@ PYBIND11_MODULE(hdqs_core, m) {
|
|
|
673
775
|
"Apply a single-qubit Kraus channel to a density matrix, in place",
|
|
674
776
|
py::arg("rho"), py::arg("n_qubits"), py::arg("qubit"),
|
|
675
777
|
py::arg("kraus_ops"));
|
|
676
|
-
|
|
778
|
+
m.def("cpu_dispatch_tier", []() {
|
|
779
|
+
#if _DISPATCH_AVAILABLE
|
|
780
|
+
return _cpu_tier();
|
|
781
|
+
#else
|
|
782
|
+
return 0;
|
|
783
|
+
#endif
|
|
784
|
+
}, "0=scalar, 1=AVX2+FMA, 2=AVX-512F+FMA -- which kernel tier this "
|
|
785
|
+
"process actually selected at runtime on the current CPU");
|
|
786
|
+
}
|
sia_script-1.0.2/setup.py
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Adds the sia.hdqs_core native extension to the build. Everything else
|
|
3
|
-
(package metadata, dependencies, version) stays in pyproject.toml — this
|
|
4
|
-
file exists only because setuptools has no purely declarative way to
|
|
5
|
-
build a C++ extension from pyproject.toml alone.
|
|
6
|
-
|
|
7
|
-
`python -m build` picks this up automatically; no change to your
|
|
8
|
-
existing build/twine workflow.
|
|
9
|
-
"""
|
|
10
|
-
import sys
|
|
11
|
-
from setuptools import setup
|
|
12
|
-
from pybind11.setup_helpers import Pybind11Extension, build_ext
|
|
13
|
-
|
|
14
|
-
# -O3 for standard optimization. -march=native is deliberately NOT used
|
|
15
|
-
# here: it would tie the compiled wheel to the exact CPU that builds it,
|
|
16
|
-
# and could crash with an "illegal instruction" fault on an end user's
|
|
17
|
-
# different (often older) CPU. This keeps published wheels portable —
|
|
18
|
-
# hdqs_engine.py falls back to a slower but fully portable code path
|
|
19
|
-
# automatically when the fast (AVX2/AVX-512) instructions aren't
|
|
20
|
-
# available at compile time, so nothing breaks, it's just not maximally
|
|
21
|
-
# fast on every machine. Building locally with -march=native (e.g. for
|
|
22
|
-
# your own benchmarking) is a separate, deliberate choice — see the
|
|
23
|
-
# session notes on this if you want that build instead.
|
|
24
|
-
extra_compile_args = ["-O3", "-std=c++17"]
|
|
25
|
-
extra_link_args = []
|
|
26
|
-
|
|
27
|
-
if sys.platform == "win32":
|
|
28
|
-
extra_compile_args = ["/O2", "/std:c++17", "/openmp"]
|
|
29
|
-
else:
|
|
30
|
-
extra_compile_args += ["-fopenmp"]
|
|
31
|
-
extra_link_args += ["-fopenmp"]
|
|
32
|
-
|
|
33
|
-
ext_modules = [
|
|
34
|
-
Pybind11Extension(
|
|
35
|
-
"sia.hdqs_core",
|
|
36
|
-
["src/hdqs_core.cpp"],
|
|
37
|
-
cxx_std=17,
|
|
38
|
-
extra_compile_args=extra_compile_args,
|
|
39
|
-
extra_link_args=extra_link_args,
|
|
40
|
-
),
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
setup(
|
|
44
|
-
package_dir={"sia": "sia"},
|
|
45
|
-
ext_modules=ext_modules,
|
|
46
|
-
cmdclass={"build_ext": build_ext},
|
|
47
|
-
zip_safe=False,
|
|
48
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|