diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000..4dd39af --- /dev/null +++ b/compile.sh @@ -0,0 +1,10 @@ +#!/bin/bash +#SBATCH --job-name=boost +#SBATCH --output=boost.log + +OPTIMIZATION_METHOD="cma" +OPTIMIZATION_CONFIG="{ \"maxiter\": 2}" + +python3 compiling.py --backend numpy --path "./results/XXZ_5seeds/sgd_10q_6l_27/" \ + --epoch 500 --steps 2 --optimization_method $OPTIMIZATION_METHOD \ + --optimization_config "$OPTIMIZATION_CONFIG" diff --git a/compiling.py b/compiling.py index d1a2980..c85d542 100644 --- a/compiling.py +++ b/compiling.py @@ -3,6 +3,7 @@ import logging import pathlib import time +from copy import deepcopy import numpy as np import qibo @@ -16,40 +17,53 @@ from boostvqe.models.dbi.double_bracket_evolution_oracles import ( FrameShiftedEvolutionOracle, MagneticFieldEvolutionOracle, - VQERotatedEvolutionOracle, XXZ_EvolutionOracle, ) from boostvqe.models.dbi.group_commutator_iteration_transpiler import ( DoubleBracketRotationType, GroupCommutatorIterationWithEvolutionOracles, - VQEBoostingGroupCommutatorIteration, ) from boostvqe.utils import ( OPTIMIZATION_FILE, PARAMS_FILE, build_circuit, - get_eo_d_initializations, optimize_D, - print_vqe_comparison_report, select_recursion_step_gd_circuit, ) logging.basicConfig(level=logging.INFO) +def dump_config(config: dict, path): + config["path"] = config["path"] + config["db_rotation"] = config["db_rotation"].value + (path / "config.json").write_text(json.dumps(config, indent=4)) + + def main(args): """VQE training.""" - path = args.path + path = pathlib.Path(args.path) + dump_path = ( + path + / f"{args.db_rotation.name}_{args.optimization_method}_{args.epoch}e_{args.steps}s" + ) + dump_path.mkdir(parents=True, exist_ok=True) config = json.loads((path / OPTIMIZATION_FILE).read_text()) + dump_config(deepcopy(vars(args)), path=dump_path) + + if args.optimization_config is None: + opt_options = {} + else: + opt_options = json.loads(args.optimization_config) - # TODO: improve loading of params try: params = np.load(path / f"parameters/params_ite{args.epoch}.npy") except FileNotFoundError: params = np.array( np.load(path / PARAMS_FILE, allow_pickle=True).tolist()[0][args.epoch] ) + nqubits = config["nqubits"] nlayers = config["nlayers"] vqe_backend = construct_backend(backend=config["backend"]) @@ -66,32 +80,38 @@ def main(args): ) vqe.circuit.set_parameters(params) - base_oracle = XXZ_EvolutionOracle( - nqubits=nqubits, steps=args.steps, order=args.order + base_oracle = XXZ_EvolutionOracle.from_nqubits( + nqubits=nqubits, delta=0.5, steps=args.steps, order=args.order ) - oracle = FrameShiftedEvolutionOracle( - base_oracle, - "my oracle", + oracle = FrameShiftedEvolutionOracle.from_evolution_oracle( before_circuit=vqe.circuit.invert(), after_circuit=vqe.circuit, + base_evolution_oracle=base_oracle, ) gci = GroupCommutatorIterationWithEvolutionOracles( - oracle, args.db_rotation, h_ref=hamiltonian + oracle, + args.db_rotation, ) # TODO: remove hardcoded magnetic field - eo_d = MagneticFieldEvolutionOracle([4 - np.sin(x / 3) for x in range(nqubits)]) + eo_d = MagneticFieldEvolutionOracle.from_b( + [4 - np.sin(x / 3) for x in range(nqubits)] + ) gci.eo_d = eo_d print( - f"The gci mode is {gci.mode_double_bracket_rotation} rotation with {gci.eo_d.name} as the oracle.\n" + f"The gci mode is {gci.double_bracket_rotation_type} rotation with {eo_d.__class__.__name__} as the oracle.\n" ) metadata = {} - print_report(report(vqe, hamiltonian, gci)) + this_report = report(vqe, hamiltonian, gci) + metadata[0] = this_report + for gci_step_nmb in range(args.steps): logging.info( - f"Optimizing GCI step {gci_step_nmb+1} with optimizer {args.optimization_method}" + "\n################################################################################\n" + + f"Optimizing GCI step {gci_step_nmb+1} with optimizer {args.optimization_method}" + + "\n################################################################################\n" ) it = time.time() if args.optimization_method == "sgd": @@ -100,12 +120,13 @@ def main(args): mode_dbr_list=[args.db_rotation], step_grid=np.linspace(1e-5, 2e-2, 30), lr_range=(1e-3, 1), - nmb_gd_epochs=args.gd_steps, + nmb_gd_epochs=opt_options["gd_epochs"], threshold=1e-4, max_eval_gd=30, please_be_visual=False, save_path="gci_step", ) + else: if gci_step_nmb == 0: p0 = [0.01] @@ -117,24 +138,27 @@ def main(args): params=p0, gci=gci, method=args.optimization_method, - maxiter=20, + **opt_options, ) best_s = optimized_params[0] best_b = optimized_params[1:] - eo_d = MagneticFieldEvolutionOracle(best_b) + eo_d = MagneticFieldEvolutionOracle.from_b(best_b) step_data = dict( best_s=best_s, - eo_d_name=eo_d.name, + eo_d_name=eo_d.__class__.__name__, eo_d_params=eo_d.params, ) logging.info(f"Total optimization time required: {time.time() - it} seconds") - metadata[gci_step_nmb] = report(vqe, hamiltonian, gci) | step_data gci.mode_double_bracket_rotation = args.db_rotation gci.eo_d = eo_d gci(best_s) - print_report(report(vqe, hamiltonian, gci)) - (args.path / "boosting_data.json").write_text(json.dumps(metadata)) + + this_report = report(vqe, hamiltonian, gci) + print_report(this_report) + metadata[gci_step_nmb + 1] = this_report | step_data + + (dump_path / "boosting_data.json").write_text(json.dumps(metadata, indent=4)) def report(vqe, hamiltonian, gci): @@ -199,31 +223,26 @@ def print_report(report: dict): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Boosting VQE with DBI.") parser.add_argument("--backend", default="qibojit", type=str, help="Qibo backend") - parser.add_argument( - "--path", - type=pathlib.Path, - default=pathlib.Path("XXZ_5seeds/moreonXXZ/sgd_10q_7l_42"), - help="Output folder", - ) + parser.add_argument("--path", type=str, help="Output folder") parser.add_argument( "--epoch", default=-1, type=int, help="VQE epoch where DBI will be applied." ) parser.add_argument("--steps", default=2, type=int, help="DBI steps") - parser.add_argument( - "--gd_steps", default=1, type=int, help="Gradient descent steps" - ) parser.add_argument("--order", default=2, type=int, help="Suzuki-Trotter order") parser.add_argument( "--db_rotation", - default=DoubleBracketRotationType.group_commutator_third_order_reduced, - type=DoubleBracketRotationType, + type=lambda arg: DoubleBracketRotationType[arg], + choices=DoubleBracketRotationType, + default="group_commutator_reduced", help="DB rotation type.", ) parser.add_argument( - "--eo_d_name", default="B Field", type=str, help="D initialization" + "--optimization_method", default="sgd", type=str, help="Optimization method" ) parser.add_argument( - "--optimization_method", default="sgd", type=str, help="Optimization method" + "--optimization_config", + type=str, + help="Options to customize the optimizer training.", ) args = parser.parse_args() main(args) diff --git a/run_sgd.sh b/run_sgd.sh deleted file mode 100755 index 5374984..0000000 --- a/run_sgd.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -#SBATCH --job-name=boostvqe -#SBATCH --output=boostvqe.log - -NQUBITS=4 -NLAYERS=2 - -DBI_STEPS=2 -NBOOST=2 -BOOST_FREQUENCY=10 - -NSHOTS=10000 -TOL=1e-8 -ACC=0.5 - -OPTIMIZER="sgd" -BACKEND="tensorflow" -OPTIMIZER_OPTIONS="{ \"optimizer\": \"Adagrad\", \"learning_rate\": 0.1, \"nmessage\": 1, \"nepochs\": $BOOST_FREQUENCY }" - -python main.py --nqubits $NQUBITS --nlayers $NLAYERS --optimizer $OPTIMIZER \ - --output_folder results/debugging --backend $BACKEND --tol $TOL \ - --dbi_step $DBI_STEPS --seed 42 \ - --boost_frequency $BOOST_FREQUENCY --nboost $NBOOST \ - --optimizer_options "$OPTIMIZER_OPTIONS" diff --git a/src/boostvqe/models/dbi/double_bracket_evolution_oracles.py b/src/boostvqe/models/dbi/double_bracket_evolution_oracles.py index 25dc7a6..570fa09 100644 --- a/src/boostvqe/models/dbi/double_bracket_evolution_oracles.py +++ b/src/boostvqe/models/dbi/double_bracket_evolution_oracles.py @@ -1,4 +1,5 @@ from copy import deepcopy +from dataclasses import dataclass from enum import Enum, auto from functools import reduce @@ -9,13 +10,11 @@ from qibo.config import raise_error from qibo.hamiltonians import AbstractHamiltonian, SymbolicHamiltonian -from boostvqe.compiling_XXZ import * +# TODO: remove this global import +from boostvqe.compiling_XXZ import nqubit_XXZ_decomposition class EvolutionOracleType(Enum): - text_strings = auto() - """If you only want to get a sequence of names of the oracle""" - numerical = auto() """If you will work with exp(is_k J_k) as a numerical matrix""" @@ -23,140 +22,66 @@ class EvolutionOracleType(Enum): """If you will use SymbolicHamiltonian""" +@dataclass class EvolutionOracle: - def __init__( - self, - h_generator: AbstractHamiltonian, - name, - mode_evolution_oracle: EvolutionOracleType = EvolutionOracleType.text_strings, - ): - if ( - mode_evolution_oracle is EvolutionOracleType.hamiltonian_simulation - and type(h_generator) is not SymbolicHamiltonian - ): - raise_error( - TypeError, - "If the evolution oracle mode will be to make Trotter-Suzuki decompositions then you must use the SymbolicHamiltonian generator", - ) - if h_generator is None and name is None: - raise_error( - NotImplementedError, - "You have to specify either a matrix and then work in the numerical mode, or SymbolicHamiltonian and work in hamiltonian_simulation mode or at least a name and work with text_strings to list DBI query lists", - ) + h: AbstractHamiltonian + evolution_oracle_type: EvolutionOracleType - if ( - mode_evolution_oracle is EvolutionOracleType.numerical - and type(h_generator) is SymbolicHamiltonian - ): - self.h = h_generator.dense - else: - self.h = h_generator - self.nqubits = self.h.nqubits - self.name = name - self.mode_evolution_oracle = mode_evolution_oracle - self.mode_find_number_of_trottersuzuki_steps = True - self.eps_trottersuzuki = 0.0001 - self.please_be_verbose = False - self.please_use_prescribed_nmb_ts_steps = False + def __post_init__(self): + self.steps = 1 def __call__(self, t_duration: float): """Returns either the name or the circuit""" return self.circuit(t_duration=t_duration) - def eval_unitary(self, t_duration): - """This wraps around `circuit` and always returns a unitary""" - if self.mode_evolution_oracle is EvolutionOracleType.numerical: - return self.circuit(t_duration) - elif self.mode_evolution_oracle is EvolutionOracleType.hamiltonian_simulation: - return self.circuit(t_duration).unitary() - - def circuit(self, t_duration: float = None): + def circuit(self, t_duration: float): """This function returns depending on `EvolutionOracleType` string, ndarray or `Circuit`. In the hamiltonian_simulation mode we evaluate an appropriate Trotter-Suzuki discretization up to `self.eps_trottersuzuki` threshold. """ - if self.mode_evolution_oracle is EvolutionOracleType.text_strings: - return self.name + str(t_duration) - elif self.mode_evolution_oracle is EvolutionOracleType.numerical: + if self.evolution_oracle_type is EvolutionOracleType.numerical: return self.h.exp(t_duration) - elif self.mode_evolution_oracle is EvolutionOracleType.hamiltonian_simulation: - if self.please_use_prescribed_nmb_ts_steps is False: - return self.discretized_evolution_circuit_binary_search( - t_duration, eps=self.eps_trottersuzuki - ) - else: - dt = t_duration / self.please_use_prescribed_nmb_ts_steps - return reduce( - Circuit.__add__, - [deepcopy(self.h).circuit(dt)] - * self.please_use_prescribed_nmb_ts_steps, - ) - - def discretized_evolution_circuit_binary_search(self, t_duration, eps=None): - nmb_trottersuzuki_steps = 1 # this is the smallest size - nmb_trottersuzki_steps_right = 800 # this is the largest size for binary search - if eps is None: - eps = self.eps_trottersuzuki - target_unitary = self.h.exp(t_duration) - - def check_accuracy(n_steps): - proposed_circuit_unitary = np.linalg.matrix_power( - deepcopy(self.h).circuit(t_duration / n_steps).unitary(), - n_steps, - ) - norm_difference = np.linalg.norm(target_unitary - proposed_circuit_unitary) - return norm_difference < eps - - nmb_trottersuzuki_steps_used = nmb_trottersuzki_steps_right - while nmb_trottersuzuki_steps <= nmb_trottersuzki_steps_right: - mid = ( - nmb_trottersuzuki_steps - + (nmb_trottersuzki_steps_right - nmb_trottersuzuki_steps) // 2 + else: + dt = t_duration / self.steps + return reduce( + Circuit.__add__, + [deepcopy(self.h).circuit(dt)] * self.steps, ) - if check_accuracy(mid): - nmb_trottersuzuki_steps_used = mid - nmb_trottersuzki_steps_right = mid - 1 - else: - nmb_trottersuzuki_steps = mid + 1 - nmb_trottersuzuki_steps = nmb_trottersuzuki_steps_used - - circuit_1_step = deepcopy(self.h.circuit(t_duration / nmb_trottersuzuki_steps)) - combined_circuit = reduce( - Circuit.__add__, [circuit_1_step] * nmb_trottersuzuki_steps - ) - assert ( - np.linalg.norm(combined_circuit.unitary() - target_unitary) < eps - ), f"{np.linalg.norm(combined_circuit.unitary() - target_unitary)},{eps}, {nmb_trottersuzuki_steps}" - return combined_circuit +@dataclass class FrameShiftedEvolutionOracle(EvolutionOracle): - def __init__( - self, + before_circuit: str + after_circuit: str + base_evolution_oracle: EvolutionOracle + + @classmethod + def from_evolution_oracle( + cls, base_evolution_oracle: EvolutionOracle, - name, before_circuit, after_circuit, ): - assert isinstance(before_circuit, type(after_circuit)) + return cls( + base_evolution_oracle=base_evolution_oracle, + before_circuit=before_circuit, + after_circuit=after_circuit, + h=base_evolution_oracle.h, + evolution_oracle_type=base_evolution_oracle.evolution_oracle_type, + ) - self.h = base_evolution_oracle.h - self.base_evolution_oracle = base_evolution_oracle - self.name = name + "(" + base_evolution_oracle.name + ")" - self.mode_evolution_oracle = base_evolution_oracle.mode_evolution_oracle - self.before_circuit = before_circuit - self.after_circuit = after_circuit - self.nqubits = base_evolution_oracle.nqubits + @property + def nqubits(self): + assert self.before_circuit.nqubits == self.after_circuit.nqubits + return self.before_circuit.nqubits def circuit(self, t_duration: float = None): - if self.mode_evolution_oracle is EvolutionOracleType.text_strings: - return self.name + "(" + str(t_duration) + ")" - elif self.mode_evolution_oracle is EvolutionOracleType.numerical: + if self.evolution_oracle_type is EvolutionOracleType.numerical: return ( self.before_circuit @ self.base_evolution_oracle(t_duration) @ self.after_circuit ) - elif self.mode_evolution_oracle is EvolutionOracleType.hamiltonian_simulation: + elif self.evolution_oracle_type is EvolutionOracleType.hamiltonian_simulation: return ( self.after_circuit + self.base_evolution_oracle.circuit(t_duration) @@ -172,63 +97,41 @@ def get_composed_circuit(self): c = Circuit(nqubits=self.nqubits) fseo = self while isinstance(fseo, FrameShiftedEvolutionOracle): - if self.mode_evolution_oracle is EvolutionOracleType.numerical: + if ( + self.base_evolution_oracle.evolution_oracle_type + is EvolutionOracleType.numerical + ): c = c @ fseo.after_circuit elif ( - self.mode_evolution_oracle is EvolutionOracleType.hamiltonian_simulation + self.base_evolution_oracle.evolution_oracle_type + is EvolutionOracleType.hamiltonian_simulation ): c = c + fseo.after_circuit fseo = fseo.base_evolution_oracle return c -class VQERotatedEvolutionOracle(FrameShiftedEvolutionOracle): - def __init__( - self, - base_evolution_oracle: EvolutionOracle, - vqe, - name="VQE Rotated EO", - ): - super().__init__( - base_evolution_oracle, - before_circuit=vqe.circuit.invert(), - after_circuit=vqe.circuit, - name="shifting by vqe", - ) - self.vqe = vqe - - +@dataclass class MagneticFieldEvolutionOracle(EvolutionOracle): - def __init__( - self, - b_list, - name="B Field", - mode_evolution_oracle: EvolutionOracleType = EvolutionOracleType.hamiltonian_simulation, - ): - self.nqubits = len(b_list) - d = SymbolicHamiltonian( - sum([b * symbols.Z(j) for j, b in zip(range(self.nqubits), b_list)]) - ) - super().__init__(d, name, mode_evolution_oracle) - self.b_list = b_list - self.please_assess_how_many_steps_to_use = ( - False # otherwise methods which cast to dense will be used - ) + b: list @property def params(self): - return self.b_list.tolist() - - def discretized_evolution_circuit_binary_search(self, t_duration, eps=None): - if self.mode_evolution_oracle is EvolutionOracleType.numerical: - return self.h.exp(t_duration) - - if self.please_assess_how_many_steps_to_use: - return super().discretized_evolution_circuit_binary_search( - t_duration, eps=eps - ) - else: - return self.h.circuit(t_duration) + if isinstance(self.b, list): + return self.b + return self.b.tolist() + + @classmethod + def from_b( + cls, + b: list, + evolution_oracle_type: EvolutionOracleType = EvolutionOracleType.hamiltonian_simulation, + ): + nqubits = len(b) + hamiltonian = SymbolicHamiltonian( + sum([bi * symbols.Z(j) for j, bi in zip(range(nqubits), b)]) + ) + return cls(h=hamiltonian, evolution_oracle_type=evolution_oracle_type, b=b) class IsingNNEvolutionOracle(EvolutionOracle): @@ -237,7 +140,7 @@ def __init__( b_list, j_list, name="H_ClassicalIsing(B,J)", - mode_evolution_oracle: EvolutionOracleType = EvolutionOracleType.hamiltonian_simulation, + evolution_oracle_type: EvolutionOracleType = EvolutionOracleType.hamiltonian_simulation, ): """ Constructs the evolution oracle for the classical Ising model @@ -255,7 +158,7 @@ def __init__( ] ) ) - super().__init__(d, name, mode_evolution_oracle) + super().__init__(d, name, evolution_oracle_type) self.b_list = b_list self.j_list = j_list self.please_assess_how_many_steps_to_use = ( @@ -268,7 +171,7 @@ def params(self): return self.b_list.tolist() + self.j_list.tolist() def discretized_evolution_circuit_binary_search(self, t_duration, eps=None): - if self.mode_evolution_oracle is EvolutionOracleType.numerical: + if self.evolution_oracle_type is EvolutionOracleType.numerical: return self.h.exp(t_duration) if self.please_assess_how_many_steps_to_use: @@ -312,90 +215,16 @@ def circuit(self, t): ) return circuit - def _test_gate_assigment(): - l = [] - for t in np.linspace(0, 4, 20): - c = Circuit(1) - c.add(gates.RZ(0, theta=t)) - u = c.unitary() - d = SymbolicHamiltonian(symbols.Z(0), nqubits=1) - l.append(np.linalg.norm(d.exp(t / 2) - u)) - plt.plot(l) - - l = [] - for t in np.linspace(0, 4, 20): - c = Circuit(2) - c.add(gates.CNOT(0, 1)) - c.add(gates.RZ(1, theta=t)) - c.add(gates.CNOT(0, 1)) - u = c.unitary() - d = SymbolicHamiltonian(symbols.Z(0) * symbols.Z(1), nqubits=2) - l.append(np.linalg.norm(d.exp(t / 2) - u)) - plt.plot(l) - - n = 3 - eo_d_Ising = IsingNNEvolutionOracle([0] * n, [1] * n) - - l = [] - for t in np.linspace(0, 4, 20): - u = circuit(eo_d_Ising, t).unitary() - l.append(np.linalg.norm(eo_d_Ising.h.exp(t) - u)) - plt.plot(l) - print(circuit(eo_d_Ising, t).draw()) - +@dataclass class XXZ_EvolutionOracle(EvolutionOracle): - def __init__( - self, - nqubits, - name="XXZ", - mode_evolution_oracle: EvolutionOracleType = EvolutionOracleType.hamiltonian_simulation, - steps=None, - order=None, - delta=0.5, - ): - super().__init__( - XXZ_EvolutionOracle.xxz_symbolic(nqubits, delta=delta), - name, - mode_evolution_oracle, - ) - - if steps is None: - self.steps = 1 - else: - self.steps = steps - if order is None: - self.order = 1 - else: - self.order = order - self.nqubits = nqubits - self.delta = delta - self.please_assess_how_many_steps_to_use = False - - def discretized_evolution_circuit_binary_search(self, t_duration, eps=None): - if self.please_assess_how_many_steps_to_use: - return super().discretized_evolution_circuit_binary_search( - t_duration, eps=eps - ) - else: - return self.h.circuit(t_duration) - - def circuit(self, t_duration, steps=None, order=None): - if steps is None: - steps = self.steps - if order is None: - order = self.order - return nqubit_XXZ_decomposition( - nqubits=self.h.nqubits, - t=t_duration, - delta=self.delta, - steps=steps, - order=order, - ) + steps: int = None + order: int = None + delta: float = 0.5 - @staticmethod - def xxz_symbolic(nqubits, delta=0.5): - return SymbolicHamiltonian( + @classmethod + def from_nqubits(cls, nqubits, delta, **kwargs): + hamiltonian = SymbolicHamiltonian( sum( [ symbols.X(j) * symbols.X(j + 1) @@ -411,3 +240,21 @@ def xxz_symbolic(nqubits, delta=0.5): ), nqubits=nqubits, ) + return cls( + h=hamiltonian, + evolution_oracle_type=EvolutionOracleType.hamiltonian_simulation, + **kwargs, + ) + + def circuit(self, t_duration, steps=None, order=None): + if steps is None: + steps = self.steps + if order is None: + order = self.order + return nqubit_XXZ_decomposition( + nqubits=self.h.nqubits, + t=t_duration, + delta=self.delta, + steps=steps, + order=order, + ) diff --git a/src/boostvqe/models/dbi/group_commutator_iteration_transpiler.py b/src/boostvqe/models/dbi/group_commutator_iteration_transpiler.py index facea03..7f7e231 100644 --- a/src/boostvqe/models/dbi/group_commutator_iteration_transpiler.py +++ b/src/boostvqe/models/dbi/group_commutator_iteration_transpiler.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from enum import Enum, auto import hyperopt @@ -15,68 +16,41 @@ class DoubleBracketRotationType(Enum): # The dbr types below need a diagonal input matrix $\hat D_k$ : - single_commutator = auto() - """Use single commutator.""" - group_commutator = auto() """Use group commutator approximation""" - group_commutator_reordered = auto() """Use group commutator approximation with reordering of the operators""" - group_commutator_reduced = auto() """Use group commutator approximation with a reduction using symmetry""" group_commutator_third_order = auto() """Higher order approximation """ group_commutator_third_order_reduced = auto() """Higher order approximation """ - group_commutator_mix_twice = auto() - group_commutator_reduced_twice = auto() - group_commutator_third_order_reduced_twice = auto() +@dataclass class GroupCommutatorIterationWithEvolutionOracles(DoubleBracketIteration): """ Class which will be later merged into the @super somehow""" - def __init__( - self, - input_hamiltonian_evolution_oracle: EvolutionOracle, - mode_double_bracket_rotation: DoubleBracketRotationType = DoubleBracketRotationType.group_commutator, - h_ref=None, - ): - if mode_double_bracket_rotation is DoubleBracketRotationType.single_commutator: - mode_double_bracket_rotation_old = ( - DoubleBracketGeneratorType.single_commutator - ) - else: - mode_double_bracket_rotation_old = ( - DoubleBracketGeneratorType.group_commutator - ) - super().__init__( - input_hamiltonian_evolution_oracle.h, mode_double_bracket_rotation_old - ) - if h_ref is not None: - self.h_ref = h_ref - else: - self.h_ref = deepcopy(input_hamiltonian_evolution_oracle.h) - self.input_hamiltonian_evolution_oracle = input_hamiltonian_evolution_oracle - - self.mode_double_bracket_rotation = mode_double_bracket_rotation + input_hamiltonian_evolution_oracle: EvolutionOracle + double_bracket_rotation_type: DoubleBracketRotationType - self.gci_unitary = [] - self.gci_unitary_dagger = [] + def __post_init__(self): self.iterated_hamiltonian_evolution_oracle = deepcopy( self.input_hamiltonian_evolution_oracle ) - self.please_evaluate_matrices = False - self.default_step_grid = np.linspace(0.001, 0.03, 10) - self.eo_d = MagneticFieldEvolutionOracle([1] * self.nqubits) - self.please_save_fig_to_pdf = False + @property + def nqubits(self): + return self.h.nqubits + + @property + def h(self): + return self.input_hamiltonian_evolution_oracle.h def __call__( self, @@ -88,68 +62,27 @@ def __call__( diagonal_association = self.eo_d # Set rotation type if mode_dbr is None: - mode_dbr = self.mode_double_bracket_rotation - - if mode_dbr is DoubleBracketRotationType.single_commutator: - raise_error( - ValueError, - "single_commutator DBR mode doesn't make sense with EvolutionOracle", - ) + mode_dbr = self.double_bracket_rotation_type # This will run the appropriate group commutator step rs_circ = self.recursion_step_circuit( step_duration, diagonal_association, mode_dbr=mode_dbr ) if ( - self.input_hamiltonian_evolution_oracle.mode_evolution_oracle + self.input_hamiltonian_evolution_oracle.evolution_oracle_type is EvolutionOracleType.numerical ): rs_circ_inv = np.linalg.inv(rs_circ) else: rs_circ_inv = rs_circ.invert() - self.iterated_hamiltonian_evolution_oracle = FrameShiftedEvolutionOracle( - deepcopy(self.iterated_hamiltonian_evolution_oracle), - str(step_duration), - rs_circ_inv, - rs_circ, - ) - - if self.please_evaluate_matrices: - if ( - self.input_hamiltonian_evolution_oracle.mode_evolution_oracle - is EvolutionOracleType.numerical - ): - self.h.matrix = rs_circ_inv_ @ self.h.matrix @ rs_circ - - elif ( - self.input_hamiltonian_evolution_oracle.mode_evolution_oracle - is EvolutionOracleType.hamiltonian_simulation - ): - self.h.matrix = ( - rs_circ_inv.unitary() @ self.h.matrix @ rs_circ.unitary() - ) - - elif ( - self.input_hamiltonian_evolution_oracle.mode_evolution_oracle - is EvolutionOracleType.text_strings - ): - raise_error(NotImplementedError) - else: - super().__call__(step_duration, diagonal_association.h.dense.matrix) - - def eval_gcr_unitary( - self, - step_duration: float, - eo_1: EvolutionOracle, - eo_2: EvolutionOracle = None, - mode_dbr: DoubleBracketRotationType = None, - ): - u = self.recursion_step_circuit(step_duration, eo_1, eo_2, mode_dbr=mode_dbr) - if eo_1.mode_evolution_oracle is EvolutionOracleType.hamiltonian_simulation: - return u.unitary() - elif eo_1.mode_evolution_oracle is EvolutionOracleType.numerical: - return u + self.iterated_hamiltonian_evolution_oracle = ( + FrameShiftedEvolutionOracle.from_evolution_oracle( + deepcopy(self.iterated_hamiltonian_evolution_oracle), + rs_circ_inv, + rs_circ, + ) + ) def group_commutator( self, @@ -162,19 +95,11 @@ def group_commutator( if eo_2 is None: eo_2 = self.iterated_hamiltonian_evolution_oracle - assert eo_1.mode_evolution_oracle.value is eo_2.mode_evolution_oracle.value - if mode_dbr is None: - gc_type = self.mode_double_bracket_rotation + gc_type = self.double_bracket_rotation_type else: gc_type = mode_dbr - if gc_type is DoubleBracketRotationType.single_commutator: - raise_error( - ValueError, - "You are trying to get the group commutator query list but your dbr mode is single_commutator and not an approximation by means of a product formula!", - ) - if gc_type is DoubleBracketRotationType.group_commutator: query_list_forward = [ deepcopy(eo_2).circuit(s_step), @@ -270,14 +195,9 @@ def group_commutator( "You are in the group commutator query list but your dbr mode is not recognized", ) - eo_mode = eo_1.mode_evolution_oracle + eo_mode = eo_1.evolution_oracle_type - if eo_mode is EvolutionOracleType.text_strings: - return { - "forwards": reduce(str.__add__, query_list_forward), - "backwards": reduce(str.__add__, query_list_backward), - } - elif eo_mode is EvolutionOracleType.hamiltonian_simulation: + if eo_mode is EvolutionOracleType.hamiltonian_simulation: return { "forwards": reduce(Circuit.__add__, query_list_forward[::-1]), "backwards": reduce(Circuit.__add__, query_list_backward[::-1]), @@ -303,7 +223,7 @@ def loss(self, step_duration: float = None, eo_d=None, mode_dbr=None): circ = self.get_composed_circuit() if step_duration is not None: circ = self.recursion_step_circuit(step_duration, eo_d, mode_dbr) + circ - return self.h_ref.expectation(circ().state()) + return self.h.expectation(circ().state()) def choose_step( self, @@ -376,76 +296,3 @@ def print_gate_count_report(self): f"The boosting circuit used {counts['nmb_cnot']} CNOT gates coming from compiled XXZ evolution and {counts['nmb_cz']} CZ gates from VQE.\n\ For {self.nqubits} qubits this gives n_CNOT/n_qubits = {counts['nmb_cnot_relative']} and n_CZ/n_qubits = {counts['nmb_cz_relative']}" ) - - -class VQEBoostingGroupCommutatorIteration(GroupCommutatorIterationWithEvolutionOracles): - def __init__( - self, - input_hamiltonian_evolution_oracle: EvolutionOracle, - mode_double_bracket_rotation: DoubleBracketRotationType = DoubleBracketRotationType.group_commutator, - path=None, - h_ref=None, - ): - super().__init__( - input_hamiltonian_evolution_oracle, mode_double_bracket_rotation, h_ref=None - ) - hamiltonian = hamiltonians.XXZ(nqubits=self.nqubits, delta=0.5) - self.h_ref = hamiltonian - self.vqe = input_hamiltonian_evolution_oracle.vqe - - eigenergies = hamiltonian.eigenvalues() - target_energy = np.min(eigenergies) - self.h.target_energy = target_energy - eigenergies.sort() - gap = eigenergies[1] - target_energy - self.h.gap = gap - self.h.ground_state = hamiltonian.eigenvectors()[:, 0] - - self.vqe_energy = hamiltonian.expectation( - input_hamiltonian_evolution_oracle.vqe.circuit().state() - ) - - b_list = [1 + np.sin(x / 3) for x in range(10)] - self.eo_d = MagneticFieldEvolutionOracle(b_list, name="D(B = 1+sin(x/3))") - self.default_step_grid = np.linspace(0.003, 0.004, 10) - - self.path = path - self.boosting_callback_data = [self.get_vqe_boosting_data()] - - def get_vqe_boosting_data(self, nmb_digits_rounding=2): - gci_loss = self.loss() - return ( - dict( - gci_loss=gci_loss, - vqe_energy=self.vqe_energy, - target_energy=self.h.target_energy, - diff_vqe_target=self.vqe_energy - self.h.target_energy, - diff_gci_target=gci_loss - self.h.target_energy, - gap=self.h.gap, - diff_vqe_target_perc=abs(self.vqe_energy - self.h.target_energy) - / abs(self.h.target_energy) - * 100, - diff_gci_target_perc=abs(gci_loss - self.h.target_energy) - / abs(self.h.target_energy) - * 100, - fidelity_witness_vqe=self.gnd_state_fidelity_witness(self.vqe_energy), - fidelity_witness_gci=self.gnd_state_fidelity_witness(gci_loss), - fidelity_vqe=self.gnd_state_fidelity( - input_state=self.vqe.circuit().state() - ), - fidelity_gci=self.gnd_state_fidelity(), - eo_d=self.eo_d, - circuit_at_step=self.get_composed_circuit(), - ) - | self.get_gate_count_dict() - ) - - def gnd_state_fidelity_witness(self, e_state=None): - if e_state is None: - e_state = self.loss() - return 1 - (e_state - self.h.target_energy) / self.h.gap - - def gnd_state_fidelity(self, input_state=None): - if input_state is None: - input_state = self.get_composed_circuit()().state() - return abs(self.h.ground_state.T.conj() @ input_state) ** 2 diff --git a/src/boostvqe/models/dbi/utils_gci_optimization.py b/src/boostvqe/models/dbi/utils_gci_optimization.py index eec96c4..6706fe0 100644 --- a/src/boostvqe/models/dbi/utils_gci_optimization.py +++ b/src/boostvqe/models/dbi/utils_gci_optimization.py @@ -211,7 +211,7 @@ def gradient_descent_circuits_lr( def get_gd_evolution_oracle(n_local, params): if n_local == 1: - return MagneticFieldEvolutionOracle(params) + return MagneticFieldEvolutionOracle.from_b(params) elif n_local == 2: return IsingNNEvolutionOracle( params[: int(len(params) / 2)], params[int(len(params) / 2) :] diff --git a/src/boostvqe/utils.py b/src/boostvqe/utils.py index 2cc3b98..7f4b70f 100644 --- a/src/boostvqe/utils.py +++ b/src/boostvqe/utils.py @@ -297,12 +297,12 @@ def select_recursion_step_gd_circuit( if eo_d is None: eo_d = gci.eo_d - if eo_d.name == "B Field": + if isinstance(eo_d, MagneticFieldEvolutionOracle): n_local = 1 - params = eo_d.b_list - elif eo_d.name == "H_ClassicalIsing(B,J)": + params = eo_d.params + elif isinstance(eo_d, IsingNNEvolutionOracle): n_local = 2 - params = eo_d.b_list + eo_d.j_list + params = eo_d.params else: raise_error(ValueError, "Evolution oracle type not supported.") @@ -352,7 +352,7 @@ def select_recursion_step_gd_circuit( if please_be_verbose: print( f"Just finished the selection: better loss {minimal_losses[minimizer_dbr_id]} for mode {mode_dbr_list[minimizer_dbr_id]},\ - with duration s={minimizer_s[minimizer_dbr_id]}, and eo_d name = {minimizer_eo_d[minimizer_dbr_id].name}" + with duration s={minimizer_s[minimizer_dbr_id]}, and eo_d name = {minimizer_eo_d[minimizer_dbr_id].__class__.__name__}" ) return ( mode_dbr_list[minimizer_dbr_id], @@ -532,13 +532,13 @@ def plot_lr_s_loss(eval_dict): def callback_D_optimization(params, gci, loss_history, params_history): params_history.append(params) - eo_d = MagneticFieldEvolutionOracle(params[1:]) + eo_d = MagneticFieldEvolutionOracle.from_b(params[1:]) loss_history.append(gci.loss(params[0], eo_d)) def loss_function_D(params, gci): """``params`` has shape [s0, b_list_0].""" - eo = MagneticFieldEvolutionOracle(params[1:]) + eo = MagneticFieldEvolutionOracle.from_b(params[1:]) return gci.loss(params[0], eo)