From 0515daa0947a6c2771e6635fd5cfbaafb918a127 Mon Sep 17 00:00:00 2001
From: David Fairbrother <DavidFair@users.noreply.github.com>
Date: Thu, 15 Dec 2016 14:45:59 +0000
Subject: [PATCH] Re #18108 Implemented long mode for PEARL and auto cal for
 POLARIS

---
 .../Diffraction/isis_powder/abstract_inst.py  | 50 +++++-----
 .../isis_powder/mock_instrument.py            |  2 +-
 scripts/Diffraction/isis_powder/pearl.py      | 47 +++++-----
 .../pearl_routines/pearl_advanced_config.py   | 94 +++++++++++++------
 .../isis_powder/pearl_routines/pearl_algs.py  | 12 +--
 .../pearl_routines/pearl_output.py            |  2 +-
 .../pearl_routines/pearl_param_mapping.py     | 40 ++++----
 scripts/Diffraction/isis_powder/polaris.py    | 50 ++++++----
 .../polaris_routines/basic_config.yaml        |  1 -
 .../polaris_advanced_config.py                | 27 ++++--
 .../polaris_routines/polaris_param_mapping.py | 10 +-
 .../routines/InstrumentSettings.py            | 57 +++++++----
 .../isis_powder/routines/common.py            | 22 +++--
 .../Diffraction/isis_powder/routines/focus.py | 12 ++-
 scripts/test/ISIS_Powder_AbstractInstTest.py  |  2 +-
 15 files changed, 266 insertions(+), 162 deletions(-)

diff --git a/scripts/Diffraction/isis_powder/abstract_inst.py b/scripts/Diffraction/isis_powder/abstract_inst.py
index 5bdb4b437be..6fe55775b20 100644
--- a/scripts/Diffraction/isis_powder/abstract_inst.py
+++ b/scripts/Diffraction/isis_powder/abstract_inst.py
@@ -35,14 +35,6 @@ class AbstractInst(object):
     def output_dir(self):
         return self._output_dir
 
-    @property
-    def default_input_ext(self):
-        return self._default_input_ext
-
-    @default_input_ext.setter
-    def default_input_ext(self, new_ext):
-        self._default_input_ext = _prefix_dot_to_ext(new_ext)
-
     @property
     def user_name(self):
         return self._user_name
@@ -71,15 +63,15 @@ class AbstractInst(object):
         return focus.focus(run_number=run_number, input_batching=input_batching,
                            perform_vanadium_norm=do_van_normalisation, instrument=self)
 
-    def _generate_out_file_paths(self, run_details, output_directory=None):
+    def generate_out_file_paths(self, run_details, output_directory=None):
         if not output_directory:
             output_directory = os.path.join(self._output_dir, run_details.label, self._user_name)
