User Guide

Quantum Model Learning Agent

The class which controls everything is QuantumModelLearningAgent. An instance of this class is used to run one of the available algorithms; many independent instances can operate simultaneously and be analysed together (e.g. to see the average reproduction of dynamics following model learning). This is referred to as a run The QMLA class provides methods for each of the available algorithms, as well as routines required therein, and methods for analysis and plotting. In short, the available algorithms are

Quantum Model Learning Agent complete model search

Quantum Hamiltonian Learning just run the parameter optimisation subroutine. Runs on the model set as true_model within the ES.

Multi-model quantum Hamiltonian learning just run the parameter optimisation subroutine. Runs on several models independently; the models are set in the list qhl_models within the ES.

The primary function of the QuantumModelLearningAgent class is to manage the model search. Models are assigned a unique model_ID upon generation. QMLA considers a set of models as a layer or a BranchQMLA. Models can reside on multiple branches. For each ES included in the instance, an Exploration Tree (ET) is built. On a given tree, the associated ES determines how to proceed, in particular by deciding which models to consider. The first branch of the tree holds the initial models \(\mu^1 = \{ M_1^1, \dots M_n^1\}\) for that ES. After the initial models have been trained and compared on \(\mu^1\), the ES uses the available information (e.g. the number of pairwise wins each model has) to construct a new set of models, \(\mu^2 = \{ M_1^2, \dots M_n^2\}\). Subsequent branches \(\mu^i\) similarly construct models based on the information available to the ES so far.

Each BranchQMLA is resident on its associated ES tree, but the branch is also known to QMLA. Branches are assigned unique IDs by QMLA, such that QMLA has a birds-eye view of all of the mdoels on all branches on all trees (in general there can be multiple ES entertained in a single instance). Indeed, a useful way to think of QMLA is as a search across a forest consisting of \(N\) trees, where each leaf is a unique model, and there can be multiple leaves per branch and multiple branches per tree, with the ultimate goal of identifying the single best leaf for describing the system.

When QMLA finds that it has completed a layer, it is ready for the next batch of work: it checks whether the ET has finished growing, in which case it begins the process of nominating the champion from that ES. Otherwise, QMLA calls on the ES (via the ET ) to request a set of models, which it places on its next branch, completely indifferent to how those models are generated, or whether they have been learned already. This allows for completely self-contained logic in the ES: QMLA will simply learn and compare the models it is presented - it is the responsibility of the ES to interpret them. As such, the core QMLA algorithm can be thought of as a simple loop: while the ES continues to return models, place those models on a branch, learn them and compare them. When all ES indicate they are finished, nominate champions from each ET; compare the champions of each tree against each other, and thus determine a global champion.

Exploration Strategy

Exploration Strategies (ES) are the engine of QMLA. The ES specifies how QMLA should proceed at each stage, most importantly by determining the next set of models for QMLA to test. These are the primary mechanism by which most users should interface with the QMLA framework: by designing an ExplorationStrategy which implements the user-specific logic required. In particular, each ES must provide a generate_models() method to construct models given information about the previous models’ training/comparisons. User ES classes can be used to specify parameters required throughout the QMLA protocol. These are all detailed in the setup methods of the ExplorationStrategy class; users should familiarise themselves with these settings before proceeding.

At minimum, a functional ES should look like:

class UserExplorationStrategy(qmla.ExplorationStrategy):
    def __init__(
        self,
        exploration_rules,
        true_model=None,
        **kwargs
    ):
        super().__init__(
            exploration_rules=exploration_rules,
            true_model=true_model,
            **kwargs
        )
        self.true_model = 'pauliSet_1_x_d1+pauliSet_1_y_d1'

An example of ES design, including a simple greedy-addition model generation method as well as seeting several parameter settings, is:

from qmla.shared_functionality import experiment_design_heuristics as edh

