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