diff --git a/Testing/SystemTests/tests/analysis/ISIS_LETReduction.py b/Testing/SystemTests/tests/analysis/ISIS_LETReduction.py index 4127e682ade24ee907d32007b5a8f53ba4c27dd7..ea33a9ac66c3077994a558cb77e09736d1a7511c 100644 --- a/Testing/SystemTests/tests/analysis/ISIS_LETReduction.py +++ b/Testing/SystemTests/tests/analysis/ISIS_LETReduction.py @@ -12,10 +12,13 @@ try: import reduce_vars as web_var except ImportError: web_var = None - -# +try: + import mantidplot as mpl +except: + mpl = None +# def find_binning_range(energy,ebin): """ function finds the binning range used in multirep mode for merlin ls=11.8,lm2=10. mult=2.8868 dt_DAE=1 @@ -95,7 +98,7 @@ class ReduceLET_OneRep(ReductionWrapper): prop['detector_van_range']=[0.5,200] prop['load_monitors_with_workspace']=True return prop - # + # @iliad def reduce(self,input_file=None,output_directory=None): @@ -223,6 +226,51 @@ class ReduceLET_MultiRep2015(ReductionWrapper): def __init__(self,web_var=None): """Sets properties defaults for the instrument with Name""" ReductionWrapper.__init__(self,'LET',web_var) + Mt = MethodType(self.do_preprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_preprocessing',Mt) + Mt = MethodType(self.do_postprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_postprocessing',Mt) + # + + def do_preprocessing(self,reducer,ws): + """ Custom function, applied to each run or every workspace, the run is divided to + in multirep mode + Applied after diagnostics but before any further reduction is invoked. + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + to preprocess + + By default, does nothing. + Add code to do custom preprocessing. + Must return pointer to the preprocessed workspace + """ + return ws + # + + def do_postprocessing(self,reducer,ws): + """ Custom function, applied to each reduced run or every reduced workspace, + the run is divided into, in multirep mode. + Applied after reduction is completed but before saving the result. + + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + after reduction to postprocess + + + By default, does nothing. + Add code to do custom postprocessing. + Must return pointer to the postprocessed workspace. + + The postprocessed workspace should be consistent with selected save method. + (E.g. if you decide to convert workspace units to wavelength, you can not save result as nxspe) + """ + return ws def set_custom_output_filename(self): """Define custom name of output files if standard one is not satisfactory @@ -249,7 +297,65 @@ class ReduceLET_MultiRep2015(ReductionWrapper): #return lambda : custom_name(self.reducer.prop_man) # use this method to use standard file name generating function return None + # + + def eval_absorption_corrections(self,test_ws=None): + """ The method to evaluate the speed and efficiency of the absorption corrections procedure, + before applying your corrections to the whole workspace and all sample runs. + + The absorption correction procedure invoked with excessive accuracy can run for too + long providing no real improvements in accuracy. This is why it is recommended to + run this procedure evaluating absorption on selected detectors and + deploy the corrections to the whole runs only after achieving satisfactory accuracy + and execution time. + + The procedure evaluate and prints the expected time to run the absorption corrections + on the whole run. + + Input: + If provided, the pointer or the name of the workspace available in analysis data service. + If it is not, the workspace is taken from PropertyManager.sample_run property + + Usage: + Reduce single run and uncomment this method in the __main__ area to evaluate + absorption corrections. + Change absorption corrections parameters below to achieve best speed and + acceptable accuracy + """ + + # Gain access to the property manager: + propman = rd.reducer.prop_man + # Set up Sample as one of: + # 1) Cylinder([Chem_formula],[Height,Radius]) + # 2) FlatPlate([Chem_formula],[Height,Width,Thick]) + # 3) HollowCylinder([Chem_formula],[Height,InnerRadius,OuterRadius]) + # 4) Sphere([[Chem_formula],Radius) + # The units are in cm + propman.correct_absorption_on = Cylinder('Fe',[10,2]) # Will be taken from def_advanced_properties + # prop['correct_absorption_on'] = if not defined here + # + # Use Monte-Carlo integration. Take sparse energy points and a few integration attempts + # to increase initial speed. Increase these numbers to achieve better accuracy. + propman.abs_corr_info = {'EventsPerPoint':3000}#,'NumberOfWavelengthPoints':30} + # See MonteCarloAbsorption for all possible properties description and possibility to define + # a sparse instrument for speed. + # + # Gain access to the workspace. The workspace should contain Ei log, containing incident energy + # (or be reduced) + if test_ws is None: + test_ws = PropertyManager.sample_run.get_workspace() + # Define spectra list to test absorption on + check_spectra = [1,200] + # Evaluate corrections on the selected spectra of the workspace and the time to obtain + # the corrections on the whole workspace. + corrections,time_to_correct_abs = self.evaluate_abs_corrections(test_ws,check_spectra) + # When accuracy and speed of the corrections is satisfactory, copy chosen abs_corr_info + # properties from above to the advanced_porperties area to run in reduction. + if mpl: + mpl.plotSpectrum(corrections,range(0,len(check_spectra))) + # + return corrections #---------------------------------------------------------------------------------------------------------------------- @@ -297,10 +403,5 @@ if __name__=="__main__": # usual way to go is to reduce workspace and save it internally rd.run_reduction() - -#### Validate reduction result against known result, obtained earlier ### - #rez,mess=rd.validate_result() - #if not rez: - # raise RuntimeError("validation failed with error: {0}".format(mess)) - #else: - # print "ALL Fine" +###### Test absorption corrections to find optimal settings for corrections algorithm +# corr = rd.eval_absorption_corrections() diff --git a/Testing/SystemTests/tests/analysis/ISIS_MAPS_DGSReduction.py b/Testing/SystemTests/tests/analysis/ISIS_MAPS_DGSReduction.py index 0f7c1eea5d5eee747a105d0ab81316130a92dc48..799b7744141568205c3f301923e0b83c2c4c3c1e 100644 --- a/Testing/SystemTests/tests/analysis/ISIS_MAPS_DGSReduction.py +++ b/Testing/SystemTests/tests/analysis/ISIS_MAPS_DGSReduction.py @@ -11,6 +11,10 @@ try: import reduce_vars as web_var except: web_var = None +try: + import mantidplot as mpl +except: + mpl = None class ReduceMAPS(ReductionWrapper): @@ -73,8 +77,52 @@ class ReduceMAPS(ReductionWrapper): def __init__(self,web_var=None): """ sets properties defaults for the instrument with Name""" ReductionWrapper.__init__(self,'MAP',web_var) + Mt = MethodType(self.do_preprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_preprocessing',Mt) + Mt = MethodType(self.do_postprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_postprocessing',Mt) + # + + def do_preprocessing(self,reducer,ws): + """ Custom function, applied to each run or every workspace, the run is divided to + in multirep mode + Applied after diagnostics but before any further reduction is invoked. + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + to preprocess + + By default, does nothing. + Add code to do custom preprocessing. + Must return pointer to the preprocessed workspace + """ + return ws # + def do_postprocessing(self,reducer,ws): + """ Custom function, applied to each reduced run or every reduced workspace, + the run is divided into, in multirep mode. + Applied after reduction is completed but before saving the result. + + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + after reduction to postprocess + + + By default, does nothing. + Add code to do custom postprocessing. + Must return pointer to the postprocessed workspace. + + The postprocessed workspace should be consistent with selected save method. + (E.g. if you decide to convert workspace units to wavelength, you can not save result as nxspe) + """ + return ws + def set_custom_output_filename(self): """ define custom name of output files if standard one is not satisfactory In addition to that, example of accessing reduction properties @@ -100,7 +148,65 @@ class ReduceMAPS(ReductionWrapper): #return lambda : custom_name(self.reducer.prop_man) # use this method to use standard file name generating function return None + # + + def eval_absorption_corrections(self,test_ws=None): + """ The method to evaluate the speed and efficiency of the absorption corrections procedure, + before applying your corrections to the whole workspace and all sample runs. + + The absorption correction procedure invoked with excessive accuracy can run for too + long providing no real improvements in accuracy. This is why it is recommended to + run this procedure evaluating absorption on selected detectors and + deploy the corrections to the whole runs only after achieving satisfactory accuracy + and execution time. + + The procedure evaluate and prints the expected time to run the absorption corrections + on the whole run. + Input: + If provided, the pointer or the name of the workspace available in analysis data service. + If it is not, the workspace is taken from PropertyManager.sample_run property + + Usage: + Reduce single run and uncomment this method in the __main__ area to evaluate + absorption corrections. + + Change absorption corrections parameters below to achieve best speed and + acceptable accuracy + """ + + # Gain access to the property manager: + propman = rd.reducer.prop_man + # Set up Sample as one of: + # 1) Cylinder([Chem_formula],[Height,Radius]) + # 2) FlatPlate([Chem_formula],[Height,Width,Thick]) + # 3) HollowCylinder([Chem_formula],[Height,InnerRadius,OuterRadius]) + # 4) Sphere([[Chem_formula],Radius) + # The units are in cm + propman.correct_absorption_on = Cylinder('Fe',[10,2]) # Will be taken from def_advanced_properties + # prop['correct_absorption_on'] = if not defined here + # + # Use Monte-Carlo integration. Take sparse energy points and a few integration attempts + # to increase initial speed. Increase these numbers to achieve better accuracy. + propman.abs_corr_info = {'EventsPerPoint':3000}#,'NumberOfWavelengthPoints':30} + # See MonteCarloAbsorption for all possible properties description and possibility to define + # a sparse instrument for speed. + # + # Gain access to the workspace. The workspace should contain Ei log, containing incident energy + # (or be reduced) + if test_ws is None: + test_ws = PropertyManager.sample_run.get_workspace() + # Define spectra list to test absorption on + check_spectra = [1,200] + # Evaluate corrections on the selected spectra of the workspace and the time to obtain + # the corrections on the whole workspace. + corrections,time_to_correct_abs = self.evaluate_abs_corrections(test_ws,check_spectra) + # When accuracy and speed of the corrections is satisfactory, copy chosen abs_corr_info + # properties from above to the advanced_porperties area to run in reduction. + if mpl: + mpl.plotSpectrum(corrections,range(0,len(check_spectra))) + # + return corrections #---------------------------------------------------------------------------------------------------------------------- if __name__ == "__main__": @@ -151,10 +257,5 @@ if __name__ == "__main__": # usual way to go is to reduce workspace and save it internally rd.run_reduction() - -#### Validate reduction result against known result, obtained earlier ### - #rez,mess=rd.validate_result() - #if not rez: - # raise RuntimeError("validation failed with error: {0}".format(mess)) - #else: - # print "ALL Fine" +###### Test absorption corrections to find optimal settings for corrections algorithm +# corr = rd.eval_absorption_corrections() diff --git a/Testing/SystemTests/tests/analysis/ISIS_MERLINReduction.py b/Testing/SystemTests/tests/analysis/ISIS_MERLINReduction.py index a33969c7547e86296754b7ad026380ba7fb97373..e3c79a0ffbb440db361412c17733c53b6ac8b5d3 100644 --- a/Testing/SystemTests/tests/analysis/ISIS_MERLINReduction.py +++ b/Testing/SystemTests/tests/analysis/ISIS_MERLINReduction.py @@ -11,6 +11,10 @@ try: import reduce_vars as web_var except: web_var = None +try: + import mantidplot as mpl +except: + mpl = None class ReduceMERLIN(ReductionWrapper): @@ -55,33 +59,161 @@ class ReduceMERLIN(ReductionWrapper): outWS = ReductionWrapper.reduce(self,input_file,output_directory) #SaveNexus(ws,Filename = 'MARNewReduction.nxs') return outWS + # + + def do_preprocessing(self,reducer,ws): + """ Custom function, applied to each run or every workspace, the run is divided to + in multirep mode + Applied after diagnostics but before any further reduction is invoked. + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + to preprocess + + By default, does nothing. + Add code to do custom preprocessing. + Must return pointer to the preprocessed workspace + """ + return ws + # + + def do_postprocessing(self,reducer,ws): + """ Custom function, applied to each reduced run or every reduced workspace, + the run is divided into, in multirep mode. + Applied after reduction is completed but before saving the result. + + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + after reduction to postprocess + + + By default, does nothing. + Add code to do custom postprocessing. + Must return pointer to the postprocessed workspace. + + The postprocessed workspace should be consistent with selected save method. + (E.g. if you decide to convert workspace units to wavelength, you can not save result as nxspe) + """ + return ws + # + + def eval_absorption_corrections(self,test_ws=None): + """ The method to evaluate the speed and efficiency of the absorption corrections procedure, + before applying your corrections to the whole workspace and all sample runs. + + The absorption correction procedure invoked with excessive accuracy can run for too + long providing no real improvements in accuracy. This is why it is recommended to + run this procedure evaluating absorption on selected detectors and + deploy the corrections to the whole runs only after achieving satisfactory accuracy + and execution time. + + The procedure evaluate and prints the expected time to run the absorption corrections + on the whole run. + + Input: + If provided, the pointer or the name of the workspace available in analysis data service. + If it is not, the workspace is taken from PropertyManager.sample_run property + + Usage: + Reduce single run and uncomment this method in the __main__ area to evaluate + absorption corrections. + + Change absorption corrections parameters below to achieve best speed and + acceptable accuracy + """ + + # Gain access to the property manager: + propman = rd.reducer.prop_man + # Set up Sample as one of: + # 1) Cylinder([Chem_formula],[Height,Radius]) + # 2) FlatPlate([Chem_formula],[Height,Width,Thick]) + # 3) HollowCylinder([Chem_formula],[Height,InnerRadius,OuterRadius]) + # 4) Sphere([[Chem_formula],Radius) + # The units are in cm + propman.correct_absorption_on = Cylinder('Fe',[10,2]) # Will be taken from def_advanced_properties + # prop['correct_absorption_on'] = if not defined here + # + # Use Monte-Carlo integration. Take sparse energy points and a few integration attempts + # to increase initial speed. Increase these numbers to achieve better accuracy. + propman.abs_corr_info = {'EventsPerPoint':3000}#,'NumberOfWavelengthPoints':30} + # See MonteCarloAbsorption for all possible properties description and possibility to define + # a sparse instrument for speed. + # + # Gain access to the workspace. The workspace should contain Ei log, containing incident energy + # (or be reduced) + if test_ws is None: + test_ws = PropertyManager.sample_run.get_workspace() + # Define spectra list to test absorption on + check_spectra = [1,200] + # Evaluate corrections on the selected spectra of the workspace and the time to obtain + # the corrections on the whole workspace. + corrections,time_to_correct_abs = self.evaluate_abs_corrections(test_ws,check_spectra) + # When accuracy and speed of the corrections is satisfactory, copy chosen abs_corr_info + # properties from above to the advanced_porperties area to run in reduction. + if mpl: + mpl.plotSpectrum(corrections,range(0,len(check_spectra))) + # + return corrections def __init__(self,web_var=None): """ sets properties defaults for the instrument with Name""" ReductionWrapper.__init__(self,'MER',web_var) + Mt = MethodType(self.do_preprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_preprocessing',Mt) + Mt = MethodType(self.do_postprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_postprocessing',Mt) #---------------------------------------------------------------------------------------------------------------------- if __name__=="__main__": - #import os - #os.environ["PATH"] = r"c:/Mantid/Code/builds/br_master/bin/Release;"+os.environ["PATH"] - - #maps_dir = 'd:/Data/MantidSystemTests/Data' - #data_dir ='d:/Data/Mantid_Testing/14_11_27' - #ref_data_dir = 'd:/Data/MantidSystemTests/SystemTests/AnalysisTests/ReferenceResults' + #maps_dir = r'd:\Data\MantidDevArea\Datastore\DataCopies\Testing\Data\SystemTest' + #data_dir = r'd:\Data\Mantid_Testing\15_03_01' + #ref_data_dir = r'd:\Data\MantidDevArea\Datastore\DataCopies\Testing\SystemTests\tests\analysis\reference' #config.setDataSearchDirs('{0};{1};{2}'.format(data_dir,maps_dir,ref_data_dir)) #config.appendDataSearchDir('d:/Data/Mantid_GIT/Test/AutoTestData') #config['defaultsave.directory'] = data_dir # folder to save resulting spe/nxspe files. Defaults are in - # execute stuff from Mantid + # execute stuff from Mantid rd = ReduceMERLIN() + #rd = ReduceLET_OneRep() rd.def_advanced_properties() rd.def_main_properties() - #using_web_data = False - #if not using_web_data: - # run_dir=os.path.dirname(os.path.realpath(__file__)) - # file = os.path.join(run_dir,'reduce_vars.py') - # rd.export_changed_values(file) +#### uncomment rows below to generate web variables and save then to transfer to ### + ## web services. + #run_dir = os.path.dirname(os.path.realpath(__file__)) + #file = os.path.join(run_dir,'reduce_vars.py') + #rd.save_web_variables(file) - rd.reduce() +#### Set up time interval (sec) for reducer to check for input data file. #### + # If this file is not present and this value is 0,reduction fails + # if this value >0 the reduction wait until file appears on the data + # search path checking after time specified below. + rd.wait_for_file = 0 # waiting time interval + +####get reduction parameters from properties above, override what you want locally ### + # and run reduction. Overriding would have form: + # rd.reducer.property_name (from the dictionary above) = new value e.g. + # rd.reducer.energy_bins = [-40,2,40] + # or + ## rd.reducer.sum_runs = False + +###### Run reduction over all run numbers or files assigned to ###### + # sample_run variable + + # return output workspace only if you are going to do + # something with it here. Running range of runs will return the array + # of workspace pointers. + #red_ws = rd.run_reduction() + # usual way to go is to reduce workspace and save it internally + rd.run_reduction() + +###### Test absorption corrections to find optimal settings for corrections algorithm +# corr = rd.eval_absorption_corrections() + #import os + #os.environ["PATH"] = r"c:/Mantid/Code/builds/br_master/bin/Release;"+os.environ["PATH"] diff --git a/Testing/SystemTests/tests/analysis/ISIS_MariReduction.py b/Testing/SystemTests/tests/analysis/ISIS_MariReduction.py index 03d5c1cf79f2e7c825d5df296914db9dd014be95..61a86f5f8267625b8e25a037416c54434bea5796 100644 --- a/Testing/SystemTests/tests/analysis/ISIS_MariReduction.py +++ b/Testing/SystemTests/tests/analysis/ISIS_MariReduction.py @@ -13,6 +13,10 @@ try: import reduce_vars as web_var except: web_var = None +try: + import mantidplot as mpl +except: + mpl = None class ReduceMARIFromFile(ReductionWrapper): @@ -83,10 +87,55 @@ class ReduceMARIFromFile(ReductionWrapper): #return lambda : custom_name(self.reducer.prop_man) # use this method to use standard file name generating function return None + # + + def do_preprocessing(self,reducer,ws): + """ Custom function, applied to each run or every workspace, the run is divided to + in multirep mode + Applied after diagnostics but before any further reduction is invoked. + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + to preprocess + + By default, does nothing. + Add code to do custom preprocessing. + Must return pointer to the preprocessed workspace + """ + return ws + # + + def do_postprocessing(self,reducer,ws): + """ Custom function, applied to each reduced run or every reduced workspace, + the run is divided into, in multirep mode. + Applied after reduction is completed but before saving the result. + + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + after reduction to postprocess + + + By default, does nothing. + Add code to do custom postprocessing. + Must return pointer to the postprocessed workspace. + + The postprocessed workspace should be consistent with selected save method. + (E.g. if you decide to convert workspace units to wavelength, you can not save result as nxspe) + """ + return ws def __init__(self,web_var_val=None): """ sets properties defaults for the instrument with Name""" ReductionWrapper.__init__(self,'MAR',web_var_val) + Mt = MethodType(self.do_preprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_preprocessing',Mt) + Mt = MethodType(self.do_postprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_postprocessing',Mt) #-------------------------------------------------------------------------------------------------# #-------------------------------------------------------------------------------------------------# #-------------------------------------------------------------------------------------------------# @@ -157,10 +206,55 @@ class ReduceMARIFromWorkspace(ReductionWrapper): ws = ReductionWrapper.reduce(self,input_file,output_directory) #SaveNexus(ws,Filename = 'MARNewReduction.nxs') return ws + # + + def do_preprocessing(self,reducer,ws): + """ Custom function, applied to each run or every workspace, the run is divided to + in multirep mode + Applied after diagnostics but before any further reduction is invoked. + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + to preprocess + + By default, does nothing. + Add code to do custom preprocessing. + Must return pointer to the preprocessed workspace + """ + return ws + # + + def do_postprocessing(self,reducer,ws): + """ Custom function, applied to each reduced run or every reduced workspace, + the run is divided into, in multirep mode. + Applied after reduction is completed but before saving the result. + + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + after reduction to postprocess + + + By default, does nothing. + Add code to do custom postprocessing. + Must return pointer to the postprocessed workspace. + + The postprocessed workspace should be consistent with selected save method. + (E.g. if you decide to convert workspace units to wavelength, you can not save result as nxspe) + """ + return ws def __init__(self,web_var_val=None): """ sets properties defaults for the instrument with Name""" ReductionWrapper.__init__(self,'MAR',web_var_val) + Mt = MethodType(self.do_preprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_preprocessing',Mt) + Mt = MethodType(self.do_postprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_postprocessing',Mt) #---------------------------------------------------------------------------------------------------------------------- @@ -212,10 +306,55 @@ class ReduceMARIMon2Norm(ReductionWrapper): outWS = ReductionWrapper.reduce(self,input_file,output_directory) #SaveNexus(ws,Filename = 'MARNewReduction.nxs') return outWS + # + + def do_preprocessing(self,reducer,ws): + """ Custom function, applied to each run or every workspace, the run is divided to + in multirep mode + Applied after diagnostics but before any further reduction is invoked. + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + to preprocess + + By default, does nothing. + Add code to do custom preprocessing. + Must return pointer to the preprocessed workspace + """ + return ws + # + + def do_postprocessing(self,reducer,ws): + """ Custom function, applied to each reduced run or every reduced workspace, + the run is divided into, in multirep mode. + Applied after reduction is completed but before saving the result. + + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + after reduction to postprocess + + + By default, does nothing. + Add code to do custom postprocessing. + Must return pointer to the postprocessed workspace. + + The postprocessed workspace should be consistent with selected save method. + (E.g. if you decide to convert workspace units to wavelength, you can not save result as nxspe) + """ + return ws def __init__(self,web_var_val=None): """ sets properties defaults for the instrument with Name""" ReductionWrapper.__init__(self,'MAR',web_var_val) + Mt = MethodType(self.do_preprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_preprocessing',Mt) + Mt = MethodType(self.do_postprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_postprocessing',Mt) #---------------------------------------------------------------------------------------------------------------------- @@ -260,10 +399,55 @@ class MARIReductionSum(ReductionWrapper): ws = ReductionWrapper.reduce(self,input_file,output_directory) #SaveNexus(ws,Filename = 'MARNewReduction.nxs') return ws + # + + def do_preprocessing(self,reducer,ws): + """ Custom function, applied to each run or every workspace, the run is divided to + in multirep mode + Applied after diagnostics but before any further reduction is invoked. + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + to preprocess + + By default, does nothing. + Add code to do custom preprocessing. + Must return pointer to the preprocessed workspace + """ + return ws + # + + def do_postprocessing(self,reducer,ws): + """ Custom function, applied to each reduced run or every reduced workspace, + the run is divided into, in multirep mode. + Applied after reduction is completed but before saving the result. + + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + after reduction to postprocess + + + By default, does nothing. + Add code to do custom postprocessing. + Must return pointer to the postprocessed workspace. + + The postprocessed workspace should be consistent with selected save method. + (E.g. if you decide to convert workspace units to wavelength, you can not save result as nxspe) + """ + return ws def __init__(self,web_var_val=None): """ sets properties defaults for the instrument with Name""" ReductionWrapper.__init__(self,'MAR',web_var_val) + Mt = MethodType(self.do_preprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_preprocessing',Mt) + Mt = MethodType(self.do_postprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_postprocessing',Mt) #---------------------------------------------------------------------------------------------------------------------- @@ -317,10 +501,55 @@ class ReduceMARIMonitorsSeparate(ReductionWrapper): outWS = ReductionWrapper.reduce(self,input_file,output_directory) #SaveNexus(outWS,Filename = 'MARNewReduction.nxs') return outWS + # + + def do_preprocessing(self,reducer,ws): + """ Custom function, applied to each run or every workspace, the run is divided to + in multirep mode + Applied after diagnostics but before any further reduction is invoked. + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + to preprocess + + By default, does nothing. + Add code to do custom preprocessing. + Must return pointer to the preprocessed workspace + """ + return ws + # + + def do_postprocessing(self,reducer,ws): + """ Custom function, applied to each reduced run or every reduced workspace, + the run is divided into, in multirep mode. + Applied after reduction is completed but before saving the result. + + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + after reduction to postprocess + + + By default, does nothing. + Add code to do custom postprocessing. + Must return pointer to the postprocessed workspace. + + The postprocessed workspace should be consistent with selected save method. + (E.g. if you decide to convert workspace units to wavelength, you can not save result as nxspe) + """ + return ws def __init__(self,web_var_val=None): """ sets properties defaults for the instrument with Name""" ReductionWrapper.__init__(self,'MAR',web_var_val) + Mt = MethodType(self.do_preprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_preprocessing',Mt) + Mt = MethodType(self.do_postprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_postprocessing',Mt) class ReduceMARIAutoEi(ReductionWrapper): @@ -402,10 +631,113 @@ class ReduceMARIAutoEi(ReductionWrapper): #return lambda : custom_name(self.reducer.prop_man) # use this method to use standard file name generating function return None + # + + def do_preprocessing(self,reducer,ws): + """ Custom function, applied to each run or every workspace, the run is divided to + in multirep mode + Applied after diagnostics but before any further reduction is invoked. + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + to preprocess + + By default, does nothing. + Add code to do custom preprocessing. + Must return pointer to the preprocessed workspace + """ + return ws + # + + def do_postprocessing(self,reducer,ws): + """ Custom function, applied to each reduced run or every reduced workspace, + the run is divided into, in multirep mode. + Applied after reduction is completed but before saving the result. + + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + after reduction to postprocess + + + By default, does nothing. + Add code to do custom postprocessing. + Must return pointer to the postprocessed workspace. + + The postprocessed workspace should be consistent with selected save method. + (E.g. if you decide to convert workspace units to wavelength, you can not save result as nxspe) + """ + return ws + + def eval_absorption_corrections(self,test_ws=None): + """ The method to evaluate the speed and efficiency of the absorption corrections procedure, + before applying your corrections to the whole workspace and all sample runs. + + The absorption correction procedure invoked with excessive accuracy can run for too + long providing no real improvements in accuracy. This is why it is recommended to + run this procedure evaluating absorption on selected detectors and + deploy the corrections to the whole runs only after achieving satisfactory accuracy + and execution time. + + The procedure evaluate and prints the expected time to run the absorption corrections + on the whole run. + + Input: + If provided, the pointer or the name of the workspace available in analysis data service. + If it is not, the workspace is taken from PropertyManager.sample_run property + + Usage: + Reduce single run and uncomment this method in the __main__ area to evaluate + absorption corrections. + + Change absorption corrections parameters below to achieve best speed and + acceptable accuracy + """ + + # Gain access to the property manager: + propman = rd.reducer.prop_man + # Set up Sample as one of: + # 1) Cylinder([Chem_formula],[Height,Radius]) + # 2) FlatPlate([Chem_formula],[Height,Width,Thick]) + # 3) HollowCylinder([Chem_formula],[Height,InnerRadius,OuterRadius]) + # 4) Sphere([[Chem_formula],Radius) + # The units are in cm + propman.correct_absorption_on = Cylinder('Fe',[10,2]) # Will be taken from def_advanced_properties + # prop['correct_absorption_on'] = if not defined here + # + # Use Monte-Carlo integration. Take sparse energy points and a few integration attempts + # to increase initial speed. Increase these numbers to achieve better accuracy. + propman.abs_corr_info = {'EventsPerPoint':3000}#,'NumberOfWavelengthPoints':30} + # See MonteCarloAbsorption for all possible properties description and possibility to define + # a sparse instrument for speed. + # + # Gain access to the workspace. The workspace should contain Ei log, containing incident energy + # (or be reduced) + if test_ws is None: + test_ws = PropertyManager.sample_run.get_workspace() + # Define spectra list to test absorption on + check_spectra = [1,200] + # Evaluate corrections on the selected spectra of the workspace and the time to obtain + # the corrections on the whole workspace. + corrections,time_to_correct_abs = self.evaluate_abs_corrections(test_ws,check_spectra) + # When accuracy and speed of the corrections is satisfactory, copy chosen abs_corr_info + # properties from above to the advanced_porperties area to run in reduction. + if mpl: + mpl.plotSpectrum(corrections,range(0,len(check_spectra))) + # + return corrections def __init__(self,web_var_val=None): """ sets properties defaults for the instrument with Name""" ReductionWrapper.__init__(self,'MAR',web_var_val) + Mt = MethodType(self.do_preprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_preprocessing',Mt) + Mt = MethodType(self.do_postprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_postprocessing',Mt) #-------------------------------------------------------------------------------------------------# @@ -439,19 +771,6 @@ if __name__ == "__main__": # search path checking after time specified below. rd.wait_for_file = 0 # waiting time interval -### Define a run number to validate reduction against future changes ############# - # Take a run number with good reduced results and build validation run - # for this result. Then place the validation run together with the reduction script. - # Next time, the script will run reduction and compare the reduction results against - # the results obtained earlier. - #rd.validate_run_number = 21968 # Enabling this property disables normal reduction - # and forces reduction to reduce run specified here and compares results against - # validation file, processed earlier or calculate this file if run for the first time. - #This would ensure that reduction script have not changed, - #allow to identify reason of changes if it was and would allow to recover the script, - #used to produce initial reduction if changes are unacceptable. - - ####get reduction parameters from properties above, override what you want locally ### # and run reduction. Overriding would have form: # rd.reducer.property_name (from the dictionary above) = new value e.g. @@ -468,3 +787,6 @@ if __name__ == "__main__": #red_ws = rd.run_reduction() # usual way to go is to reduce workspace and save it internally rd.run_reduction() + +###### Test absorption corrections to find optimal settings for corrections algorithm +# corr = rd.eval_absorption_corrections() diff --git a/instrument/LET_Parameters.xml b/instrument/LET_Parameters.xml index 229217281a13df47c75ef1c78878e36bfd13d214..307130c66405b8ddcd611c0ab272da79325062c8 100644 --- a/instrument/LET_Parameters.xml +++ b/instrument/LET_Parameters.xml @@ -414,17 +414,39 @@ <value val="False"/> </parameter> -<!-- The semicolon separated list of possible log names, containing information on crystl rotation. - First found log will be used togethere with motor_offset to identify crystal rotation +<!-- The semicolon separated list of possible log names, containing information on crystal rotation. + First found log will be used together with motor_offset to identify crystal rotation (psi in Horace) --> <parameter name="motor_log_names" type="string"> <value val="CCR_ROT;Rot"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, the reducer keywords are the leftmost values of the keyword assignments below diff --git a/instrument/LET_Parameters_dr1to12.xml b/instrument/LET_Parameters_dr1to12.xml index 7dc8b5fa8b6a0429ad2879ad1c4327c118a7bc00..f6b1279703b658dfd2e6848d022c8616f52302d7 100644 --- a/instrument/LET_Parameters_dr1to12.xml +++ b/instrument/LET_Parameters_dr1to12.xml @@ -414,18 +414,41 @@ <value val="False"/> </parameter> -<!-- The semicolon separated list of possible log names, containing information on crystl rotation. - First found log will be used togethere with motor_offset to identify crystal rotation +<!-- The semicolon separated list of possible log names, containing information on crystal rotation. + First found log will be used together with motor_offset to identify crystal rotation (psi in Horace) --> <parameter name="motor_log_names" type="string"> <value val="CCR_ROT;Rot"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + + + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, the reducer keywords are the leftmost values of the keyword assignments below diff --git a/instrument/LET_Parameters_dr2to12.xml b/instrument/LET_Parameters_dr2to12.xml index b1907b5869f18bb7824c8a24c58cda21270c9211..43569aa3eca466950fd00f0dcb99eb31c9ed1b84 100644 --- a/instrument/LET_Parameters_dr2to12.xml +++ b/instrument/LET_Parameters_dr2to12.xml @@ -414,17 +414,38 @@ <value val="False"/> </parameter> -<!-- The semicolon separated list of possible log names, containing information on crystl rotation. - First found log will be used togethere with motor_offset to identify crystal rotation +<!-- The semicolon separated list of possible log names, containing information on crystal rotation. + First found log will be used together with motor_offset to identify crystal rotation (psi in Horace) --> <parameter name="motor_log_names" type="string"> <value val="CCR_ROT;Rot"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, diff --git a/instrument/LET_Parameters_dr2to9.xml b/instrument/LET_Parameters_dr2to9.xml index 58c8e9bf70e0167ccd3b8095a26e5aed5aedc52a..32f59fc4a728dcbaefb71e52fffbde61ac02ca7d 100644 --- a/instrument/LET_Parameters_dr2to9.xml +++ b/instrument/LET_Parameters_dr2to9.xml @@ -414,17 +414,38 @@ <value val="False"/> </parameter> -<!-- The semicolon separated list of possible log names, containing information on crystl rotation. - First found log will be used togethere with motor_offset to identify crystal rotation +<!-- The semicolon separated list of possible log names, containing information on crystal rotation. + First found log will be used together with motor_offset to identify crystal rotation (psi in Horace) --> <parameter name="motor_log_names" type="string"> <value val="CCR_ROT;Rot"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, diff --git a/instrument/LET_Parameters_dr3to10.xml b/instrument/LET_Parameters_dr3to10.xml index 256fe193272545719da00adcd985617bb08baa5e..cdb528b460db049f9607b8e7e08f797b253ec42a 100644 --- a/instrument/LET_Parameters_dr3to10.xml +++ b/instrument/LET_Parameters_dr3to10.xml @@ -414,17 +414,39 @@ <value val="False"/> </parameter> -<!-- The semicolon separated list of possible log names, containing information on crystl rotation. - First found log will be used togethere with motor_offset to identify crystal rotation +<!-- The semicolon separated list of possible log names, containing information on crystal rotation. + First found log will be used together with motor_offset to identify crystal rotation (psi in Horace) --> <parameter name="motor_log_names" type="string"> <value val="CCR_ROT;Rot"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, diff --git a/instrument/LET_Parameters_dr3to11.xml b/instrument/LET_Parameters_dr3to11.xml index c46297bf739324d3e8269370ad57503c1c243b01..b1441ee05437f76af37e6669a11d9fa77c36e5d1 100644 --- a/instrument/LET_Parameters_dr3to11.xml +++ b/instrument/LET_Parameters_dr3to11.xml @@ -414,18 +414,40 @@ <value val="False"/> </parameter> -<!-- The semicolon separated list of possible log names, containing information on crystl rotation. - First found log will be used togethere with motor_offset to identify crystal rotation +<!-- The semicolon separated list of possible log names, containing information on crystal rotation. + First found log will be used together with motor_offset to identify crystal rotation (psi in Horace) --> <parameter name="motor_log_names" type="string"> <value val="CCR_ROT;Rot"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, the reducer keywords are the leftmost values of the keyword assignments below diff --git a/instrument/MAPS_Parameters.xml b/instrument/MAPS_Parameters.xml index c68ec39f93028ca2030dc16e93146fc6a2d97ffa..4c2557b4016eba27a1444f0e48e230d658bdc71a 100644 --- a/instrument/MAPS_Parameters.xml +++ b/instrument/MAPS_Parameters.xml @@ -419,17 +419,39 @@ <value val="False"/> </parameter> -<!-- The semicolon separated list of possible log names, containing information on crystl rotation. - First found log will be used togethere with motor_offset to identify crystal rotation +<!-- The semicolon separated list of possible log names, containing information on crystal rotation. + First found log will be used together with motor_offset to identify crystal rotation (psi in Horace) --> <parameter name="motor_log_namesmotor_log_names" type="string"> <value val="wccr;Rot"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, the reducer keywords are the leftmost values of the keyword assignments below diff --git a/instrument/MAPS_Parameters_2017_06_02.xml b/instrument/MAPS_Parameters_2017_06_02.xml index 0d807fa36bb81c8c97ebf89b1335b26abb68a0a1..80cce2bb583f3293b1be9be1ea99eb694cafd702 100644 --- a/instrument/MAPS_Parameters_2017_06_02.xml +++ b/instrument/MAPS_Parameters_2017_06_02.xml @@ -419,17 +419,39 @@ <value val="False"/> </parameter> -<!-- The semicolon separated list of possible log names, containing information on crystl rotation. - First found log will be used togethere with motor_offset to identify crystal rotation +<!-- The semicolon separated list of possible log names, containing information on crystal rotation. + First found log will be used together with motor_offset to identify crystal rotation (psi in Horace) --> <parameter name="motor_log_namesmotor_log_names" type="string"> <value val="wccr;Rot"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, the reducer keywords are the leftmost values of the keyword assignments below diff --git a/instrument/MARI_Parameters.xml b/instrument/MARI_Parameters.xml index 406d41f85af810440ace64de2c1ee643d88d679e..2ed1c1badc285a6806ff0b5de4c17ccf97dfcdd6 100644 --- a/instrument/MARI_Parameters.xml +++ b/instrument/MARI_Parameters.xml @@ -427,12 +427,35 @@ <parameter name="motor_log_names" type="string"> <value val="wccr;Rot"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + + + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, the reducer keywords are the leftmost values of the keyword assignments below diff --git a/instrument/MARI_Parameters_19900101_20160911.xml b/instrument/MARI_Parameters_19900101_20160911.xml index 1162fdd091ea9980a26aff0e3b4216e9d12f24ed..35549b66f57f226a843106aab83363ecf41cdd8a 100644 --- a/instrument/MARI_Parameters_19900101_20160911.xml +++ b/instrument/MARI_Parameters_19900101_20160911.xml @@ -415,18 +415,39 @@ <value val="False"/> </parameter> -<!-- The semicolon separated list of possible log names, containing information on crystl rotation. - First found log will be used togethere with motor_offset to identify crystal rotation +<!-- The semicolon separated list of possible log names, containing information on crystal rotation. + First found log will be used together with motor_offset to identify crystal rotation (psi in Horace) MARI of course not uses it and probably not writes such log, but - it is goot to have it here for testing purtposes --> + it is goot to have it here for testing purposes --> <parameter name="motor_log_names" type="string"> <value val="wccr;Rot"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, diff --git a/instrument/MERLIN_Parameters.xml b/instrument/MERLIN_Parameters.xml index 88bdbdc9deb9d4998ac1dc70eae58f000471b206..4b68b5e9d889a3b4b77fcf166d82a45a164db684 100644 --- a/instrument/MERLIN_Parameters.xml +++ b/instrument/MERLIN_Parameters.xml @@ -420,11 +420,32 @@ <parameter name="motor_log_names" type="string"> <value val="CCR_ROT;wccr"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, diff --git a/instrument/MERLIN_Parameters_2017_02.xml b/instrument/MERLIN_Parameters_2017_02.xml index 391803ba18a83613da8a359f2f34769880ac8932..f7bdd996c6ed22eba344f64b861083510b712a82 100644 --- a/instrument/MERLIN_Parameters_2017_02.xml +++ b/instrument/MERLIN_Parameters_2017_02.xml @@ -425,6 +425,26 @@ <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, diff --git a/instrument/MERLIN_Parameters_2018_03.xml b/instrument/MERLIN_Parameters_2018_03.xml index 8ae55cbec86913160257fc38f556d76d6d3b2cf9..8478a8e79e4a37331c2e99db15e236bbcf87f5c6 100644 --- a/instrument/MERLIN_Parameters_2018_03.xml +++ b/instrument/MERLIN_Parameters_2018_03.xml @@ -425,6 +425,29 @@ <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + + + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, diff --git a/instrument/MERLIN_Parameters_after2013_4.xml b/instrument/MERLIN_Parameters_after2013_4.xml index 3a47b550dcef5803c01630ba1964c22e4d916c05..e70bd2ae309383f5aefd4e3ea133e122539e7a81 100644 --- a/instrument/MERLIN_Parameters_after2013_4.xml +++ b/instrument/MERLIN_Parameters_after2013_4.xml @@ -420,11 +420,32 @@ <parameter name="motor_log_names" type="string"> <value val="CCR_ROT;wccr"/> </parameter> -<!-- Initial value used to identify crytsal rotation angle psi=motor_offset+wccr.timeAverageValue() --> +<!-- Initial value used to identify crystal rotation angle psi=motor_offset+wccr.timeAverageValue() --> <parameter name="motor_offset"> <value val="None"/> </parameter> +<parameter name="correct_absorption_on" type="string"> + <value val="None"/> + <description is="If defined, the property would initiate the run of the absorption corrections procedure, specific for the measured sample. + The corrections themselves are controlled by one of the absorption shapes classes, defined for Direct inelastic reduction. + These shapes are Cylinder, Plate, HollowCylinder and other defined in the AbsorptionShapes module within the direct inelastic + reduction scripts folder"/> +</parameter> +<parameter name="abs_corr_info" type="string"> + <value val="None"/> + <description is="The property describes the type of the absorption corrections to run and, if necessary, + additional properties of the absorption algorithm to use. The acceptable types are: + is_mc:True (for Monte-Carlo corrections) This option is default option if neither is_mc nor is_fast key is present. + is_fast:True (for numerical and, when possible, analytical corrections) + The property string may have a form: + is_mc:True,NumberOfWavelengthPoints:50,EventsPerPoint:100 or any other non-sample related properties, accepted by Monte-Carlo corrections algorithm + or + is_fast:True,ExpMethod:FastApprox,ElementSize:10 or any other non-sample related accepted by AbsorptionCorrection algorithm + If set up in a Python script, the info can be provided both as a string above and as a Python dictionary. + "/> +</parameter> + <!-- List of the words which can be used as a command line arguments to define reducer keywords the form is reducer_keword1=synonim1=synonim2=synonim3;reducer_keword1=synonim1a, so, diff --git a/scripts/Inelastic/Direct/AbsorptionShapes.py b/scripts/Inelastic/Direct/AbsorptionShapes.py new file mode 100644 index 0000000000000000000000000000000000000000..48d6d18a47975ad683f8ba3d7d4046a703cfc555 --- /dev/null +++ b/scripts/Inelastic/Direct/AbsorptionShapes.py @@ -0,0 +1,692 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + + +from __future__ import (absolute_import, division, print_function) +from mantid.kernel import funcinspect +from mantid.simpleapi import * + +import random +import types +import ast +import collections + + +class anAbsorptionShape(object): + """ The parent class for all shapes, used to perform various absorption corrections + in direct inelastic analysis. + + Contains material and environment properties necessary for absorption corrections calculations, + as recognized by SetSampleMaterial algorithm. + + See SetSampleMaterial for full list of properties available to set. + + Usage: + Instantiate as: + anShape = anAbsorptionShape([ChemicalFormula,NumberDensity]) + where: + -- ChemicalFormula is the string, describing the sample formula + -- NumberDensity is the number density of the sample in number + of atoms or formula units per cubic Angstrom. If provided, will be used + instead of the one, calculated from the Chemical formula + e.g: + anShape = anAbsorptionShape(['Li',0.1]) + or + anShape = anAbsorptionShape({'ChemicalFormula':'Formula','NumberDensity':Number}) + e.g: + anShape = anAbsorptionShape({'ChemicalFormula':'(Li7)2-C-H4-N-Cl6','NumberDensity':0.8}) + or + anShape = anAdsrbShape(Dictionary with any number of key-value pairs recognized by SetSampleMaterial algorithm) + e.g: + anShape = anAbsorptionShape({'AtomicNumber':12,'AttenuationXSection':0.5,'SampleMassDensity':120}) + + The Material definition should unambiguously define sample material for absorption calculations. + + The set material value may be accessed or changes through the property material: e.g. + mat = anShape.material + or + anShape.material = {'ChemicalFormula':'Br'} (change existing ChemicalFormula) + + Note: + Adding dictionary appends or modifies existing properties, but adding a list, clears up all properties, previously + set-up on class. + The list can contain up two members where first corresponds to the ChemicalFormula and the second one -- to the + material's number density + """ + + # The list of the shapes defined in the module and used as the basis of the + # absorption shapes factory + _Defined_Shapes = {} + + def __init__(self,MaterialValue=None): + # environment according to existing definitions of the environment + self._Environment = {} + # default sample material (formula) + self._Material = {} + # the shape holder. Not defined in this class but the holder for all + # other classes + self._ShapeDescription = {} + + # the workspace used for testing correct properties settings + rhash = random.randint(1,100000) + self._testWorkspace = CreateSampleWorkspace(OutputWorkspace= + '_adsShape_' + str(rhash),NumBanks=1,BankPixelWidth=1) # noqa: E127 + + if MaterialValue is not None: + self.material = MaterialValue + # If true, SetSample algorithm can set-up this shape. If not, a generic + # CreateSampleShape algorithm should be used + self._CanSetSample = True + # some shapes have axis, some does not. This to distinguish between them and + # make default axis specific to instrument + self._shape_has_axis = False + # Property describes if a shape axis direction have been changed. + # Some instruments (e.g. MARI) have non-standart default axis directions + self._axis_is_default=True + # + + def __del__(self): + DeleteWorkspace(self._testWorkspace) + # + + def __str__(self): + """ Convert an absorption shape into a string representation""" + return str(self._ShapeDescription) + '!' + str(self._Material) + + # + @property + def material(self): + """ Contains the material, used in absorbtion correction calculations""" + return self._Material + + @material.setter + def material(self,value): + if value is None: + self._Material = {} + return + if isinstance(value,str): + value = [value] + if isinstance(value,(list,tuple)): + if len(value) == 2: + self._Material = {'ChemicalFormula':value[0],'SampleNumberDensity':float(value[1])} + elif len(value) == 1: + self._Material = {'ChemicalFormula':value[0]} + else: + raise TypeError( + '*** If material is defined by list or tuple,' + ' it may consist of 1 or 2 members,' + ' defining the material formula and the material number density') + elif isinstance(value,dict): + for key,val in value.items(): + self._Material[key] = val + else: + raise TypeError( + '*** Material accepts only list or tuple containing up to 2 values' + ' corresponding to material formula and material number density' + ' or dictionary with keys, recognized by SetSampleMaterial algorithm') + # + Mater_properties = self._Material + if 'ChemicalFormula' in Mater_properties: + if not isinstance(Mater_properties['ChemicalFormula'],str): + raise TypeError('*** The chemical formula for the material must be described by a string') + # Test if the material property are recognized by SetSampleMaterial + # algorithm. + SetSampleMaterial(self._testWorkspace,**Mater_properties) + + # + def correct_absorption(self,ws,*args,**kwargs): + """ The generic method, which switches between fast and Monte-Carlo absorption corrections + depending on the type and properties variable provided as second input + + kwargs is a dictionary, with at least one key, describing type of + corrections (fast or Monte-Carlo) and other keys (if any) + containing additional properties of the correspondent correction + algorithm. + + Returns: + 1) absorption-corrected workspace + 2) if two output arguments are provided, second would be workspace with absorption corrections coefficients + """ + n_outputs,var_names = funcinspect.lhs_info('both') + + if args is None or len(args) == 0: + if kwargs is None: + corr_properties = {} + else: + corr_properties = kwargs + else: + corr_properties = args[0] + if corr_properties is None: + corr_properties = {} + else: + if not isinstance(corr_properties,dict): + raise TypeError( + '*** Second non-keyword argument of the correct_absorption routine' + ' (if present) should be a dictionary containing ' + ' additional parameters for selected AbsorptionCorrections algorithm') + + correction_base_ws = ConvertUnits(ws,'Wavelength',EMode='Direct') + Mater_properties = self._Material + SetSampleMaterial(correction_base_ws,**Mater_properties) + + if self._shape_has_axis: + self._check_MARI_axis_(ws) + + if self._CanSetSample: + shape_description = self._ShapeDescription + SetSample(correction_base_ws,Geometry=shape_description) + + mc_corrections = corr_properties.pop('is_mc', False) + fast_corrections = corr_properties.pop('is_fast',False) + if not(mc_corrections or fast_corrections) or (mc_corrections and fast_corrections): + fast_corrections = False # Case when both keys are true or false reverts to default + + if fast_corrections: + #raise RuntimeError('Analytical absorption corrections are not currently implemented in Direct mode') + abs_corrections = self._fast_abs_corrections(correction_base_ws,corr_properties) + else: + abs_corrections = self._mc_abs_corrections(correction_base_ws,corr_properties) + + abs_corrections = ConvertUnits(abs_corrections,'DeltaE',EMode='Direct') + ws = ws / abs_corrections + + DeleteWorkspace(correction_base_ws) + if ws.name() != var_names[0]: + RenameWorkspace(ws,var_names[0]) + if n_outputs == 1: + #DeleteWorkspace(abs_corrections) + return ws + elif n_outputs == 2: + if abs_corrections.name() != var_names[1]: + RenameWorkspace(abs_corrections,var_names[1]) + return (ws,abs_corrections) + + def _fast_abs_corrections(self,correction_base_ws,kwarg={}): + """ Method to correct absorption on a shape using fast (Numerical Integration) method + if such method is available for the shape + + Not available on arbitrary shapes + Inputs: + ws -- workspace to correct. Should be in the units of wavelength + **kwarg -- dictionary of the additional keyword arguments to provide as input for + the absorption corrections algorithm + These arguments should not be related to the sample as the sample should already be defined + Returns: + workspace with absorption corrections. + """ + adsrbtn_correctios = AbsorptionCorrection(correction_base_ws,**kwarg) + return adsrbtn_correctios + + # + def _mc_abs_corrections(self,correction_base_ws,kwarg={}): + """ Method to correct absorption on a shape using Mont-Carlo integration + Inputs: + ws -- workspace to correct. Should be in the units of wavelength + **kwarg -- dictionary of the additional keyword arguments to provide as input for + the absorption corrections algorithm + These arguments should not be related to the sample as the sample should + already be defined. + Returns: + workspace with absorption corrections. + """ + adsrbtn_correctios = MonteCarloAbsorption(correction_base_ws,**kwarg) + return adsrbtn_correctios + + # + @staticmethod + def from_str(str_val): + """ Retrieve absorption shape from a string representation + + Implements shapes factory, so every new shape class should be subscribed to it + """ + if len(anAbsorptionShape._Defined_Shapes) == 0: + anAbsorptionShape._Defined_Shapes = \ + {'Cylinder': Cylinder(), 'FlatPlate':FlatPlate(), + 'HollowCylinder':HollowCylinder(),'Sphere': Sphere()} # noqa: E127 + + if not isinstance(str_val,str): + raise ValueError( + 'The input of the "from_str" function should be a string representing a diary.' + ' Actually it is: {0}'.format(type(str_val))) + str_list = str_val.split('!') + shape_par = ast.literal_eval(str_list[0]) + mater_par = ast.literal_eval(str_list[1]) + + the_shape_id = shape_par.pop('Shape',None) + if the_shape_id is None: + raise ValueError( + 'The input of the "from_str" function = {0} but does not contain the ' + 'Shape description e.g. the Key Shape:ShapeName'.format(str_val)) + theShape = anAbsorptionShape._Defined_Shapes[the_shape_id] + theShape.material = mater_par + theShape.shape = shape_par + return theShape + + # + def _set_list_property(self,value,shape_name,mandatory_prop_list,opt_prop_list,opt_val_list): + """ General function to build list property for various absorption corrections algorithms + taking it from various forms of users input and converting it into standard key-value + dictionary. + """ + if value is None or not value: + self._axis_is_default = True + return {} + if not isinstance(value, collections.Iterable): + value = [value] + n_elements = len(value) + if n_elements < len(mandatory_prop_list): + raise TypeError( + '*** {0} shape parameter needes at least {1} imput parameters namely: {2}' + .format(shape_name,len(mandatory_prop_list),mandatory_prop_list)) + + shape_dict = {'Shape':shape_name} + all_prop = mandatory_prop_list + opt_prop_list + if isinstance(value,(list,tuple)): + for i in range(0,n_elements): + val = value[i] + if isinstance(val,(list,tuple)): + val = [float(x) for x in val] + else: + val = float(val) + shape_dict[all_prop[i]] = val + elif isinstance(value,dict): + for key,val in value.items(): + if isinstance(val,(list,tuple)): + val = [float(x) for x in val] + else: + val = float(val) + shape_dict[key] = val + else: + raise TypeError( + '*** {0} shape parameter accepts only list or tuple containing from {1} to {2}' + ' values corresponding to the {0} parameters: {3} or the dictionary with these,' + ' as recognized by SetSample algorithm Geometry property'. + format( shape_name,len(mandatory_prop_list),len(all_prop),all_prop)) + # + if 'Axis' in shape_dict: + self._axis_is_default = False + for ik in range(0,len(opt_prop_list)): + if not opt_prop_list[ik] in shape_dict: + opt_val = opt_val_list[ik] + if isinstance(opt_val,types.FunctionType): + shape_dict[opt_prop_list[ik]] = opt_val(shape_dict) + else: + shape_dict[opt_prop_list[ik]] = opt_val + + return shape_dict + # + + def _check_MARI_axis_(self,workspace): + """ method verifies, if default axis needs to be changed for MARI""" + if self._axis_is_default: + instrument = workspace.getInstrument() + instr_name = instrument.getName() + short_n = instr_name[0:3] + if short_n.lower() == 'mar': + self._ShapeDescription['Axis'] = [1.,0.,0.] + + +##--------------------------------------------------------------------------------------------------- +class Cylinder(anAbsorptionShape): + """Define the absorbing cylinder and calculate absorption corrections for this cylinder + + Usage: + abs = Cylinder(SampleMaterial,CylinderParameters) + The SampleMaterial is the list or dictionary as described on anAbsorptionShape class + + and + + CylinderParameters can have the form: + + a) The list consisting of 2 to 4 members.: + CylinderParameters = [Height,Radius,[[Axis],[Center]] + where Height, Radius are the cylinder height and radius in cm and + Axis, if present is the direction of the cylinder wrt to the beam direction [0,0,1] + e.g.: + abs = Cylinder(['Al',0.1],[10,2,[1,0,0],[0,0,0]]) + b) The diary: + CylinderParameters = {Height:Height_value,Radius:Radius_value,[Axis:axisValue],[Center:TheSampleCentre]} + e.g: + abs = Cylinder(['Al',0.1],[Height:10,Radius:2,Axis:[1,0,0]]) + + Usage of the defined Cylinder class instance: + Correct absorption on the cylinder using CylinderAbsorption algorithm: + ws = abs.correct_absorption(ws) % it is default + + Correct absorption on the defined cylinder using Monte-Carlo Absorption algorithm: + ws = ads.correct_absorption(ws,{is_mc:True,AdditionalMonte-Carlo Absorption parameters}); + """ + def __init__(self,Material=None,CylinderParams=None): + + anAbsorptionShape.__init__(self,Material) + self.shape = CylinderParams + self._shape_has_axis = True + + @property + def shape(self): + return self._ShapeDescription + + @shape.setter + def shape(self,value): + shape_dict = self._set_list_property(value, + 'Cylinder',['Height','Radius'], # noqa + ['Axis','Center'],[[0.,1.,0.],[0.,0.,0.]]) # noqa + + self._ShapeDescription = shape_dict + + if len(shape_dict) != 0: + # Test if the shape property is recognized by CreateSampleShape + # algorithm. + SetSample(self._testWorkspace,Geometry=shape_dict) + # + + def _fast_abs_corrections(self,correction_base_ws,kwarg={}): + """ Method to correct absorption on a shape using fast (Numerical Integration) method + """ + kw = kwarg.copy() + elem_size = kw.pop('NumberOfSlices',None) + if elem_size is not None: + shape_dic = self.shape + n_slices = int(shape_dic['Height']/elem_size) + if n_slices <1: + n_slices = 1 + n_annul = int(shape_dic['Radius']*2*3.1415926/elem_size) + if n_annul < 1: + n_annul = 1 + kw['NumberOfSlices'] = n_slices + kw['NumberOfAnnuli'] = n_annul + if 'Emode' not in kw: + kw['Emode'] = 'Direct' + adsrbtn_correctios = AbsorptionCorrection(correction_base_ws,**kw) + return adsrbtn_correctios + + +##--------------------------------------------------------------------------------------------------- +class FlatPlate(anAbsorptionShape): + """Define the absorbing plate and calculate absorption corrections from this plate + + Usage: + abs = FlatPlate(SampleMaterial,PlateParameters) + The SampleMaterial is the list or dictionary as described on anAbsorptionShape class + + and + + PlateParameters can have the form: + + a) The list consisting of 3 to 5 members e.g.: + PlateParameters = [Height,Width,Thickness,[Centre,[Angle]]] + where Height,Width,Thickness are the plate sizes in cm, + the Center, if present, a list of three values indicating the [X,Y,Z] position of the center. + he reference frame of the defined instrument is used to set the coordinate system for the shape + + The Angle argument for a flat plate shape is expected to be in degrees and is defined as the + angle between the positive beam axis and the normal to the face perpendicular to the beam axis + when it is not rotated, increasing in an anti-clockwise sense. The rotation is performed about + the vertical axis of the instrument's reference frame. + + e.g.: + abs = FlatPlate(['Al',0.1],[10,2,0.2,[1,0,0],5]) + b) The diary: + PlateParameters = {Height:Height_value,Width:Width_value,Thickness:Thickness_value etc} + e.g: + abs = FlatPlate(['Al',0.1],['Height':10,'Width':4,'Thickness':2,'Angle':30) + + Usage of the defined Plate class instance: + Correct absorption on the defined plate using AbsorptionCorrections algorithm: + ws = abs.correct_absorption(ws,{'is_fast':True,Additiona FlatPlateAbsorption algorithm parameters}) + + Correct absorption on the defined Plate using Monte-Carlo Absorption algorithm: + ws = ads.correct_absorption(ws,{'is_mc':True,AdditionalMonte-Carlo Absorption parameters}); + """ + def __init__(self,Material=None,PlateParams=None): + + anAbsorptionShape.__init__(self,Material) + self.shape = PlateParams + + @property + def shape(self): + return self._ShapeDescription + + @shape.setter + def shape(self,value): + shape_dict = self._set_list_property(value, + 'FlatPlate',['Height','Width','Thick'], + ['Center','Angle'],[[0.,0.,0.],0.]) # noqa: E127 + + self._ShapeDescription = shape_dict + + if len(shape_dict) != 0: + # Test if the shape property is recognized by CreateSampleShape + # algorithm. + SetSample(self._testWorkspace,Geometry=shape_dict) + + # + def _fast_abs_corrections(self,correction_base_ws,kwarg={}): + """ Method to correct absorption on the FlatPlate using fast (Numerical Integration) method + """ + kw = kwarg.copy() + prop_dict = {'Height':'SampleHeight','Width':'SampleWidth','Thick':'SampleThickness'} + for key,val in prop_dict.items(): + kw[val] = self._ShapeDescription[key] + if 'Emode' not in kw: + kw['Emode'] = 'Direct' + adsrbtn_correctios = FlatPlateAbsorption(correction_base_ws,**kw) + return adsrbtn_correctios + + +##--------------------------------------------------------------------------------------------------- +class HollowCylinder(anAbsorptionShape): + """Define the Hollow absorbing cylinder and calculate absorption corrections for this cylinder + + Usage: + abs = HollowCylinder(SampleMaterial,CylinderParameters) + The SampleMaterial is the list or dictionary as described on anAbsorptionShape class + + and + + CylinderParameters can have the form: + + a) The list consisting of 3 to 5 members.: + CylinderParameters = [Height,InnerRadus,OuterRadus,[[Axis],[Center]] + where Height, InnerRadus and OuterRadus are the cylinder height and radius-es in cm and + Axis, if present is the direction of the cylinder wrt. to the beam direction [0,0,1] + e.g.: + abs = HollowCylinder(['Al',0.1],[10,2,4,[0,1,0],[0,0,0]]) + b) The diary: + CylinderParameters = {'Height':Height_value,'InnerRadus':value1, + 'OuterRadus':value2,[Axis:axisValue],[Center:TheSampleCentre]} + e.g: + abs = HollowCylinder(['Al',0.1],[Height:10,InnerRadus:2,OuterRadus:2,Axis:[1,0,0]]) + + Usage of the defined HollowCylinder class instance: + Correct absorption on the cylinder using CylinderAbsorption algorithm: + ws = abs.correct_absorption(ws) % it is default + + Correct absorption on the defined cylinder using Monte-Carlo Absorption algorithm: + ws = ads.correct_absorption(ws,{is_mc:True,AdditionalMonte-Carlo Absorption parameters}); + """ + def __init__(self,Material=None,CylinderParams=None): + + anAbsorptionShape.__init__(self,Material) + self.shape = CylinderParams + self._CanSetSample = False + self._shape_has_axis = True + # + + @property + def shape(self): + return self._ShapeDescription + + @shape.setter + def shape(self,value): + shape_dict = self._set_list_property(value, + 'HollowCylinder', + ['Height','InnerRadius','OuterRadius'], + ['Axis','Center'],[[0.,1.,0.],[0.,0.,0.]]) # noqa: E127 + # + self._ShapeDescription = shape_dict + if len(shape_dict) != 0: + # Test if the shape property is recognized by CreateSampleShape + # algorithm. + self._add_xml_hollow_cylinder(self._testWorkspace) + + # + def _add_xml_hollow_cylinder(self,ws): + # xml shape is normaly defined in meters + sample_xml_template = """<hollow-cylinder id="HOLL_CYL"> + <centre-of-bottom-base x="{0}" y="{1}" z="{2}" /> + <axis x="{3}" y="{4}" z="{5}" /> + <inner-radius val="{6}" /> + <outer-radius val="{7}" /> + <height val="{8}" /> + </hollow-cylinder>""""" + shape_dic = self._ShapeDescription + Cenr = [c * 0.01 for c in shape_dic['Center']] + Axis = shape_dic['Axis'] + sample_shape = \ + sample_xml_template.format(Cenr[0],Cenr[1],Cenr[2], + Axis[0],Axis[1],Axis[2], + 0.01 * shape_dic['InnerRadius'], + 0.01 * shape_dic['OuterRadius'], + 0.01 * shape_dic['Height']) + CreateSampleShape(ws,sample_shape) + + # + def _fast_abs_corrections(self,correction_base_ws,kwarg={}): + """ Method to correct absorption on the HollowCylinder using fast (Numerical Integration) method + """ + self._add_xml_hollow_cylinder(correction_base_ws) + + if 'Emode' not in kwarg: + kwarg['Emode'] = 'Direct' + adsrbtn_correctios = AbsorptionCorrection(correction_base_ws,**kwarg) + return adsrbtn_correctios + + # + def _mc_abs_corrections(self,correction_base_ws,kwarg={}): + """ Method to correct absorption on the HollowCylinder using Monte-Carlo integration + Inputs: + ws -- workspace to correct. Should be in the units of wavelength + **kwarg -- dictionary of the additional keyword arguments to provide as input for + the absorption corrections algorithm + These arguments should not be related to the sample as the sample should already be defined. + Returns: + workspace with absorption corrections. + """ + self._add_xml_hollow_cylinder(correction_base_ws) + adsrbtn_correctios = MonteCarloAbsorption(correction_base_ws,**kwarg) + return adsrbtn_correctios + + +##--------------------------------------------------------------------------------------------------- +class Sphere(anAbsorptionShape): + """Define the absorbing sphere and calculate absorption corrections from this sphere + + Usage: + abs = Sphere(SampleMaterial,SphereParameters) + The SampleMaterial is the list or dictionary as described on anAbsorptionShape class + + and + + SphereParameters can have the form: + + a) The list consisting of 1 to 2 members e.g.: + SphereParameters = [Radius,[Center]] + where Radius is the sphere radius in cm, + the Center, if present, a list of three values indicating the [X,Y,Z] position of the center. + The reference frame of the defined instrument is used to set the coordinate system + for the shape. + + e.g.: + abs = Sphere(['Al',0.1],[10,[1,0,0]]) + b) The diary: + SphereParameters = {Radius:value,Centre:[1,1,1] etc} + e.g: + abs = Sphere(['Al',0.1],['Radius':10) + + Usage of the defined Sphere class instance: + + Correct absorption on the defined Sphere using AbsorptionCorrections algorithm: + ws = abs.correct_absorption(ws) + + Correct absorption on the defined Sphere using Monte-Carlo Absorption algorithm: + ws = ads.correct_absorption(ws,{'is_mc':True,AdditionalMonte-Carlo Absorption parameters}); + """ + def __init__(self,Material=None,SphereParams=None): + + anAbsorptionShape.__init__(self,Material) + self.shape = SphereParams + self._CanSetSample = False + + # + @property + def shape(self): + return self._ShapeDescription + + # + @shape.setter + def shape(self,value): + shape_dict = self._set_list_property(value, + 'Sphere',['Radius'], + ['Center'],[[0.,0.,0.]]) # noqa: E127 + + self._ShapeDescription = shape_dict + if len(shape_dict) != 0: + # Test if the shape property is recognized by CreateSampleShape + # algorithm. + self._add_xml_sphere(self._testWorkspace) + + def _add_xml_sphere(self,ws): + # xml shape is normaly defined in meters + sample_xml_template = """<sphere id="WHOLE_SPHERE"> + <centre x="{0}" y="{1}" z="{2}" /> + <radius val="{3}" /> + </sphere>""""" + shape_dic = self._ShapeDescription + Cenr = [c * 0.01 for c in shape_dic['Center']] + sample_shape = \ + sample_xml_template.format(Cenr[0],Cenr[1],Cenr[2], + 0.01 * shape_dic['Radius']) + CreateSampleShape(ws,sample_shape) + + # + def _fast_abs_corrections(self,correction_base_ws,kwarg={}): + """ Method to correct absorption on the Sphere using fast (Numerical Integration) method + (Analytical integration) method. + If the method is invoked without parameters, optimized SphericalAbsorption algorithm is + deployed to calculate corrections. If there are some parameters, more general + AbsorptionCorrections method is invoked with the parameters provided. + """ + kw = kwarg.copy() + if 'Emode' not in kwarg: + kw['Emode'] = 'Direct' + if kw['Emode'].lower() == 'elastic': + adsrbtn_correctios = SphericalAbsorption( + correction_base_ws,SphericalSampleRadius=self._ShapeDescription['Radius']) + else: + self._add_xml_sphere(correction_base_ws) + adsrbtn_correctios = AbsorptionCorrection(correction_base_ws,**kw) + return adsrbtn_correctios + # + + def _mc_abs_corrections(self,correction_base_ws,kwarg={}): + """ Method to correct absorption on the Sphere using Monte-Carlo integration + Inputs: + ws -- workspace to correct. Should be in the units of wavelength + **kwarg -- dictionary of the additional keyword arguments to provide as input for + the absorption corrections algorithm + These arguments should not be related to the sample as the sample should already be defined. + Returns: + workspace with absorption corrections. + """ + self._add_xml_sphere(correction_base_ws) + adsrbtn_correctios = MonteCarloAbsorption(correction_base_ws,**kwarg) + return adsrbtn_correctios + + +##--------------------------------------------------------------------------------------------------- +if __name__ == "__main__": + pass diff --git a/scripts/Inelastic/Direct/DirectEnergyConversion.py b/scripts/Inelastic/Direct/DirectEnergyConversion.py index 429e13e4bb22390accc6682607984b8028362261..8bd1155159366221ef71cb0d01d1338cff5752ee 100644 --- a/scripts/Inelastic/Direct/DirectEnergyConversion.py +++ b/scripts/Inelastic/Direct/DirectEnergyConversion.py @@ -152,7 +152,6 @@ class DirectEnergyConversion(object): #------------------------------------------------------------------------------- #pylint: disable=too-many-branches #pylint: disable=too-many-locals - def diagnose(self, white,diag_sample=None,**kwargs): """run diagnostics on the provided workspaces. @@ -362,7 +361,7 @@ class DirectEnergyConversion(object): #pylint: disable=too-many-branches #pylint: disable=too-many-locals #pylint: disable=W0621 - +# flake8: noqa def convert_to_energy(self,wb_run=None,sample_run=None,ei_guess=None,rebin=None,map_file=None, monovan_run=None,wb_for_monovan_run=None,**kwargs): """ One step conversion of run into workspace containing information about energy transfer @@ -376,8 +375,8 @@ class DirectEnergyConversion(object): # output workspace name. try: - _,r = funcinspect.lhs_info('both') - out_ws_name = r[0] + names = funcinspect.lhs_info('names') + out_ws_name = names[0] #pylint: disable=bare-except except: out_ws_name = None @@ -518,6 +517,14 @@ class DirectEnergyConversion(object): else: # single energy uses single workspace and all TOF are used tof_range = None + + # Do custom preprocessing if such operation is defined + try: + ws_to_preprocess = PropertyManager.sample_run.get_workspace() + ws_to_preprocess = self.do_preprocessing(ws_to_preprocess) + PropertyManager.sample_run.synchronize_ws(ws_to_preprocess) + except AttributeError: + pass #--------------- # #Run the conversion first on the sample @@ -538,10 +545,19 @@ class DirectEnergyConversion(object): ei_guess,mono_ws_base,tof_range, cut_ind,num_ei_cuts) else: pass # no absolute units corrections - # ensure that the sample_run name is intact with workspace + # ensure that the sample_run name is intact with the sample workspace PropertyManager.sample_run.synchronize_ws(deltaE_ws_sample) + if prop_man.correct_absorption_on is not None: + abs_shape = prop_man.correct_absorption_on + deltaE_ws_sample = abs_shape.correct_absorption(deltaE_ws_sample,prop_man.abs_corr_info) # # + # Do custom post-processing if such operation is defined + try: + deltaE_ws_sample = self.do_postprocessing(deltaE_ws_sample) + PropertyManager.sample_run.synchronize_ws(deltaE_ws_sample) + except AttributeError: + pass self.save_results(deltaE_ws_sample) # prepare output workspace @@ -550,10 +566,9 @@ class DirectEnergyConversion(object): if self._multirep_mode: result.append(deltaE_ws_sample) else: - if results_name != out_ws_name: - RenameWorkspace(InputWorkspace=results_name,OutputWorkspace=out_ws_name) - results_name = out_ws_name - result = mtd[results_name] + if results_name != out_ws_name: # This actually returns deltaE_ws_sample.name() + # to the state, defined in ADS. Intentionally skip renaming here. + result = PropertyManager.sample_run.synchronize_ws(deltaE_ws_sample) else: result = deltaE_ws_sample else: # delete workspace if no output is requested @@ -610,7 +625,6 @@ class DirectEnergyConversion(object): return False else: return True - #------------------------------------------------------------------------------------------ #pylint: disable=too-many-arguments @@ -1902,8 +1916,8 @@ class DirectEnergyConversion(object): low,upp = self.wb_integr_range white_tag = 'NormBy:{0}_IntergatedIn:{1:0>10.2f}:{2:0>10.2f}'.format(self.normalise_method,low,upp) return white_tag - # + # def _clear_old_results(self): """Remove workspaces, processed earlier and not used any more""" ws_list = self._old_runs_list @@ -1911,7 +1925,15 @@ class DirectEnergyConversion(object): if ws_name in mtd: DeleteWorkspace(ws_name) object.__setattr__(self,'_old_runs_list',[]) + # + def do_preprocessing(self,ws,*argi): + """ stub for custom preprocessing function. Should be redefined in the reduction script.""" + return ws + + def do_postprocessing(self,ws,*argi): + """ stub for custom postprocessing function. Should be redefined in the reduction script.""" + return ws def get_failed_spectra_list_from_masks(masked_wksp,prop_man): diff --git a/scripts/Inelastic/Direct/NonIDF_Properties.py b/scripts/Inelastic/Direct/NonIDF_Properties.py index 5bb5a59c0cc67f87a048909658c327a76c5ccd2b..53c0c3bc54efe1f2cd4763e4dd0f5c7928745017 100644 --- a/scripts/Inelastic/Direct/NonIDF_Properties.py +++ b/scripts/Inelastic/Direct/NonIDF_Properties.py @@ -110,7 +110,7 @@ class NonIDF_Properties(object): If not explicitly set, white beam for sample run is used.""") # TODO: do something about it. Second white is explicitly used in # diagnostics but not accessed at all - second_white = RunDescriptor("""Second white beam run resutlts currently unused in the workflow + second_white = RunDescriptor("""Second white beam run results currently unused in the workflow despite being referred to in Diagnostics. In a future it should be enabled.""") # diff --git a/scripts/Inelastic/Direct/PropertiesDescriptors.py b/scripts/Inelastic/Direct/PropertiesDescriptors.py index a8ce205c0719d75fd4156beae0ea557c5a19113c..4c22949de19aaaa4f1dacf372371686afdefa7c5 100644 --- a/scripts/Inelastic/Direct/PropertiesDescriptors.py +++ b/scripts/Inelastic/Direct/PropertiesDescriptors.py @@ -13,12 +13,14 @@ from __future__ import (absolute_import, division, print_function) import os import numpy as np import math +import re from collections import Iterable import mantid.simpleapi as mantid from mantid import api import Direct.ReductionHelpers as prop_helpers +from Direct.AbsorptionShapes import * import collections @@ -328,15 +330,16 @@ class IncidentEnergy(PropDescriptor): self._autoEiCalculated = False if ei_mon_spec is None: ei_mon_spec = instance.ei_mon_spectra - guess_ei_ws = mantid.GetAllEi(Workspace=monitor_ws, Monitor1SpecID=ei_mon_spec[0], - Monitor2SpecID=ei_mon_spec[1]) + guess_ei_ws = GetAllEi( + Workspace=monitor_ws, Monitor1SpecID=ei_mon_spec[0], + Monitor2SpecID=ei_mon_spec[1]) guessEi = guess_ei_ws.readX(0) fin_ei = [] for ei in guessEi: try: - ei_ref, _, _, _ = mantid.GetEi(InputWorkspace=monitor_ws, - Monitor1Spec=ei_mon_spec[0], Monitor2Spec=ei_mon_spec[1], - EnergyEstimate=ei) + ei_ref, _, _, _ = GetEi(InputWorkspace=monitor_ws, + Monitor1Spec=ei_mon_spec[0], Monitor2Spec=ei_mon_spec[1], + EnergyEstimate=ei) fin_ei.append(ei_ref) # pylint: disable=bare-except except: @@ -350,7 +353,7 @@ class IncidentEnergy(PropDescriptor): self._num_energies = len(fin_ei) self._cur_iter_en = 0 # Clear dataservice from unnecessary workspace - mantid.DeleteWorkspace(guess_ei_ws) + DeleteWorkspace(guess_ei_ws) def validate(self, instance, owner=None): # @@ -368,8 +371,6 @@ class IncidentEnergy(PropDescriptor): return (False, 2, "Incident energy have to be positive number or list of positive numbers.\n" "Got single negative incident energy {0} ".format(inc_en)) return (True, 0, '') - - # end IncidentEnergy # ----------------------------------------------------------------------------------------- @@ -480,10 +481,7 @@ class EnergyBins(PropDescriptor): if not owner.incident_energy.autoEi_mode() and ebin[2] > ei: return (False, 2, 'Max rebin range {0:f} exceeds incident energy {1:f}'.format(ebin[2], ei)) return (True, 0, '') - - # ----------------------------------------------------------------------------------------- - # end EnergyBins # ----------------------------------------------------------------------------------------- @@ -548,8 +546,6 @@ class SaveFileName(PropDescriptor): def set_custom_print(self, routine): self._custom_print = routine - - # end SaveFileName # ----------------------------------------------------------------------------------------- @@ -574,8 +570,6 @@ class InstrumentDependentProp(PropDescriptor): def __set__(self, instance, values): raise AttributeError("Property {0} can not be assigned. It defined by instrument".format(self._prop_name)) - - # end InstrumentDependentProp # ----------------------------------------------------------------------------------------- @@ -590,8 +584,6 @@ class VanadiumRMM(PropDescriptor): def __set__(self, instance, value): raise AttributeError("Can not change vanadium rmm") - - # end VanadiumRMM # ----------------------------------------------------------------------------------------- # END Descriptors for NonIDF_Properties class @@ -695,8 +687,6 @@ class mon2NormalizationEnergyRange(PropDescriptor): return (False, 2, message) return result - - # ----------------------------------------------------------------------------------------- @@ -838,8 +828,6 @@ class DetCalFile(PropDescriptor): pass self._det_cal_file = file_name return (True, file_name) - - # end DetCalFile # ----------------------------------------------------------------------------------------- @@ -884,8 +872,6 @@ class MapMaskFile(PropDescriptor): else: self._file_name = file_name return (True, file_name) - - # end MapMaskFile # ----------------------------------------------------------------------------------------- @@ -980,8 +966,6 @@ class HardMaskOnly(prop_helpers.ComplexProperty): # pylint: disable=bare-except except: pass - - # end HardMaskOnly # ----------------------------------------------------------------------------------------- @@ -1079,8 +1063,6 @@ class MonovanIntegrationRange(prop_helpers.ComplexProperty): return False, 1, 'monovan integration is suspiciously wide: [{0}:{1}]. ' \ 'This may be incorrect'.format(the_range[0], the_range[1]) return True, 0, '' - - # end MonovanIntegrationRange @@ -1238,8 +1220,6 @@ class SpectraToMonitorsList(PropDescriptor): else: result = [int(spectra_list)] return result - - # end SpectraToMonitorsList # ----------------------------------------------------------------------------------------- @@ -1308,8 +1288,6 @@ class SaveFormat(PropDescriptor): return (False, 1, 'No internal save format is defined. Results may be lost') else: return (True, 0, '') - - # end SaveFormat # ----------------------------------------------------------------------------------------- @@ -1358,8 +1336,6 @@ class DiagSpectra(PropDescriptor): return bank_spectra else: raise ValueError("Spectra For diagnostics can be a string inthe form (num1,num2);(num3,num4) etc. or None") - - # end class DiagSpectra # ----------------------------------------------------------------------------------------- @@ -1407,8 +1383,6 @@ class BackbgroundTestRange(PropDescriptor): return False, 1, ' Background test range is TOF range, its max value looks suspiciously big={0}'.format( test_range[1]) return True, 0, '' - - # end BackbgroundTestRange # ----------------------------------------------------------------------------------------- @@ -1446,8 +1420,6 @@ class MultirepTOFSpectraList(PropDescriptor): else: rez = [int(value)] self._spectra_list = rez - - # end MultirepTOFSpectraList @@ -1529,8 +1501,6 @@ class MonoCorrectionFactor(PropDescriptor): if self._cor_factor <= 0: return (False, 2, 'Mono-correction factor has to be positive if specified: {0}'.format(self._cor_factor)) return (True, 0, '') - - # end MonoCorrectionFactor @@ -1561,8 +1531,6 @@ class MotorLogName(PropDescriptor): else: val_list = [str(value)] self._log_names = val_list - - # end MotorLogName @@ -1587,8 +1555,6 @@ class MotorOffset(PropDescriptor): self._offset = None else: self._offset = float(value) - - # end MotorOffset @@ -1680,10 +1646,177 @@ class RotationAngle(PropDescriptor): def dependencies(self): return ['motor_log_names', 'motor_offset'] - # end RotationAngle +class AbsCorrInfo(PropDescriptor): + """ Class responsible for providing additional values for + absorption corrections algorithms. + + The values should contain of the algorithm selector currently + is_fast:True for AbsorptionCorrection algorithms family + or + is_mc:True for MonteCarloAbsorption correction algorithms family + + accompanied by the dictionary of all non-sample related properties + accepted by these algorithms. + + If this key is missing, the class assumes that MonteCarloAbsorption + algorithm (is_mc:True) is selected. + + The value for the property can be provided as dictionary or as + string representation of this dictionary. + The whole contents of the previous dictionary is removed + on assignment of the new dictionary. + """ + # The set of properties acceptable by MonteCarloAbsorption algorithm: + _MC_corrections_accepts = { + 'NumberOfWavelengthPoints':lambda x: int(x), + 'EventsPerPoint':lambda x : int(x), + 'SeedValue':lambda x : int(x), + 'Interpolation':lambda x : list_checker(x,('Linear','CSpline'),'MonteCarloAbsorptions "Interpolation"'), + 'SparseInstrument':lambda x : bool(x),'NumberOfDetectorRows':lambda x: int(x), + 'NumberOfDetectorColumns':lambda x: int(x),'MaxScatterPtAttempts':lambda x: int(x)} + # The set of properties acceptable by AbsorptionCorrection algorithm: + _Fast_corrections_accepts = { + 'ScatterFrom':lambda x : list_checker(x,('Sample', 'Container', 'Environment'),'AbsorptionCorrections "ScatterFrom"'), + 'NumberOfWavelengthPoints':lambda x: float(x), + 'ExpMethod':lambda x : list_checker(x,('Normal', 'FastApprox'),'AbsorptionCorrections "ExpMethod"'), + 'EMode':lambda x: list_checker(x,('Direct', 'Indirect', 'Elastic'),'AbsorptionCorrections "EMode"'), + 'EFixed':lambda x: float(x),'ElementSize':lambda x: float(x)} + + def __init__(self): + self._alg_prop = {} + # Use Monte-Carlo corrections by default + self._is_fast = False + + def __get__(self, instance, owner=None): + if instance is None: + return self + alg_prop = self._alg_prop + if self._is_fast: + alg_prop['is_fast'] = True + else: + alg_prop['is_mc'] = True + return alg_prop + + def __set__(self, instance, value): + if value is None or len(value) == 0: + # Clear all previous values and set up the defaults + self._alg_prop = {'is_mc':True} + self._is_fast = False + return + val_dict = {} + if isinstance(value, str): + val = re.sub('[{}\[\]"=:;,\']', ' ', value) + val_list = re.split('\s+',val) + ik = 0 + while ik < len(val_list): + key = val_list[ik] + if len(key)==0: + ik+=1 + continue + val = val_list[ik+1] + if key in ('is_fast','is_mc'): + self._algo_selector(key,val) + ik +=2 + continue + val_dict[key] = val + ik +=2 + elif isinstance(value, dict): + val_dict = value + is_fast=val_dict.pop('is_fast',None) + if is_fast is not None: + self._algo_selector('is_fast',is_fast) + is_mc = val_dict.pop('is_mc',None) + if is_mc is not None: + self._algo_selector('is_mc',is_mc) + + else: + raise(KeyError( + 'AbsCorrInfo accepts only a dictionary ' # noqa + 'with AbsorptionCorrections algorithm properties ' # noqa + 'or string representation of such dictionary')) # noqa + + if self._is_fast: + algo_name = 'AdsorptionCorrection' + acceptable_prop = AbsCorrInfo._Fast_corrections_accepts + else: + algo_name = 'MonteCarloAbsorption' + acceptable_prop = AbsCorrInfo._MC_corrections_accepts + + for key,val in val_dict.items(): + check_normalizer = acceptable_prop.get(key,None) + if check_normalizer is None: + raise KeyError('The key {0} is not acceptable key for {1} algorithm'.format(key,algo_name)) + val_dict[key] = check_normalizer(val) + # Store new dictionary in the property + self._alg_prop = val_dict + + def __str__(self): + alg_prop = self.__get__(self,AbsCorrInfo) + return str(alg_prop) + + def _algo_selector(self,algo_key,key_val): + if algo_key == 'is_fast': + self._is_fast = bool(key_val) + elif algo_key == 'is_mc': + self._is_fast = not bool(key_val) + + +class AbsorptionShapesContainer(PropDescriptor): + """ Class to keep AbsorptionShape classes, + responsible for making absorption corrections + for the absorption on correspondent shapes. + + NOT IMPLEMENTED: should also handle conversion + from a shape to its text representaton and v.v. + (using AbsorptionShape appropriate classes) + + """ + def __init__(self): + self._theShapeHolder = None + + # + def __get__(self, instance, owner=None): + if instance is None: + return self + + return self._theShapeHolder + + def __set__(self, instance, value): + if value is None: + self._theShapeHolder = None + return + if isinstance(value,str): + self._theShapeHolder = anAbsorptionShape.from_str(value) + elif isinstance(value,anAbsorptionShape): + self._theShapeHolder = value + else: + raise ValueError('The property accepts only anAbsorptionShape type classes or their string representation') + + def __str__(self): + if self._theShapeHolder is None: + return 'None' + else: + return self._theShapeHolder.str() + + +def list_checker(val,list_in,mess_base): + """ Helper function to check the value val (first input) belongs to the + set, specified as the second input. + + If this is not true, raise ValueError indicating what property is wrong. + To do indication, mess_base should contain the string, referring to + the invalid property. + """ + if val in list_in: + return val + else: + raise ValueError( + '{0} property can only have values from the set of: {1}' # noqa + .format(mess_base,str(list_in))) # noqa + # ----------------------------------------------------------------------------------------- # END Descriptors for PropertyManager itself # ----------------------------------------------------------------------------------------- diff --git a/scripts/Inelastic/Direct/PropertyManager.py b/scripts/Inelastic/Direct/PropertyManager.py index 1be8e2931801bed4d5b7cf12d82f770337fbdea3..58e7b988000a1d952e249aef68202f20cad096e5 100644 --- a/scripts/Inelastic/Direct/PropertyManager.py +++ b/scripts/Inelastic/Direct/PropertyManager.py @@ -255,6 +255,13 @@ class PropertyManager(NonIDF_Properties): motor_log_names= MotorLogName() motor_offset = MotorOffset() psi = RotationAngle(motor_log_names,motor_offset) + # Properties responsible for the Absorption Corrections + # The property, containing information about additional properties of selected + # adsorption corrections algorithm + abs_corr_info = AbsCorrInfo() + # The property, containing the adsorption shapes class which would + # perform actual adsorption corrections on the selected shape + correct_absorption_on = AbsorptionShapesContainer() #---------------------------------------------------------------------------------------------------------------- def getChangedProperties(self): diff --git a/scripts/Inelastic/Direct/ReductionHelpers.py b/scripts/Inelastic/Direct/ReductionHelpers.py index ed73fe02e5340569c6eb02b46ad8a445d7ac0e54..5dd9017d74c6a12a851ef04661352b045c11a721 100644 --- a/scripts/Inelastic/Direct/ReductionHelpers.py +++ b/scripts/Inelastic/Direct/ReductionHelpers.py @@ -10,6 +10,7 @@ from mantid import config import os import re from six.moves import range + """ Set of functions to assist with processing instrument parameters relevant to reduction. """ @@ -32,7 +33,8 @@ class ComplexProperty(object): rez = list() for key in self._other_prop: rez.append(spec_dict[key]) - # process very important case of property dependent on two other properties. Make it tuple + # process very important case of property dependent on two other + # properties. Make it tuple if len(rez) == 2: return (rez[0],rez[1]) else: @@ -48,13 +50,13 @@ class ComplexProperty(object): raise KeyError("Complex property values can be set equal to the same length values list") if isinstance(instance,dict): - spec_dict = instance + spec_dict = instance else: spec_dict = instance.__dict__ #changed_prop=[]; for key,val in zip(self._other_prop,value): - spec_dict[key] =val + spec_dict[key] = val #changed_prop.append(key); #return changed_prop; @@ -119,7 +121,7 @@ def get_default_idf_param_list(pInstrument,synonims_list=None): for name in params: if synonims_list: if name in synonims_list: - key_name=synonims_list[name] + key_name = synonims_list[name] else: key_name = name else: @@ -149,7 +151,7 @@ def build_properties_dict(param_map,synonims,descr_list=[]) : final_name = str(synonims[name]) else: final_name = str(name) - prelim_dict[final_name]=None + prelim_dict[final_name] = None param_keys = list(prelim_dict.keys()) properties_dict = dict() @@ -169,8 +171,8 @@ def build_properties_dict(param_map,synonims,descr_list=[]) : keys_candidates = val.split(":") n_keys = len(keys_candidates) # - if n_keys>1 : # this is the property we want to modify - result=list() + if n_keys > 1 : # this is the property we want to modify + result = list() for key in keys_candidates : if key in synonims: rkey = str(synonims[key]) @@ -183,17 +185,17 @@ def build_properties_dict(param_map,synonims,descr_list=[]) : if is_descriptor: descr_dict[final_name] = result else: - properties_dict['_'+final_name]=ComplexProperty(result) + properties_dict['_' + final_name] = ComplexProperty(result) else: if is_descriptor: descr_dict[final_name] = keys_candidates[0] else: - properties_dict[final_name] =keys_candidates[0] + properties_dict[final_name] = keys_candidates[0] else: if is_descriptor: descr_dict[final_name] = val else: - properties_dict[final_name]=val + properties_dict[final_name] = val return (properties_dict,descr_dict) @@ -226,12 +228,13 @@ def build_subst_dictionary(synonims_list=None) : return synonims_list if not isinstance(synonims_list, str) : raise AttributeError("The synonyms field of Reducer object has to be special format string or the dictionary") - # we are in the right place and going to transform string into dictionary + # we are in the right place and going to transform string into + # dictionary subst_lines = synonims_list.split(";") - rez = dict() + rez = dict() for lin in subst_lines : - lin=lin.strip() + lin = lin.strip() keys = lin.split("=") if len(keys) < 2 : raise AttributeError("The pairs in the synonyms fields have to have form key1=key2=key3 with at least two values present") @@ -241,9 +244,9 @@ def build_subst_dictionary(synonims_list=None) : for i in range(1,len(keys)) : if len(keys[i]) == 0 : raise AttributeError("The pairs in the synonyms fields have to have form key1=key2=key3 with at least two values present, " - "but the key"+str(i)+" is empty") + "but the key" + str(i) + " is empty") kkk = keys[i].strip() - rez[kkk]=keys[0].strip() + rez[kkk] = keys[0].strip() return rez @@ -256,13 +259,13 @@ def gen_getter(keyval_dict,key): and gen_getter(keyval_dict,C) == [10,20]; """ if key not in keyval_dict: - name = '_'+key + name = '_' + key if name not in keyval_dict: raise KeyError('Property with name: {0} is not among the class properties '.format(key)) else: name = key - a_val= keyval_dict[name] + a_val = keyval_dict[name] if isinstance(a_val,ComplexProperty): return a_val.__get__(keyval_dict) else: @@ -282,7 +285,7 @@ def gen_setter(keyval_dict,key,val): """ if key not in keyval_dict: - name = '_'+key + name = '_' + key if name not in keyval_dict: raise KeyError(' Property name: {0} is not defined'.format(key)) else: @@ -311,8 +314,8 @@ def check_instrument_name(old_name,new_name): return (None,None,None) # Instrument name might be a prefix, query Mantid for the full name - short_name='' - full_name='' + short_name = '' + full_name = '' try : instrument = config.getFacility().instrument(new_name) short_name = instrument.shortName() @@ -327,13 +330,13 @@ def check_instrument_name(old_name,new_name): instrument = facility.instrument(new_name) short_name = instrument.shortName() full_name = instrument.name() - if len(short_name)>0 : + if len(short_name) > 0 : break except: pass #config.setString('default.facility',old_facility) - if len(short_name)==0 : - raise KeyError(" Can not find/set-up the instrument: "+new_name+' in any supported facility') + if len(short_name) == 0 : + raise KeyError(" Can not find/set-up the instrument: " + new_name + ' in any supported facility') new_name = short_name @@ -345,25 +348,25 @@ def parse_single_name(filename): """ Process single run name into """ filepath,fname = os.path.split(filename) if ':' in fname: - fl,fr=fname.split(':') - path1,ind1,ext1=parse_single_name(fl) - path2,ind2,ext2=parse_single_name(fr) - if ind1>ind2: + fl,fr = fname.split(':') + path1,ind1,ext1 = parse_single_name(fl) + path2,ind2,ext2 = parse_single_name(fr) + if ind1 > ind2: raise ValueError('Invalid file number defined using colon : left run number ' '{0} has to be large then right {1}'.format(ind1,ind2)) - number = list(range(ind1[0],ind2[0]+1)) - if len(filepath)>0: - filepath=[filepath]*len(number) + number = list(range(ind1[0],ind2[0] + 1)) + if len(filepath) > 0: + filepath = [filepath] * len(number) else: - filepath=path1*len(number) - if len(ext2[0])>0: - fext = ext2*len(number) + filepath = path1 * len(number) + if len(ext2[0]) > 0: + fext = ext2 * len(number) else: - fext = ext1*len(number) + fext = ext1 * len(number) return (filepath,number,fext) - fname,fext = os.path.splitext(fname) + fname,fext = os.path.splitext(fname) fnumber = re.findall('\d+', fname) if len(fnumber) == 0: number = 0 @@ -379,17 +382,17 @@ def parse_run_file_name(run_string): runs = run_string.split(',') filepath = [] - filenum = [] - fext = [] - anExt = '' + filenum = [] + fext = [] + anExt = '' for run in runs: path,ind,ext1 = parse_single_name(run) filepath+=path filenum+=ind fext+=ext1 - non_empty = [x for x in fext if len(x) >0] - if len(non_empty)>0: + non_empty = [x for x in fext if len(x) > 0] + if len(non_empty) > 0: anExt = non_empty[-1] for i,val in enumerate(fext): if len(val) == 0: @@ -397,8 +400,8 @@ def parse_run_file_name(run_string): if len(filenum) == 1: filepath = filepath[0] - filenum = filenum[0] - fext = fext[0] + filenum = filenum[0] + fext = fext[0] # extensions should be either all the same or all defined return (filepath,filenum,fext) # diff --git a/scripts/Inelastic/Direct/ReductionWrapper.py b/scripts/Inelastic/Direct/ReductionWrapper.py index bbd8923831f8e2909cb10bf54d9eff289c321f84..2a8ea37df779c22f709387636914ffa305299379 100644 --- a/scripts/Inelastic/Direct/ReductionWrapper.py +++ b/scripts/Inelastic/Direct/ReductionWrapper.py @@ -13,10 +13,12 @@ from mantid.kernel import funcinspect from Direct.PropertyManager import PropertyManager # this import is used by children from Direct.DirectEnergyConversion import DirectEnergyConversion +from types import MethodType # noqa import os import re import time from six import iteritems + try: import h5py h5py_installed = True @@ -248,6 +250,46 @@ class ReductionWrapper(object): User expected to overload this method within class instantiation """ return None + def evaluate_abs_corrections(self,test_ws,spectra_to_correct): + """ Evaluate absorption corrections from the input workspace + Input: + test_ws -- the workspace to calculate corrections for. + The corrections themselves should be defined by + the following data reduction properties: + propmen.correct_absorption_on = TheShapeOfTheSample -- define sample parameters + propmen.abs_corr_info = {} Dictionary with additional correction parameters + (can be empty) + spectra_to_correct -- list of the spectra to correct absorption for. + If this list is empty, the corrections are calculated for the whole workspace, + which can cause problems for plotting. + + Returns: + corrections -- the workspace containing the absorption corrections + for the spectra, specified in spectra_to_correct variable. + """ + + n_spectra = test_ws.getNumberHistograms() + decrement = len(spectra_to_correct) + if decrement>0: + red_ws = ExtractSpectra(test_ws,WorkspaceIndexList=spectra_to_correct) + else: + decrement = n_spectra + + prop_man = self.reducer.prop_man + abs_shape = prop_man.correct_absorption_on + start_time = time.time() + ws,corrections = abs_shape.correct_absorption(red_ws,prop_man.abs_corr_info) + end_time = time.time() + estimated_time = (end_time-start_time)*n_spectra/decrement + prop_man.log( + "**************************************************************************************************",'notice') + prop_man.log( + "*** Estimated time to run absorption corrections on the final workspace is: {0:.1f}sec". + format(estimated_time),'notice') + prop_man.log( + "**************************************************************************************************",'notice') + return (corrections,estimated_time) + #pylint: disable=too-many-branches def build_or_validate_result(self,Error=1.e-6,ToleranceRelErr=True): """ Method validates results of the reduction against reference file or workspace. @@ -675,8 +717,8 @@ def iliad(reduce): #seq = inspect.stack() # output workspace name. try: - _,r = funcinspect.lhs_info('both') - out_ws_name = r[0] + name = funcinspect.lhs_info('names') + out_ws_name = name[0] # no-exception-type(s) specified. Who knows what exception this internal procedure rises... #pylint: disable=W0702 except: @@ -727,14 +769,27 @@ def iliad(reduce): if isinstance(rez, list): # multirep run, just return as it is return rez - if not(rez is None) and out_ws_name and rez.name() != out_ws_name: + if rez is not None and out_ws_name and rez.name() != out_ws_name: # the function does not return None, pylint is wrong #pylint: disable=W1111 - rez = RenameWorkspace(InputWorkspace=rez, OutputWorkspace=out_ws_name) - + rez = PropertyManager.sample_run.synchronize_ws(rez,out_ws_name) return rez return iliad_wrapper +# + + +def custom_operation(custom_fun): + DirectEnergyConversion.__setattr__() + + def custom_fun_wrapper(*args): + # execute decorated function + ws = custom_fun(*args) + #print "in decorator: ",properties + #host = args[0] + return ws + + return custom_fun_wrapper if __name__ == "__main__": diff --git a/scripts/Inelastic/Direct/RunDescriptor.py b/scripts/Inelastic/Direct/RunDescriptor.py index ca71ccb4f4116a3b2272e108f64314469c63125a..1f7f76e14db687fa4c47eb7fdb965a528b899db5 100644 --- a/scripts/Inelastic/Direct/RunDescriptor.py +++ b/scripts/Inelastic/Direct/RunDescriptor.py @@ -742,7 +742,7 @@ class RunDescriptor(PropDescriptor): return self._build_ws_name() #-------------------------------------------------------------------------------------------------------------------- - def synchronize_ws(self,workspace=None): + def synchronize_ws(self,workspace=None,new_ws_name = None): """Synchronize workspace name (after workspace may have changed due to algorithm) with internal run holder name. Accounts for the situation when @@ -753,8 +753,10 @@ class RunDescriptor(PropDescriptor): """ if not workspace: workspace = mtd[self._ws_name] - - new_name = self._build_ws_name() + if new_ws_name: + new_name = new_ws_name + else: + new_name = self._build_ws_name() old_name = workspace.name() if new_name != old_name: # Compatibility with old test code comes here: @@ -767,6 +769,8 @@ class RunDescriptor(PropDescriptor): workspace.setMonitorWorkspace(mon_ws) RenameWorkspace(InputWorkspace=old_name,OutputWorkspace=new_name,RenameMonitors=True) self._ws_name = new_name + + return mtd[new_name] #-------------------------------------------------------------------------------------------------------------------- @staticmethod diff --git a/scripts/test/AbsorptionShapesTest.py b/scripts/test/AbsorptionShapesTest.py new file mode 100644 index 0000000000000000000000000000000000000000..67d6370bb0d217a7422ba944c9925ad8e9052480 --- /dev/null +++ b/scripts/test/AbsorptionShapesTest.py @@ -0,0 +1,245 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# & Institut Laue - Langevin +# SPDX - License - Identifier: GPL - 3.0 + +from __future__ import (absolute_import, division, print_function) +import os +from mantid.simpleapi import * +from mantid import api +import unittest +import numpy as np +from Direct.AbsorptionShapes import (anAbsorptionShape,Cylinder,FlatPlate,HollowCylinder,Sphere) + +class AdsorbtionShapesTest(unittest.TestCase): + def __init__(self, methodName): + return super(AdsorbtionShapesTest, self).__init__(methodName) + + def test_an_Absrpn_shape_parent(self): + ash = anAbsorptionShape(['V']); + res = ash.material; + self.assertEqual(res['ChemicalFormula'],'V') + + ash.material = 'Cr' + res = ash.material; + self.assertEqual(res['ChemicalFormula'],'Cr') + + + ash.material = ['Br',10] + res = ash.material; + self.assertEqual(res['ChemicalFormula'],'Br') + self.assertEqual(res['SampleNumberDensity'],10) + + + ash.material = {'ChemicalFormula':'Al','SampleNumberDensity':0.5} + res = ash.material; + self.assertEqual(res['ChemicalFormula'],'Al') + self.assertEqual(res['SampleNumberDensity'],0.5) + + self.assertRaises(TypeError,anAbsorptionShape.material.__set__,ash,[1,2,3]) + self.assertRaises(TypeError,anAbsorptionShape.material.__set__,ash,[1,2]) + + ash = anAbsorptionShape({'AtomicNumber':12,'AttenuationXSection':0.5,'SampleMassDensity':120}) + res = ash.material; + self.assertEqual(res['AtomicNumber'],12) + self.assertEqual(res['AttenuationXSection'],0.5) + self.assertEqual(res['SampleMassDensity'],120) + + # Add extra material property, consistent with other properties. + ash.material = {'ScatteringXSection':20} + res = ash.material; + self.assertEqual(res['AttenuationXSection'],0.5) + self.assertEqual(res['ScatteringXSection'],20) + self.assertEqual(len(res),4) + + def test_adsrp_cylinder(self): + ash = Cylinder('V',[10,2]) + res = ash.shape + self.assertEqual(res['Height'],10) + self.assertEqual(res['Radius'],2) + self.assertTrue(ash._axis_is_default) + + ash.shape = [5,1,[0,1,0],[0.,0.,-0.5]] + res = ash.shape + self.assertEqual(res['Height'],5) + self.assertEqual(res['Radius'],1) + self.assertEqual(res['Axis'],[0,1,0]) + self.assertEqual(res['Center'],[0,0,-0.5]) + self.assertFalse(ash._axis_is_default) + + + ash.shape = {'Height':5,'Radius':2,'Axis':[1,0,0],'Center':[0.,0.,0.]} + res = ash.shape; + self.assertEqual(res['Height'],5) + self.assertEqual(res['Radius'],2) + self.assertEqual(res['Axis'],[1,0,0]) + self.assertEqual(res['Center'],[0,0,0]) + + test_ws = CreateSampleWorkspace(NumBanks=1,BankPixelWidth=1) + test_ws = ConvertUnits(test_ws,'DeltaE',Emode='Direct',EFixed=2000) + cor_ws,corrections = ash.correct_absorption(test_ws,is_fast=True) + n_bins = corrections.blocksize() + corr_ranges = [n_bins,corrections.readY(0)[0],corrections.readY(0)[n_bins-1]] + np.testing.assert_almost_equal(corr_ranges,[97,0.0,0.0],4) + mccor_ws,mc_corr = ash.correct_absorption(test_ws,NumberOfWavelengthPoints=20) + n_bins = mc_corr.blocksize() + mccorr_ranges = [n_bins,mc_corr.readY(0)[0],mc_corr.readY(0)[n_bins-1]] + np.testing.assert_almost_equal(mccorr_ranges ,[97,0.2657,0.0271],4) + + def test_MARI_axis_cylinder(self): + """ Test that default axis for MARI is different""" + ash = Cylinder('Fe',[10,2]) + res = ash.shape + self.assertEqual(res['Height'],10) + self.assertEqual(res['Radius'],2) + self.assertTrue(ash._axis_is_default) + test_ws = CreateSampleWorkspace(NumBanks=1,BankPixelWidth=1) + test_ws = ConvertUnits(test_ws,'DeltaE',Emode='Direct',EFixed=2000) + cor_ws,corrections = ash.correct_absorption(test_ws,is_fast=True) + res = ash.shape + self.assertEqual(res['Axis'],[0.,1.,0]) + + EditInstrumentGeometry (test_ws,[1],[0],InstrumentName='MARI') + cor_ws,corrections = ash.correct_absorption(test_ws,is_fast=True) + res = ash.shape + self.assertEqual(res['Axis'],[1.,0.,0]) + + + def test_adsrp_Plate(self): + ash = FlatPlate('V',[10,2,0.1]) + res = ash.shape + self.assertEqual(res['Height'],10) + self.assertEqual(res['Width'],2) + self.assertEqual(res['Thick'],0.1) + + ash.shape = [5,1,0.2,[0,1,0],10] + res = ash.shape; + self.assertEqual(res['Height'],5) + self.assertEqual(res['Width'],1) + self.assertEqual(res['Thick'],0.2) + self.assertEqual(res['Center'],[0,1,0]) + self.assertEqual(res['Angle'],10) + + ash.shape = {'Height':5,'Width':1,'Thick':2,'Center':[0.,0.,0.],'Angle':20} + res = ash.shape; + self.assertEqual(res['Height'],5) + self.assertEqual(res['Width'],1) + self.assertEqual(res['Thick'],2) + self.assertEqual(res['Center'],[0,0,0]) + self.assertEqual(res['Angle'],20) + + test_ws = CreateSampleWorkspace(NumBanks=1,BankPixelWidth=1) + test_ws = ConvertUnits(test_ws,'DeltaE',Emode='Direct',EFixed=2000) + + cor_ws,corrections = ash.correct_absorption(test_ws,is_fast=True,ElementSize=5) + n_bins = corrections.blocksize() + corr_ranges = [n_bins,corrections.readY(0)[0],corrections.readY(0)[n_bins-1]] + np.testing.assert_almost_equal(corr_ranges,[97, 0., 0.],4) + + mccor_ws,mc_corr = ash.correct_absorption(test_ws,is_mc=True,NumberOfWavelengthPoints=20) + n_bins = mc_corr.blocksize() + mccorr_ranges = [n_bins,mc_corr.readY(0)[0],mc_corr.readY(0)[n_bins-1]] + np.testing.assert_almost_equal(mccorr_ranges ,[97,0.5253,0.1296],4) + + + def test_adsrp_hollow_cylinder(self): + ash = HollowCylinder('V',[10,2,4]) + res = ash.shape + self.assertEqual(res['Height'],10) + self.assertEqual(res['InnerRadius'],2) + self.assertEqual(res['OuterRadius'],4) + + ash.shape = [5,1,2,[1,0,0],[0,0,0]] + res = ash.shape; + self.assertEqual(res['Height'],5) + self.assertEqual(res['InnerRadius'],1) + self.assertEqual(res['OuterRadius'],2) + self.assertEqual(res['Axis'],[1,0,0]) + self.assertEqual(res['Center'],[0,0,0]) + + + ash.shape = {'Height':5,'InnerRadius':0.01,'OuterRadius':2,'Center':[0.,0.,0.],'Axis':[0,1,0]} + res = ash.shape; + self.assertEqual(res['Height'],5) + self.assertEqual(res['InnerRadius'],0.01) + self.assertEqual(res['OuterRadius'],2) + self.assertEqual(res['Axis'],[0,1,0]) + self.assertEqual(res['Center'],[0,0,0]) + + + test_ws = CreateSampleWorkspace(NumBanks=1,BankPixelWidth=1) + test_ws = ConvertUnits(test_ws,'DeltaE',Emode='Direct',EFixed=2000) + cor_ws,corrections = ash.correct_absorption(test_ws,is_fast=True,ElementSize=5) + n_bins = corrections.blocksize() + corr_ranges = [n_bins,corrections.readY(0)[0],corrections.readY(0)[n_bins-1]] + np.testing.assert_almost_equal(corr_ranges,[97,0.0,0.000],4) + + mccor_ws,mc_corr = ash.correct_absorption(test_ws,is_mc=True,NumberOfWavelengthPoints=20) + n_bins = mc_corr.blocksize() + mccorr_ranges = [n_bins,mc_corr.readY(0)[0],mc_corr.readY(0)[n_bins-1]] + np.testing.assert_almost_equal(mccorr_ranges ,[97,0.2657,0.0303],4) + # + def test_string_conversion(self): + """ check if shape conversion to string representation works""" + ash = HollowCylinder('V',[10,2,4]) + ash_str = str(ash) + ash_rec = anAbsorptionShape.from_str(ash_str) + + self.assertTrue(isinstance(ash_rec,HollowCylinder)) + self.assertDictEqual(ash.material,ash_rec.material) + self.assertDictEqual(ash.shape,ash_rec.shape) + + ash = Sphere(['Al',10],10) + ash_str = str(ash) + ash_rec = anAbsorptionShape.from_str(ash_str) + + self.assertTrue(isinstance(ash_rec,Sphere)) + self.assertDictEqual(ash.material,ash_rec.material) + self.assertDictEqual(ash.shape,ash_rec.shape) + + # + def test_adsrp_sphere(self): + ash = Sphere('Al',10) + res = ash.shape + self.assertEqual(res['Radius'],10) + + ash.shape = [5,[1,0,0]] + res = ash.shape; + rad = res['Radius'] + self.assertEqual(rad,5) + cen = res['Center'] + self.assertEqual(cen,[1,0,0]) + + + ash.shape = {'Radius':3,'Center':[0.,0.,0.]} + res = ash.shape; + rad = res['Radius'] + self.assertEqual(rad,3) + cen = res['Center'] + self.assertEqual(cen,[0,0,0]) + + + test_ws = CreateSampleWorkspace(NumBanks=1,BankPixelWidth=1) + test_ws = ConvertUnits(test_ws,'DeltaE',Emode='Direct',EFixed=2000) + cor_ws,corrections = ash.correct_absorption(test_ws,is_fast=True,ElementSize=6) + n_bins = corrections.blocksize() + corr_ranges = [n_bins,corrections.readY(0)[0],corrections.readY(0)[n_bins-1]] + np.testing.assert_almost_equal(corr_ranges,[97,0.0,0.0],4) + + cor_ws,corrections = ash.correct_absorption(test_ws,is_fast=True) + n_bins = corrections.blocksize() + corr_ranges = [n_bins,corrections.readY(0)[0],corrections.readY(0)[n_bins-1]] + np.testing.assert_almost_equal(corr_ranges,[97,0.0,0.0],4) + + mccor_ws,mc_corr = ash.correct_absorption(test_ws,is_mc=True,NumberOfWavelengthPoints=20) + n_bins = mc_corr.blocksize() + mccorr_ranges = [n_bins,mc_corr.readY(0)[0],mc_corr.readY(0)[n_bins-1]] + np.testing.assert_almost_equal(mccorr_ranges ,[97,0.6645,0.5098],4) + + + +if __name__=="__main__": + #ast = AdsorbtionShapesTest('test_adsrp_cylinder') + #ast.test_adsrp_cylinder() + unittest.main() diff --git a/scripts/test/CMakeLists.txt b/scripts/test/CMakeLists.txt index 0e0ada5db78e398284794f0b5d3e3fa22e912276..3bc1e07fd377d880236a3b64e6fc8907ddc59a55 100644 --- a/scripts/test/CMakeLists.txt +++ b/scripts/test/CMakeLists.txt @@ -14,6 +14,7 @@ set(TEST_PY_FILES AbinsLoadDMOL3Test.py AbinsLoadGAUSSIANTest.py AbinsPowderDataTest.py + AbsorptionShapesTest.py ConvertToWavelengthTest.py CrystalFieldMultiSiteTest.py CrystalFieldTest.py diff --git a/scripts/test/DirectPropertyManagerTest.py b/scripts/test/DirectPropertyManagerTest.py index d7aea504a0a7d723d792fd5068d5d95fcd1538b2..7d39ac68ff5506c5ec7205eeb241a54f3018a6f6 100644 --- a/scripts/test/DirectPropertyManagerTest.py +++ b/scripts/test/DirectPropertyManagerTest.py @@ -14,6 +14,7 @@ import numpy as np import sys import copy from Direct.PropertyManager import PropertyManager +from Direct.AbsorptionShapes import * #----------------------------------------------------------------------------------------------------------------------------------------- @@ -440,7 +441,7 @@ class DirectPropertyManagerTest(unittest.TestCase): propman.log_changed_values() - def test_set_defailts_from_instrument(self) : + def test_set_defaults_from_instrument(self) : ws = CreateSampleWorkspace(NumBanks=1, BankPixelWidth=4, NumEvents=100) SetInstrumentParameter(ws,ParameterName="TestParam1",Value="3.5",ParameterType="Number") @@ -478,7 +479,7 @@ class DirectPropertyManagerTest(unittest.TestCase): self.assertTrue('TestParam2' in changes) self.assertTrue('TestParam3' in changes) - def test_set_complex_defailts_from_instrument(self) : + def test_set_complex_defaults_from_instrument(self) : ws = CreateSampleWorkspace(NumBanks=1, BankPixelWidth=4, NumEvents=10) SetInstrumentParameter(ws,ParameterName="Param1",Value="BaseParam1:BaseParam2",ParameterType="String") @@ -1228,8 +1229,81 @@ class DirectPropertyManagerTest(unittest.TestCase): for valExp,valReal in zip(exp_rez,rez): self.assertAlmostEqual(valExp,valReal) + def test_abs_corr_info(self): + propman = self.prop_man + + defaults = propman.abs_corr_info + self.assertTrue(defaults['is_mc']) + # the algorithm sets up the properties but does not verifis if the prperties + # are acceptable by the corrections algorithm + propman.abs_corr_info = "{is_mc: True, NumberOfWavelengthPoints: 200; MaxScatterPtAttempts=20, SparseInstrument=True}" + + propss = propman.abs_corr_info + self.assertTrue(propss['is_mc']) + self.assertEqual(propss['NumberOfWavelengthPoints'],200) + self.assertEqual(propss['MaxScatterPtAttempts'],20) + self.assertEqual(propss['SparseInstrument'],True) + + # Properties acceptable by MoneCarloAbsorption algorithm + propman.abs_corr_info = {'is_mc':True,'NumberOfWavelengthPoints':20,'EventsPerPoint':'200','SeedValue':31090,\ + 'Interpolation':'CSpline','SparseInstrument':'True','NumberOfDetectorRows':20,'NumberOfDetectorColumns':'10',\ + 'MaxScatterPtAttempts':200} + + propss = propman.abs_corr_info + self.assertTrue(propss['is_mc']) + self.assertTrue(propss['SparseInstrument']) + self.assertEqual(propss['Interpolation'],'CSpline') + + str_val = str(propman.abs_corr_info) + + propman.abs_corr_info = str_val + + rec_prop = propman.abs_corr_info + + self.assertDictEqual(propss,rec_prop) + # Properties acceptable by AbsorptionCorrection algorithm + ac_properties = {'ScatterFrom':'Sample','NumberOfWavelengthPoints':10,'ExpMethod':'FastApprox',\ + 'EMode':'Direct','EFixed':10,'ElementSize':2} + ac_properties['is_fast'] = True + propman.abs_corr_info = str(ac_properties) + + rec_prop = propman.abs_corr_info + self.assertDictEqual(ac_properties,rec_prop) + + propman.abs_corr_info = None + rec_prop = propman.abs_corr_info + self.assertDictEqual(rec_prop,{'is_mc':True}) + + self.assertRaises(KeyError,PropertyManager.abs_corr_info.__set__,propman.abs_corr_info,[1,2,3]) + self.assertRaises(KeyError,PropertyManager.abs_corr_info.__set__,propman.abs_corr_info,{'TheKeyNotRecognizedByAlgorithm':10}) + # + def test_abs_shapes_container(self): + propman = self.prop_man + + cyl = Cylinder(['Al',0.1],[10,1]) + propman.correct_absorption_on = cyl + + got = propman.correct_absorption_on + self.assertEqual(got,cyl) + + str_rep_cyl = str(propman.correct_absorption_on) + is_in = 'Shape' in str_rep_cyl + self.assertTrue(is_in) + is_in = 'Cylinder' in str_rep_cyl + self.assertTrue(is_in) + + propman.correct_absorption_on = str_rep_cyl + + got = propman.correct_absorption_on + + self.assertTrue(isinstance(got,Cylinder)) + self.assertEqual(got.material,cyl.material) + self.assertEqual(got.shape,cyl.shape) + + + if __name__ == "__main__": - #tester = DirectPropertyManagerTest('test_average_accuracy') + #tester = DirectPropertyManagerTest('test_abs_shapes_container') #tester.run() unittest.main() diff --git a/scripts/test/MariReduction.py b/scripts/test/MariReduction.py index b46051330b1c675033121fab566a1a7b1282ae6c..72a8082ca46f60856266de9acd40f8ad3ea11d9e 100644 --- a/scripts/test/MariReduction.py +++ b/scripts/test/MariReduction.py @@ -6,12 +6,17 @@ # SPDX - License - Identifier: GPL - 3.0 + """ Sample MARI reduction scrip used in testing ReductionWrapper """ import os +from Direct.AbsorptionShapes import * from Direct.ReductionWrapper import * try: import reduce_vars as web_var except: web_var = None +try: + import mantidplot as mpl +except: + mpl = None @@ -46,38 +51,230 @@ class ReduceMARI(ReductionWrapper): prop['hard_mask_file'] = "mar11015.msk" prop['det_cal_file'] = 11060 prop['save_format'] = '' + # Uncomment two following properties to correct for absorption + # on sample or sample container during the experiment. + # 1) Define the sample material and sample shape: + # + #prop['correct_absorption_on'] = Cylinder(['Fe'],{'Height':10,'Radius':2}) + # + # The shapes currently available are: + # Cylinder, FlatPlate, HollowCylinder and Sphere + # Each class accepts two parameters, namely dictionary or list describing + # sample material, as accepted by SetSampleMaterial algorithm + # and Shape parameters as accepted by SetSample algorithm. + + # Go to iPython window, type from AbsorptionShapes import * and type + # help(Cylinder), + # help(Sphere) or help(anAbsorptionShape) to learn more about different + # ways of setting the material and shape parameters: + ##---------------------------- + # 2) Provide additional parameters defining speed and accuracy of the + # absorption corrections algorithm in abs_corr_info dictionary. + # + #prop['abs_corr_info'] = {'is_mc':True} + # + #Two generic algorithms are currently available to + # correct for absorption: + # a) MonteCarloAbsorption (invoked by 'is_mc':True key of abs_corr_info + # property and by default) + # and + # b) AbsorptionCorrections (invoked by 'is_fast':True key of + # abs_corr_info property). This algorithm has number of optimizations + # for the special shapes cases and need further scientific validations + + # All other properties, to be provided in the input dictionary of this + # property are the non-sample related properties of the + # correspondent correction algorithm. + # + # Test the speed and the accuracy of the selected algorithm + # reducing all your data by running eval_absorption_corrections method below. + ##------------------------------- + return prop # #-------------------------------------------------------------------------------------------------# @iliad def reduce(self,input_file=None,output_directory=None): """ Method executes reduction over single file - Overload only if custom reduction is needed + Modify only if custom pre or post-processing is needed, namely: """ - ws = ReductionWrapper.reduce(self,input_file,output_directory) - #SaveNexus(ws,Filename = 'MARNewReduction.nxs') - return ws -#------------------------------------------------------------------------------------------------ # - def validate_result(self,build_validation=False): - """ Change this method to verify different results """ - - # Two commented code rows below define location of the validation file in this script folder - # (which is incorrect for Mantid development, as the files are on the data search path or - # mantid distribution, as the files are not there) - #run_dir = os.path.dirname(os.path.realpath(__file__)) - #validation_file = os.path.join(run_dir,"MARIReduction.nxs") - - # build_validation -- if true, build and overwrite new workspace rather then validating the old one - # if file is missing, the validation tries to build it - rez,message = ReductionWrapper.build_or_validate_result(self,11001,"MARIReduction.nxs", - build_validation,1.e-3) - return rez,message + + """ + Define custom preprocessing procedure, to be applied to the whole + run, obtained from the instrument before diagnostics is applied + 1) Get access to the pointer to the input workspace + (the workspace will be loaded and calibrated at this point if it has not been done yet) + using sample_run property class: + run_ws = PropertyManager.sample_run.get_worksapce() + 2) Get access to any property defined in Instrument_Properties.xml file + or redefined in the reduction script: + properties = self.reducer.prop_man + e.g: + RunNumber = properties.sample_run + ei = properties.incident_energy + Perform custom preprocessing procedure (with the values you specified) + preprocessed_ws = custom_preprocessing_procedure(run_ws,RunNumber,ei,...) + 3) Store preprocessed workspace in the sample_run property for further analysis from preprocessing + for the case where the workspace name have changed in the Analysis Data Service + (this situation is difficult to predict so better always do this operation) + PropertyManager.sample_run.synchronize_ws(preprocessed_ws) + """ + + WS = ReductionWrapper.reduce(self,input_file,output_directory) + + """ Defined custrom post-processing procedure, in the way, similar to + the preprocessing procedure, using the WS pointer, returned by the reduction procedure + above. If the run is reduced in multirep mode, the WS is the list of the reduced + workspace pointers, so the procedure should be applied to each workspace. + + At this stage standard saving had already been done, so save your results if you need them + in a future: + for i in range(0:len(WS)): + SaveNexus(WS[i],Filename = 'CustomFilenameN%d.nxs'.format(i)) + """ + return WS + # + + def do_preprocessing(self,reducer,ws): + """ Custom function, applied to each run or every workspace, the run is divided to + in multirep mode + Applied after diagnostics but before any further reduction is invoked. + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + to preprocess + + By default, does nothing. + Add code to do custom preprocessing. + Must return pointer to the preprocessed workspace + """ + return ws + # + + def do_postprocessing(self,reducer,ws): + """ Custom function, applied to each reduced run or every reduced workspace, + the run is divided into, in multirep mode. + Applied after reduction is completed but before saving the result. + + Inputs: + self -- initialized instance of the instrument reduction class + reducer -- initialized instance of the reducer + (DirectEnergyConversion class initialized for specific reduction) + ws the workspace, describing the run or partial run in multirep mode + after reduction to postprocess + + + By default, does nothing. + Add code to do custom postprocessing. + Must return pointer to the postprocessed workspace. + + The postprocessed workspace should be consistent with selected save method. + (E.g. if you decide to convert workspace units to wavelength, you can not save result as nxspe) + """ + return ws + # + def set_custom_output_filename(self): + """define custom name of output files if standard one is not satisfactory + + In addition to that, example of accessing complex reduction properties + Simple reduction properties can be accessed as e.g.: value= prop_man.sum_runs + """ + def custom_name(prop_man): + """Sample function which builds filename from + incident energy and run number and adds some auxiliary information + to it. + """ + # Note -- properties have the same names as the list of advanced + # and main properties + # Note: the properties are stored in prop_man class and accessed as + # below. + ei = PropertyManager.incident_energy.get_current() + # sample run is more then just list of runs, so we use + # the formalization below to access its methods + if self.reducer.prop_man.filename_prefix: + return reduced_filename(0, ei, False, self.reducer.prop_man.filename_prefix) + else: + runs_list = PropertyManager.sample_run.get_run_list() + return reduced_filename(runs_list, ei, self.reducer.prop_man.sum_runs) + # Uncomment this to use custom filename function + #return lambda : custom_name(self.reducer.prop_man) + # Uncomment this to use standard file name generating function + return None + + # + def eval_absorption_corrections(self,test_ws=None): + """ The method to evaluate the speed and efficiency of the absorption corrections procedure, + before applying your corrections to the whole workspace and all sample runs. + + The absorption correction procedure invoked with excessive accuracy can run for too + long providing no real improvements in accuracy. This is why it is recommended to + run this procedure evaluating absorption on selected detectors and + deploy the corrections to the whole runs only after achieving satisfactory accuracy + and execution time. + + The procedure evaluate and prints the expected time to run the absorption corrections + on the whole run. + + Input: + If provided, the pointer or the name of the workspace available in analysis data service. + If it is not, the workspace is taken from PropertyManager.sample_run property + + Usage: + Reduce single run and uncomment this method in the __main__ area to evaluate + adsorption corrections. + + Change adsorption corrections parameters below to achieve best speed and + acceptable accuracy + """ + + # Gain access to the property manager: + propman = rd.reducer.prop_man + # Set up Sample as one of: + # 1) Cylinder([Chem_formula],[Height,Radius]) + # 2) FlatPlate([Chem_formula],[Height,Width,Thick]) + # 3) HollowCylinder([Chem_formula],[Height,InnerRadius,OuterRadius]) + # 4) Sphere([[Chem_formula],Radius) + # The units are in cm + propman.correct_absorption_on = Cylinder('Fe',[10,2]) # Will be taken from def_advanced_properties + # prop['correct_absorption_on'] = if not defined here + # + # Use Monte-Carlo integration. Take sparse energy points and a few integration attempts + # to increase initial speed. Increase these numbers to achieve better accuracy. + propman.abs_corr_info = {'EventsPerPoint':3000}#,'NumberOfWavelengthPoints':30} + # See MonteCarloAbsorption for all possible properties description and possibility to define + # a sparse instrument for speed. + # + # Gain access to the workspace. The workspace should contain Ei log, containing incident energy + # (or be reduced) + if test_ws is None: + test_ws = PropertyManager.sample_run.get_workspace() + # Define spectra list to test absorption on. Empty list will + # define absorption on the whole workspace. + check_spectra = [1,200] + # Evaluate corrections on the selected spectra of the workspace and the time to obtain + # the corrections on the whole workspace. + corrections,time_to_correct_abs = self.evaluate_abs_corrections(test_ws,check_spectra) + # When accuracy and speed of the corrections is satisfactory, copy chosen abs_corr_info + # properties from above to the advanced_porperties area to run in reduction. + if mpl is not None: + n_spectra = len(check_spectra) + if n_spectra == 0: + n_specra = corrections.getNumberHistograms() + mpl.plotSpectrum(corrections,range(0,n_spectra)) + # + return corrections + +#------------------------------------------------------------------------------------------------ def __init__(self,web_var=None): """ sets properties defaults for the instrument with Name""" ReductionWrapper.__init__(self,'MAR',web_var) -#-------------------------------------------------------------------------------------------------# -#-------------------------------------------------------------------------------------------------# + Mt = MethodType(self.do_preprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_preprocessing',Mt) + Mt = MethodType(self.do_postprocessing, self.reducer) + DirectEnergyConversion.__setattr__(self.reducer,'do_postprocessing',Mt) #-------------------------------------------------------------------------------------------------# def main(input_file=None,output_directory=None): """ This method is used to run code from web service @@ -94,7 +291,7 @@ def main(input_file=None,output_directory=None): output_folder = '' return output_folder -if __name__ == "__main__": +if __name__ == "__main__" or __name__ == "__builtin__": #-------------------------------------------------------------------------------------------------# # SECTION USED TO RUN REDUCTION FROM MANTID SCRIPT WINDOW # #-------------------------------------------------------------------------------------------------# @@ -126,5 +323,8 @@ if __name__ == "__main__": # Web-like reduction over sequence of files #rd.reduce() -###### Run reduction on all files provided as parameters ###### - red_ws = rd.run_reduction() +###### Run reduction on all files provided as parameters ###### + rd.run_reduction() + +###### Test absorption corrections to find optimal settings for corrections algorithm ###### +# corr = rd.eval_absorption_corrections() \ No newline at end of file diff --git a/scripts/test/ReductionWrapperTest.py b/scripts/test/ReductionWrapperTest.py index 800b73fa1cdc4003f3e333a0ddd3c3120c2dd686..377fd94a020879e97403b918bbc829416f1019fe 100644 --- a/scripts/test/ReductionWrapperTest.py +++ b/scripts/test/ReductionWrapperTest.py @@ -130,8 +130,8 @@ class ReductionWrapperTest(unittest.TestCase): import reduce_vars as rv - self.assertEqual(rv.standard_vars,main_prop) - self.assertEqual(rv.advanced_vars,adv_prop) + self.assertDictEqual(rv.standard_vars,main_prop) + self.assertDictEqual(rv.advanced_vars,adv_prop) self.assertTrue(hasattr(rv,'variable_help')) imp.reload(mr)