class UserExplorationStrategy(qmla.ExplorationStrategy):
    def __init__(
        self,
        exploration_rules,
        true_model=None,
        **kwargs
    ):
        super().__init__(
            exploration_rules=exploration_rules,
            true_model=true_model,
            **kwargs
        )
        # Overwrite true model
        self.true_model = 'pauliSet_1_x_d1+pauliSet_1_y_d1'

        # Overwrite modular functionality
        self.model_heuristic_subroutine = edh.VolumeAdaptiveParticleGuessHeuristic

        # Overwrite parameters
        self.max_num_qubits = 2
        self.num_probes = 10
        self.qinfer_resampler_a = 0.95

        # User specific attributes (not available by default in QMLA)
        self.model_base_terms = [
            "pauliSet_1_x_d2",
            "pauliSet_1_y_d2",
            "pauliSet_1_z_d2",
            "pauliSet_2_x_d2",
            "pauliSet_2_y_d2",
            "pauliSet_2_z_d2",
        ]
        self.search_exhausted = False


    def generate_models(
        self,
        model_list,
        **kwargs
    ):
        if self.spawn_stage[-1] == None:
            # Use spawn_stage for easy signals between calls to this method
            # e.g. to alter the functionality after some condition is method

            self.spawn_stage.append("one_parameter_models")
            return self.model_base_terms

        previous_champion = model_list[0]
        champion_terms = previous_champion.split("+")
        nonpresent_terms = list(set(self.model_base_terms) - set(champion_terms))
        new_models = [
            "{}+{}".format(previous_champion, term) for term in nonpresent_terms
        ]

        if len(new_models) == 1:
            # After this, there will be no more to test,
            # so signal to QMLA that this ES is finished.
            self.search_exhausted = True

        return new_models

    def check_tree_completed(
        self,
        spawn_step,
        **kwargs
    ):
        r"""
        QMLA asks the exploration tree whether it has finished growing;
        the exploration tree queries the exploration strategy through this method.
        """
        return self.search_exhausted

In order to implement a new ES, QMLA searches in the directory qmla/exploration_strategies, so the user’s ES must be import ed to the qmla/exploration_strategies/__init__.py. QMLA retrieves the ES through calls to the function get_exploration_class(), by searching for the ES specified in the Launch script. For example, the launch script (e.g. at qmla/launch/local_launch.sh) should be updated to call the user’s ES, e.g.

#!/bin/bash

###############
# QMLA run configuration
###############
num_instances=1
run_qhl=0
experiments=500
particles=2000

###############
# Choose an exploration strategy
###############

exploration_strategy='UserExplorationStrategy'

A complete step-by-step example of implementing custom ES is given in section_tutorial. Users should ensure they understand the options for launching QMLA as outlined in Launch.

Each ES is assigned a unique Exploration Tree (ET), although most users need not alter the infrastructure of the ET or QMLA.

Models

Construction

Models are specified by a string of terms separated by +, e.g. pauliSet_1_x_d1+pauliSet_1_y_d1. Model names are unique and are assigned a model_id upon generation within QuantumModelLearningAgent : QMLA will recognise if a model string has already been proposed and therefore been assigned a model_id, rather than retraining models which is computationally expensive. The uniqueness of models is ensured by the terms being sorted alphabetically internally within the string (e.g. pauliSet_1_x_d1+pauliSet_1_y_d1 instead of pauliSet_1_y_d1+pauliSet_1_x_d1), but note QMLA ensures this internally so users do not need to enfore it in their generate_models().

The strings are processed into models as follows. By separating models into their terms (model_name.split('+')), the cardinality (number of terms, \(n\)) is found. An \(n-\) dimensional Gaussian is constructed to represent the parameter distribution for the model; individual parameters can be specified in gaussian_prior_means_and_widths of _setup_model_learning(). The terms are then processed into matrices. A number of String to matrix processing functions are available by default; new processing functions can be added by the user but must be incorporated in process_basic_operator() so that QMLA will know where to find them.

Classes

Models are central to the QMLA framework so it sensible to identify their core functionality so we can design software to facilitate them. In particular, there are three forms of classes which each depict models, but fulfil different roles. In brief, these classes are

ModelInstanceForLearning

Class used for the training of individual models.

ModelInstanceForComparison

Class used for comparing models which have already been trained

ModelInstanceForStorage

Class retained by QuantumModelLearningAgent, storing the results of the model’s training and comparisons.

We next detail each of these roles of the model concept.

Training

