Beispiel #1
0
    def _create_computers(self):
        """
        Create the default timeslot, fidelity and propagator computers
        """
        # The time slot computer. By default it is set to _UpdateAll
        # can be set to _DynUpdate in the configuration
        # (see class file for details)
        if self.config.amp_update_mode == 'DYNAMIC':
            self.tslot_computer = tslotcomp.TSlotCompDynUpdate(self)
        else:
            self.tslot_computer = tslotcomp.TSlotCompUpdateAll(self)

        # set the default fidelity computer
        self.fid_computer = fidcomp.FidCompUnitary(self)
        # set the default propagator computer
        self.prop_computer = propcomp.PropCompDiag(self)
Beispiel #2
0
def gen_config(param_fname='unctrlsympl_params.ini'):
    """
    Generate the configuration for the oscillator
    In general values from the parameter file overwrite those hardcoded
    and these in turn are overwritten by cmdline args
    Returns
    -------
    Optimizer
    """

    parser = argparse.ArgumentParser(
        description="Command line argument parser")
    parser.add_argument('-p',
                        '--param_file',
                        type=str,
                        default="",
                        help="Parameters file name")
    parser.add_argument('-o',
                        '--output_dir',
                        type=str,
                        default="",
                        help="output sub directory")
    parser.add_argument('-j',
                        '--job_id',
                        type=int,
                        default=0,
                        help="Job id (from bsub)")
    parser.add_argument('-i',
                        '--job_idx',
                        type=int,
                        default=0,
                        help="Job index (from bsub batch array)")
    parser.add_argument('-I',
                        '--idx_opt',
                        type=str,
                        default='',
                        help="Job index option (what to do with it)")
    #parser.add_argument('-S', '--job_size', type=int, default=0,
    #                                help="Number of jobs in array")
    parser.add_argument('-N',
                        '--num_qubits',
                        type=int,
                        default=0,
                        help="Number of qubits")
    parser.add_argument('-u',
                        '--init_amps',
                        type=str,
                        default=0,
                        help="File name of initial amplitudes")
    parser.add_argument('-m',
                        '--num_tslots',
                        type=int,
                        default=0,
                        help="Number of timeslots")
    parser.add_argument('-T',
                        '--evo_time',
                        type=float,
                        default=0,
                        help="Total evolution time")
    parser.add_argument('--evo_time_npi',
                        type=int,
                        default=0,
                        help="Total evolution time in mulitples of pi")
    parser.add_argument('--evo_time_npi_g0s',
                        type=int,
                        default=0,
                        help="Total evolution time in mulitples of pi "
                        "scaled by first coupling constant")
    parser.add_argument('-a',
                        '--numer_acc',
                        type=float,
                        default=-1.0,
                        help="Numerical accuracy")
    parser.add_argument('-e',
                        '--fid_err_targ',
                        type=float,
                        default=0.0,
                        help="Fidelity error target")
    parser.add_argument('-n',
                        '--num_cpus',
                        type=int,
                        default=0,
                        help="Fidelity error target")
    parser.add_argument('-M',
                        '--mp_opt',
                        type=str,
                        default='',
                        help="Multiprocessing option")
    parser.add_argument('--mem_opt',
                        type=int,
                        default=0,
                        help="Memory optimising level")
    parser.add_argument('--pulse_scaling',
                        type=int,
                        default=0.0,
                        help="Initial pulse scaling")
    parser.add_argument('--max_wall_time',
                        type=int,
                        default=0.0,
                        help="Maximum simulation wall time")

    args = vars(parser.parse_args())

    cfg = optimconfig.OptimConfig()
    cfg.log_level = log_level

    cfg.use_param_file = True

    if len(args['param_file']) > 0:
        param_fname = args['param_file']
        cfg.param_fpath = os.path.join(os.getcwd(), param_fname)
        if not os.path.isfile(cfg.param_fpath):
            raise ValueError(
                "Commandline argument parameter "
                "file name '{}' does not exist.".format(param_fname))
        else:
            print("Parameters will be read from:\n{}".format(cfg.param_fpath))
    elif os.path.abspath(param_fname):
        cfg.param_fname = os.path.basename(param_fname)
        cfg.param_fpath = param_fname
        param_fname = cfg.param_fname
    else:
        cfg.param_fpath = os.path.join(os.getcwd(), param_fname)
        if (not os.path.isfile(cfg.param_fpath)):
            print("Default parameter file {} not present. "
                  "Using defaults in code.").format(param_fname)
            cfg.use_param_file = False
    cfg.param_fname = param_fname

    # Script operational parameters
    cfg.job_id = 0
    cfg.job_idx = 0
    cfg.verbosity = 0
    cfg.output_dir = None
    cfg.output_base_name = 'general'
    cfg.double_print = True
    cfg.output_files = True
    cfg.plot_result = False
    cfg.plot_file_type = 'PNG'
    cfg.save_plots = False
    cfg.save_results = True
    cfg.data_file_ext = "txt"
    cfg.out_file_ext = "txt"

    # Optimizer config
    cfg.optim_method = 'LBFGSB'
    cfg.p_type = 'RND'
    cfg.check_grad = False
    cfg.gen_stats = True
    cfg.stats_type = 'standard'
    cfg.report_stats = True
    cfg.save_initial_amps = False
    cfg.save_final_amps = False
    cfg.fid_type = 'pure_choi_local'
    cfg.amp_lbound = -np.Inf
    cfg.amp_ubound = np.Inf
    cfg.num_reps = 2
    cfg.num_cpus = 1
    cfg.mp_opt = ""
    cfg.max_mp_scens = np.inf
    cfg.ext_mp = False
    cfg.num_threads = 1

    if cfg.use_param_file:
        # load the config parameters
        # note these will overide those above if present in the file
        print("Loading config parameters from {}".format(cfg.param_fpath))
        loadparams.load_parameters(cfg.param_fpath, config=cfg)

    # override with command line params (if any)
    if args['num_qubits'] > 0:
        cfg.num_qubits = args['num_qubits']
        print("Using num_qubits={} from command line".format(cfg.num_qubits))
    if len(args['output_dir']) > 0:
        cfg.output_dir = args['output_dir']
        print("Using output_dir '{}' from command line".format(cfg.output_dir))
    if args['num_cpus'] > 0:
        cfg.num_cpus = args['num_cpus']
        print("Using num_cpus={} from command line".format(cfg.num_cpus))

    if len(args['mp_opt']) > 0:
        cfg.mp_opt = args['mp_opt']
        print("Using mp_opt={} from command line".format(cfg.mp_opt))

    cfg.plot_file_ext = cfg.plot_file_type.lower()
    if args['job_id'] > 0:
        cfg.job_id = args['job_id']

    if cfg.job_id:
        print("Processing job ID: {}".format(cfg.job_id))
        if args['job_idx'] > 0:
            cfg.job_idx = args['job_idx']
        if cfg.job_idx:
            print("Processing job array index: {}".format(cfg.job_idx))
            cfg.out_file_ext = "{}.{}.{}".format(cfg.job_id, cfg.job_idx,
                                                 cfg.data_file_ext)
            cfg.plot_file_ext = "{}.{}.{}".format(cfg.job_id, cfg.job_idx,
                                                  cfg.plot_file_type)
        else:
            cfg.out_file_ext = "{}.{}".format(cfg.job_id, cfg.data_file_ext)
            cfg.plot_file_ext = "{}.{}".format(cfg.job_id, cfg.plot_file_type)

    logger.setLevel(cfg.log_level)
    if not cfg.output_dir:
        cfg.output_dir = cfg.target_id_text

    dyn = dynamics.DynamicsUnitary(cfg)
    dyn.dense_oper = True
    dyn.num_tslots_list = []
    dyn.st_num_tslots = 10
    dyn.d_num_tslots = 5
    dyn.num_num_tslots = 10

    dyn.evo_time = 10.0
    # For a specific list of evo_time values based on job_idx
    dyn.evo_time_list = [7, 10, 12, 20]
    # For a range of evo_time based on job_idx (idx_opt='evo_time')
    # Start evo_time
    dyn.st_evo_time = 0.2
    # Evo time step size
    dyn.d_evo_time = 0.2
    # Number of evo_time
    dyn.num_evo_time = 50
    dyn.num_qubits = 6

    dyn.target_type = 'CNOT'
    dyn.interact = 'Ising'
    dyn.topology = 'chain'
    dyn.ctrls_type = 'XY'
    dyn.coup_const = [1.0]
    # Used to change the order of the qubits in the Hilbert space
    # so that the two qubit gate will always be between 0 and 1,
    # but the couplings will be permuted based on hspace_order
    dyn.hspace_order = []
    # When True the hspace_order will be automatically generated
    # based on the other attributes
    dyn.auto_hspace = False
    # Separation of the first and second qubits in the coupling Hilbert space
    # 0 implies adjacent, -1 implies random
    dyn.hspace_01_sep = 0
    # Index of the first qubit in the coupling Hilbert space
    # -1 implies random
    dyn.hspace_0_idx = 0
    # If True then same coupling constant will be used for
    # all interactions between qubit pairs
    dyn.iso_coup = True

    # dynamical decoup attribs
    # These are the decoup amplitudes in the three axes
    dyn.decoup_x = 0.0
    dyn.decoup_y = 0.0
    dyn.decoup_z = 0.0
    # num_decoup_tslots = None means use the specific mask
    # num_decoup_tslots = 0 use all
    # num_decoup_tslots +ve is the number of tslots from start
    # num_decoup_tslots -ve is the number of zeros at the end
    dyn.num_decoup_tslots = 0
    dyn.decoup_tslots = []
    #dyn.decoup_tslot_mask = []

    if cfg.use_param_file:
        # load the dynamics parameters
        # note these will overide those above if present in the file
        print("Loading dynamics parameters from {}".format(cfg.param_fpath))
        loadparams.load_parameters(cfg.param_fpath, dynamics=dyn)

    if args['num_qubits'] > 0:
        dyn.num_qubits = args['num_qubits']
        print("num_qubits = {} from command line".format(dyn.num_qubits))

    # If job_idx is given, then use it to calculate some other parameter
    # (which supersedes all other settings)
    if cfg.job_idx and len(args['idx_opt']) > 0:
        cfg.ext_mp = True
        opt_str = args['idx_opt'].lower()
        if opt_str == 'evo_time':
            print("basing evo_time on job_idx... ")
            num_evo_time = len(dyn.evo_time_list)
            if num_evo_time > 0:
                print("...using evo_time_list")
                # get the evo time from the list
                T_idx = (cfg.job_idx - 1) % num_evo_time
                dyn.evo_time = dyn.evo_time_list[T_idx]
            else:
                print("...using start and increment")
                # calculate the evo_time from job_idx
                T_idx = (cfg.job_idx - 1) % dyn.num_evo_time
                dyn.evo_time = dyn.st_evo_time + dyn.d_evo_time * float(T_idx)
            print("evo_time={} for job idx {}".format(dyn.evo_time,
                                                      cfg.job_idx))
        elif opt_str == 'num_tslots':
            print("basing num_tslots on job_idx... ")
            num_nts = len(dyn.num_tslots_list)
            if num_nts > 0:
                print("...using num_tslots_list")
                nts_idx = (cfg.job_idx - 1) % num_nts
                dyn.num_tslots = dyn.num_tslots_list[nts_idx]
            else:
                print("...using start and increment")
                # calculate the num_tslots from job_idx
                nts_idx = (cfg.job_idx - 1) % dyn.num_num_tslots
                dyn.num_tslots = dyn.st_num_tslots + dyn.d_num_tslots * nts_idx
            print("num_tslots={} for job idx {}".format(
                dyn.num_tslots, cfg.job_idx))
        else:
            raise ValueError("No option for idx_opt '{}' "
                             "in command line".format(opt_str))

    if args['evo_time'] > 0:
        dyn.evo_time = args['evo_time']
        print("Using evo_time={} from command line".format(dyn.evo_time))

    if args['evo_time_npi'] > 0:
        dyn.evo_time = np.pi * args['evo_time_npi']
        print("Using evo_time={} from command line evo_time_npi".format(
            dyn.evo_time))
    if args['evo_time_npi_g0s'] > 0:
        dyn.evo_time = np.pi * args['evo_time_npi_g0s'] / dyn.coup_const[0]
        print("Using evo_time={} from command line evo_time_npi_g0s".format(
            dyn.evo_time))

    if args['num_tslots'] > 0:
        dyn.num_tslots = args['num_tslots']
        print("Using num_tslots={} from command line".format(dyn.num_tslots))

    if args['mem_opt'] > 0:
        dyn.memory_optimization = args['mem_opt']
        print("Using mem_opt={} from command line".format(
            dyn.memory_optimization))

    print("evo_time={}".format(dyn.evo_time))
    print("num_tslots={}".format(dyn.num_tslots))

    if dyn.num_tslots == 0:
        dyn.num_tslots = dyn.num_qubits * 4 + 8
        print("num_tslots calculated and set to be {}".format(dyn.num_tslots))

    if len(dyn.coup_const) == 1:
        dyn.coup_const = dyn.coup_const[0]

    dyn.prop_computer = propcomp.PropCompFrechet(dyn)

    if dyn.dense_oper:
        dyn.oper_dtype = np.ndarray
    else:
        dyn.oper_dtype = Qobj

    # Create the TerminationConditions instance
    tc = termcond.TerminationConditions()
    tc.fid_err_targ = 1e-3
    tc.min_gradient_norm = 1e-30
    tc.max_iter = 2400
    tc.max_wall_time = 120 * 60.0
    tc.accuracy_factor = 1e5

    if cfg.use_param_file:
        # load the termination condition parameters
        # note these will overide those above if present in the file
        print("Loading termination condition parameters from {}".format(
            cfg.param_fpath))
        loadparams.load_parameters(cfg.param_fpath, term_conds=tc)
    if args['fid_err_targ'] > 0.0:
        tc.fid_err_targ = args['fid_err_targ']
        print("fid_err_targ = {} from command line".format(tc.fid_err_targ))
    if args['max_wall_time'] > 0.0:
        tc.max_wall_time = args['max_wall_time']
        print("max_wall_time = {} from command line".format(tc.max_wall_time))

    # Fidelity oomputer
    ft = cfg.fid_type.lower()
    if ft == 'pure_choi_local':
        dyn.fid_computer = FidCompPureChoiLocal(dyn)
    elif ft == 'pure_choi_global':
        dyn.fid_computer = FidCompPureChoiGlobal(dyn)
    elif ft == 'unit_global':
        dyn.fid_computer = fidcomp.FidCompUnitary(dyn)
        dyn.fid_computer.local = False
    else:
        raise errors.UsageError("Unknown fid type {}".format(cfg.fid_type))
    fid_comp = dyn.fid_computer
    fid_comp.numer_acc = 0.0
    fid_comp.numer_acc_exact = False
    fid_comp.st_numer_acc = 0.01
    fid_comp.end_numer_acc = 0.2
    fid_comp.success_prop_uthresh = 0.95
    fid_comp.success_prop_lthresh = 0.01

    if cfg.use_param_file:
        # load the pulse generator parameters
        # note these will overide those above if present in the file
        print("Loading fidcomp parameters from {}".format(cfg.param_fpath))
        loadparams.load_parameters(cfg.param_fpath,
                                   obj=dyn.fid_computer,
                                   section='fidcomp')

    if args['numer_acc'] >= 0.0:
        fid_comp.numer_acc = args['numer_acc']
        print("numer_acc = {} from command line".format(fid_comp.numer_acc))

    if not fid_comp.numer_acc_exact:
        fid_comp.numer_acc = round_sigfigs(
            fid_comp.numer_acc * tc.fid_err_targ, 6)
        fid_comp.st_numer_acc = round_sigfigs(
            fid_comp.st_numer_acc * tc.fid_err_targ, 6)
        fid_comp.end_numer_acc = round_sigfigs(
            fid_comp.end_numer_acc * tc.fid_err_targ, 6)

    # Pulse generator
    p_gen = pulsegen.create_pulse_gen(pulse_type=cfg.p_type, dyn=dyn)
    p_gen.all_ctrls_in_one = False
    p_gen.lbound = cfg.amp_lbound
    p_gen.ubound = cfg.amp_ubound
    if cfg.use_param_file:
        # load the pulse generator parameters
        # note these will overide those above if present in the file
        print("Loading pulsegen parameters from {}".format(cfg.param_fpath))
        loadparams.load_parameters(cfg.param_fpath, pulsegen=p_gen)

    if not isinstance(p_gen, pulsegen.PulseGenCrab):
        if not (np.isinf(cfg.amp_lbound) or np.isinf(cfg.amp_ubound)):
            p_gen.scaling = cfg.amp_ubound - cfg.amp_lbound
            p_gen.offset = (cfg.amp_ubound + cfg.amp_lbound) / 2.0

    if args['pulse_scaling'] > 0.0:
        p_gen.scaling = args['pulse_scaling']
        print("p_gen.scaling = {} from command line".format(p_gen.scaling))

    # Create the Optimiser instance
    if cfg.optim_method is None:
        raise errors.UsageError("Optimisation method must be specified "
                                "via 'optim_method' parameter")

    om = cfg.optim_method.lower()
    if om == 'bfgs':
        optim = optimizer.OptimizerBFGS(cfg, dyn)
    elif om == 'lbfgsb':
        optim = optimizer.OptimizerLBFGSB(cfg, dyn)
    else:
        # Assume that the optim_method is valid
        optim = optimizer.Optimizer(cfg, dyn)
        optim.method = cfg.optim_method
    if cfg.verbosity > 1:
        print("Created optimiser of type {}".format(type(optim)))

    optim.config = cfg
    optim.dynamics = dyn
    optim.termination_conditions = tc
    optim.pulse_generator = p_gen
    optim.method_approach = 'DEF'
    optim.dumping = 'SUMMARY'

    if cfg.use_param_file:
        # load the optimiser parameters
        # note these will overide those above if present in the file
        print("Loading optimiser parameters from {}".format(cfg.param_fpath))
        loadparams.load_parameters(cfg.param_fpath, optim=optim)
    optim.termination_conditions = tc

    if cfg.gen_stats:
        # Create a stats object
        # Note that stats object is optional
        # if the Dynamics and Optimizer stats attribute is not set
        # then no stats will be collected, which could improve performance
        stats_type = 'standard'
        try:
            stats_type = cfg.stats_type.lower()
        except:
            pass
        if stats_type == 'local':
            sts = qsostats.StatsFidCompLocal()
        else:
            sts = stats.Stats()
        dyn.stats = sts
        optim.stats = sts

    # ****************************************************************
    # Define the physics of the problem

    print("Configuring drift...")
    #The 4 below are all Qobj
    #    Sx = sigmax()
    #    Sy = sigmay()
    #    Sz = sigmaz()
    #    Si = identity(2)

    nq = dyn.num_qubits
    # ***** Drift *****
    print("... for {} qubits".format(nq))

    H_d = qso.get_drift(dyn)
    print("Drift dims {}".format(H_d.dims))

    # Normalise based on ising chain
    H_d_ising_chain = qso.get_drift(dyn,
                                    topology='chain',
                                    interact='ising',
                                    coup_const=1.0)
    norm_fact = H_d_ising_chain.norm() / H_d.norm()
    print("Normalising drift with factor {}".format(norm_fact))
    H_d = H_d * norm_fact

    # **** Controls ****
    H_c, Sx_cidx, Sy_cidx, Sz_cidx = qso.get_ctrls(dyn)
    n_ctrls = len(H_c)

    #t0 evo
    U_0 = qso.get_initial_op(dyn)

    #*** Target ****
    U_targ, U_local_targs = qso.get_target(dyn)

    sub_dims = []
    for k in range(len(U_local_targs)):
        sub_dims.append(U_local_targs[k].dims[0][0])

    #Enforcing all dimensions are correct for the qBranch code
    #Not all of these are needed, but this is safer
    for k in range(len(H_c)):
        H_c[k].dims = [sub_dims, sub_dims]
    H_d.dims = [sub_dims, sub_dims]
    U_0.dims = [sub_dims, sub_dims]
    U_targ.dims = [sub_dims, sub_dims]

    dyn.drift_dyn_gen = H_d
    dyn.ctrl_dyn_gen = H_c
    dyn.initial = U_0
    dyn.target = U_targ

    # These are the indexes to the controls on the three axes
    dyn.Sx_cidx = Sx_cidx
    dyn.Sy_cidx = Sy_cidx
    dyn.Sz_cidx = Sz_cidx

    fid_comp.U_local_targs = U_local_targs
    fid_comp.sub_dims = sub_dims
    fid_comp.num_sub_sys = len(U_local_targs)

    print("Num acc: {}".format(fid_comp.numer_acc))

    return optim
