def _create_circuit(self, size): parameters = pnp.random.uniform(low=-pnp.pi, high=pnp.pi, size=(size, size)) return MaskedCircuit.full_circuit(parameters=parameters, layers=size, wires=size)
def create_circuit(size: int, layer_size: int = 1): if layer_size == 1: parameters = pnp.random.uniform(low=-pnp.pi, high=pnp.pi, size=(size, size)) else: parameters = pnp.random.uniform( low=-pnp.pi, high=pnp.pi, size=(size, size, layer_size) ) return MaskedCircuit.full_circuit(parameters=parameters, layers=size, wires=size)
def basic_variational_circuit(params, rotations, masked_circuit: MaskedCircuit): full_parameters = masked_circuit.expanded_parameters(params) wires = masked_circuit.wires dropout_mask = masked_circuit.full_mask(DropoutMask) for wire, _is_masked in enumerate(masked_circuit.mask(Axis.WIRES)): qml.RY(np.pi / 4, wires=wire) r = -1 for layer, _is_layer_masked in enumerate(masked_circuit.mask(Axis.LAYERS)): for wire, _is_wire_masked in enumerate(masked_circuit.mask(Axis.WIRES)): r += 1 if dropout_mask[layer][wire]: continue if rotations[r] == 0: rotation = qml.RX elif rotations[r] == 1: rotation = qml.RY else: rotation = qml.RZ rotation(full_parameters[layer][wire], wires=wire) for wire in range(0, wires - 1, 2): if ( Axis.ENTANGLING in masked_circuit.masks and masked_circuit.mask(Axis.ENTANGLING)[layer, wire] ): continue qml.CZ(wires=[wire, wire + 1]) for wire in range(1, wires - 1, 2): if ( Axis.ENTANGLING in masked_circuit.masks and masked_circuit.mask(Axis.ENTANGLING)[layer, wire] ): continue qml.CZ(wires=[wire, wire + 1])
def test_mask(self): """ The aggregated mask is built dynamically from the registered masks for the different axes. The test ensures that the mask covers the whole set of parameters. """ size = 3 # test circuit with all masks mc = MaskedCircuit.full_circuit( parameters=pnp.random.uniform(low=0, high=1, size=(size, size)), layers=size, wires=size, ) assert mc.full_mask(DropoutMask).size == size * size # test circuit with no masks mc = MaskedCircuit( parameters=pnp.random.uniform(low=0, high=1, size=(size, size)), layers=size, wires=size, ) assert mc.full_mask(DropoutMask).size == size * size # test circuit containing only layer mask mc = MaskedCircuit( parameters=pnp.random.uniform(low=0, high=1, size=(size, size)), layers=size, wires=size, masks=[(Axis.LAYERS, DropoutMask)], ) assert mc.full_mask(DropoutMask).size == size * size
def _branch( self, masked_circuit: MaskedCircuit ) -> Optional[Dict[str, MaskedCircuit]]: if not self.perturb or self.dropout is None: return None branches = {} for key in self.dropout: branches[key] = MaskedCircuit.execute(masked_circuit, self.dropout[key]) return branches
def _create_circuit_with_entangling_gates(self, size): parameters = pnp.random.uniform(low=-pnp.pi, high=pnp.pi, size=(size, size)) return MaskedCircuit.full_circuit( parameters=parameters, layers=size, wires=size, entangling_mask=DropoutMask(shape=(size, size - 1)), )
def test_default_value_shrink(self): mp = MaskedCircuit.full_circuit( parameters=pnp.random.uniform(low=-pnp.pi, high=pnp.pi, size=(4, 3, 2)), layers=4, wires=3, default_value=0, ) mp.mask(Axis.LAYERS)[:] = True mp.shrink(axis=Axis.LAYERS) assert pnp.sum(mp.parameters == 0) == 6
def test_default_value_perturb(self): mp = MaskedCircuit.full_circuit( parameters=pnp.random.uniform(low=-pnp.pi, high=pnp.pi, size=(4, 3, 2)), layers=4, wires=3, default_value=0, ) mp.mask(Axis.PARAMETERS)[:] = True mp.perturb(axis=Axis.PARAMETERS, amount=0.5, mode=Mode.INVERT) assert pnp.sum(mp.parameters == 0) == round(0.5 * 4 * 3 * 2)
def test_execute(self): mp = self._create_circuit(3) perturb_operation = { "perturb": { "amount": 1, "axis": Axis.PARAMETERS, "mode": Mode.SET, } } # test empty operations assert MaskedCircuit.execute(mp, []) == mp # test existing method MaskedCircuit.execute(mp, [perturb_operation]) assert pnp.sum(mp.full_mask(DropoutMask)) == 1 # test existing method with copy new_mp = MaskedCircuit.execute(mp, [{ "clear": {} }, { "copy": {} }, perturb_operation]) assert mp != new_mp assert pnp.sum(new_mp.full_mask(DropoutMask)) == 1 # test non-existing method with pytest.raises(AttributeError): MaskedCircuit.execute(mp, [{"non_existent": {"test": 1}}])
def variational_circuit(params, masked_circuit: MaskedCircuit = None): full_parameters = masked_circuit.expanded_parameters(params) for layer, layer_hidden in enumerate(masked_circuit.mask(Axis.LAYERS)): if not layer_hidden: for wire, wire_hidden in enumerate(masked_circuit.mask(Axis.WIRES)): if not wire_hidden: if not masked_circuit.mask(Axis.PARAMETERS)[layer][wire][0]: qml.RX(full_parameters[layer][wire][0], wires=wire) if not masked_circuit.mask(Axis.PARAMETERS)[layer][wire][1]: qml.RY(full_parameters[layer][wire][1], wires=wire) for wire in range(0, masked_circuit.mask(Axis.LAYERS).size - 1, 2): qml.CZ(wires=[wire, wire + 1]) for wire in range(1, masked_circuit.mask(Axis.LAYERS).size - 1, 2): qml.CZ(wires=[wire, wire + 1]) return qml.probs(wires=range(len(masked_circuit.mask(Axis.WIRES))))
def create_freezable_circuit(size: int, layer_size: int = 1): if layer_size == 1: parameters = pnp.random.uniform(low=-pnp.pi, high=pnp.pi, size=(size, size)) else: parameters = pnp.random.uniform( low=-pnp.pi, high=pnp.pi, size=(size, size, layer_size) ) return MaskedCircuit( parameters=parameters, layers=size, wires=size, masks=( (axis, mask_type) for axis in Axis if axis is not Axis.ENTANGLING for mask_type in [DropoutMask, FreezeMask] ), )
def init_parameters( layers: int, current_layers: int, wires: int, default_value: Optional[float], dynamic_parameters: bool = True, mask_type: Type[Mask] = DropoutMask, ) -> MaskedCircuit: params_uniform = np.random.uniform(low=-np.pi, high=np.pi, size=(current_layers, wires)) params_zero = np.zeros((layers - current_layers, wires)) params_combined = np.concatenate((params_uniform, params_zero)) mc = MaskedCircuit.full_circuit( parameters=params_combined, layers=layers, wires=wires, default_value=default_value, entangling_mask=DropoutMask(shape=(layers, wires - 1)), dynamic_parameters=dynamic_parameters, mask_type=mask_type, ) mc.mask(Axis.LAYERS, mask_type=mask_type)[current_layers:] = True return mc
def step( self, masked_circuit: MaskedCircuit, optimizer, objective_fn, *args, ensemble_steps: int = 0, ) -> EnsembleResult: """ The parameter `ensemble_steps` defines the number of training steps that are executed for each ensemble branch in addition to one training step that is done before the branching. """ # first one trainingstep params, _cost, _gradient = optimizer.step_cost_and_grad( objective_fn, masked_circuit.differentiable_parameters, *args, masked_circuit=masked_circuit, ) masked_circuit.differentiable_parameters = params # then branching branches = self._branch(masked_circuit=masked_circuit) basic_active_gates = masked_circuit.active().unwrap() if branches is None: return EnsembleResult( branch=masked_circuit, branch_name="center", active=basic_active_gates, cost=objective_fn( masked_circuit.differentiable_parameters, masked_circuit=masked_circuit, ).unwrap(), gradient=_gradient, brutto_steps=1, netto_steps=1, brutto=basic_active_gates, netto=basic_active_gates, ensemble=False, ) branch_costs = [] branch_gradients = [] for branch in branches.values(): for _ in range(ensemble_steps): params, _cost, _gradient = optimizer.step_cost_and_grad( objective_fn, *args, branch.differentiable_parameters, masked_circuit=branch, ) branch.differentiable_parameters = params branch_costs.append( objective_fn(branch.differentiable_parameters, masked_circuit=branch) ) branch_gradients.append(_gradient) minimum_index = branch_costs.index(min(branch_costs)) branch_name = list(branches.keys())[minimum_index] selected_branch = branches[branch_name] # FIXME: as soon as real (in terms of masked) parameters are used, # no mask has to be applied # until then real gradients must be calculated as gradients also # contain values from dropped gates return EnsembleResult( branch=selected_branch, branch_name=branch_name, active=selected_branch.active(), cost=branch_costs[minimum_index].unwrap(), gradient=branch_gradients[minimum_index], brutto_steps=1 + len(branches) * ensemble_steps, netto_steps=1 + ensemble_steps, brutto=( basic_active_gates + sum( [branch.active() * ensemble_steps for branch in branches.values()] ) ).unwrap(), netto=( basic_active_gates + selected_branch.active() * ensemble_steps ).unwrap(), ensemble=True, )
def test_init(self): mp = self._create_circuit(3) assert mp assert len(mp.mask(Axis.WIRES)) == 3 assert len(mp.mask(Axis.LAYERS)) == 3 assert mp.mask(Axis.PARAMETERS).shape == (3, 3) size = 3 with pytest.raises(AssertionError): MaskedCircuit( parameters=pnp.random.uniform(low=0, high=1, size=(size, size)), layers=size - 1, wires=size, ) with pytest.raises(AssertionError): MaskedCircuit( parameters=pnp.random.uniform(low=0, high=1, size=(size, size)), layers=size + 1, wires=size, ) with pytest.raises(AssertionError): MaskedCircuit( parameters=pnp.random.uniform(low=0, high=1, size=(size, size)), layers=size, wires=size - 1, ) with pytest.raises(AssertionError): MaskedCircuit( parameters=pnp.random.uniform(low=0, high=1, size=(size, size)), layers=size, wires=size + 1, ) with pytest.raises(AssertionError): MaskedCircuit.full_circuit( parameters=pnp.random.uniform(low=0, high=1, size=(size, size)), layers=size, wires=size, entangling_mask=DropoutMask(shape=(size + 1, size)), ) with pytest.raises(NotImplementedError): MaskedCircuit( parameters=pnp.random.uniform(low=0, high=1, size=(size, size)), layers=size, wires=size, masks=[(Axis.ENTANGLING, DropoutMask)], ) mc = MaskedCircuit.full_circuit( parameters=pnp.random.uniform(low=0, high=1, size=(size, size)), layers=size, wires=size, wire_mask=DropoutMask(shape=(size, ), mask=pnp.ones((size, ), dtype=bool)), ) assert pnp.array_equal(mc.mask(Axis.WIRES), pnp.ones((size, ), dtype=bool))