QMLA relies on a subroutine for training individual candidate models: it is imperative that a given candidate is optimised against the system, as otherwise it might appear as a relatively weak candidate compared with its potential. In principle, any parameter learning subroutine can fulfil this role in QMLA, such as Hamiltonian tomography or using neural networks for parameter estimation. The in-built facility for this subroutine is quantum Hamiltonian Learning (QHL). We do not descibe the QHL protocol here but readers can refer to [WGFC13a], [WGFC13b] for details.

ModelInstanceForLearning is a disposable class which instatiates indepdendently from QuantumModelLearningAgent. It trains a given model via qmla.remote_learn_model_parameters(), performs analysis on the trained model, summarises the outcome of the training and sends a concise data packet to the database, before being deleted. The model training refers to quantum Hamiltonian learning, performed in conjunction with [QInfer], via update_model(). Importantly, QMLA trains models simply by calling qmla.remote_learn_model_parameters(): this function acts emph{remotely} and is therefore independent, allowing for multiple instance of the function and ModelInstanceForLearning to run simultaneously. As such, this class mechanism allows for emph{parallel processing} within QMLA, enabling speedup proportional to the number of processes available (for the model training stages).

Comparisons

Like the training subroutine, in principle QMLA can operate with any model comparison subroutine, but in practice we use Bayes factors (BF). This is a quantity which is used to distinguish between models.

ModelInstanceForComparison is a disposable class which reads the redis database to retrieve information about the trainng of the given model_id. It then reconstructs the model, e.g. based on the final estimated mean of the parameter distribution. Then, to compare models, remote_bayes_factor_calculation() interfaces two instances of ModelInstanceForComparison such that each model is exposed to the opponent’s experiments for further updates, such that the two models under consideration have identical experiment records (at least partially whereupon the BF is based), allowing for meaningful comparison among the two. This is achieved through update_log_likelihood().

Similiar to the training stage, remote_bayes_factor_calculation() can be run in parallel to provide a large speedup to the overall QMLA protocol.

Storage

Finally, ModelInstanceForStorage is a much smaller onject than the previous forms of the model, which retains only the useful information for storage/analysis within the bigger picture in QuantumModelLearningAgent. It retrieves the succinct summaries of the training/comparisons pertainng to a single model which are stored on the redis database, allowing for later anlaysis as required by QMLA. The retrieval of trained model data is performed in model_update_learned_values().

Modular functionality

A large amount of the design of an ES involves implementation of subroutines: there are a number of methods of ExplorationStrategy which can be overwritten in order to achieve functionality specific to the target system. In this section we describe these subroutines. Many of the subroutines have a number of sensible implementations: we make QMLA emph{modular} by providing a set of pre-built subroutines, and allow them to be easily swapped so that a new ES can benefit from arbitrary combiniations of subroutines. The subroutines are called by wrapper methods in ExplorationStrategy; to set which function is called, change the attribute in the definition of the custom ES. Alternatively, directly overwrite the wrapper. The pre-built functions are in qmla/shared_functionality.

Within ExplorationStrategy, these modular functions are set in _setup_modular_subroutines().

An example of setting each of these subroutines is

from qmla.shared_functionality import experiment_design_heuristics as edh
from qmla.shared_functionality import expectation_value_functions as ev
from qmla.shared_functionality import \
    qmla.shared_functionality.probe_set_generation as probes
from qmla.shared_functionality import qinfer_model_interface as qii
from qmla.shared_functionality import prior_distributions
from qmla.shared_functionality import latex_model_names as lm

class UserExplorationStrategy(qmla.ExplorationStrategy):
    def __init__(
        self,
        exploration_rules,
        true_model=None,
        **kwargs
    ):
        super().__init__(
            exploration_rules=exploration_rules,
            true_model=true_model,
            **kwargs
        )
        # Overwrite true model
        self.true_model = 'pauliSet_1_x_d1+pauliSet_1_y_d1'

        # Overwrite expectation value subroutine
        self.expectation_value_subroutine = ev.default_expectation_value

        # Overwrite probe generation subroutines
        self.system_probes_generation_subroutine = probes.plus_probes_dict
        self.plot_probes_generation_subroutine = probes.zero_state_probes

        # Overwrite exeperiment design heuristic
        self.model_heuristic_subroutine = edh.VolumeAdaptiveParticleGuessHeuristic

        # Overwrite QInfer interface
        self.qinfer_model_subroutine = qii.QInferModelQMLA

        # Overwrite prior distribution subroutine
        self.prior_distribution_subroutine = priors.gaussian_prior

        # Overwrite latex mapping subroutine
        self.latex_string_map_subroutine = lm.lattice_set_grouped_pauli