def gen_optim_objects(cfg, verbosity=None):
    """
    Generate the optimiser objects based on the configuration

    Returns
    -------
    Optimizer
    """
    if verbosity is None:
        verbosity = cfg.verbosity

    def printv(msg, verb_tresh=1):
        if verbosity and verbosity >= verb_tresh:
            print(msg)

    dyn = dynamics.DynamicsUnitary(cfg)
    dyn.dense_oper = True
    dyn.memory_optimization = 1

    # Number of timeslots for the pulse discretisation
    dyn.num_tslots = 12
    # For a range of evo_time based on job_idx (cfg.idx_opt='num_tslots')
    # For a specific list of num_tslots values based on job_idx
    dyn.num_tslots_list = []
    # Or generate a range
    dyn.st_num_tslots = 10
    dyn.d_num_tslots = 5
    dyn.num_num_tslots = 10

    # Time allowed for the gate to evolve
    dyn.evo_time = 10.0
    # For a range of evo_time based on job_idx (cfg.idx_opt='evo_time'):
    # For a specific list of evo_time values based on job_idx
    dyn.evo_time_list = [7, 10, 12, 20]
    # Or generate a range
    dyn.st_evo_time = 0.2
    dyn.d_evo_time = 0.2
    dyn.num_evo_time = 50

    # Any number of qubits >= 3 can choosen.
    # However larger numbers take a lot of processing
    dyn.num_qubits = 3

    # Only CNOT implemented for target_type
    dyn.target_type = 'CNOT'
    # interact options: Ising|Heisenberg| or combinations of xyz
    dyn.interact = 'Ising'
    # topology options: chain|star|full|ring
    dyn.topology = 'chain'
    # ctrls_type options: combinations of xyz
    dyn.ctrls_type = 'XY'
    # If True then same coupling constant will be used for
    # all interactions between qubit pairs
    dyn.iso_coup = True
    # Coupling strength for each interaction
    # If a single float, then this will be used for all interactions
    # (single float list will be converted to float)
    # Otherwise
    # If iso_coup=True then should be one list item per iteracting pair
    # If iso_coup=False then should be blocks of list items per iteracting pair
    # e.g. for 4 qubit Heisenberg chain:
    # [g_0x, g_1x, g_2x, g_0y, g_1y, g_2y, g_0z, g_1z, g_2z]
    # Note that the number of interacting pairs is n-1 for chain and star,
    # n for ring, and n*(n-1)/2 for fully connected

    dyn.coup_const = [1.0]
    # Used to change the order of the qubits in the Hilbert space
    # The two qubit gate will always be between 0 and 1,
    # but the couplings will be permuted based on hspace_order
    dyn.hspace_order = []
    # When True the hspace_order will be automatically generated
    # based on the other attributes
    dyn.auto_hspace = False
    # Separation of the first and second qubits in the coupling Hilbert space
    # 0 implies adjacent, -1 implies random
    dyn.hspace_01_sep = 0
    # Index of the first qubit in the coupling Hilbert space
    # -1 implies random
    dyn.hspace_0_idx = 0


    # dynamical decoup attribs
    # These are the decoup amplitudes in the three axes
    dyn.decoup_x = 0.0
    dyn.decoup_y = 0.0
    dyn.decoup_z = 0.0
    # num_decoup_tslots = None means use the specific mask
    # num_decoup_tslots = 0 use all
    # num_decoup_tslots +ve is the number of tslots from start
    # num_decoup_tslots -ve is the number of zeros at the end
    dyn.num_decoup_tslots = 0
    dyn.decoup_tslots = []
    #dyn.decoup_tslot_mask = []

    if hasattr(cfg, 'args') and isinstance(cfg.args, argparse.ArgumentParser):
        parse_cl_args = True
    else:
        parse_cl_args = False

    if cfg.use_param_file:
        # load the dynamics parameters
        # note these will overide those above if present in the file
        printv("Loading dynamics parameters from {}".format(cfg.param_fpath))
        loadparams.load_parameters(cfg.param_fpath, dynamics=dyn)

    if parse_cl_args:
        if cfg.args['num_qubits'] > 0:
            dyn.num_qubits = cfg.args['num_qubits']
            printv("num_qubits = {} from command line".format(dyn.num_qubits))

        # If job_idx is given, then use it to calculate some other parameter
        # (which supersedes all other settings)
        if cfg.job_idx and len(cfg.args['idx_opt']) > 0:
            cfg.ext_mp = True
            opt_str = cfg.args['idx_opt'].lower()
            if opt_str == 'evo_time':
                printv("basing evo_time on job_idx... ")
                num_evo_time = len(dyn.evo_time_list)
                if num_evo_time > 0:
                    printv("...using evo_time_list")
                    # get the evo time from the list
                    T_idx = (cfg.job_idx - 1) % num_evo_time
                    dyn.evo_time = dyn.evo_time_list[T_idx]
                else:
                    printv("...using start and increment")
                    # calculate the evo_time from job_idx
                    T_idx = (cfg.job_idx - 1) % dyn.num_evo_time
                    dyn.evo_time = (dyn.st_evo_time
                                    + dyn.d_evo_time*float(T_idx))
                printv("evo_time={} for job idx {}".format(dyn.evo_time,
                                                          cfg.job_idx))
            elif opt_str == 'num_tslots':
                printv("basing num_tslots on job_idx... ")
                num_nts = len(dyn.num_tslots_list)
                if num_nts > 0:
                    printv("...using num_tslots_list")
                    nts_idx = (cfg.job_idx - 1) % num_nts
                    dyn.num_tslots = dyn.num_tslots_list[nts_idx]
                else:
                    printv("...using start and increment")
                    # calculate the num_tslots from job_idx
                    nts_idx = (cfg.job_idx - 1) % dyn.num_num_tslots
                    dyn.num_tslots = (dyn.st_num_tslots
                                      + dyn.d_num_tslots*nts_idx)
                printv("num_tslots={} for job idx {}".format(dyn.num_tslots,
                                                          cfg.job_idx))
            else:
                raise ValueError("No option for idx_opt '{}' "
                                "in command line".format(opt_str))

        if cfg.args['evo_time'] > 0:
            dyn.evo_time = cfg.args['evo_time']
            printv("Using evo_time={} from command line".format(dyn.evo_time))

        if cfg.args['evo_time_npi'] > 0:
            dyn.evo_time = np.pi*cfg.args['evo_time_npi']
            printv("Using evo_time={} from command line evo_time_npi".format(
                                                            dyn.evo_time))
        if cfg.args['evo_time_npi_g0s'] > 0:
            dyn.evo_time = np.pi*cfg.args['evo_time_npi_g0s']/dyn.coup_const[0]
            printv("Using evo_time={} from command line "
                   "evo_time_npi_g0s".format(dyn.evo_time))

        if cfg.args['num_tslots'] > 0:
            dyn.num_tslots = cfg.args['num_tslots']
            printv("Using num_tslots={} from command line".format(
                                                            dyn.num_tslots))

        if cfg.args['mem_opt'] > 0:
            dyn.memory_optimization = cfg.args['mem_opt']
            printv("Using mem_opt={} from command line".format(
                                                    dyn.memory_optimization))

    printv("evo_time={}".format(dyn.evo_time))
    printv("num_tslots={}".format(dyn.num_tslots))

    if dyn.num_tslots == 0:
        dyn.num_tslots = dyn.num_qubits*4 + 8
        printv("num_tslots calculated and set to be {}".format(dyn.num_tslots))

    if len(dyn.coup_const) == 1:
        dyn.coup_const = dyn.coup_const[0]

    dyn.prop_computer = propcomp.PropCompFrechet(dyn)

    if dyn.dense_oper:
        dyn.oper_dtype = np.ndarray
    else:
        dyn.oper_dtype = Qobj

    # Pulse optimisation termination conditions
    # Create the TerminationConditions instance
    tc = termcond.TerminationConditions()
    # Target for the infidelity (1 - gate fidelity)
    tc.fid_err_targ = 1e-3
    # Sum of the gradients wrt optimisation parameters (timeslot amplitudes)
    tc.min_gradient_norm = 1e-30
    # Number of iterations of the algorithm
    tc.max_iter = 2400
    # Computation time (in seconds)
    tc.max_wall_time = 120*60.0
    # Relative change in fid_err between iterations
    tc.accuracy_factor = 1e5

    if cfg.use_param_file:
        # load the termination condition parameters
        # note these will overide those above if present in the file
        printv("Loading termination condition parameters from {}".format(
                cfg.param_fpath))
        loadparams.load_parameters(cfg.param_fpath, term_conds=tc)
    if parse_cl_args:
        if cfg.args['fid_err_targ'] > 0.0:
            tc.fid_err_targ = cfg.args['fid_err_targ']
            printv("fid_err_targ = {} from cmd line".format(tc.fid_err_targ))
        if cfg.args['max_wall_time'] > 0.0:
            tc.max_wall_time = cfg.args['max_wall_time']
            printv("max_wall_time = {} from cmd line".format(tc.max_wall_time))

    # Fidelity oomputer
    ft = cfg.fid_type.lower()
    if ft == 'pure_choi_local':
        dyn.fid_computer = FidCompPureChoiLocal(dyn)
    elif ft == 'pure_choi_global':
        dyn.fid_computer = FidCompPureChoiGlobal(dyn)
    elif ft == 'unit_global':
        dyn.fid_computer = fidcomp.FidCompUnitary(dyn)
        dyn.fid_computer.local = False
    else:
        raise errors.UsageError("Unknown fid type {}".format(cfg.fid_type))
    fid_comp = dyn.fid_computer
    # If True and optim dumping set to at least SUMMARY, then the
    # global fid will be added to the summary (when using pure_choi_local)
    fid_comp.dump_global_choi_fid = False
    # This is the maximum precsion that sub-system fidelities are 'measured'
    # see choi_closed_fidcomp.my_round for details
    # Zero implies full machine precision
    fid_comp.numer_acc = 0.0
    # These next parameters are used in the automatic search for the numerical
    # accuracy threshold
    # If numer_acc_exact==false, then numer_acc, st_numer_acc, end_numer_acc
    # will be treated as proportion of fid_err_targ (during config)
    fid_comp.numer_acc_exact = False
    fid_comp.st_numer_acc = 0.01
    fid_comp.end_numer_acc = 0.2
    # These proportions are used to determine the boundaries for the search
    # They are proportions of the number number of successful repeats
    # for the scenario.
    fid_comp.success_prop_uthresh = 0.95
    fid_comp.success_prop_lthresh = 0.01

    if cfg.use_param_file:
        # load the pulse generator parameters
        # note these will overide those above if present in the file
        printv("Loading fidcomp parameters from {}".format(cfg.param_fpath))
        loadparams.load_parameters(cfg.param_fpath, obj=dyn.fid_computer,
                                    section='fidcomp')

    if parse_cl_args:
        if cfg.args['numer_acc'] >= 0.0:
            fid_comp.numer_acc = cfg.args['numer_acc']
            printv("numer_acc = {} from cmd line".format(fid_comp.numer_acc))

    if not fid_comp.numer_acc_exact:
        fid_comp.numer_acc = round_sigfigs(
                                fid_comp.numer_acc*tc.fid_err_targ, 6)
        fid_comp.st_numer_acc = round_sigfigs(
                                fid_comp.st_numer_acc*tc.fid_err_targ, 6)
        fid_comp.end_numer_acc = round_sigfigs(
                                fid_comp.end_numer_acc*tc.fid_err_targ, 6)

    # Pulse generator
    p_gen = pulsegen.create_pulse_gen(pulse_type=cfg.p_type, dyn=dyn)
    p_gen.all_ctrls_in_one = False
    p_gen.lbound = cfg.amp_lbound
    p_gen.ubound = cfg.amp_ubound
    if cfg.use_param_file:
        # load the pulse generator parameters
        # note these will overide those above if present in the file
        printv("Loading pulsegen parameters from {}".format(cfg.param_fpath))
        loadparams.load_parameters(cfg.param_fpath, pulsegen=p_gen)

    if not isinstance(p_gen, pulsegen.PulseGenCrab):
        if not (np.isinf(cfg.amp_lbound) or np.isinf(cfg.amp_ubound)):
            p_gen.scaling = cfg.amp_ubound - cfg.amp_lbound
            p_gen.offset = (cfg.amp_ubound + cfg.amp_lbound) / 2.0

    if parse_cl_args:
        if cfg.args['pulse_scaling'] > 0.0:
            p_gen.scaling = cfg.args['pulse_scaling']
            printv("p_gen.scaling = {} from cmd line".format(p_gen.scaling))

    # Create the Optimiser instance
    if cfg.optim_method is None:
        raise errors.UsageError("Optimisation method must be specified "
                                "via 'optim_method' parameter")

    om = cfg.optim_method.lower()
    if om == 'bfgs':
        optim = optimizer.OptimizerBFGS(cfg, dyn)
    elif om == 'lbfgsb':
        optim = optimizer.OptimizerLBFGSB(cfg, dyn)
    else:
        # Assume that the optim_method is valid
        optim = optimizer.Optimizer(cfg, dyn)
        optim.method = cfg.optim_method
    if cfg.verbosity > 1:
        printv("Created optimiser of type {}".format(type(optim)), 2)

    optim.config = cfg
    optim.dynamics = dyn
    optim.termination_conditions = tc
    optim.pulse_generator = p_gen
    optim.method_approach = 'DEF'
    optim.dumping = 'SUMMARY'

    if cfg.use_param_file:
        # load the optimiser parameters
        # note these will overide those above if present in the file
        printv("Loading optimiser parameters from {}".format(cfg.param_fpath))
        loadparams.load_parameters(cfg.param_fpath, optim=optim)
    optim.termination_conditions = tc

    if cfg.gen_stats:
        # Create a stats object
        # Note that stats object is optional
        # if the Dynamics and Optimizer stats attribute is not set
        # then no stats will be collected, which could improve performance
        stats_type = 'standard'
        try:
            stats_type = cfg.stats_type.lower()
        except:
            pass
        if stats_type == 'local':
            sts = qsostats.StatsFidCompLocal()
        else:
            sts = stats.Stats()
        dyn.stats = sts
        optim.stats = sts

    return optim
