Source code for bqskit.passes.io.intermediate

"""This module implements the various intermediate results classes."""
from __future__ import annotations

import logging
import pickle
from os import listdir
from os import mkdir
from os.path import exists
from re import findall

from bqskit.compiler.basepass import BasePass
from bqskit.compiler.passdata import PassData
from bqskit.ir.circuit import Circuit
from bqskit.ir.gates.circuitgate import CircuitGate
from bqskit.ir.lang.qasm2.qasm2 import OPENQASM2Language
from bqskit.ir.operation import Operation
from bqskit.passes.util.converttou3 import ToU3Pass

_logger = logging.getLogger(__name__)


[docs] class SaveIntermediatePass(BasePass): """ The SaveIntermediate class. The SaveIntermediatePass stores individual CircuitGates in pickle or qasm format. """
[docs] def __init__( self, path_to_save_dir: str, project_name: str | None = None, save_as_qasm: bool = True, ) -> None: """ Constructor for the SaveIntermediatePass. Args: path_to_save_dir (str): Path to the directory in which inter- qasm for circuit blocks should be saved. project_name (str): Name of the project files. Raises: ValueError: If `path_to_save_dir` is not an existing directory. """ if exists(path_to_save_dir): self.pathdir = path_to_save_dir if self.pathdir[-1] != '/': self.pathdir += '/' else: raise ValueError( f'Path {path_to_save_dir} does not exist', ) self.projname = project_name if project_name is not None \ else 'unnamed_project' enum = 1 if exists(self.pathdir + self.projname): while exists(self.pathdir + self.projname + f'_{enum}'): enum += 1 self.projname += f'_{enum}' _logger.warning( f'Path {path_to_save_dir} already exists, ' f'saving to {self.pathdir + self.projname} ' 'instead.', ) mkdir(self.pathdir + self.projname) self.as_qasm = save_as_qasm
[docs] async def run(self, circuit: Circuit, data: PassData) -> None: """Perform the pass's operation, see BasePass for more info.""" # Gather and enumerate CircuitGates to save blocks_to_save: list[tuple[int, Operation]] = [] for enum, op in enumerate(circuit): if isinstance(op.gate, CircuitGate): blocks_to_save.append((enum, op)) # Set up path and file names structure_file = self.pathdir + self.projname + '/structure.pickle' block_skeleton = self.pathdir + self.projname + '/block_' num_digits = len(str(len(blocks_to_save))) structure_list: list[list[int]] = [] # NOTE: Block numbers are gotten by iterating through the circuit so # there is no guarantee that the blocks were partitioned in that order. for enum, block in blocks_to_save: enum = str(enum).zfill(num_digits) # type: ignore structure_list.append(block.location) # type: ignore subcircuit = Circuit(block.num_qudits) subcircuit.append_gate( block.gate, list( range( block.num_qudits, ), ), block.params, ) subcircuit.unfold((0, 0)) await ToU3Pass().run(subcircuit, PassData(subcircuit)) if self.as_qasm: with open(block_skeleton + f'{enum}.qasm', 'w') as f: f.write(OPENQASM2Language().encode(subcircuit)) else: with open( f'{block_skeleton}{enum}.pickle', 'wb', ) as f: pickle.dump(subcircuit, f) with open(structure_file, 'wb') as f: pickle.dump(structure_list, f)
[docs] class RestoreIntermediatePass(BasePass):
[docs] def __init__(self, project_directory: str, load_blocks: bool = True): """ Constructor for the RestoreIntermediatePass. Args: project_directory (str): Path to the checkpoint block files. This directory must also contain a valid "structure.pickle" file. load_blocks (bool): If True, blocks in the project directory will be loaded to the block_list during the constructor. Otherwise the user must explicitly call load_blocks() themselves. Defaults to True. Raises: ValueError: If `project_directory` does not exist or if `structure.pickle` is invalid. """ self.proj_dir = project_directory if not exists(self.proj_dir): raise TypeError( f"Project directory '{self.proj_dir}' does not exist.", ) if not exists(self.proj_dir + '/structure.pickle'): raise TypeError( f'Project directory `{self.proj_dir}` does not ' 'contain `structure.pickle`.', ) with open(self.proj_dir + '/structure.pickle', 'rb') as f: self.structure = pickle.load(f) if not isinstance(self.structure, list): raise TypeError('The provided `structure.pickle` is not a list.') self.block_list: list[str] = [] if load_blocks: self.reload_blocks()
[docs] def reload_blocks(self) -> None: """ Updates the `block_list` variable with the current contents of the `proj_dir`. Raises: ValueError: if there are more block files than indices in the `structure.pickle`. """ files = listdir(self.proj_dir) self.block_list = [f for f in files if 'block_' in f] if len(self.block_list) > len(self.structure): raise ValueError( 'More block files than indicies in `structure.pickle`', )
[docs] async def run(self, circuit: Circuit, data: PassData) -> None: """ Perform the pass's operation, see BasePass for more info. Raises: ValueError: if a block file and the corresponding index in `structure.pickle` are differnt lengths. """ # If the circuit is empty, just append blocks in order if circuit.depth == 0: for block in self.block_list: # Get block block_num = int(findall(r'\d+', block)[0]) with open(self.proj_dir + '/' + block) as f: block_circ = OPENQASM2Language().decode(f.read()) # Get location block_location = self.structure[block_num] if block_circ.num_qudits != len(block_location): raise ValueError( f'{block} and `structure.pickle` locations are ' 'different sizes.', ) # Append to circuit circuit.append_circuit(block_circ, block_location)
# Check if the circuit has been partitioned, if so, try to replace # blocks