Skip to content

Commit 85a8042

Browse files
authored
Merge branch 'main' into undo-indent-size-for-makefiles
2 parents b7985f0 + 0c242e7 commit 85a8042

7 files changed

Lines changed: 191 additions & 12 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Summary: check the subject and description of each PR for basic best
16+
# practices and fail with an error if a PR doesn't meet the conditions.
17+
18+
name: Pull request text checker
19+
run-name: >-
20+
Check title and description of
21+
PR #${{github.event.inputs.pr-number || github.event.pull_request.number}}
22+
by ${{github.actor}}
23+
24+
on:
25+
pull_request:
26+
types:
27+
- edited
28+
- opened
29+
- reopened
30+
- synchronize
31+
branches:
32+
- main
33+
34+
permissions: read-all
35+
36+
jobs:
37+
check-text-length:
38+
name: "Minimum length check"
39+
runs-on: ubuntu-slim
40+
timeout-minutes: 5
41+
steps:
42+
- name: Check the lengths of the PR title and description
43+
env:
44+
SHELLOPTS: ${{runner.debug && 'xtrace' || '' }}
45+
run: |
46+
pr_title=$(jq -r ".pull_request.title" "${GITHUB_EVENT_PATH}")
47+
pr_body=$(jq -r ".pull_request.body" "${GITHUB_EVENT_PATH}")
48+
49+
read -ra title_words <<< "${pr_title}"
50+
if [[ ${#title_words[@]} -lt 2 ]]; then
51+
echo "::error::PR title must contain at least two words."
52+
error=1
53+
fi
54+
if [[ -z "${pr_body}" || ${#pr_body} -lt 10 ]]; then
55+
echo "::error::PR description must be at least 10 characters long."
56+
error=1
57+
fi
58+
59+
if [[ -v error ]]; then
60+
{
61+
printf "❌ Problem(s) found with the PR title and/or description."
62+
} >> "${GITHUB_STEP_SUMMARY}"
63+
exit 1
64+
fi

Makefile

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ CXXFLAGS := $(BASE_CXXFLAGS) $(CXXFLAGS)
4646
NVCCFLAGS := $(BASE_NVCCFLAGS) $(NVCCFLAGS)
4747
HIPCCFLAGS := $(BASE_HIPCCFLAGS) $(HIPCCFLAGS)
4848

49+
# GCC 15 and Binutils 2.45+ generate SFrame stack unwinding info. The current
50+
# SFrame only supports a subset of x86_64 registers. When GCC optimizes AVX*
51+
# instructions, it uses additional registers, and that causes the assembler to
52+
# produce (many) warnings. They are harmless for qsim because it does not rely
53+
# on SFrame. Silence those warnings if the assembler supports it.
54+
SUPPORTS_GSFRAME := $(shell $(CXX) -Wa,--gsframe=no -c -x c++ /dev/null \
55+
-o /dev/null 2>/dev/null && echo "true")
56+
ifeq ($(SUPPORTS_GSFRAME),true)
57+
CXXFLAGS += -Wa,--gsframe=no
58+
NVCCFLAGS += -Xcompiler -Wa,--gsframe=no
59+
HIPCCFLAGS += -Wa,--gsframe=no
60+
endif
61+
4962
LTO_FLAGS := -flto=auto
5063
USING_CLANG := $(shell $(CXX) --version | grep -isq clang && echo "true")
5164
ifeq ($(USING_CLANG),true)
@@ -126,7 +139,7 @@ ifneq (,$(strip $(CUQUANTUM_ROOT)))
126139
endif
127140
endif
128141

129-
# Export all variables to subprocesses without having to export them individually.
142+
# Export all vars to subprocesses without having to export them individually.
130143
.EXPORT_ALL_VARIABLES:
131144

132145
# The rest is build rules and make targets.
@@ -214,7 +227,7 @@ run-tests tests: $(TESTS)
214227
.PHONY: check-cuquantum-root-set
215228
check-cuquantum-root-set:
216229
@if test -z "$(CUQUANTUM_ROOT)"; then \
217-
echo Error: '$$CUQUANTUM_ROOT must be set in order to use cuStateVec.'; \
230+
echo Error: 'Must set $$CUQUANTUM_ROOT to use cuStateVec.'; \
218231
exit 1; \
219232
fi
220233

@@ -241,7 +254,7 @@ clean:
241254
-$(MAKE) -C pybind_interface/ clean
242255

243256
LOCAL_VARS = TARGETS TESTS PYTESTS PYTESTFLAGS CXX CXXFLAGS NVCC NVCCFLAGS $\
244-
HIPCC HIPCCFLAGS CUDA_PATH CUQUANTUM_ROOT CUSTATEVECFLAGS
257+
HIPCC HIPCCFLAGS CUDA_PATH CUQUANTUM_ROOT CUSTATEVECFLAGS SUPPORTS_GSFRAME
245258

246259
.PHONY: print-vars
247260
print-vars: ; @$(foreach n,$(sort $(LOCAL_VARS)),echo $n=$($n);)

docs/choose_hw.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,18 @@ depth beyond 20 qubits.
311311
* The total small circuits runtime overhead for an N qubit circuit
312312
depends on the circuit depth and on N. The overhead can be large enough to
313313
conceal the $2^N$ growth in runtime.
314+
* The NumPy version matters for large simulations:
315+
* NumPy 1.x defines a constant `NPY_MAXDIMS`, which is set to 32 in the
316+
NumPy source code. Many internal NumPy structures, such as those for
317+
iteration and coordinate tracking, use fixed-size buffers based on this
318+
value. Although the C++ core of qsim is not affected by this (since it
319+
does not use NumPy), this NumPy limit prevents state vectors from having
320+
more than 32 qubits when using Python. Attempting to use 33 or more
321+
qubits results in an error.
322+
* NumPy 2.x increased this limit to 64 dimensions, and qsim can work with
323+
more than 32 qubit when NumPy 2.x is used. If you encounter unexpected
324+
failures, memory errors, or hard limits on the number of qubits, ensure
325+
you are running a recent NumPy version before further debugging.
314326

315327
## Sample benchmarks
316328

lib/vectorspace_custatevecex.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -546,9 +546,6 @@ class VectorSpaceCuStateVecEx {
546546

547547
ErrorCheck(custatevecExStateVectorSynchronize(dest.get()));
548548

549-
// TODO: do we need that?
550-
dest.to_normal_order();
551-
552549
return true;
553550
}
554551

@@ -576,9 +573,6 @@ class VectorSpaceCuStateVecEx {
576573

577574
ErrorCheck(custatevecExStateVectorSynchronize(dest.get()));
578575

579-
// TODO: do we need that?
580-
dest.to_normal_order();
581-
582576
return true;
583577
}
584578

qsimcirq/qsim_simulator.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,8 +525,10 @@ def _simulate_impl(
525525
qsim_state = fullstate_simulator_fn(options, initial_state)
526526
elif isinstance(initial_state, np.ndarray):
527527
qsim_state = fullstate_simulator_fn(options, input_vector)
528-
assert qsim_state.dtype == np.float32
529-
assert qsim_state.ndim == 1
528+
if qsim_state.dtype != np.float32:
529+
raise TypeError("qsim_state must have dtype np.float32.")
530+
if qsim_state.ndim != 1:
531+
raise ValueError("qsim_state must be a 1D array.")
530532

531533
yield prs, qsim_state.view(np.complex64), cirq_order
532534

qsimcirq_tests/qsimcirq_test.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import sympy
1919

2020
import qsimcirq
21+
import qsimcirq.qsim_circuit
2122

2223

2324
class NoiseTrigger(cirq.Gate):
@@ -128,6 +129,26 @@ def _unitary_(self):
128129
qsimSim.compute_amplitudes(cirq_circuit, bitstrings=[0b0, 0b1])
129130

130131

132+
def test_translate_matrix_gate_too_big():
133+
gate = cirq.MatrixGate(np.eye(128))
134+
with pytest.raises(
135+
NotImplementedError, match="only up to 6-qubit gates are supported"
136+
):
137+
qsimcirq.qsim_circuit._translate_MatrixGate(gate)
138+
139+
140+
def test_simulate_matrix_gate_too_big():
141+
qubits = cirq.LineQubit.range(7)
142+
gate = cirq.MatrixGate(np.eye(128))
143+
circuit = cirq.Circuit(gate.on(*qubits))
144+
145+
qsim_sim = qsimcirq.QSimSimulator()
146+
with pytest.raises(
147+
NotImplementedError, match="only up to 6-qubit gates are supported"
148+
):
149+
qsim_sim.compute_amplitudes(circuit, bitstrings=[0b0, 0b1])
150+
151+
131152
def test_cirq_giant_identity():
132153
# Pick qubits.
133154
a, b, c, d, e, f, g, h = [
@@ -180,6 +201,61 @@ def _decompose_(self, qubits):
180201
assert result_hist[1] > 0
181202

182203

204+
def test_translate_cirq_to_qtrajectory():
205+
q0, q1 = cirq.LineQubit.range(2)
206+
207+
# General circuit with unitary, mixture, and channel.
208+
circuit = cirq.Circuit(
209+
cirq.H(q0),
210+
cirq.CNOT(q0, q1),
211+
cirq.bit_flip(0.1)(q0),
212+
cirq.depolarize(0.1)(q1),
213+
)
214+
qsim_circuit = qsimcirq.QSimCircuit(circuit)
215+
qsim_ncircuit, moment_indices = qsim_circuit.translate_cirq_to_qtrajectory()
216+
217+
assert isinstance(qsim_ncircuit, qsimcirq.qsim.NoisyCircuit)
218+
assert qsim_ncircuit.num_qubits == 2
219+
# The circuit has 3 moments, and 4 gates are translated in total.
220+
assert moment_indices == [1, 2, 4]
221+
222+
# Edge case: empty circuit.
223+
circuit_empty = cirq.Circuit()
224+
qsim_circuit_empty = qsimcirq.QSimCircuit(circuit_empty)
225+
qsim_ncircuit_empty, moment_indices_empty = (
226+
qsim_circuit_empty.translate_cirq_to_qtrajectory()
227+
)
228+
229+
assert isinstance(qsim_ncircuit_empty, qsimcirq.qsim.NoisyCircuit)
230+
assert qsim_ncircuit_empty.num_qubits == 0
231+
assert moment_indices_empty == []
232+
233+
# Edge case: circuit with only unitary gates.
234+
circuit_unitary = cirq.Circuit(cirq.X(q0), cirq.H(q1))
235+
qsim_circuit_unitary = qsimcirq.QSimCircuit(circuit_unitary)
236+
qsim_ncircuit_unitary, moment_indices_unitary = (
237+
qsim_circuit_unitary.translate_cirq_to_qtrajectory()
238+
)
239+
240+
assert isinstance(qsim_ncircuit_unitary, qsimcirq.qsim.NoisyCircuit)
241+
assert qsim_ncircuit_unitary.num_qubits == 2
242+
assert moment_indices_unitary == [2]
243+
244+
# Edge case: unparseable operation.
245+
class UnparseableOp(cirq.Operation):
246+
@property
247+
def qubits(self):
248+
return (q0,)
249+
250+
def with_qubits(self, *new_qubits):
251+
return self
252+
253+
circuit_unparseable = cirq.Circuit(UnparseableOp())
254+
qsim_circuit_unparseable = qsimcirq.QSimCircuit(circuit_unparseable)
255+
with pytest.raises(ValueError, match="Encountered unparseable op"):
256+
qsim_circuit_unparseable.translate_cirq_to_qtrajectory()
257+
258+
183259
@pytest.mark.parametrize("mode", ["noiseless", "noisy"])
184260
def test_cirq_qsim_simulate(mode: str):
185261
# Pick qubits.
@@ -2196,3 +2272,21 @@ def test_1d_representation():
21962272
want = np.array([0.0 - 0.5j, 0.0 + 0.5j, 0.0 - 0.5j, 0.0 + 0.5j])
21972273
_, res, _ = qsim_sim.simulate_into_1d_array(c)
21982274
np.testing.assert_allclose(res, np.array(want, dtype=np.complex64))
2275+
2276+
2277+
def test_get_seed():
2278+
# Test range.
2279+
qsim_sim = qsimcirq.QSimSimulator(seed=42)
2280+
for _ in range(100):
2281+
seed = qsim_sim.get_seed()
2282+
assert 0 <= seed < 2**31 - 1
2283+
2284+
# Test determinism.
2285+
sim1 = qsimcirq.QSimSimulator(seed=42)
2286+
sim2 = qsimcirq.QSimSimulator(seed=42)
2287+
assert sim1.get_seed() == sim2.get_seed()
2288+
2289+
# Test subsequent calls.
2290+
sim = qsimcirq.QSimSimulator(seed=42)
2291+
seeds = {sim.get_seed() for _ in range(10)}
2292+
assert len(seeds) > 1

tests/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ endif
4040

4141
CXX_FILES := $(BASIC_FILES) $(SSE_FILES) $(AVX2_FILES) $(AVX512_FILES)
4242
CXX_TARGETS := $(CXX_FILES:%.cc=%.x)
43-
CXXFLAGS := $(CXXFLAGS) $(SSE_FLAGS) $(AVX2_FLAGS) $(AVX512_FLAGS) $(BMI2_FLAGS)
43+
CXXFLAGS += $(SSE_FLAGS) $(AVX2_FLAGS) $(AVX512_FLAGS) $(BMI2_FLAGS)
4444

4545
CUDA_FILES := $(wildcard *cuda_test.cu)
4646
CUDA_TARGETS := $(CUDA_FILES:%cuda_test.cu=%cuda_test.x)

0 commit comments

Comments
 (0)