Beispiel #4
0
def create_pulse_optimizer(drift,
                           ctrls,
                           initial,
                           target,
                           num_tslots=None,
                           evo_time=None,
                           tau=None,
                           amp_lbound=-np.Inf,
                           amp_ubound=np.Inf,
                           fid_err_targ=1e-10,
                           min_grad=1e-10,
                           max_iter=500,
                           max_wall_time=180,
                           optim_alg='LBFGSB',
                           max_metric_corr=10,
                           accuracy_factor=1e7,
                           dyn_type='GEN_MAT',
                           prop_type='DEF',
                           fid_type='DEF',
                           phase_option=None,
                           fid_err_scale_factor=None,
                           amp_update_mode='ALL',
                           init_pulse_type='RND',
                           pulse_scaling=1.0,
                           pulse_offset=0.0,
                           log_level=logging.NOTSET,
                           gen_stats=False):
    """
    Generate the objects of the appropriate subclasses
    required for the pulse optmisation based on the parameters given
    Note this method may be preferable to calling optimize_pulse
    if more detailed configuration is required before running the
    optmisation algorthim, or the algorithm will be run many times,
    for instances when trying to finding global the optimum or
    minimum time optimisation

    Parameters
    ----------

    drift : Qobj
        the underlying dynamics generator of the system

    ctrls : List of Qobj
        a list of control dynamics generators. These are scaled by
        the amplitudes to alter the overall dynamics

    initial : Qobj
        starting point for the evolution.
        Typically the identity matrix

    target : Qobj
        target transformation, e.g. gate or state, for the time evolution

    num_tslots : integer or None
        number of timeslots.
        None implies that timeslots will be given in the tau array

    evo_time : float or None
        total time for the evolution
        None implies that timeslots will be given in the tau array

    tau : array[num_tslots] of floats or None
        durations for the timeslots.
        if this is given then num_tslots and evo_time are dervived
        from it
        None implies that timeslot durations will be equal and
        calculated as evo_time/num_tslots

    amp_lbound : float or list of floats
        lower boundaries for the control amplitudes
        Can be a scalar value applied to all controls
        or a list of bounds for each control

    amp_ubound : float or list of floats
        upper boundaries for the control amplitudes
        Can be a scalar value applied to all controls
        or a list of bounds for each control

    fid_err_targ : float
        Fidelity error target. Pulse optimisation will
        terminate when the fidelity error falls below this value

    mim_grad : float
        Minimum gradient. When the sum of the squares of the
        gradients wrt to the control amplitudes falls below this
        value, the optimisation terminates, assuming local minima

    max_iter : integer
        Maximum number of iterations of the optimisation algorithm

    max_wall_time : float
        Maximum allowed elapsed time for the  optimisation algorithm

    optim_alg : string
        Multi-variable optimisation algorithm
        options are BFGS, LBFGSB
        (see Optimizer classes for details)

    max_metric_corr : integer
        The maximum number of variable metric corrections used to define
        the limited memory matrix. That is the number of previous
        gradient values that are used to approximate the Hessian
        see the scipy.optimize.fmin_l_bfgs_b documentation for description
        of m argument
        (used only in L-BFGS-B)

    accuracy_factor : float
        Determines the accuracy of the result.
        Typical values for accuracy_factor are: 1e12 for low accuracy;
        1e7 for moderate accuracy; 10.0 for extremely high accuracy
        scipy.optimize.fmin_l_bfgs_b factr argument.
        (used only in L-BFGS-B)

    dyn_type : string
        Dynamics type, i.e. the type of matrix used to describe
        the dynamics. Options are UNIT, GEN_MAT, SYMPL
        (see Dynamics classes for details)

    prop_type : string
        Propagator type i.e. the method used to calculate the
        propagtors and propagtor gradient for each timeslot
        options are DEF, APPROX, DIAG, FRECHET, AUG_MAT
        DEF will use the default for the specific dyn_type
        (see PropagatorComputer classes for details)

    fid_type : string
        Fidelity error (and fidelity error gradient) computation method
        Options are DEF, UNIT, TRACEDIFF, TD_APPROX
        DEF will use the default for the specific dyn_type
        (See FideliyComputer classes for details)

    phase_option : string
        determines how global phase is treated in fidelity
        calculations (fid_type='UNIT' only). Options:
            PSU - global phase ignored
            SU - global phase included

    fid_err_scale_factor : float
        (used in TRACEDIFF FidelityComputer and subclasses only)
        The fidelity error calculated is of some arbitary scale. This
        factor can be used to scale the fidelity error such that it may
        represent some physical measure
        If None is given then it is caculated as 1/2N, where N
        is the dimension of the drift.

    amp_update_mode : string
        determines whether propagators are calculated
        Options: DEF, ALL, DYNAMIC (needs work)
        DEF will use the default for the specific dyn_type
        (See TimeslotComputer classes for details)

    init_pulse_type : string
        type / shape of pulse(s) used to initialise the
        the control amplitudes. Options include:
            RND, LIN, ZERO, SINE, SQUARE, TRIANGLE, SAW
        (see PulseGen classes for details)

    pulse_scaling : float
        Linear scale factor for generated pulses
        By default initial pulses are generated with amplitudes in the
        range (-1.0, 1.0). These will be scaled by this parameter

    pulse_offset : float
        Line offset for the pulse. That is this value will be added
        to any initial pulses generated.

    log_level : integer
        level of messaging output from the logger.
        Options are attributes of qutip.logging,
        in decreasing levels of messaging, are:
        DEBUG_INTENSE, DEBUG_VERBOSE, DEBUG, INFO, WARN, ERROR, CRITICAL
        Anything WARN or above is effectively 'quiet' execution,
        assuming everything runs as expected.
        The default NOTSET implies that the level will be taken from
        the QuTiP settings file, which by default is WARN
        Note value should be set using set_log_level

    gen_stats : boolean
        if set to True then statistics for the optimisation
        run will be generated - accessible through attributes
        of the stats object

    Returns
    -------

        Instance of an Optimizer, through which the
        Config, Dynamics, PulseGen, and TerminationConditions objects
        can be accessed as attributes.
        The PropagatorComputer, FidelityComputer and TimeslotComputer objects
        can be accessed as attributes of the Dynamics object, e.g.
            optimizer.dynamics.fid_computer
        The optimisation can be run through the optimizer.run_optimization
    """

    # check parameters
    if not isinstance(drift, Qobj):
        raise TypeError("drift must be a Qobj")
    else:
        drift = drift.full()

    if not isinstance(ctrls, (list, tuple)):
        raise TypeError("ctrls should be a list of Qobj")
    else:
        j = 0
        for ctrl in ctrls:
            if not isinstance(ctrl, Qobj):
                raise TypeError("ctrls should be a list of Qobj")
            else:
                ctrls[j] = ctrl.full()
                j += 1

    if not isinstance(initial, Qobj):
        raise TypeError("initial must be a Qobj")
    else:
        initial = initial.full()

    if not isinstance(target, Qobj):
        raise TypeError("target must be a Qobj")
    else:
        target = target.full()

    cfg = optimconfig.OptimConfig()
    cfg.optim_alg = optim_alg
    cfg.max_metric_corr = max_metric_corr
    cfg.accuracy_factor = accuracy_factor
    cfg.amp_update_mode = amp_update_mode
    cfg.dyn_type = dyn_type
    cfg.prop_type = prop_type
    cfg.fid_type = fid_type
    cfg.pulse_type = init_pulse_type
    cfg.phase_option = phase_option
    cfg.amp_lbound = amp_lbound
    cfg.amp_ubound = amp_ubound

    if log_level == logging.NOTSET:
        log_level = logger.getEffectiveLevel()
    else:
        logger.setLevel(log_level)

    cfg.log_level = log_level

    # Create the Dynamics instance
    if dyn_type == 'GEN_MAT' or dyn_type is None or dyn_type == '':
        dyn = dynamics.DynamicsGenMat(cfg)
    elif dyn_type == 'UNIT':
        dyn = dynamics.DynamicsUnitary(cfg)
    elif dyn_type == 'SYMPL':
        dyn = dynamics.DynamicsSymplectic(cfg)
    else:
        raise errors.UsageError("No option for dyn_type: " + dyn_type)

    # Create the PropagatorComputer instance
    # The default will be typically be the best option
    if prop_type == 'DEF' or prop_type is None or prop_type == '':
        # Do nothing use the default for the Dynamics
        pass
    elif prop_type == 'APPROX':
        if not isinstance(dyn.prop_computer, propcomp.PropCompApproxGrad):
            dyn.prop_computer = propcomp.PropCompApproxGrad(dyn)
    elif prop_type == 'DIAG':
        if not isinstance(dyn.prop_computer, propcomp.PropCompDiag):
            dyn.prop_computer = propcomp.PropCompDiag(dyn)
    elif prop_type == 'AUG_MAT':
        if not isinstance(dyn.prop_computer, propcomp.PropCompAugMat):
            dyn.prop_computer = propcomp.PropCompAugMat(dyn)
    elif prop_type == 'FRECHET':
        if not isinstance(dyn.prop_computer, propcomp.PropCompFrechet):
            dyn.prop_computer = propcomp.PropCompFrechet(dyn)
    else:
        raise errors.UsageError("No option for prop_type: " + prop_type)

    # Create the FideliyComputer instance
    # The default will be typically be the best option
    # Note: the FidCompTraceDiffApprox is a subclass of FidCompTraceDiff
    # so need to check this type first
    if fid_type == 'DEF' or fid_type is None or fid_type == '':
        # None given, use the default for the Dynamics
        pass
    elif fid_type == 'TDAPPROX':
        if not isinstance(dyn.fid_computer, fidcomp.FidCompTraceDiffApprox):
            dyn.fid_computer = fidcomp.FidCompTraceDiffApprox(dyn)
    elif fid_type == 'TRACEDIFF':
        if not isinstance(dyn.fid_computer, fidcomp.FidCompTraceDiff):
            dyn.fid_computer = fidcomp.FidCompTraceDiff(dyn)
    elif fid_type == 'UNIT':
        if not isinstance(dyn.fid_computer, fidcomp.FidCompUnitary):
            dyn.fid_computer = fidcomp.FidCompUnitary(dyn)
    else:
        raise errors.UsageError("No option for fid_type: " + fid_type)

    if isinstance(dyn.fid_computer, fidcomp.FidCompUnitary):
        dyn.fid_computer.set_phase_option(phase_option)

    if isinstance(dyn.fid_computer, fidcomp.FidCompTraceDiff):
        dyn.fid_computer.scale_factor = fid_err_scale_factor

    # Create the Optimiser instance
    # The class of the object will determine which multivar optimisation
    # algorithm is used
    if optim_alg == 'BFGS':
        optim = optimizer.OptimizerBFGS(cfg, dyn)
    elif optim_alg == 'LBFGSB':
        optim = optimizer.OptimizerLBFGSB(cfg, dyn)
    elif optim_alg is None:
        raise errors.UsageError("Optimisation algorithm must be specified "
                                "via 'optim_alg' parameter")
    else:
        raise errors.UsageError("No option for optim_alg: " + optim_alg)

    # Create the TerminationConditions instance
    tc = termcond.TerminationConditions()
    tc.fid_err_targ = fid_err_targ
    tc.min_gradient_norm = min_grad
    tc.max_iterations = max_iter
    tc.max_wall_time = max_wall_time
    optim.termination_conditions = tc

    if gen_stats:
        # Create a stats object
        # Note that stats object is optional
        # if the Dynamics and Optimizer stats attribute is not set
        # then no stats will be collected, which could improve performance
        if amp_update_mode == 'DYNAMIC':
            sts = stats.StatsDynTsUpdate()
        else:
            sts = stats.Stats()

        dyn.stats = sts
        optim.stats = sts

    # Configure the dynamics
    dyn.drift_dyn_gen = drift
    dyn.ctrl_dyn_gen = ctrls
    dyn.initial = initial
    dyn.target = target
    if tau is None:
        # Check that parameters have been supplied to generate the
        # timeslot durations
        try:
            evo_time / num_tslots
        except:
            raise errors.UsageError(
                "Either the timeslot durations should be supplied as an "
                "array 'tau' or the number of timeslots 'num_tslots' "
                "and the evolution time 'evo_time' must be given.")

        dyn.num_tslots = num_tslots
        dyn.evo_time = evo_time
    else:
        dyn.tau = tau

    # this function is called, so that the num_ctrls attribute will be set
    dyn.get_num_ctrls()

    # Create a pulse generator of the type specified
    p_gen = pulsegen.create_pulse_gen(pulse_type=init_pulse_type, dyn=dyn)
    p_gen.scaling = pulse_scaling
    p_gen.offset = pulse_offset
    p_gen.lbound = amp_lbound
    p_gen.ubound = amp_ubound

    # If the pulse is a periodic type, then set the pulse to be one complete
    # wave
    if isinstance(p_gen, pulsegen.PulseGenPeriodic):
        p_gen.num_waves = 1.0
    optim.pulse_generator = p_gen

    if log_level <= logging.DEBUG:
        logger.debug("Optimisation config summary...\n"
                     "  object classes:\n"
                     "    optimizer: " + optim.__class__.__name__ +
                     "\n    dynamics: " + dyn.__class__.__name__ +
                     "\n    tslotcomp: " +
                     dyn.tslot_computer.__class__.__name__ +
                     "\n    fidcomp: " + dyn.fid_computer.__class__.__name__ +
                     "\n    propcomp: " +
                     dyn.prop_computer.__class__.__name__ +
                     "\n    pulsegen: " + p_gen.__class__.__name__)

    return optim