From 247bb33e9b6364eae22266f1048a3c00b1baaf72 Mon Sep 17 00:00:00 2001
From: wilsonm <michael.wilson@ed.ac.uk>
Date: Tue, 22 Sep 2020 13:38:35 +0100
Subject: [PATCH] update vp-nextfitruncard to use the API, update the relevant
 provider to be able to update description and reformate provider plus
 docstring

---
 validphys2/src/validphys/eff_exponents.py     | 68 +++++++++----
 .../validphys/scripts/vp_nextfitruncard.py    | 98 +++----------------
 2 files changed, 59 insertions(+), 107 deletions(-)

diff --git a/validphys2/src/validphys/eff_exponents.py b/validphys2/src/validphys/eff_exponents.py
index 172589f387..937b969c21 100644
--- a/validphys2/src/validphys/eff_exponents.py
+++ b/validphys2/src/validphys/eff_exponents.py
@@ -400,40 +400,64 @@ fmt = lambda a: float(significant_digits(a, 4))
 
 next_fit_eff_exps_table = collect("next_effective_exponents_table", ("fitpdfandbasis",))
 
-def next_effective_exponents_yaml(fit: FitSpec, next_fit_eff_exps_table):
-    """-Returns a table in yaml format called NextEffExps.yaml
-       -Prints the yaml table in the report
-    using `effective_exponents_table` this provider outputs the yaml runcard to run
-    a fit with identical input as the specified `fit` with the t0 and preprocessing iterated.
 
-    This action must be used in a report and should be wrapped in a code block to be formatted
-    correctly, for example:
+def next_effective_exponents_yaml(
+    fit: FitSpec, next_fit_eff_exps_table, _updated_description=None
+):
+    """
+    Using `effective_exponents_table` this provider outputs the yaml runcard
+    used to specify settings of ``fit`` but having iterated the following
+    sections:
+    - Modifies the random seeds
+    - Updates the preprocessing exponents
+    - Updates the description if ``_updated_description`` is provided
+
+    this should facilitate running a new fit with identical input settings
+    as the specified ``fit`` with the t0, seeds and preprocessing iterated.
+
+    This action can be used in a report but should be wrapped in a code block
+    to be formatted correctly, for example:
 
     ```yaml
     {@next_effective_exponents_runcard@}
     ```
 
-    """
+    alternatively, using the API, the yaml dump returned by this function can
+    be written to a file e.g
+
+    >>> from validphys.api import API
+    >>> yaml_output = API.next_effective_exponents_yaml(
+    ...     fit=<fit name>,
+    ...     _updated_description="My iterated fit"
+    ... )
+    >>> with open("output.yml", "w+") as f:
+    ...     f.write(yaml_output)
 
+    """
     df_effexps = next_fit_eff_exps_table[0]
     # Use round trip loader rather than safe_load in fit.as_input()
-    with open(fit.path/'filter.yml', 'r') as f:
+    with open(fit.path / "filter.yml", "r") as f:
         filtermap = yaml.load(f, yaml.RoundTripLoader)
-    previous_exponents = filtermap['fitting']['basis']
-    basis = filtermap['fitting']['fitbasis']
+    previous_exponents = filtermap["fitting"]["basis"]
+    basis = filtermap["fitting"]["fitbasis"]
     checked = check_basis(basis, None)
-    basis = checked['basis']
-    flavours = checked['flavours']
+    basis = checked["basis"]
+    flavours = checked["flavours"]
 
     runcard_flavours = basis.to_known_elements(
-        [ref_fl['fl'] for ref_fl in previous_exponents]).tolist()
+        [ref_fl["fl"] for ref_fl in previous_exponents]
+    ).tolist()
     for fl in flavours:
-        alphas = df_effexps.loc[(f'${fl}$', r'$\alpha$')].values
-        betas = df_effexps.loc[(f'${fl}$', r'$\beta$')].values
-        previous_exponents[runcard_flavours.index(fl)]['smallx'] = [fmt(alpha) for alpha in alphas]
-        previous_exponents[runcard_flavours.index(fl)]['largex'] = [fmt(beta) for beta in betas]
-    #iterate t0
-    filtermap['datacuts']['t0pdfset'] = fit.name
+        alphas = df_effexps.loc[(f"${fl}$", r"$\alpha$")].values
+        betas = df_effexps.loc[(f"${fl}$", r"$\beta$")].values
+        previous_exponents[runcard_flavours.index(fl)]["smallx"] = [
+            fmt(alpha) for alpha in alphas
+        ]
+        previous_exponents[runcard_flavours.index(fl)]["largex"] = [
+            fmt(beta) for beta in betas
+        ]
+    # iterate t0
+    filtermap["datacuts"]["t0pdfset"] = fit.name
 
     # Update seeds with pseudorandom numbers between 0 and 1e10
     # Check if seeds exist especially since extra seeds needed in n3fit vs nnfit
@@ -450,4 +474,8 @@ def next_effective_exponents_yaml(fit: FitSpec, next_fit_eff_exps_table):
     if "filterseed" in closuretest_data:
         closuretest_data["filterseed"] = random.randrange(0, 1e10)
 
+    # update description if necessary
+    if _updated_description is not None:
+        filtermap["description"] = _updated_description
+
     return yaml.dump(filtermap, Dumper=yaml.RoundTripDumper)