Probes

The probe is the input state used during the learning procedure. Different probes permit different biases on the information available to the algorithm; it is essential to consider which probes are appropriate for learning different classes of models. In general the training procedure loops over the available probes, to minimise the chance of favouring some models due to bias inherent in the probe. For example, if the probe is (close to) an eigenstate of one candidate model, that model will never learn effectively since there will be little variation in measurements correspdonding to evolving the probe according to that model. Intuitively, the most informative probe for a given model is a superposition of its eigenstates, since any evolution in this basis will be reflected by the measurement.

The default set of probes is to use a random set. Alternative sets include \(|+\rangle^{\otimes N}\) or \(|0\rangle^{\otimes N}\). Probes are generated in a dictionary, of which the keys are (probe_id, num_qubits); probe_id runs from 1 to the num_probes attribute of the ExplorationStrategy controls; the num_qubits runs from 1 to max_num_qubits.

There are a number of sets of probes required, all similarly set by specifying the subroutine:

system_probes_generation_subroutine

Probes used for evolution on the target system

simulator_probes_generation_subroutine

Probes correspdonding exactly to those used on the system. These should be the same so that the likelihood function is meaningful, but in realistic cases there may be slight differences in probe preparation, e.g. due to expected noise in an experimental system. Therefore it is possible to specify a different set. Note to enable this functionality, shared_probes must also be set to False.

plot_probes_generation_subroutine

Probes used for plots throughout the protocol. Plots should be in the same basis for consistency; we generate them once per run to save time, since the plot probes are the same everywhere. The standard plotting probes are \(|+\rangle^{\otimes N}\).

evaluation_probe_generation_subroutine

Some ES use evaluation datasets within model selection; to specify a different generator than system_probes_generation_subroutine, set this attribute. Defaults to None.

Experiment design heuristic

In order for the model Training to perform well, it is essential that the parameter learning subroutine is fed useful, meaningful data. We use an experiment design heuristic (EDH) to generate informative experiments. The EDH can encompass custom logic for particular use cases, although the most common (particle guess heuristic [WGFC13a]) attempts to select an evolution time \(t\) which can distinguish between strong and weak parameterisations (particles) based on the current distribution.

Primarily the EDH must choose an evolution time \(t\) and probe, since these two together specify an entire experiment in most use cases. QMLA can consider more complex experiment designs, in which case the EDH must also choose informative values for all inputs.

QInfer interface

As mentioned, the workhorse of model Training is [QInfer]. The default behaviour of QInferModelQMLA is to call likelihood() for both the calculation of the datum from the system, and the likelihoods of all the particles through the simulator. This too can be replaced, for example if calls to the system need to interface with a real experiment, or the particles should be computed through a quantum simulator.

Prior distribution

QInfer works by taking an initial prior distribution, which it iteratively narrows based on quantum likelihood estimation. This process of narrowing the distribution is what we call emph{learning}: after \(N_E\) experiments worth of data, the mean of the remaining distribution is considered as the optimised parameterisation.

The prior can be altered to incorporate the user’s prior knowledge about the system. The default generator for the prior is to construct an \(n\) dimensional Gaussian through gaussian_prior(). Importantly, the range of each term’s parameter can be different, e.g. near-neighbour couplings having much higher frequency than distant neighbours. Terms’ prior mean and width can be specified in gaussian_prior_means_and_widths. Terms which do not have specific means/widths in gaussian_prior_means_and_widths are assigned based on the ExplorationStrategy attributes min_param, max_param: the defaults are

mean = (max_param + min_param)/2;

width= (max_param - min_param)/4.

To overwrite this, e.g. to change the default width of each parameter’s distribution, users can implement a new prior generation function to replace prior_distribution_subroutine.

self.gaussian_prior_means_and_widths = {
    'pauliSet_1_x_d1' : (5, 1),
    'pauliSet_1_y_d1' : (150, 25),
    'pauliSet_1_z_d1' : (1e6, 1e2)
}
self.min_param = 0
self.max_param = 10

self.prior_distribution_subroutine = alternatve_prior_generation

