Source code for n3fit.performfit

"""
    Fit action controller
"""

# Backend-independent imports
import logging

import n3fit.checks
from n3fit.vpinterface import N3PDF

log = logging.getLogger(__name__)


# Action to be called by validphys
# All information defining the NN should come here in the "parameters" dict
[docs] @n3fit.checks.can_run_multiple_replicas @n3fit.checks.check_multireplica_qed @n3fit.checks.check_fiatlux_pdfs_id @n3fit.checks.check_polarized_configs def performfit( *, experiments_data, n3fit_checks_action, # wrapper for all checks replicas, # checks specific to performfit replicas_nnseed_fitting_data_dict, posdatasets_fitting_pos_dict, integdatasets_fitting_integ_dict, theoryid, fiatlux, basis, fitbasis, positivity_bound, sum_rules=True, parameters, replica_path, output_path, save=None, load=None, hyperscanner=None, hyperopt=None, kfold_parameters, tensorboard=None, debug=False, maxcores=None, double_precision=False, parallel_models=False, ): """ This action will (upon having read a validcard) process a full PDF fit for a set of replicas. The input to this function is provided by validphys and/or defined in the runcards or commandline arguments. This controller is provided with: 1. Seeds generated using the replica number and the seeds defined in the runcard. 2. Loaded datasets with replicas generated. 2.1 Loaded positivity/integrability sets. The workflow of this controller is as follows: 1. Generate a ModelTrainer object holding information to create the NN and perform a fit (at this point no NN object has been generated) 1.1 (if hyperopt) generates the hyperopt scanning dictionary taking as a base the fitting dictionary and the runcard's hyperscanner dictionary 2. Pass the dictionary of parameters to ModelTrainer for the NN to be generated and the fit performed 2.1 (if hyperopt) Loop over point 4 for `hyperopt` number of times 3. Once the fit is finished, output the PDF grid and accompanying files Parameters ---------- genrep: bool Whether or not to generate MC replicas. (Only used for checks) data: validphys.core.DataGroupSpec containing the datasets to be included in the fit. (Only used for checks) experiments_data: list[validphys.core.DataGroupSpec] similar to `data` but now passed as argument to `ModelTrainer` replicas_nnseed_fitting_data_dict: list[tuple] list with element for each replica (typically just one) to be fitted. Each element is a tuple containing the replica number, nnseed and ``fitted_data_dict`` containing all of the data, metadata for each group of datasets which is to be fitted. posdatasets_fitting_pos_dict: list[dict] list of dictionaries containing all data and metadata for each positivity dataset integdatasets_fitting_integ_dict: list[dict] list of dictionaries containing all data and metadata for each integrability dataset theoryid: validphys.core.TheoryIDSpec Theory which is used to generate theory predictions from model during fit. Object also contains some metadata on the theory settings. fiatlux: dict dictionary containing the params needed from LuxQED basis: list[dict] preprocessing information for each flavour to be fitted. fitbasis: str Valid basis which the fit is to be ran in. Available bases can be found in :py:mod:`validphys.pdfbases`. sum_rules: str Whether to impose sum rules in fit. By default set to True="ALL" parameters: dict Mapping containing parameters which define the network architecture/fitting methodology. replica_path: pathlib.Path path to the output of this run output_path: str name of the fit save: None, str model file where weights will be saved, used in conjunction with ``load``. load: None, str model file from which to load weights from. hyperscanner: dict dictionary containing the details of the hyperscanner hyperopt: int if given, number of hyperopt iterations to run kfold_parameters: None, dict dictionary with kfold settings used in hyperopt. tensorboard: None, dict mapping containing tensorboard settings if it is to be used. By default it is None and tensorboard is not enabled. debug: bool activate some debug options maxcores: int maximum number of (logical) cores that the backend should be aware of double_precision: bool whether to use double precision parallel_models: bool whether to run models in parallel """ from n3fit.backends import set_initial_state # If debug is active, the initial state will be fixed so that the run is reproducible set_initial_state(debug=debug, max_cores=maxcores, double_precision=double_precision) from n3fit.stopwatch import StopWatch stopwatch = StopWatch() # All potentially backend dependent imports should come inside the fit function # so they can eventually be set from the runcard from n3fit.io.writer import WriterWrapper from n3fit.model_trainer import ModelTrainer # Note that this can be run in sequence or in parallel # To do both cases in the same loop, we uniformize the replica information as: # - sequential: a list over replicas, each entry containing tuples of length 1 # - parallel: a list of length 1, containing tuples over replicas # # Add inner tuples replicas_info = [ ((replica,), (experiment,), (nnseed,)) for replica, experiment, nnseed in replicas_nnseed_fitting_data_dict ] n_models = len(replicas_info) if parallel_models: # Move replicas from outer list to inner tuples replicas, experiments, nnseeds = [], [], [] for replica, experiment, nnseed in replicas_info: replicas.extend(replica) experiments.extend(experiment) nnseeds.extend(nnseed) replicas_info = [(tuple(replicas), tuple(experiments), tuple(nnseeds))] log.info( "Starting parallel fits from replica %d to %d", replicas[0], replicas[0] + n_models - 1 ) else: log.info( "Starting sequential fits from replica %d to %d", replicas[0], replicas[0] + n_models - 1, ) for replica_idxs, exp_info, nnseeds in replicas_info: log.info("Starting replica fit " + str(replica_idxs)) # Generate a ModelTrainer object # this object holds all necessary information to train a PDF (up to the NN definition) the_model_trainer = ModelTrainer( experiments_data, exp_info, posdatasets_fitting_pos_dict, integdatasets_fitting_integ_dict, basis, fitbasis, nnseeds, positivity_bound, debug=debug, kfold_parameters=kfold_parameters, max_cores=maxcores, model_file=load, sum_rules=sum_rules, theoryid=theoryid, lux_params=fiatlux, replicas=replica_idxs, ) # This is just to give a descriptive name to the fit function pdf_gen_and_train_function = the_model_trainer.hyperparametrizable # Read up the parameters of the NN from the runcard stopwatch.register_times("replica_set") ######################################################################## # ### Hyperopt # # If hyperopt is active the parameters of NN will be substituted by the# # hyoperoptimizable variables. # # Hyperopt will run for --hyperopt number of iterations before leaving # # this block # ######################################################################## if hyperopt: from n3fit.hyper_optimization.hyper_scan import hyper_scan_wrapper # Note that hyperopt will not run in parallel or with more than one model _for now_ replica_path_set = replica_path / f"replica_{replica_idxs[0]}" true_best = hyper_scan_wrapper( replica_path_set, the_model_trainer, hyperscanner, max_evals=hyperopt ) print("##################") print("Best model found: ") for k, i in true_best.items(): print(f" {k} : {i} ") # In general after we do the hyperoptimization we do not care about the fit # so just let this die here break ####################################################################### end of hyperopt # Ensure hyperopt is off the_model_trainer.set_hyperopt(False) # Enable the tensorboard callback if tensorboard is not None: profiling = tensorboard.get("profiling", False) weight_freq = tensorboard.get("weight_freq", 0) if parallel_models and n_models != 1: # If using tensorboard when running in parallel # dump the debugging data to the nnfit folder replica_path_set = replica_path else: replica_path_set = replica_path / f"replica_{replica_idxs[0]}" log_path = replica_path_set / "tboard" the_model_trainer.enable_tensorboard(log_path, weight_freq, profiling) ############################################################################# # ### Fit # # This function performs the actual fit, it reads all the parameters in the # # "parameters" dictionary, uses them to generate the NN and trains the net # ############################################################################# result = pdf_gen_and_train_function(parameters) stopwatch.register_ref("replica_fitted", "replica_set") stopping_object = result["stopping_object"] log.info("Stopped at epoch=%d", stopping_object.stop_epoch) final_time = stopwatch.stop() all_chi2s = the_model_trainer.evaluate(stopping_object) pdf_models = result["pdf_model"].split_replicas() q0 = theoryid.get_description().get("Q0") pdf_instances = [N3PDF(pdf_model, fit_basis=basis, Q=q0) for pdf_model in pdf_models] writer_wrapper = WriterWrapper( replica_idxs, pdf_instances, stopping_object, all_chi2s, theoryid, final_time ) writer_wrapper.write_data(replica_path, output_path.name, save) if tensorboard is not None: log.info("Tensorboard logging information is stored at %s", log_path)