diff --git a/validphys2/src/validphys/scripts/vp_nextfitruncard.py b/validphys2/src/validphys/scripts/vp_nextfitruncard.py
index 0386067006..ed7f0c1114 100644
--- a/validphys2/src/validphys/scripts/vp_nextfitruncard.py
+++ b/validphys2/src/validphys/scripts/vp_nextfitruncard.py
@@ -19,19 +19,12 @@ import argparse
 import os
 import pathlib
 import sys
-import random
 import logging
 import prompt_toolkit
 
-import NNPDF as nnpath
-
-from reportengine.compat import yaml
 from reportengine import colors
-from reportengine.floatformatting import significant_digits
 
-from validphys.eff_exponents import next_effective_exponents_table
-from validphys.loader import Loader, PDFNotFound
-from validphys.pdfbases import check_basis
+from validphys.api import API
 
 
 # Take command line arguments
@@ -97,94 +90,25 @@ def main():
     # Check whether runcard with same name already exists in the path
     if runcard_path_out.exists() and not force:
         log.error(
-            f"Destination path {runcard_path_out.absolute()} already exists. If you wish to "
-            "overwrite it, use the --force option."
-        )
-        sys.exit(1)
-
-    results_path = pathlib.Path(nnpath.get_results_path())
-    fit_path = results_path / input_fit
-
-    if not fit_path.is_dir():
-        log.error(
-            "Could not find the specified fit. The following path is not a directory: "
-            f"{fit_path.absolute()}. If the requested fit does exist, you can download it with "
-            "`vp-get fit <fit_name>`."
+            "Destination path %s already exists. If you wish to "
+            "overwrite it, use the --force option.",
+            runcard_path_out.absolute(),
         )
         sys.exit(1)
 
-    runcard_path_in = fit_path / "filter.yml"
-
-    with open(runcard_path_in, "r") as infile:
-        runcard_data = yaml.load(infile, Loader=yaml.RoundTripLoader)
-        log.info(f"Input runcard successfully read from {runcard_path_in.absolute()}.")
-
-    # Update runcard with settings needed for iteration
-
     # Update description of fit interactively
-    description = runcard_data["description"]
+    description = API.fit(fit=input_fit).as_input()["description"]
+
     updated_description = interactive_description(description)
-    runcard_data["description"] = updated_description
-
-    # Iterate t0
-    runcard_data["datacuts"]["t0pdfset"] = input_fit
-
-    # Update seeds with pseudorandom numbers between 0 and 1e10
-    # Check if seeds exist especially since extra seeds needed in n3fit vs nnfit
-    # Start with seeds in "fitting" section of runcard
-    fitting_data = runcard_data["fitting"]
-    fitting_seeds = ["seed", "trvlseed", "nnseed", "mcseed"]
-
-    for seed in fitting_seeds:
-        if seed in fitting_data:
-            fitting_data[seed] = random.randrange(0, 1e10)
-
-    # Next "closuretest" section of runcard
-    closuretest_data = runcard_data["closuretest"]
-    if "filterseed" in closuretest_data:
-        closuretest_data["filterseed"] = random.randrange(0, 1e10)
-
-    # Update preprocessing exponents
-    # Firstly, find new exponents from PDF set that corresponds to fit
-    basis = runcard_data["fitting"]["fitbasis"]
-    checked = check_basis(basis, None)
-    basis = checked["basis"]
-    flavours = checked["flavours"]
-    l = Loader()
-    try:
-        pdf = l.check_pdf(input_fit)
-    except PDFNotFound:
-        log.error(
-            f"Could not find the PDF set {input_fit}. If the requested PDF set does exist, you can "
-            "download it with `vp-get pdf <pdf_name>`."
-        )
-        sys.exit(1)
-    new_exponents = next_effective_exponents_table(
-        pdf=pdf, basis=basis, flavours=flavours
-    )
 
-    # Define function that we will use to round exponents to 4 significant figures
-    rounder = lambda a: float(significant_digits(a, 4))
-
-    # Update previous_exponents with new values
-    previous_exponents = runcard_data["fitting"]["basis"]
-    runcard_flavours = basis.to_known_elements(
-        [ref_fl["fl"] for ref_fl in previous_exponents]
-    ).tolist()
-    for fl in flavours:
-        alphas = new_exponents.loc[(f"${fl}$", r"$\alpha$")].values
-        betas = new_exponents.loc[(f"${fl}$", r"$\beta$")].values
-        previous_exponents[runcard_flavours.index(fl)]["smallx"] = [
-            rounder(alpha) for alpha in alphas
-        ]
-        previous_exponents[runcard_flavours.index(fl)]["largex"] = [
-            rounder(beta) for beta in betas
-        ]
+    iterated_runcard_yaml = API.next_effective_exponents_yaml(
+        fit=input_fit, _updated_description=updated_description
+    )
 
     # Write new runcard to file
     with open(runcard_path_out, "w") as outfile:
-        yaml.dump(runcard_data, outfile, Dumper=yaml.RoundTripDumper)
-        log.info(f"Runcard for iterated fit written to {runcard_path_out.absolute()}.")
+        outfile.write(iterated_runcard_yaml)
+        log.info("Runcard for iterated fit written to %s.", runcard_path_out.absolute())
 
     # Open new runcard with default editor, or if one is not set, with vi
     EDITOR = os.environ.get("EDITOR") if os.environ.get("EDITOR") else "vi"
-- 
GitLab