def _remove_reset_from_program(program: Program) -> Program: """ Trim the RESET from a program because in measure_observables it is re-added. :param program: Program to remove RESET(s) from. :return: Trimmed Program. """ p = program.copy_everything_except_instructions() for inst in program: if not isinstance(inst, (Reset, ResetQubit)): p.inst(inst) return p
def add_dd(program: Program): new_program = program.copy_everything_except_instructions() counts = [0, 0] for gate in program: try: if len(gate.qubits) > 1: if abs(counts[0] - counts[1]) >= 2: min_ind = int(counts[0] > counts[1]) times = max(int(abs(counts[0] - counts[1]) / 4), 1) p = add_decoherence_noise( Program(get_dd_sec(min_ind) * times)) new_program.inst(p) counts = [0, 0] else: counts[gate.qubits[0].index] += 1 except AttributeError: pass new_program.inst(gate) return new_program
def rewrite_arithmetic(prog: Program) -> RewriteArithmeticResponse: """Rewrite compound arithmetic expressions. The basic motivation is that a parametric program may have gates with compound arguments which cannot be evaluated natively on the underlying control hardware. The solution provided here is to translate a program like DECLARE theta REAL DECLARE beta REAL RZ(3 * theta) 0 RZ(beta+theta) 0 into something like DECLARE theta REAL DECLARE beta REAL DECLARE __P REAL[2] RZ(__P[0]) 0 RZ(__P[1]) 0 along with a "recalculation table" mapping new memory references to their corresponding arithmetic expressions, { ParameterAref('__P', 0): "((3.0)*theta[0])", ParameterAref('__P', 1): "(beta[0]+theta[0])" } When executing the parametric program with specific values for `theta` and `beta`, the PyQuil client will patch in values for `__P` by evaluating the expressions in the recalculation table. :param prog: A program. :returns: A RewriteArithmeticResponse, containing the updated program along with its memory descriptors and a recalculation table. """ def spec(inst: Declare) -> ParameterSpec: return ParameterSpec(type=inst.memory_type, length=inst.memory_size) def aref(ref: MemoryReference) -> ParameterAref: return ParameterAref(name=ref.name, index=ref.offset) updated = prog.copy_everything_except_instructions() old_descriptors = {inst.name: spec(inst) for inst in prog if isinstance(inst, Declare)} recalculation_table = {} seen_exprs: Dict[str, MemoryReference] = {} # generate a unique name. it's nice to do this in a deterministic fashion # rather than globbing in a UUID suffix = len(old_descriptors) while f"__P{suffix}" in old_descriptors: suffix += 1 mref_name = f"__P{suffix}" mref_idx = 0 for inst in prog: if isinstance(inst, Gate): new_params = [] for param in inst.params: if isinstance(param, (Real, MemoryReference)): new_params.append(param) elif isinstance(param, Expression): expr = str(param) if expr in seen_exprs: new_params.append(seen_exprs[expr]) else: new_mref = MemoryReference(mref_name, mref_idx) seen_exprs[expr] = new_mref mref_idx += 1 recalculation_table[aref(new_mref)] = expr new_params.append(new_mref) else: raise ValueError(f"Unknown parameter type {type(param)} in {inst}.") updated.inst(Gate(inst.name, new_params, inst.qubits)) else: updated.inst(inst) if mref_idx > 0: updated._instructions.insert(0, Declare(mref_name, "REAL", mref_idx)) return RewriteArithmeticResponse( quil=updated.out(), original_memory_descriptors=old_descriptors, recalculation_table=recalculation_table, )
def rewrite_arithmetic(prog: Program) -> RewriteArithmeticResponse: """Rewrite compound arithmetic expressions. The basic motivation is that a parametric program may have gates with compound arguments which cannot be evaluated natively on the underlying control hardware. The solution provided here is to translate a program like DECLARE theta REAL DECLARE beta REAL RZ(3 * theta) 0 RZ(beta+theta) 0 into something like DECLARE theta REAL DECLARE beta REAL DECLARE __P REAL[2] RZ(__P[0]) 0 RZ(__P[1]) 0 along with a "recalculation table" mapping new memory references to their corresponding arithmetic expressions, { ParameterAref('__P', 0): "((3.0)*theta[0])", ParameterAref('__P', 1): "(beta[0]+theta[0])" } When executing the parametric program with specific values for `theta` and `beta`, the PyQuil client will patch in values for `__P` by evaluating the expressions in the recalculation table. :param prog: A program. :returns: A RewriteArithmeticResponse, containing the updated program along with its memory descriptors and a recalculation table. """ def spec(inst: Declare) -> ParameterSpec: return ParameterSpec(type=inst.memory_type, length=inst.memory_size) def aref(ref: MemoryReference) -> ParameterAref: return ParameterAref(name=ref.name, index=ref.offset) updated = prog.copy_everything_except_instructions() old_descriptors = { inst.name: spec(inst) for inst in prog if isinstance(inst, Declare) } recalculation_table: Dict[ParameterAref, str] = {} seen_exprs: Dict[str, MemoryReference] = {} # generate a unique name. it's nice to do this in a deterministic fashion # rather than globbing in a UUID suffix = len(old_descriptors) while f"__P{suffix}" in old_descriptors: suffix += 1 mref_name = f"__P{suffix}" mref_idx = 0 def expr_mref(expr: object) -> MemoryReference: """ Get a suitable MemoryReference for a given expression. """ nonlocal mref_idx expr = str(expr) if expr in seen_exprs: return seen_exprs[expr] new_mref = MemoryReference(mref_name, mref_idx) seen_exprs[expr] = new_mref mref_idx += 1 recalculation_table[aref(new_mref)] = expr return new_mref for inst in prog: if isinstance(inst, Gate): new_params: List[Union[Real, MemoryReference]] = [] for param in inst.params: if isinstance(param, Real): new_params.append(param) elif isinstance(param, Expression): # Quil gate angles are in radians, # but downstream processing expects revolutions expr = str(Div(param, 2 * np.pi)) new_params.append(expr_mref(expr)) else: raise ValueError( f"Unknown parameter type {type(param)} in {inst}.") updated.inst(Gate(inst.name, new_params, inst.qubits)) elif isinstance(inst, (SetFrequency, ShiftFrequency)): if isinstance(inst.freq, Real): updated.inst(inst) continue try: fdefn = prog.frames[inst.frame] except KeyError: raise ValueError( f"Unable to rewrite {inst} without DEFFRAME {inst.frame}.") if fdefn.sample_rate is None: raise ValueError( f"Unable to rewrite {inst} on frame with undefined SAMPLE-RATE." ) if fdefn.center_frequency: expr = Sub(inst.freq, fdefn.center_frequency) else: expr = inst.freq expr = Div(expr, fdefn.sample_rate) expr = str(expr) updated.inst(inst.__class__(inst.frame, expr_mref(expr))) elif isinstance(inst, (SetPhase, ShiftPhase)): if isinstance(inst.phase, Real): updated.inst(inst) else: # Quil phases are in radians # but downstream processing expects revolutions expr = str(Div(inst.phase, 2 * np.pi)) updated.inst(inst.__class__(inst.frame, expr_mref(expr))) elif isinstance(inst, SetScale): if isinstance(inst.scale, Real): updated.inst(inst) else: # scale is in [-4,4) # binary patching assumes periodic with period 1 # so we divide by 8... expr = str(Div(inst.scale, 8)) updated.inst(SetScale(inst.frame, expr_mref(expr))) else: updated.inst(inst) if mref_idx > 0: updated._instructions.insert(0, Declare(mref_name, "REAL", mref_idx)) return RewriteArithmeticResponse( quil=updated.out(), original_memory_descriptors=old_descriptors, recalculation_table=recalculation_table, )