@@ -116,6 +116,51 @@ def __repr__(self) -> str:
116
116
return _compat .dataclass_repr (self , namespace = 'cirq_google' )
117
117
118
118
119
+ @dataclasses .dataclass
120
+ class ExecutableGroupResultFilesystemRecord :
121
+ """Filename references to the constituent parts of a `cg.ExecutableGroupResult`.
122
+
123
+ Args:
124
+ runtime_configuration_path: A filename pointing to the `runtime_configuration` value.
125
+ shared_runtime_info_path: A filename pointing to the `shared_runtime_info` value.
126
+ executable_result_paths: A list of filenames pointing to the `executable_results` values.
127
+ run_id: The unique `str` identifier from this run. This is used to locate the other
128
+ values on disk.
129
+ """
130
+
131
+ runtime_configuration_path : str
132
+ shared_runtime_info_path : str
133
+ executable_result_paths : List [str ]
134
+
135
+ run_id : str
136
+
137
+ def load (self , * , base_data_dir : str = "." ) -> ExecutableGroupResult :
138
+ """Using the filename references in this dataclass, load a `cg.ExecutableGroupResult`
139
+ from its constituent parts.
140
+
141
+ Args:
142
+ base_data_dir: The base data directory. Files should be found at
143
+ {base_data_dir}/{run_id}/{this class's paths}
144
+ """
145
+ data_dir = f"{ base_data_dir } /{ self .run_id } "
146
+ return ExecutableGroupResult (
147
+ runtime_configuration = cirq .read_json_gzip (
148
+ f'{ data_dir } /{ self .runtime_configuration_path } '
149
+ ),
150
+ shared_runtime_info = cirq .read_json_gzip (f'{ data_dir } /{ self .shared_runtime_info_path } ' ),
151
+ executable_results = [
152
+ cirq .read_json_gzip (f'{ data_dir } /{ exe_path } ' )
153
+ for exe_path in self .executable_result_paths
154
+ ],
155
+ )
156
+
157
+ def _json_dict_ (self ) -> Dict [str , Any ]:
158
+ return dataclass_json_dict (self , namespace = 'cirq.google' )
159
+
160
+ def __repr__ (self ) -> str :
161
+ return _compat .dataclass_repr (self , namespace = 'cirq_google' )
162
+
163
+
119
164
@dataclasses .dataclass
120
165
class QuantumRuntimeConfiguration :
121
166
"""User-requested configuration of how to execute a given `cg.QuantumExecutableGroup`.
@@ -138,22 +183,63 @@ def __repr__(self) -> str:
138
183
return _compat .dataclass_repr (self , namespace = 'cirq_google' )
139
184
140
185
186
+ def _safe_to_json (obj : Any , * , part_path : str , nominal_path : str , bak_path : str ):
187
+ """Safely update a json file.
188
+
189
+ 1. The new value is written to a "part" file
190
+ 2. The previous file atomically replaces the previous backup file, thereby becoming the
191
+ current backup file.
192
+ 3. The part file is atomically renamed to the desired filename.
193
+ """
194
+ cirq .to_json_gzip (obj , part_path )
195
+ if os .path .exists (nominal_path ):
196
+ os .replace (nominal_path , bak_path )
197
+ os .replace (part_path , nominal_path )
198
+
199
+
200
+ def _update_updatable_files (
201
+ egr_record : ExecutableGroupResultFilesystemRecord ,
202
+ shared_rt_info : SharedRuntimeInfo ,
203
+ data_dir : str ,
204
+ ):
205
+ """Safely update ExecutableGroupResultFilesystemRecord.json.gz and SharedRuntimeInfo.json.gz
206
+ during an execution run.
207
+ """
208
+ _safe_to_json (
209
+ shared_rt_info ,
210
+ part_path = f'{ data_dir } /SharedRuntimeInfo.json.gz.part' ,
211
+ nominal_path = f'{ data_dir } /SharedRuntimeInfo.json.gz' ,
212
+ bak_path = f'{ data_dir } /SharedRuntimeInfo.json.gz.bak' ,
213
+ )
214
+ _safe_to_json (
215
+ egr_record ,
216
+ part_path = f'{ data_dir } /ExecutableGroupResultFilesystemRecord.json.gz.part' ,
217
+ nominal_path = f'{ data_dir } /ExecutableGroupResultFilesystemRecord.json.gz' ,
218
+ bak_path = f'{ data_dir } /ExecutableGroupResultFilesystemRecord.json.gz.bak' ,
219
+ )
220
+
221
+
141
222
def execute (
142
223
rt_config : QuantumRuntimeConfiguration ,
143
224
executable_group : QuantumExecutableGroup ,
144
225
base_data_dir : str = "." ,
145
226
) -> ExecutableGroupResult :
146
227
"""Execute a `cg.QuantumExecutableGroup` according to a `cg.QuantumRuntimeConfiguration`.
147
228
229
+ The ExecutableGroupResult's constituent parts will be saved to disk as they become
230
+ available. Within the "{base_data_dir}/{run_id}" directory we save:
231
+ - The `cg.QuantumRuntimeConfiguration` at the start of the execution as a record
232
+ of *how* the executable group was run.
233
+ - A `cg.SharedRuntimeInfo` which is updated throughout the run.
234
+ - An `cg.ExecutableResult` for each `cg.QuantumExecutable` as they become available.
235
+ - A `cg.ExecutableGroupResultFilesystemRecord` which is updated throughout the run.
236
+
148
237
Args:
149
238
rt_config: The `cg.QuantumRuntimeConfiguration` specifying how to execute
150
239
`executable_group`.
151
240
executable_group: The `cg.QuantumExecutableGroup` containing the executables to execute.
152
- base_data_dir: A filesystem path to write data. We write
153
- "{base_data_dir}/{run_id}/ExecutableGroupResult.json.gz"
154
- containing the `cg.ExecutableGroupResult` as well as one file
155
- "{base_data_dir}/{run_id}/ExecutableResult.{i}.json.gz" per `cg.ExecutableResult` as
156
- each executable result becomes available.
241
+ base_data_dir: Each data file will be written to the "{base_data_dir}/{run_id}/" directory,
242
+ which must not already exist.
157
243
158
244
Returns:
159
245
The `cg.ExecutableGroupResult` containing all data and metadata for an execution.
@@ -174,15 +260,21 @@ def execute(
174
260
# coverage: ignore
175
261
raise ValueError ("Please provide a non-empty `base_data_dir`." )
176
262
177
- os .makedirs (f'{ base_data_dir } /{ run_id } ' , exist_ok = False )
178
-
179
- # Results object that we will fill in in the main loop.
180
- exegroup_result = ExecutableGroupResult (
181
- runtime_configuration = rt_config ,
182
- shared_runtime_info = SharedRuntimeInfo (run_id = run_id ),
183
- executable_results = list (),
263
+ # Set up data saving, save runtime configuration.
264
+ data_dir = f'{ base_data_dir } /{ run_id } '
265
+ os .makedirs (data_dir , exist_ok = False )
266
+ egr_record = ExecutableGroupResultFilesystemRecord (
267
+ runtime_configuration_path = 'QuantumRuntimeConfiguration.json.gz' ,
268
+ shared_runtime_info_path = 'SharedRuntimeInfo.json.gz' ,
269
+ executable_result_paths = [],
270
+ run_id = run_id ,
184
271
)
185
- cirq .to_json_gzip (exegroup_result , f'{ base_data_dir } /{ run_id } /ExecutableGroupResult.json.gz' )
272
+ cirq .to_json_gzip (rt_config , f'{ data_dir } /{ egr_record .runtime_configuration_path } ' )
273
+
274
+ # Set up to-be-updated objects.
275
+ shared_rt_info = SharedRuntimeInfo (run_id = run_id )
276
+ _update_updatable_files (egr_record , shared_rt_info , data_dir )
277
+ executable_results = []
186
278
187
279
# Loop over executables.
188
280
sampler = rt_config .processor .get_sampler ()
@@ -206,9 +298,18 @@ def execute(
206
298
runtime_info = runtime_info ,
207
299
raw_data = sampler_run_result ,
208
300
)
209
- cirq .to_json_gzip (exe_result , f'{ base_data_dir } /{ run_id } /ExecutableResult.{ i } .json.gz' )
210
- exegroup_result .executable_results .append (exe_result )
211
- print (f'\r { i + 1 } / { n_executables } ' , end = '' , flush = True )
301
+ # Do bookkeeping for finished ExecutableResult
302
+ exe_result_path = f'ExecutableResult.{ i } .json.gz'
303
+ cirq .to_json_gzip (exe_result , f"{ data_dir } /{ exe_result_path } " )
304
+ executable_results .append (exe_result )
305
+ egr_record .executable_result_paths .append (exe_result_path )
306
+
307
+ _update_updatable_files (egr_record , shared_rt_info , data_dir )
308
+ print (f'\r { i + 1 } / { n_executables } ' , end = '' , flush = True )
212
309
print ()
213
310
214
- return exegroup_result
311
+ return ExecutableGroupResult (
312
+ runtime_configuration = rt_config ,
313
+ shared_runtime_info = shared_rt_info ,
314
+ executable_results = executable_results ,
315
+ )
0 commit comments