Skip to content

Commit e5173b7

Browse files
Fix docs/google snippet error and add to snippets_test (#7803)
The PR fixes the md files' snippet errors under docs/google by adding test substitution and MagicMock, also fixing some outdated APIs. On the test side, adding necessary imports for cirq_google to let the tests being able to find necessary variables. This also extends the `test_substitution` markup to allow multi-line replacement text. Partially implements #7787 --------- Co-authored-by: Pavol Juhas <juhas@google.com>
1 parent d837721 commit e5173b7

5 files changed

Lines changed: 141 additions & 78 deletions

File tree

dev_tools/snippets_test.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
"""Tests for executable snippets in documentation.
15+
r"""Tests for executable snippets in documentation.
1616
1717
This tests code snippets that are executable in `.md` documentation. It covers
1818
all such files under the docs directory, as well as the top-level README file.
@@ -42,10 +42,14 @@
4242
<!---test_substitution
4343
pattern
4444
substitution
45+
substitution-line-2
4546
--->
4647
4748
where pattern is the regex matching pattern (passed to re.compile) and
48-
substitution is the replacement string.
49+
substitution is the replacement string. The replacement string may
50+
span several lines and it recognizes escape sequences as in `re.sub`,
51+
for example, `\1` stands for the text matched by the first parentheses
52+
group in the pattern and `\g<0>` for the entire matched string.
4953
"""
5054

5155
from __future__ import annotations
@@ -82,10 +86,10 @@ def test_can_run_readme_code_snippets():
8286

8387
def find_docs_code_snippets_paths() -> Iterator[str]:
8488
for filename in DOCS_FOLDER.rglob('*.md'):
85-
# Skip files under either 'hardware' and 'google'
89+
# Skip files under 'hardware'
8690
# TODO: #7787 - revisit which of these can be fixed and enabled later.
8791
path = str(filename.relative_to(DOCS_FOLDER))
88-
if not path.startswith(('hardware', 'google')):
92+
if not path.startswith('hardware'):
8993
yield path
9094

9195

@@ -114,7 +118,7 @@ def find_markdown_code_snippets(content: str) -> list[tuple[str, int]]:
114118

115119
def find_markdown_test_overrides(content: str) -> list[tuple[Pattern, str]]:
116120
test_sub_text = find_code_snippets("<!---test_substitution\n(.*?)--->", content)
117-
substitutions = [line.split('\n')[:-1] for line, _ in test_sub_text]
121+
substitutions = [line.rstrip().split('\n', maxsplit=1) for line, _ in test_sub_text]
118122
return [(re.compile(match), sub) for match, sub in substitutions]
119123

120124

@@ -255,6 +259,9 @@ def assert_code_snippets_run_in_sequence(snippets: list[tuple[str, int]], assume
255259

256260
if assume_import:
257261
exec('import cirq', state)
262+
exec('import cirq_google', state)
263+
exec('import unittest.mock as mock', state)
264+
exec('import sympy', state)
258265

259266
for content, line_number in snippets:
260267
assert_code_snippet_executes_correctly(content, state, line_number)

docs/google/calibration.md

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,23 @@ A dropdown menu will let you choose the current characterization or historical
2020
metrics from a previous run. Calibration metrics can also be retrieved
2121
programmatically using an engine instance or with a job.
2222

23+
<!---test_substitution
24+
engine = cg.Engine\(project_id=.*
25+
\g<0>
26+
engine = mock.create_autospec(cirq_google.Engine, instance=True)
27+
mock_engine_processor = mock.create_autospec(cirq_google.EngineProcessor, instance=True)
28+
engine.configure_mock(**{"get_processor.return_value": mock_engine_processor})
29+
--->
30+
<!---test_substitution
31+
PROJECT_ID|PROGRAM_ID|PROCESSOR_ID|CALIBRATION_SECONDS|START_SECONDS|END_SECONDS|JOB_ID
32+
'placeholder'
33+
--->
2334
```python
2435
import cirq_google as cg
2536

2637
# Create an Engine object to use.
27-
# Replace YOUR_PROJECT_ID with the id from your cloud project.
28-
engine = cg.Engine(project_id=YOUR_PROJECT_ID)
38+
# Replace PROJECT_ID with the id from your cloud project.
39+
engine = cg.Engine(project_id=PROJECT_ID)
2940
processor = engine.get_processor(processor_id=PROCESSOR_ID)
3041

3142
# Get the latest calibration metrics.
@@ -36,23 +47,24 @@ latest_calibration = processor.get_current_calibration()
3647
previous_calibration = processor.get_calibration(CALIBRATION_SECONDS)
3748

3849
# If you would like to find a calibration from a time-frame, use this.
39-
calibration_list = processor.list_calibration(START_SECONDS, END_SECONDS)
50+
calibration_list = processor.list_calibrations(START_SECONDS, END_SECONDS)
4051

52+
## TODO: #7910 - fix or delete this block
4153
# If you know the job-id, you can retrieve the calibration that the job used.
42-
job = engine.get_job("projects/" + PROJECT_ID
43-
+ "/programs/"+PROGRAM_ID
44-
+ "/jobs/" + JOB_ID)
45-
job_calibration = cg.EngineJob(cg.JobConfig(), job, engine).get_calibration()
54+
# job = engine.get_job("projects/" + PROJECT_ID
55+
# + "/programs/"+ PROGRAM_ID
56+
# + "/jobs/" + JOB_ID)
57+
# job_calibration = cg.EngineJob(PROJECT_ID, PROGRAM_ID, JOB_ID, cg.engine.engine.EngineContext()).get_calibration()
4658

4759
# The calibration can be iterated through using something like the following.
4860
for metric_name in latest_calibration:
49-
print(metric_name)
50-
print('------')
51-
for qubit_or_pair in latest_calibration[metric_name]:
52-
# Note that although the value is often singular,
53-
# the metric_value is of the type list and can have multiple values.
54-
metric_value = latest_calibration[metric_name][qubit_or_pair]
55-
print(f'{qubit_or_pair} = {metric_value}')
61+
print(metric_name)
62+
print('------')
63+
for qubit_or_pair in latest_calibration[metric_name]:
64+
# Note that although the value is often singular,
65+
# the metric_value is of the type list and can have multiple values.
66+
metric_value = latest_calibration[metric_name][qubit_or_pair]
67+
print(f'{qubit_or_pair} = {metric_value}')
5668
```
5769

5870
Calibration metrics will also soon be available from the

docs/google/devices.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ For example, this circuit will execute the two gates in parallel:
3939

4040
```python
4141
cirq.Circuit(
42-
cirq.Moment(cirq.X(cirq.GridQubit(4,4)), cirq.X(cirq.GridQubit(4,5)))
42+
cirq.Moment(cirq.X(cirq.GridQubit(4, 4)), cirq.X(cirq.GridQubit(4, 5)))
4343
)
4444
```
4545

@@ -48,8 +48,8 @@ This circuit will execute the two gates in serial:
4848

4949
```python
5050
cirq.Circuit(
51-
cirq.Moment(cirq.X(cirq.GridQubit(4,4))),
52-
cirq.Moment(cirq.X(cirq.GridQubit(4,5)))
51+
cirq.Moment(cirq.X(cirq.GridQubit(4, 4))),
52+
cirq.Moment(cirq.X(cirq.GridQubit(4, 5))),
5353
)
5454
```
5555

@@ -58,8 +58,8 @@ is virtual and its moment will disappear:
5858

5959
```python
6060
cirq.Circuit(
61-
cirq.Moment(cirq.Z(cirq.GridQubit(4,4))),
62-
cirq.Moment(cirq.X(cirq.GridQubit(4,5)))
61+
cirq.Moment(cirq.Z(cirq.GridQubit(4, 4))),
62+
cirq.Moment(cirq.X(cirq.GridQubit(4, 5))),
6363
)
6464
```
6565

@@ -89,7 +89,7 @@ in 0.02 increments.
8989

9090

9191
```python
92-
descriptor = cirq_google.study.DeviceParameter( ["q4_8", "piAmp"])
92+
descriptor = cirq_google.study.DeviceParameter(["q4_8", "piAmp"])
9393
sweep = cirq.Linspace("q4_8.piAmp", 0, 1, 51, metadata=descriptor)
9494
```
9595

@@ -295,9 +295,9 @@ It can be accessed by using `cirq_google.Sycamore`. This device has two possible
295295
two-qubits gates that can be used.
296296

297297
* Square root of ISWAP. The gate `cirq.ISWAP ** 0.5` or `cirq.ISWAP ** -0.5` can be
298-
used on this device.
298+
used on this device.
299299
* Sycamore gate. This gate, equivalent to FSimGate(π/2, π/6) can be used as `cirq_google.SYC`
300-
or by using `cirq.FsimGate(numpy.pi/2,numpy.pi/6)`.
300+
or by using `cirq.FsimGate(numpy.pi/2,numpy.pi/6)`.
301301

302302

303303
### Sycamore23

docs/google/engine.md

Lines changed: 79 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ gcloud client:
2323

2424
From a colab, you can execute:
2525

26+
<!---test_substitution
27+
from google\.colab import auth
28+
auth = mock.MagicMock()
29+
--->
2630
```python
2731
from google.colab import auth
2832
auth.authenticate_user(clear_output=False)
@@ -41,42 +45,52 @@ You can use this instance to run quantum circuits or sweeps (parameterized
4145
variants of a general circuit).
4246

4347
<!---test_substitution
44-
results = job.results.*
45-
results = None
46-
--->
47-
<!---test_substitution
48-
print.results.idx.*
49-
print()
48+
engine = cirq_google.Engine\b.*
49+
# This pattern matches in all snippets.
50+
# We repeat the expression first to ensure Engine argumets are correct.
51+
\g<0>
52+
# Then we create various mock objects that derive from engine calls.
53+
engine = mock_engine = mock.create_autospec(cirq_google.Engine, instance=True)
54+
mock_sampler = mock.create_autospec(cirq.Sampler, instance=True)
55+
mock_engine_job = mock.create_autospec(cirq_google.EngineJob, instance=True,
56+
program_id="program_id", job_id="job_id")
57+
mock_engine_program = mock.create_autospec(cirq_google.EngineProgram, instance=True,
58+
program_id="program_id")
59+
# Finally let us import datetime for snippets that use it below
60+
import datetime
5061
--->
5162
<!---test_substitution
52-
engine = cirq_google.Engine(.*)
53-
engine = MockEngine()
63+
sampler = engine.get_sampler.processor_id=PROCESSOR_ID.*
64+
mock_engine.configure_mock(**{"get_sampler.return_value": mock_sampler})
65+
\g<0>
5466
--->
5567
<!---test_substitution
56-
cg.Engine(.*)
57-
cirq.Simulator()
68+
results = sampler.run\b
69+
mock_result = mock.create_autospec(cirq.Result, instance=True)
70+
mock_sampler.configure_mock(**{"run.return_value": mock_result})
71+
\g<0>
5872
--->
5973
<!---test_substitution
60-
sampler = .*
61-
sampler = engine
74+
PROJECT_ID|PROCESSOR_ID
75+
'placeholder'
6276
--->
6377
```python
6478
import cirq
65-
import cirq_google as cg
79+
import cirq_google
6680

6781
# A simple sample circuit
6882
qubit = cirq.GridQubit(5, 2)
6983
circuit = cirq.Circuit(
70-
cirq.X(qubit)**0.5, # Square root of NOT.
71-
cirq.measure(qubit, key='result') # Measurement.
84+
cirq.X(qubit) ** 0.5, # Square root of NOT.
85+
cirq.measure(qubit, key='result'), # Measurement.
7286
)
7387

7488
# Create an Engine object.
75-
# Replace YOUR_PROJECT_ID with the id from your cloud project.
76-
engine = cg.Engine(project_id=YOUR_PROJECT_ID)
89+
# Replace PROJECT_ID with the id from your cloud project.
90+
engine = cirq_google.Engine(project_id=PROJECT_ID)
7791

7892
# Create a sampler from the engine
79-
sampler = engine.get_sampler(processor_id='PROCESSOR_ID')
93+
sampler = engine.get_sampler(processor_id=PROCESSOR_ID)
8094

8195
# This will run the circuit and return the results in a 'Result'
8296
results = sampler.run(circuit, repetitions=1000)
@@ -152,26 +166,36 @@ Currently, getting the program and job ids can only be done through the
152166
You can then use `get_program` and `get_job` to retrieve the results.
153167
See below for an example:
154168

169+
<!---test_substitution
170+
job = engine.run_sweep.program=circuit
171+
mock_engine.configure_mock(**{
172+
"run_sweep.return_value": mock_engine_job,
173+
"get_program.return_value": mock_engine_program,
174+
})
175+
mock_engine_program.configure_mock(**{"get_job.return_value": mock_engine_job})
176+
\g<0>
177+
--->
155178
```python
156179
# Initialize the engine object
157-
engine = cirq_google.Engine(project_id='YOUR_PROJECT_ID')
180+
engine = cirq_google.Engine(project_id=PROJECT_ID)
158181

159182
# Create an example circuit
160183
qubit = cirq.GridQubit(5, 2)
161184
circuit = cirq.Circuit(
162-
cirq.X(qubit)**sympy.Symbol('t'),
163-
cirq.measure(qubit, key='result')
185+
cirq.X(qubit) ** sympy.Symbol('t'),
186+
cirq.measure(qubit, key='result'),
164187
)
165188
param_sweep = cirq.Linspace('t', start=0, stop=1, length=10)
166189

167190
# Run the circuit
168-
job = e.run_sweep(program=circuit,
169-
params=param_sweep,
170-
repetitions=1000,
171-
processor_id='PROCESSOR_ID',
172-
gate_set=GATE_SET)
191+
job = engine.run_sweep(
192+
program=circuit,
193+
params=param_sweep,
194+
repetitions=1000,
195+
processor_id=PROCESSOR_ID,
196+
)
173197

174-
# Save the program and jo id for later
198+
# Save the program and job id for later
175199
program_id = job.program_id
176200
job_id = job.job_id
177201

@@ -187,7 +211,6 @@ historical_job = engine.get_program(program_id=program_id).get_job(job_id=job_id
187211

188212
# Retrieve the results
189213
historical_results = historical_job.results()
190-
191214
```
192215

193216
If you did not save the ids, you can still find them from your
@@ -200,17 +223,24 @@ by using our list methods.
200223
To list the executions of your circuit, i.e., the jobs, you can use `cirq_google.Engine.list_jobs()`.
201224
You can search in all the jobs within your project using filtering criteria on creation time, execution state and labels.
202225

226+
<!---test_substitution
227+
jobs = engine.list_jobs.created_after=datetime.date.*
228+
mock_engine.configure_mock(**{"list_jobs.return_value": [mock_engine_job]})
229+
\g<0>
230+
--->
203231
```python
204-
from cirq_google.engine.client.quantum import enums
232+
from cirq_google.cloud import quantum
205233

206234
# Initialize the engine object
207-
engine = cirq_google.Engine(project_id='YOUR_PROJECT_ID')
235+
engine = cirq_google.Engine(project_id=PROJECT_ID)
208236

209237
# List all the jobs on the project since 2020/09/20 that succeeded:
210-
jobs = engine.list_jobs(created_after=datetime.date(2020,9,20),
211-
execution_states=[enums.ExecutionStatus.State.SUCCESS])
238+
jobs = engine.list_jobs(
239+
created_after=datetime.date(2020, 9, 20),
240+
execution_states=[quantum.ExecutionStatus.State.SUCCESS],
241+
)
212242
for j in jobs:
213-
print(j.job_id, j.status(), j.create_time())
243+
print(j.job_id, j.status(), j.create_time())
214244
```
215245

216246
### Listing programs
@@ -219,23 +249,29 @@ To list the different instances of your circuits uploaded, i.e., the programs, y
219249
Similar to jobs, filtering makes it possible to list programs by creation time and labels.
220250
With an existing `cirq_google.EngineProgram` object, you can list any jobs that were run using that program.
221251

252+
<!---test_substitution
253+
programs = engine.list_programs[(].*
254+
mock_engine.configure_mock(**{"list_programs.return_value": [mock_engine_program]})
255+
mock_engine_program.configure_mock(**{"list_jobs.return_value": [mock_engine_job]})
256+
\g<0>
257+
--->
222258
```python
223-
from cirq_google.engine.client.quantum import enums
259+
from cirq_google.cloud import quantum
224260

225261
# Initialize the engine object
226-
engine = cirq_google.Engine(project_id='YOUR_PROJECT_ID')
262+
engine = cirq_google.Engine(project_id=PROJECT_ID)
227263

228264
# List all the programs on the project since 2020/09/20 that have
229265
# the "variational" label with any value and the "experiment" label
230266
# with value "vqe001":
231267
programs = engine.list_programs(
232-
created_after=datetime.date(2020,9,20),
233-
has_labels={"variational":"*", "experiment":"vqe001"}
234-
)
268+
created_after=datetime.date(2020, 9, 20),
269+
has_labels={"variational": "*", "experiment": "vqe001"},
270+
)
235271
for p in programs:
236-
print(p.program_id, p.create_time())
237-
# the same filtering parametrization is available as in engine.list_jobs()
238-
# for example here we list the jobs under the programs that failed
239-
for j in p.list_jobs(execution_states=[enums.ExecutionStatus.State.FAILURE]):
240-
print(j.job_id, j.status())
272+
print(p.program_id, p.create_time())
273+
# the same filtering parametrization is available as in engine.list_jobs()
274+
# for example here we list the jobs under the programs that failed
275+
for j in p.list_jobs(execution_states=[quantum.ExecutionStatus.State.FAILURE]):
276+
print(j.job_id, j.status())
241277
```

0 commit comments

Comments
 (0)