-        file_name = self.generate_inst_file_name(run_number=run_details.run_number)
-        nxs_file = os.path.join(output_directory, (str(file_name) + ".nxs"))
-        gss_file = os.path.join(output_directory, (str(file_name) + ".gss"))
-        tof_xye_file = os.path.join(output_directory, (str(file_name) + "_tof_xye.dat"))
-        d_xye_file = os.path.join(output_directory, (str(file_name) + "_d_xye.dat"))
-        out_name = str(file_name)
+        file_name = str(self.generate_output_file_name(run_number=run_details.run_number))
+        nxs_file = os.path.join(output_directory, (file_name + ".nxs"))
+        gss_file = os.path.join(output_directory, (file_name + ".gsas"))
+        tof_xye_file = os.path.join(output_directory, (file_name + "_tof_xye.dat"))
+        d_xye_file = os.path.join(output_directory, (file_name + "_d_xye.dat"))
+        out_name = file_name
 
         out_file_names = {"nxs_filename": nxs_file,
                           "gss_filename": gss_file,
@@ -91,23 +83,30 @@ class AbstractInst(object):
         return out_file_names
 
     def _generate_input_full_path(self, run_number, input_dir):
-        # Uses runtime polymorphism to generate the full run name
-        file_name = self.generate_inst_file_name(run_number)
-        return os.path.join(input_dir, (file_name + self._default_input_ext))
+        file_name = self.generate_input_file_name(run_number)
+        return os.path.join(input_dir, file_name)
 
     # Instrument specific properties
 
+    @staticmethod
+    def can_auto_gen_vanadium_cal():
+        return False
+
     @abstractmethod
     def get_run_details(self, run_number_string):
         pass
 
     @staticmethod
     @abstractmethod
-    def generate_inst_file_name(run_number):
+    def generate_input_file_name(run_number):
         pass
 
     # --- Instrument optional hooks ----#
 
+    @abstractmethod
+    def generate_output_file_name(self, run_number):
+        raise NotImplementedError("Output names not implemented")
+
     def apply_solid_angle_efficiency_corr(self, ws_to_correct, run_details):
         return ws_to_correct
 
@@ -135,13 +134,8 @@ class AbstractInst(object):
     def spline_vanadium_ws(self, focused_vanadium_ws):
         return None
 
+    def crop_banks_to_user_tof(self, focused_banks):
+        return focused_banks
 
-# ----- Private Implementation ----- #
-# These should only be called by the abstract instrument class
-
-
-def _prefix_dot_to_ext(ext):
-    if not ext.startswith('.'):
-        return '.' + ext
-    else:
-        return ext
+    def generate_auto_vanadium_calibration(self, run_details):
+        raise NotImplementedError("Automatic vanadium corrections have not been implemented for this instrument.")
diff --git a/scripts/Diffraction/isis_powder/mock_instrument.py b/scripts/Diffraction/isis_powder/mock_instrument.py
index e319866b32e..a44b02a5c2e 100644
--- a/scripts/Diffraction/isis_powder/mock_instrument.py
+++ b/scripts/Diffraction/isis_powder/mock_instrument.py
@@ -29,7 +29,7 @@ class MockInstrument(AbstractInst):
         return calibration_details
 
     @staticmethod
-    def generate_inst_file_name(run_number):
+    def generate_input_file_name(run_number):
         return "generate_inst_file_name" + str(run_number)
 
     @staticmethod
diff --git a/scripts/Diffraction/isis_powder/pearl.py b/scripts/Diffraction/isis_powder/pearl.py
index 4f264035f08..8dbb2cadd6a 100644
--- a/scripts/Diffraction/isis_powder/pearl.py
+++ b/scripts/Diffraction/isis_powder/pearl.py
@@ -10,37 +10,29 @@ from isis_powder.pearl_routines import pearl_algs, pearl_output, pearl_advanced_
 
 class Pearl(AbstractInst):
     def __init__(self, **kwargs):
-        expected_attr = ["user_name", "config_file_name", "calibration_dir", "output_dir", "attenuation_file_name",
-                         "cal_map_path", "van_absorb_file"]
         basic_config_dict = yaml_parser.open_yaml_file_as_dictionary(kwargs.get("config_file", None))
         self._inst_settings = InstrumentSettings.InstrumentSettings(
-           attr_mapping=pearl_param_mapping.attr_mapping, adv_conf_dict=pearl_advanced_config.variables,
+           attr_mapping=pearl_param_mapping.attr_mapping, adv_conf_dict=pearl_advanced_config.get_all_adv_variables(),
            basic_conf_dict=basic_config_dict, kwargs=kwargs)
 
-        self._inst_settings.check_expected_attributes_are_set(expected_attr_names=expected_attr)
-
         super(Pearl, self).__init__(user_name=self._inst_settings.user_name,
                                     calibration_dir=self._inst_settings.calibration_dir,
                                     output_dir=self._inst_settings.output_dir)
 
-        self._ads_workaround = 0
         self._cached_run_details = None
         self._cached_run_details_number = None
 
     def focus(self, run_number, **kwargs):
-        self._inst_settings.update_attributes_from_kwargs(kwargs=kwargs)
-        expected_attr = ["absorb_corrections", "long_mode", "tt_mode", "perform_atten", "van_norm"]
-        self._inst_settings.check_expected_attributes_are_set(expected_attr_names=expected_attr)
-
+        self._switch_long_mode_inst_settings(kwargs.get("long_mode"))
+        self._inst_settings.update_attributes(kwargs=kwargs)
         return self._focus(run_number=run_number, input_batching=InputBatchingEnum.Summed,
                            do_van_normalisation=self._inst_settings.van_norm)
 
     def create_calibration_vanadium(self, run_in_range, **kwargs):
-        kwargs["tt_mode"] = "tt88"
+        self._switch_long_mode_inst_settings(kwargs.get("long_mode"))
         kwargs["perform_attenuation"] = False
-        self._inst_settings.update_attributes_from_kwargs(kwargs=kwargs)
-        expected_attr = ["long_mode", "van_norm", "absorb_corrections"]
-        self._inst_settings.check_expected_attributes_are_set(expected_attr_names=expected_attr)
+        kwargs["tt_mode"] = "tt88"
+        self._inst_settings.update_attributes(kwargs=kwargs)
 
         run_details = self.get_run_details(run_number_string=int(run_in_range))
         run_details.run_number = run_details.vanadium_run_numbers
@@ -69,27 +61,32 @@ class Pearl(AbstractInst):
         return run_details
 
     @staticmethod
-    def generate_inst_file_name(run_number):
+    def generate_input_file_name(run_number):
         return _generate_file_name(run_number=run_number)
 
-    # Hook overrides
+    def generate_output_file_name(self, run_number):
+        output_name = "PEARL" + str(run_number)
+        # Append each mode of operation
+        output_name += "_" + self._inst_settings.tt_mode
+        output_name += "_long" if self._inst_settings.absorb_corrections else ""
+        return output_name
 
     def attenuate_workspace(self, input_workspace):
         attenuation_path = self._attenuation_full_path
         return pearl_algs.attenuate_workspace(attenuation_file_path=attenuation_path, ws_to_correct=input_workspace)
 
     def normalise_ws(self, ws_to_correct, run_details=None):
-        if not run_details:
-            raise RuntimeError("Run details was not passed into PEARL: normalise_ws")
         monitor_ws = common.get_monitor_ws(ws_to_process=ws_to_correct, run_number_string=run_details.run_number,
                                            instrument=self)
         normalised_ws = pearl_algs.normalise_ws_current(ws_to_correct=ws_to_correct, monitor_ws=monitor_ws,
-                                                        spline_coeff=20)
+                                                        spline_coeff=self._inst_settings.monitor_spline,
+                                                        integration_range=self._inst_settings.monitor_integration_range,
+                                                        lambda_values=self._inst_settings.monitor_lambda)
         common.remove_intermediate_workspace(monitor_ws)
         return normalised_ws
 
     def get_monitor_spectra_index(self, run_number):