Latex name mapping

Each model string format requires a method which can map the string to a Latex string. This is because much of the analysis automatically generated by QMLA refers to individual models or terms, so it is useful that these can be rendered into a readable format, rather than the raw string used to generate the matrices used by the algorithm. The mapping function should be able to operate either on single terms or entire models strings. If using terms like pauliSet_i_t_dN, the default pauli_set_latex_name() should work. Further examples, specific to models of bespoke ES are grouped_pauli_terms(), fermi_hubbard_latex().

>>> from qmla.shared_functionality.latex_model_names import grouped_pauli_terms
>>> self.latex_string_map_subroutine = grouped_pauli_terms

Output and Analysis

When a run is launched (either locally or remotely), a results directory is built for that run. In that directory, results are stored in several formats from each instance.

By default, QMLA provides a set of analyses, generating several plots in the sub-directories of the run’s results directory.

Analyses are available on various levels:

Run

results across a number of instances.

Example: the number of instance wins for champion models.

Example: average dynamics reproduced by champion models.

Instance

Performance of a single insance.

Example: models generated and the branches on which they reside

Model

Individual model performance within an instance.

Example: parameter estimation through QHL.

Example: pairwise comparison between models.

Comparisons

Pairwise comparison of models’ performance.

Example: dynamics of both candidates (with respect to a single basis).

Within the Launch scripts, there is a plot_level variable which informs QMLA of how many plots to produce by default. This gives users a level of control over how much analysis is performed. For instance, while testing an Exploration Strategy, a higher degree of testing may be required, so plots relating to every individual model are desired. For large runs, however, where a large number of models are generated/compared, plotting each model’s training performance is overly cumbersome and is unneccessary.

The plots generated at each plot level are:

Launch

There are two mechanisms for launching QMLA: locally and in parallel. Both of are available through bash scripts in qmla/launch. When launched in parallel, the model training/comparison subroutines are run on remote processes, e.g. in a compute cluster. In either case, the user has a set of top-level controls, bearing in mind that the majority of user requirements are implemented in the ExplorationStrategy. Following the setting of these controls, the remainder of the launch script call a number of bash and Python scripts for the actual implementation, which most users should not need to alter.

The available controls to the user are

num_instances

number of instance in the run

run_qhl

if 1, only implements QHL on the true_model attribute of the ES, i.e. run_quantum_hamiltonian_learning().

run_qhl_mulit_model

if 1, only implements QHL on the qhl_models attribute (list) of the ES, i.e. run_quantum_hamiltonian_learning_multiple_models(). if both this and :run_qhl: are 0, then the full QMLA protocol is run (run_complete_qmla()).

exp

Number of experiments used during model training

prt

Number of particles used during model training

plot_level

specifies the granularity of plots generated. See Output and Analysis

debug_mode

(bool) whether to run QMLA in degug mode. Should not be required by most users; this mode merely logs further data in the instances’ log files, which can be found in the run results directory.

exploration_strategy

specify the name of the ES class to use.

alt_exploration_strategies

list of alternative ES for the case where multiple ET s are considered. This list should be in brackets with elements separated by spaces (i.e no commas). Note that in parallel_launch.sh, this must be enabled through setting multiple_exploration_strategies=1, while in local_launch.sh it is sufficient that the list is not empty.

An example of the top few lines of local_launch.sh is then given by

#!/bin/bash

###############
# QMLA run configuration
###############
num_instances=100
run_qhl=0 # perform QHL on known (true) model
run_qhl_mulit_model=0 # perform QHL for defined list of models.
exp=500 # number of experiments
prt=2000 # number of particles

###############
# QMLA settings - user
###############
plot_level=4
debug_mode=0

###############
# QMLA settings - default
###############
do_further_qhl=0
q_id=0
use_rq=0
further_qhl_factor=1
further_qhl_num_runs=$num_instances
plots=0
number_best_models_further_qhl=5

###############
# Choose exploration strategy/strategies
###############

exploration_strategy='UserExplorationStrategy'

alt_exploration_strategies=(
    'IsingLatticeSet'
    'Genetic'
)

Redis server

QMLA uses a redis server as a database and job broker for the implementation of remote tasks. This is launhed automatically when using parallel_launch.sh, but using local_launch.sh, must be initiated in terminal as