-        return 1
+        return self._inst_settings.monitor_spec_no
 
     def spline_vanadium_ws(self, focused_vanadium_spectra):
         return common.spline_vanadium_for_focusing(focused_vanadium_spectra=focused_vanadium_spectra,
@@ -109,13 +106,21 @@ class Pearl(AbstractInst):
         grouped_d_spacing = mantid.GroupWorkspaces(InputWorkspaces=output_spectra, OutputWorkspace=group_name)
         return grouped_d_spacing
 
+    def crop_banks_to_user_tof(self, focused_banks):
+        return common.crop_banks_in_tof(focused_banks, self._inst_settings.tof_cropping_values)
+
     def crop_short_long_mode(self, ws_to_crop):
-        out_ws = common.crop_in_tof(ws_to_rebin=ws_to_crop, x_max=19900)
+        out_ws = common.crop_in_tof(ws_to_crop=ws_to_crop, x_min=self._inst_settings.raw_data_crop_vals[0],
+                                    x_max=self._inst_settings.raw_data_crop_vals[-1])
         return out_ws
 
     def generate_vanadium_absorb_corrections(self, run_details, ws_to_match):
         return pearl_algs.generate_vanadium_absorb_corrections(van_ws=ws_to_match)
 
+    def _switch_long_mode_inst_settings(self, long_mode_on):
+        self._inst_settings.update_attributes(advanced_config=pearl_advanced_config.get_long_mode_dict(long_mode_on),
+                                              suppress_warnings=True)
+
 
 def _generate_file_name(run_number):
     digit = len(str(run_number))
diff --git a/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py b/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py
index 8cff4d2f8cb..c636d676343 100644
--- a/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py
+++ b/scripts/Diffraction/isis_powder/pearl_routines/pearl_advanced_config.py
@@ -1,32 +1,65 @@
-file_names = {
-    "vanadium_absorb_file": "pearl_absorp_sphere_10mm_newinst2_long.nxs",
-    "tt88_grouping": "pearl_group_12_1_TT88.cal",
-    "tt70_grouping": "pearl_group_12_1_TT70.cal",
-    "tt35_grouping": "pearl_group_12_1_TT35.cal"
+from __future__ import (absolute_import, division, print_function)
+
+general_params = {
+    "monitor_spectrum_number": 1,
+    "monitor_spline_coefficient": 20,
+    "spline_coefficient": 60
 }
 
-tof_cropping_ranges = [
-    (1500, 19900),  # Bank 1
-    (1500, 19900),  # Bank 2
-    (1500, 19900),  # Bank 3
-    (1500, 19900),  # Bank 4
-    (1500, 19900),  # Bank 5
-    (1500, 19900),  # Bank 6
-    (1500, 19900),  # Bank 7
-    (1500, 19900),  # Bank 8
-    (1500, 19900),  # Bank 9
-    (1500, 19900),  # Bank 10
-    (1500, 19900),  # Bank 11
-    (1500, 19900),  # Bank 12
-    (1500, 19900),  # Bank 13
-    (1500, 19900)   # Bank 14
+long_mode_off_params = {
+    "file_names": {
+        "vanadium_absorb_file": "pearl_absorp_sphere_10mm_newinst2_long.nxs",
+        "tt88_grouping": "pearl_group_12_1_TT88.cal",
+        "tt70_grouping": "pearl_group_12_1_TT70.cal",
+        "tt35_grouping": "pearl_group_12_1_TT35.cal"
+    },
+
+    # This needs to be greater than the bank TOF cropping values or you will get data that divides to 0/inf
+    "monitor_lambda_crop_range": (0.03, 6.00),
+    "monitor_integration_range": (0.6, 5.0),
+    "raw_data_tof_cropping": (0, 19995),
+    "tof_cropping_ranges": [
+        (1500, 19900),  # Bank 1
+        (1500, 19900),  # Bank 2
+        (1500, 19900),  # Bank 3
+        (1500, 19900),  # Bank 4
+        (1500, 19900),  # Bank 5
+        (1500, 19900),  # Bank 6
+        (1500, 19900),  # Bank 7
+        (1500, 19900),  # Bank 8
+        (1500, 19900),  # Bank 9
+        (1500, 19900),  # Bank 10
+        (1500, 19900),  # Bank 11
+        (1500, 19900),  # Bank 12
+        (1500, 19900),  # Bank 13
+        (1500, 19900)   # Bank 14
     ]
+}
 
-script_params = {
-    "spline_coefficient": 60,
-    "bank_tof_crop_values": tof_cropping_ranges,
+long_mode_on_params = {
+    # This needs to be greater than the bank TOF cropping values or you will get data that divides to 0/inf
+    "monitor_lambda_crop_range": (5.9, 12.0),
+    "monitor_integration_range": (6, 10),
+    "raw_data_tof_cropping": (20295, 39995),
+    "tof_cropping_ranges": [
+        (20300, 39990),  # Bank 1
+        (20300, 39990),  # Bank 2
+        (20300, 39990),  # Bank 3
+        (20300, 39990),  # Bank 4
+        (20300, 39990),  # Bank 5
+        (20300, 39990),  # Bank 6
+        (20300, 39990),  # Bank 7
+        (20300, 39990),  # Bank 8
+        (20300, 39990),  # Bank 9
+        (20300, 39990),  # Bank 10
+        (20300, 39990),  # Bank 11
+        (20300, 39990),  # Bank 12
+        (20300, 39990),  # Bank 13
+        (20300, 39990)   # Bank 14
+    ]
 }
 
+
 variable_help = {
     "file_names": {
         "vanadium_absorb_file_name": "Takes the name of the calculated vanadium absorption corrections. This file "
@@ -45,7 +78,14 @@ variable_help = {
     }
 }
 
-variables = {
-    "file_names_dict": file_names,
-    "script_params_dict": script_params
-}
+
+def get_all_adv_variables(is_long_mode_on=False):
+    long_mode_params = long_mode_on_params if is_long_mode_on else long_mode_off_params
+    advanced_config_dict = {}
+    advanced_config_dict.update(general_params)
+    advanced_config_dict.update(long_mode_params)
+    return advanced_config_dict
+
+
+def get_long_mode_dict(is_long_mode):
+    return long_mode_on_params if is_long_mode else long_mode_off_params
diff --git a/scripts/Diffraction/isis_powder/pearl_routines/pearl_algs.py b/scripts/Diffraction/isis_powder/pearl_routines/pearl_algs.py
index c0b8a949ceb..b386a666c93 100644
--- a/scripts/Diffraction/isis_powder/pearl_routines/pearl_algs.py
+++ b/scripts/Diffraction/isis_powder/pearl_routines/pearl_algs.py
@@ -47,7 +47,7 @@ def get_run_details(run_number_string, inst_settings):
 
     splined_vanadium_name = _generate_splined_van_name(absorb_on=inst_settings.absorb_corrections,
                                                        long_mode=inst_settings.long_mode,
-                                                       vanadium_run_string=run_number_string)
+                                                       vanadium_run_string=vanadium_run_numbers)
 
     calibration_dir = inst_settings.calibration_dir
     cycle_calibration_dir = os.path.join(calibration_dir, label)
@@ -70,12 +70,10 @@ def get_run_details(run_number_string, inst_settings):
     return run_details
 
 
-def normalise_ws_current(ws_to_correct, monitor_ws, spline_coeff):
-    lambda_lower = 0.03  # TODO move these into config
-    lambda_upper = 6.00
+def normalise_ws_current(ws_to_correct, monitor_ws, spline_coeff, lambda_values, integration_range):
     processed_monitor_ws = mantid.ConvertUnits(InputWorkspace=monitor_ws, Target="Wavelength")
     processed_monitor_ws = mantid.CropWorkspace(InputWorkspace=processed_monitor_ws,
-                                                XMin=lambda_lower, XMax=lambda_upper)
+                                                XMin=lambda_values[0], XMax=lambda_values[-1])
     ex_regions = numpy.zeros((2, 4))
     ex_regions[:, 0] = [3.45, 3.7]
     ex_regions[:, 1] = [2.96, 3.2]
@@ -91,8 +89,10 @@ def normalise_ws_current(ws_to_correct, monitor_ws, spline_coeff):
 
     normalised_ws = mantid.ConvertUnits(InputWorkspace=ws_to_correct, Target="Wavelength", OutputWorkspace=ws_to_correct)
     normalised_ws = mantid.NormaliseToMonitor(InputWorkspace=normalised_ws, MonitorWorkspace=splined_monitor_ws,
-                                              IntegrationRangeMin=0.6, IntegrationRangeMax=5.0,
+                                              IntegrationRangeMin=integration_range[0],
+                                              IntegrationRangeMax=integration_range[-1],
                                               OutputWorkspace=normalised_ws)
+
     normalised_ws = mantid.ConvertUnits(InputWorkspace=normalised_ws, Target="TOF", OutputWorkspace=normalised_ws)
 
     common.remove_intermediate_workspace(processed_monitor_ws)
diff --git a/scripts/Diffraction/isis_powder/pearl_routines/pearl_output.py b/scripts/Diffraction/isis_powder/pearl_routines/pearl_output.py
index 64082e9b4a9..9b7e1bb4108 100644
--- a/scripts/Diffraction/isis_powder/pearl_routines/pearl_output.py
+++ b/scripts/Diffraction/isis_powder/pearl_routines/pearl_output.py
@@ -8,7 +8,7 @@ import isis_powder.routines.common as common
 
 
 def generate_and_save_focus_output(instrument, processed_spectra, run_details, perform_attenuation, focus_mode=None):
-    output_file_paths = instrument._generate_out_file_paths(run_details=run_details)
+    output_file_paths = instrument.generate_out_file_paths(run_details=run_details)
 
     if focus_mode == "all":
         processed_nexus_files = _focus_mode_all(output_file_paths, processed_spectra)
diff --git a/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py b/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py
index f135d7b811a..6e5901e8b5a 100644
--- a/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py
+++ b/scripts/Diffraction/isis_powder/pearl_routines/pearl_param_mapping.py
@@ -1,20 +1,26 @@
 from __future__ import (absolute_import, division, print_function)
 
 #                 Maps friendly user name -> script name
-attr_mapping = [("attenuation_file_name",   "attenuation_file_name"),
-                ("config_file",             "config_file_name"),
-                ("calibration_config_path", "cal_map_path"),
-                ("calibration_directory",   "calibration_dir"),
-                ("do_absorb_corrections",   "absorb_corrections"),
-                ("focus_mode",              "focus_mode"),
-                ("long_mode",               "long_mode"),
-                ("output_directory",        "output_dir"),
-                ("perform_attenuation",     "perform_atten"),
-                ("spline_coefficient",      "spline_coefficient"),
-                ("tt88_grouping",           "tt88_grouping"),
-                ("tt70_grouping",           "tt70_grouping"),
-                ("tt35_grouping",           "tt35_grouping"),
-                ("tt_mode",                 "tt_mode"),
-                ("user_name",               "user_name"),
-                ("vanadium_absorb_file",    "van_absorb_file"),
-                ("vanadium_normalisation",  "van_norm")]
+attr_mapping = [("attenuation_file_name",       "attenuation_file_name"),
+                ("config_file",                 "config_file_name"),
+                ("calibration_config_path",     "cal_map_path"),
+                ("calibration_directory",       "calibration_dir"),
+                ("do_absorb_corrections",       "absorb_corrections"),
+                ("focus_mode",                  "focus_mode"),
+                ("long_mode",                   "long_mode"),
+                ("monitor_lambda_crop_range",   "monitor_lambda"),
+                ("monitor_integration_range",   "monitor_integration_range"),
+                ("monitor_spectrum_number",     "monitor_spec_no"),
+                ("monitor_spline_coefficient",  "monitor_spline"),
+                ("output_directory",            "output_dir"),
+                ("perform_attenuation",         "perform_atten"),
+                ("raw_data_tof_cropping",       "raw_data_crop_vals"),
+                ("spline_coefficient",          "spline_coefficient"),
+                ("tof_cropping_ranges",         "tof_cropping_values"),
+                ("tt88_grouping",               "tt88_grouping"),
+                ("tt70_grouping",               "tt70_grouping"),
+                ("tt35_grouping",               "tt35_grouping"),
+                ("tt_mode",                     "tt_mode"),
+                ("user_name",                   "user_name"),
+                ("vanadium_absorb_file",        "van_absorb_file"),
+                ("vanadium_normalisation",      "van_norm")]
diff --git a/scripts/Diffraction/isis_powder/polaris.py b/scripts/Diffraction/isis_powder/polaris.py
index 71cc412dcfb..35067c711cf 100644
--- a/scripts/Diffraction/isis_powder/polaris.py
+++ b/scripts/Diffraction/isis_powder/polaris.py
@@ -12,15 +12,11 @@ from isis_powder.polaris_routines import polaris_advanced_config, polaris_algs,
 
 class Polaris(AbstractInst):
     def __init__(self, **kwargs):
-        expected_keys = ["user_name", "calibration_dir", "output_dir", "cal_mapping_file",
-                         "solid_angle_on", "chopper_on"]
         basic_config_dict = yaml_parser.open_yaml_file_as_dictionary(kwargs.get("config_file", None))
         self._inst_settings = InstrumentSettings.InstrumentSettings(
             attr_mapping=polaris_param_mapping.attr_mapping, adv_conf_dict=polaris_advanced_config.variables,
             basic_conf_dict=basic_config_dict, kwargs=kwargs)
 
-        self._inst_settings.check_expected_attributes_are_set(expected_attr_names=expected_keys)
-
         super(Polaris, self).__init__(user_name=self._inst_settings.user_name,
                                       calibration_dir=self._inst_settings.calibration_dir,
                                       output_dir=self._inst_settings.output_dir)
@@ -31,16 +27,21 @@ class Polaris(AbstractInst):
 
         self._ads_workaround = 0
 
-    def focus(self, run_number, input_mode, do_van_normalisation=True):
-        return self._focus(run_number=run_number, input_batching=input_mode, do_van_normalisation=do_van_normalisation)
+    def focus(self, run_number, input_mode, **kwargs):
+        self._inst_settings.update_attributes(kwargs=kwargs)
+        return self._focus(run_number=run_number, input_batching=input_mode,
+                           do_van_normalisation=self._inst_settings.do_van_normalisation)
+
+    def create_calibration_vanadium(self, run_in_range, **kwargs):
+        self._inst_settings.update_attributes(kwargs=kwargs)
 
-    def create_calibration_vanadium(self, run_in_range, do_absorb_corrections=True, gen_absorb_correction=False):
         run_details = self.get_run_details(run_number_string=int(run_in_range))
         run_details.run_number = run_details.vanadium_run_numbers
-        return self._create_calibration_vanadium(vanadium_runs=run_details.vanadium_run_numbers,
-                                                 empty_runs=run_details.empty_runs,
-                                                 do_absorb_corrections=do_absorb_corrections,
-                                                 gen_absorb_correction=gen_absorb_correction)
+
+        return self._create_calibration_vanadium(
+            vanadium_runs=run_details.vanadium_run_numbers, empty_runs=run_details.empty_runs,
+            do_absorb_corrections=self._inst_settings.do_absorb_corrections,
+            gen_absorb_correction=self._inst_settings.gen_absorb_corrections)
 
     def get_default_group_names(self):
         return self._calibration_grouping_names
@@ -66,21 +67,26 @@ class Polaris(AbstractInst):
         return run_details
 
     @staticmethod
-    def generate_inst_file_name(run_number):
+    def generate_input_file_name(run_number):
         if isinstance(run_number, list):
             updated_list = ["POL" + str(val) for val in run_number]
             return updated_list
         else:
             return "POL" + str(run_number)
 
+    def generate_output_file_name(self, run_number):
+        return self.generate_input_file_name(run_number=run_number)
+
+    @staticmethod
+    def can_auto_gen_vanadium_cal():
+        return True
+
     def normalise_ws(self, ws_to_correct, run_details=None):
         normalised_ws = mantid.NormaliseByCurrent(InputWorkspace=ws_to_correct, OutputWorkspace=ws_to_correct)
         return normalised_ws
 
     def apply_solid_angle_efficiency_corr(self, ws_to_correct, run_details):
-        solid_angle_on = bool(polaris_advanced_config.script_params["apply_solid_angle_corrections"])
-
-        if not solid_angle_on:
+        if not self._inst_settings.solid_angle_on:
             return ws_to_correct
 
         corrections = polaris_algs.generate_solid_angle_corrections(run_details=run_details, instrument=self)
@@ -91,8 +97,8 @@ class Polaris(AbstractInst):
         return ws_to_correct
 
     def spline_vanadium_ws(self, focused_vanadium_spectra, instrument_version=''):
-        masking_file_name = polaris_advanced_config.file_names["bragg_peaks_masking"]
-        spline_coeff = polaris_advanced_config.script_params["b_spline_coefficient"]
+        masking_file_name = self._inst_settings.masking_file_name
+        spline_coeff = self._inst_settings.spline_coeff
         masking_file_path = os.path.join(self.calibration_dir, masking_file_name)
         output = polaris_algs.process_vanadium_for_focusing(bank_spectra=focused_vanadium_spectra,
                                                             spline_number=spline_coeff,
@@ -104,7 +110,7 @@ class Polaris(AbstractInst):
 
     def output_focused_ws(self, processed_spectra, run_details, output_mode=None):
         d_spacing_group, tof_group = polaris_algs.split_into_tof_d_spacing_groups(processed_spectra)
-        output_paths = self._generate_out_file_paths(run_details=run_details)
+        output_paths = self.generate_out_file_paths(run_details=run_details)
 
         polaris_output.save_polaris_focused_data(d_spacing_group=d_spacing_group, tof_group=tof_group,
                                                  output_paths=output_paths, run_number=run_details.run_number)
@@ -112,5 +118,11 @@ class Polaris(AbstractInst):
         return d_spacing_group
 
     def crop_short_long_mode(self, ws_to_crop):
-        cropped_ws = common.crop_in_tof(ws_to_rebin=ws_to_crop, x_min=800, x_max=20000)
+        cropped_ws = common.crop_in_tof(ws_to_crop=ws_to_crop, x_min=800, x_max=20000)
         return cropped_ws
+
+    def crop_banks_to_user_tof(self, focused_banks):
+        return common.crop_banks_in_tof(focused_banks, self._inst_settings.tof_cropping_values)
+
+    def generate_auto_vanadium_calibration(self, run_details):
+        self.create_calibration_vanadium(run_in_range=run_details.run_number)
diff --git a/scripts/Diffraction/isis_powder/polaris_routines/basic_config.yaml b/scripts/Diffraction/isis_powder/polaris_routines/basic_config.yaml
index 493e0a6fed6..0b3a9f27deb 100644
--- a/scripts/Diffraction/isis_powder/polaris_routines/basic_config.yaml
+++ b/scripts/Diffraction/isis_powder/polaris_routines/basic_config.yaml
@@ -1,7 +1,6 @@
 # Basic setup
 
 user_name : "Mantid_David"
-apply_solid_angle : "True"
 
 calibration_directory : "C:\\Users\\ieb35538\\Documents\\Repos\\a_mantidBuild\\ExternalData\\Testing\\Data\\SystemTest\\POLARIS\\Calibration"
 output_directory : "C:\\Users\\ieb35538\\Documents\\Repos\\a_mantidBuild\\ExternalData\\Testing\\Data\\SystemTest\\POLARIS\\DataOut"
diff --git a/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py b/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py
index 36c605e200f..d9d49a5c889 100644
--- a/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py
+++ b/scripts/Diffraction/isis_powder/polaris_routines/polaris_advanced_config.py
@@ -1,13 +1,28 @@
 file_names = {
-    "bragg_peaks_masking": "VanaPeaks.dat"
+    "masking_file_name": "VanaPeaks.dat"
 }
 
 script_params = {
-    "apply_solid_angle_corrections": False,
-    "b_spline_coefficient": 100
+    "apply_solid_angle": False,
+    "spline_coefficient": 100
+}
+
+tof_cropping_ranges = [
+    (1500, 19900),  # Bank 1
+    (1500, 19900),  # Bank 2
+    (1500, 19900),  # Bank 3
+    (1500, 19900),  # Bank 4
+    (1500, 19900),  # Bank 5
+    ]
+
+variables = {
+    "file_names_dict": file_names,
+    "script_params": script_params,
+    "tof_cropping_ranges": tof_cropping_ranges
 }
 
 absorption_correction_params = {
+    # These are read directly by the generate absorb corrections functions instead of being parsed
     "cylinder_sample_height": 4.0,
     "cylinder_sample_radius": 0.4,
 
@@ -20,9 +35,3 @@ absorption_correction_params = {
     "number_of_wavelength_points": 100,
     "exponential_method": "Normal"
 }
-
-variables = {
-    "absorb_param_dict": absorption_correction_params,
-    "file_names_dict": file_names,
-    "script_params": script_params
-}
diff --git a/scripts/Diffraction/isis_powder/polaris_routines/polaris_param_mapping.py b/scripts/Diffraction/isis_powder/polaris_routines/polaris_param_mapping.py
index 544d8af461b..287cfb29c83 100644
--- a/scripts/Diffraction/isis_powder/polaris_routines/polaris_param_mapping.py
+++ b/scripts/Diffraction/isis_powder/polaris_routines/polaris_param_mapping.py
@@ -6,5 +6,11 @@ attr_mapping = [("apply_solid_angle",           "solid_angle_on"),
                 ("calibration_mapping_file",    "cal_mapping_file"),
                 ("chopper_on",                  "chopper_on"),
                 ("config_file",                 "config_file"),
-                ("output_directory", "output_dir"),
-                ("user_name", "user_name")]
+                ("do_van_normalisation",        "do_van_normalisation"),
+                ("do_absorb_corrections",       "do_absorb_corrections"),
+                ("generate_absorb_corrections", "gen_absorb_corrections"),
+                ("masking_file_name",           "masking_file_name"),
+                ("spline_coefficient",          "spline_coeff"),
+                ("tof_cropping_ranges",         "tof_cropping_values"),
+                ("output_directory",            "output_dir"),
+                ("user_name",                   "user_name")]
diff --git a/scripts/Diffraction/isis_powder/routines/InstrumentSettings.py b/scripts/Diffraction/isis_powder/routines/InstrumentSettings.py
index 0508f5d64ce..0bd2bb1e1e1 100644
--- a/scripts/Diffraction/isis_powder/routines/InstrumentSettings.py
+++ b/scripts/Diffraction/isis_powder/routines/InstrumentSettings.py
@@ -16,6 +16,10 @@ class InstrumentSettings(object):
     # Holds instance variables updated at runtime
     def __init__(self, attr_mapping, adv_conf_dict=None, basic_conf_dict=None, kwargs=None):
         self._attr_mapping = attr_mapping
+        self._adv_config_dict = adv_conf_dict
+        self._basic_conf_dict = basic_conf_dict
+        self._kwargs = kwargs
+
         self._unknown_keys_found = False
         self._parse_attributes(dict_to_parse=adv_conf_dict)
         self._parse_attributes(dict_to_parse=basic_conf_dict)
@@ -24,10 +28,14 @@ class InstrumentSettings(object):
             _print_known_keys(attr_mapping)
 
     def __getattr__(self, item):
-        raise AttributeError("The attribute with script name: '" + str(item) + "' was requested but was not set."
-                             " This means the list of expected parameters is incorrect/incomplete, the check was "
-                             "skipped or a script attribute name has changed."
-                             "\nPlease contact the development team.\n")
+        map_entry = next((attr_tuple for attr_tuple in self._attr_mapping if item == attr_tuple[-1]), None)
+        if map_entry:
+            # User forgot to enter the param:
+            raise AttributeError("The parameter with name: '" + str(map_entry[0]) + "' is required but was not set or "
+                                 "passed.\nPlease set this configuration option and try again")
+        else:
+            raise AttributeError("The attribute in the script with name " + str(item) + " is unknown to the mapping."
+                                 "\nPlease contact the development team.")
 
     def check_expected_attributes_are_set(self, expected_attr_names):
         for expected_attr in expected_attr_names:
@@ -39,21 +47,25 @@ class InstrumentSettings(object):
         expected_params_dict = dict(found_tuple_list)
         self._check_attribute_is_set(expected_params_dict)
 
-    def update_attributes_from_kwargs(self, kwargs):
+    def update_attributes(self, advanced_config=None, basic_config=None, kwargs=None, suppress_warnings=False):
+        self._adv_config_dict = advanced_config if advanced_config else self._adv_config_dict
+        self._basic_conf_dict = basic_config if basic_config else self._basic_conf_dict
+        self._kwargs = kwargs if kwargs else self._kwargs
+
         has_known_keys_already_been_printed = self._unknown_keys_found
-        self._parse_attributes(dict_to_parse=kwargs)
+        # Only update if one in hierarchy below it has been updated
+        if advanced_config:
+            self._parse_attributes(self._adv_config_dict, suppress_warnings=suppress_warnings)
+        if advanced_config or basic_config:
+            self._parse_attributes(self._basic_conf_dict,
+                                   suppress_warnings=(not bool(basic_config or suppress_warnings)))
+        if advanced_config or basic_config or kwargs:
+            self._parse_attributes(self._kwargs, suppress_warnings=(not bool(kwargs or suppress_warnings)))
+
         if not has_known_keys_already_been_printed and self._unknown_keys_found:
             _print_known_keys(self._attr_mapping)
 
-    def _check_attribute_is_set(self, expected_attributes_dict):
-        for config_name in expected_attributes_dict:
-            try:
-                getattr(self, expected_attributes_dict[config_name])
-            except AttributeError:
-                raise ValueError("Required parameter '" + str(config_name) +
-                                 "' was not set in any of the config files or passed as a parameter.\n")
-
-    def _parse_attributes(self, dict_to_parse):
+    def _parse_attributes(self, dict_to_parse, suppress_warnings=False):
         if not dict_to_parse:
             return
 
@@ -67,14 +79,19 @@ class InstrumentSettings(object):
             found_attribute = next((attr_tuple for attr_tuple in self._attr_mapping
                                     if config_key == attr_tuple[0]), None)
             if found_attribute:
-                # The first element of the attribute is the config name and the last element is the name scripts use
-                self._update_attribute(attr_name=found_attribute[-1], attr_val=dict_to_parse[found_attribute[0]])
-            else:
+                # The first element of the attribute is the config name and the last element is the friendly name
+                self._update_attribute(attr_name=found_attribute[-1], attr_val=dict_to_parse[found_attribute[0]],
+                                       friendly_name=found_attribute[0], suppress_warnings=suppress_warnings)
+            elif not suppress_warnings:
                 warnings.warn("Ignoring unknown configuration key: " + str(config_key))
                 self._unknown_keys_found = True
                 continue
 
-    def _update_attribute(self, attr_name, attr_val):
+    def _update_attribute(self, attr_name, attr_val, friendly_name, suppress_warnings):
+        # Does the attribute exist - has it changed and are we suppressing warnings
+        if hasattr(self, attr_name) and getattr(self, attr_name) != attr_val and not suppress_warnings:
+            warnings.warn("Replacing parameter: '" + str(friendly_name) + "' which was previously set to: '" +
+                          str(getattr(self, attr_name)) + "' with new value: '" + str(attr_val) + "'")
         setattr(self, attr_name, attr_val)
 
 
@@ -83,5 +100,5 @@ def _print_known_keys(master_mapping):
     print("----------------------------------")
     sorted_attributes = sorted(master_mapping, key=lambda tup: tup[0])
     for tuple_entry in sorted_attributes:
-        print (tuple_entry[0] + '\t', end="")
+        print (tuple_entry[0] + ', ', end="")
     print("\n----------------------------------")
diff --git a/scripts/Diffraction/isis_powder/routines/common.py b/scripts/Diffraction/isis_powder/routines/common.py
index 714a3058c2a..5d1b441ede2 100644
--- a/scripts/Diffraction/isis_powder/routines/common.py
+++ b/scripts/Diffraction/isis_powder/routines/common.py
@@ -15,13 +15,23 @@ def create_calibration_by_names(calibration_runs, startup_objects, grouping_file
                            out_grouping_file_name=grouping_file_name, instrument=startup_objects)
 
 
-def crop_in_tof(ws_to_rebin, x_min=None, x_max=None):
-    if isinstance(ws_to_rebin, list):
+def crop_banks_in_tof(bank_list, crop_values_list):
+    if len(bank_list) != len(crop_values_list):
+        raise RuntimeError("The number of TOF cropping values does not match the number of banks for this instrument")
+    output_list = []
+    for spectra, cropping_values in zip(bank_list, crop_values_list):
+        output_list.append(crop_in_tof(ws_to_crop=spectra, x_min=cropping_values[0], x_max=cropping_values[-1]))
+
+    return output_list
+
+
+def crop_in_tof(ws_to_crop, x_min=None, x_max=None):
+    if isinstance(ws_to_crop, list):
         cropped_ws = []
-        for ws in ws_to_rebin:
+        for ws in ws_to_crop:
             cropped_ws.append(_crop_single_ws_in_tof(ws, x_max=x_max, x_min=x_min))
     else:
-        cropped_ws = _crop_single_ws_in_tof(ws_to_rebin, x_max=x_max, x_min=x_min)
+        cropped_ws = _crop_single_ws_in_tof(ws_to_crop, x_max=x_max, x_min=x_min)
 
     return cropped_ws
 
@@ -53,7 +63,7 @@ def extract_ws_spectra(ws_to_split):
 
 def extract_and_crop_spectra(focused_ws, instrument):
     ws_spectra = extract_ws_spectra(ws_to_split=focused_ws)
-    ws_spectra = instrument.crop_short_long_mode(ws_to_crop=ws_spectra)
+    ws_spectra = instrument.crop_banks_to_user_tof(ws_spectra)
     return ws_spectra
 
 
@@ -182,7 +192,7 @@ def _load_list_of_files(run_numbers_list, instrument):
     _check_load_range(list_of_runs_to_load=run_numbers_list)
 
     for run_number in run_numbers_list:
-        file_name = instrument.generate_inst_file_name(run_number=run_number)
+        file_name = instrument.generate_input_file_name(run_number=run_number)
         read_ws = mantid.Load(Filename=file_name)
         ws_name = generate_unique_workspace_name(original_name=file_name)
         read_ws_list.append(mantid.RenameWorkspace(InputWorkspace=read_ws, OutputWorkspace=ws_name))
diff --git a/scripts/Diffraction/isis_powder/routines/focus.py b/scripts/Diffraction/isis_powder/routines/focus.py
index 596ea73ba8e..30294eeb269 100644
--- a/scripts/Diffraction/isis_powder/routines/focus.py
+++ b/scripts/Diffraction/isis_powder/routines/focus.py
@@ -5,6 +5,7 @@ import mantid.simpleapi as mantid
 import isis_powder.routines.common as common
 from isis_powder.routines.common_enums import InputBatchingEnum
 import os
+import warnings
 
 
 def focus(run_number, instrument, input_batching, perform_vanadium_norm=True):
@@ -20,9 +21,14 @@ def _focus_one_ws(ws, run_number, instrument, perform_vanadium_norm):
 
     # Check the necessary splined vanadium file has been created
     if not os.path.isfile(run_details.splined_vanadium_file_path):
-        raise ValueError("Processed vanadium runs not found at this path: "
-                         + str(run_details.splined_vanadium_file_path) +
-                         " \nHave you created a vanadium calibration with these settings yet?\n")
+        if instrument.can_auto_gen_vanadium_cal():
+            warnings.warn("\nAttempting to automatically generate vanadium calibration at this path: "
+                          + str(run_details.splined_vanadium_file_path) + " for these settings.\n")
+            instrument.generate_auto_vanadium_calibration(run_details=run_details)
+        else:
+            ValueError("Processed vanadium runs not found at this path: "
+                       + str(run_details.splined_vanadium_file_path) +
+                       " \nHave you created a vanadium calibration with these settings yet?\n")
 
     # Compensate for empty sample if specified
     input_workspace = common.subtract_sample_empty(ws_to_correct=ws, instrument=instrument,
diff --git a/scripts/test/ISIS_Powder_AbstractInstTest.py b/scripts/test/ISIS_Powder_AbstractInstTest.py
index 44d4379ac60..27911d7f871 100644
--- a/scripts/test/ISIS_Powder_AbstractInstTest.py
+++ b/scripts/test/ISIS_Powder_AbstractInstTest.py
@@ -22,7 +22,7 @@ class isis_powder_AbstractInstTest(unittest.TestCase):
     def test_generate_full_input_path(self):
         inst = self._get_abstract_inst_all_specified()
         run_number = 12345
-        inst_file_name = inst.generate_inst_file_name(run_number)
+        inst_file_name = inst.generate_input_file_name(run_number)
         input_path = "test"
         reference_output = os.path.join(input_path, (inst_file_name + self.default_ext))
 
-- 
GitLab