Unverified Commit 9c31ef8d authored by Savici, Andrei T's avatar Savici, Andrei T Committed by GitHub
Browse files

Merge pull request #32524 from mantidproject/deprecated_alias_python_off_master

Implement alias deprecation in Python
parents 6460dd91 9e710c2c
......@@ -171,12 +171,14 @@ public:
/// Function to return all of the seeAlso (these are not validated) algorithms
/// related to this algorithm.A default implementation is provided.
const std::vector<std::string> seeAlso() const override { return {}; };
/// Function to return any aliases to the algorithm; A default implementation
/// is provided
/// function to return any aliases to the algorithm; A default implementation is provided
const std::string alias() const override { return ""; }
/// Flag to indicate if the algorithm is called by its alias.
bool calledByAlias = false;
/// Expiration date (in ISO8601 format) for the algorithm aliases; default implementation for no expiration date
const std::string aliasDeprecated() const override { return ""; }
/// function to return URL for algorithm documentation; A default
/// implementation is provided.
/// Override if the algorithm is not part of the Mantid distribution.
......
......@@ -68,6 +68,9 @@ public:
/// Function to return all of the seeAlso algorithms related to this algorithm
virtual const std::vector<std::string> seeAlso() const = 0;
/// Expiration date (in ISO8601 format) for the algorithm aliases. Empty if no expiration date
virtual const std::string aliasDeprecated() const = 0;
/// function to return any aliases of the algorithm.
virtual const std::string alias() const = 0;
......
......@@ -183,6 +183,12 @@ loading.multifilelimit =
# Hide algorithms that use a Property Manager by default.
algorithms.categories.hidden=Workflow\\Inelastic\\UsesPropertyManager;Workflow\\SANS\\UsesPropertyManager;DataHandling\\LiveData\\Support;Deprecated;Utility\\Development;Remote
# Response upon invoking an algorithm with its alias, when the alias is deprecated.
# Allowed values are:
# "Log": log a warning indicating the alias is deprecated, along with the name to be used
# "Raise": raise a RuntimeError if the deprecated deadline has been met
algorithms.alias.deprecated = Log
# All interface categories are shown by default.
interfaces.categories.hidden =
......
......@@ -54,8 +54,13 @@ public:
/// Returns seeAlso related algorithms.
const std::vector<std::string> seeAlso() const override;
/// Allow the method returning the algorithm aliases to be overridden
const std::string alias() const override;
/// Returns optional documentation URL of the algorithm
/// Allow the method returning the expiration date (in ISO8601 format) for the algorithm aliases to be overridden
const std::string aliasDeprecated() const override;
const std::string helpURL() const override;
/// Allow the isRunning method to be overridden
bool isRunning() const override;
......
......@@ -365,6 +365,9 @@ void export_ialgorithm() {
class_<IAlgorithm, bases<IPropertyManager>, boost::noncopyable>("IAlgorithm", "Interface for all algorithms", no_init)
.def("name", &IAlgorithm::name, arg("self"), "Returns the name of the algorithm")
.def("alias", &IAlgorithm::alias, arg("self"), "Return the aliases for the algorithm")
.def("aliasDeprecated", &IAlgorithm::aliasDeprecated, arg("self"),
"Deprecation date (in ISO8601 format) for the algorithm aliases. "
"Returns empty string if no deprecation date")
.def("version", &IAlgorithm::version, arg("self"), "Returns the version number of the algorithm")
.def("cancel", &IAlgorithm::cancel, arg("self"), "Request that the algorithm stop running")
.def("category", &IAlgorithm::category, arg("self"), "Returns the category containing the algorithm")
......
......@@ -117,6 +117,18 @@ template <typename BaseAlgorithm> const std::string AlgorithmAdapter<BaseAlgorit
}
}
/**
* Returns the expiration date (in ISO8601 format) of algorithm aliases. If not overridden, returns the
* base algorithm implementation
*/
template <typename BaseAlgorithm> const std::string AlgorithmAdapter<BaseAlgorithm>::aliasDeprecated() const {
try {
return callMethod<std::string>(getSelf(), "aliasDeprecated");
} catch (UndefinedAttributeError &) {
return BaseAlgorithm::aliasDeprecated();
}
}
/**
* Returns the summary of the algorithm. If not overridden
* it returns defaultSummary
......
......@@ -29,6 +29,8 @@ Importing this module starts the FrameworkManager instance.
# std libs
from collections import OrderedDict, namedtuple
from contextlib import contextmanager
import datetime
from dateutil.parser import parse as parse_date
import os
import sys
......@@ -37,6 +39,7 @@ import mantid
from mantid import api as _api, kernel as _kernel
from mantid import apiVersion # noqa: F401
from mantid.kernel import plugins as _plugin_helper
from mantid.kernel import ConfigService, logger
from mantid.kernel.funcinspect import customise_func as _customise_func, lhs_info as _lhs_info, \
replace_signature as _replace_signature, LazyFunctionSignature
......@@ -947,7 +950,7 @@ def set_properties(alg_object, *args, **kwargs):
do_set_property(key, value)
def _create_algorithm_function(name, version, algm_object):
def _create_algorithm_function(name, version, algm_object): # noqa: C901
"""
Create a function that will set up and execute an algorithm.
The help that will be displayed is that of the most recent version.
......@@ -956,12 +959,34 @@ def _create_algorithm_function(name, version, algm_object):
:param algm_object: the created algorithm object.
"""
def algorithm_wrapper():
"""
Creates a wrapper object around the algorithm functions.
def algorithm_wrapper(alias=None):
r"""
@brief Creates a wrapper object around the algorithm functions.
@param str alias: Non-empty when the algorithm is to be invoked with this alias instead of its name.
Default `None` indicates an alias is not being used.
"""
class Wrapper:
__slots__ = ["__name__", "__signature__"]
__slots__ = ["__name__", "__signature__", "_alias"]
@staticmethod
def _init_alias(algm_alias):
r"""
@brief Encapsulate alias features on a namedtuple
@param str algm_alias
"""
deprecated = algm_object.aliasDeprecated() # non-empty string when alias set to be deprecated
if deprecated:
try:
parse_date(deprecated)
except ValueError:
deprecated = ''
logger.error(f'Alias deprecation date {deprecated} must be in ISO8601 format')
AlgorithmAlias = namedtuple('AlgorithmAlias', 'name, deprecated')
return AlgorithmAlias(algm_alias, deprecated)
def __init__(self, algm_alias=None):
self._alias = self._init_alias(algm_alias) if algm_alias else None
def __getattribute__(self, item):
obj = object.__getattribute__(self, item)
......@@ -983,6 +1008,14 @@ def _create_algorithm_function(name, version, algm_object):
If both startProgress and endProgress are supplied they will
be used.
"""
# Check at runtime whether to throw upon alias deprecation.
if self._alias and self._alias.deprecated:
deprecated = parse_date(self._alias.deprecated) < datetime.datetime.today()
deprecated_action = ConfigService.Instance().get('algorithms.alias.deprecated', 'Log').lower()
if deprecated and deprecated_action == 'raise':
raise RuntimeError(f'Use of algorithm alias {self._alias.name} not allowed. Use {name} instead')
_version = version
if "Version" in kwargs:
_version = kwargs["Version"]
......@@ -1031,21 +1064,23 @@ def _create_algorithm_function(name, version, algm_object):
msg = '{}-v{}: {}'.format(algm.name(), algm.version(), str(e))
raise RuntimeError(msg) from e
if self._alias and self._alias.deprecated:
logger.error(f'Algorithm alias {self._alias.name} is deprecated. Use {name} instead')
return _gather_returns(name, lhs, algm)
# Set the signature of the callable to be one that is only generated on request.
Wrapper.__call__.__signature__ = LazyFunctionSignature(alg_name=name)
return Wrapper()
# enddef
# Insert definition in to global dict
algm_wrapper = algorithm_wrapper()
algm_wrapper.__name__ = name
wrapper = Wrapper(algm_alias=alias)
wrapper.__name__ = name
return wrapper
globals()[name] = algm_wrapper
# Register aliases - split on whitespace
for alias in algm_object.alias().strip().split():
globals()[alias] = algm_wrapper
# endfor
globals()[alias] = algorithm_wrapper(alias=alias)
algm_wrapper = algorithm_wrapper()
globals()[name] = algorithm_wrapper() # Insert definition in to global dict
return algm_wrapper
......
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright &copy; 2020 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source,
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
# package imports
from mantid.kernel import logger
# third-party imports
from dateutil.parser import parse as parse_date
# standard imports
import functools
def deprecated_alias(deprecation_date): # decorator factory
r"""
:brief: Class decorator marking the algorithm's alias as deprecated
:details: It's assumed that the algorithm implements the alias method
:param str deprecation_date: date of deprecation for the alias, in ISO8601 format
"""
try:
parse_date(deprecation_date)
except ValueError:
logger.error(f'Alias deprecation date {deprecation_date} must be in ISO8601 format')
def decorator_instance(cls):
cls_aliasDeprecated = cls.aliasDeprecated
@functools.wraps(cls_aliasDeprecated)
def new_aliasDeprecated(self):
return deprecation_date
cls.aliasDeprecated = new_aliasDeprecated
return cls
return decorator_instance
......@@ -5,7 +5,7 @@
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
# 3rd party
# package imports
from mantid import config, FileFinder
# standard
......
......@@ -176,8 +176,8 @@ set(PYTHON_PLUGINS
algorithms/SortDetectors.py
algorithms/StatisticsOfTableWorkspace.py
algorithms/StringToPng.py
algorithms/SuggestTibCNCS.py
algorithms/SuggestTibHYSPEC.py
algorithms/CNCSSuggestTIB.py
algorithms/HYSPECSuggestTIB.py
algorithms/Symmetrise.py
algorithms/TOFTOFCropWorkspace.py
algorithms/TOFTOFMergeRuns.py
......
......@@ -5,8 +5,13 @@
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
#pylint: disable=no-init,invalid-name
# package imports
from mantid.api import PythonAlgorithm, AlgorithmFactory
from mantid.kernel import FloatBoundedValidator,Direction
from mantid.utils.deprecator import deprecated_alias
# third party imports
import numpy as np
#pylint: disable=too-few-public-methods
......@@ -16,36 +21,45 @@ class Interval(object):
"""Simple class that provides check for overlapping intervals
"""
def __init__(self,minv,maxv):
self.min=minv
self.max=maxv
def __init__(self, minv, maxv):
self.min = minv
self.max = maxv
def overlap(self, other):
if other.max >self.min and other.max <self.max:
if other.max > self.min and other.max < self.max:
return True
if other.min >self.min and other.min<self.max:
if other.min > self.min and other.min < self.max:
return True
if other.min<self.min and other.max>self.max:
if other.min < self.min and other.max > self.max:
return True
return False
class SuggestTibCNCS(PythonAlgorithm):
@deprecated_alias('2021-09-16')
class CNCSSuggestTIB(PythonAlgorithm):
""" Check if certain sample logs exists on a workspace
"""
@staticmethod
def e2v(energy):
return np.sqrt(energy / 5.227e-6)
def alias(self):
r"""Alternative name to this algorithm"""
return 'SuggestTibCNCS'
def category(self):
""" Return category
"""
return "Inelastic\\Utility"
def seeAlso(self):
return [ "SuggestTibHYSPEC" ]
return ["HYSPECSuggestTIB"]
def name(self):
""" Return name
"""
return "SuggestTibCNCS"
return "CNCSSuggestTIB"
def summary(self):
""" Return summary
......@@ -55,98 +69,95 @@ class SuggestTibCNCS(PythonAlgorithm):
def PyInit(self):
""" Declare properties
"""
val=FloatBoundedValidator()
val.setBounds(0.5,50) #reasonable incident nergy range for CNCS
self.declareProperty("IncidentEnergy",0.,val,"Incident energy (0.5 to 50 meV)")
self.declareProperty("TibMin",0.,Direction.Output)
self.declareProperty("TibMax",0.,Direction.Output)
val = FloatBoundedValidator()
val.setBounds(0.5, 50) # reasonable incident energy range for CNCS
self.declareProperty("IncidentEnergy", 0., val, "Incident energy (0.5 to 50 meV)")
self.declareProperty("TibMin", 0., Direction.Output)
self.declareProperty("TibMax", 0., Direction.Output)
return
def e2v(self,energy):
return np.sqrt(energy/5.227e-6)
#pylint: disable=too-many-branches
# pylint: disable=too-many-branches
def PyExec(self):
""" Main execution body
"""
#get parameter
# get parameter
energy = self.getProperty("IncidentEnergy").value
frame=1e6/60.
#calculate tel, tmin, tmax, tinf, tpulse
tel=1e6*(3.5+36.262)/self.e2v(energy)
tmin=tel-frame*0.5
if tmin<0:
tmin=0.
tmax=tmin+frame
tinf=1e6*(36.262)/self.e2v(energy)
if tinf<tmin:
tinf+=frame
tpulse=frame*np.floor(tmax/frame)
#check for TIB
dtib=3500. # default length of TIB range
dtibreduced=2500 #reduced range
dtinfminus=500
dtinfplus=1500
dtpulseminus=50
dtpulseplus=1500
#Create intervals that cannot be used for TIB. For ease,
#move everything to times lower than t_inf, make sure
#one doesn't overlap with the frame edge, then if the TIB
#interval is in the previous frame, jut move it up
intervalList=[]
intervalList.append(Interval(tinf-dtinfminus,tinf)) #interval close to t_inf, on the lower side
#intervaldenoting frame edge. This will make sure that one cannot get an interval overlapping t_min
intervalList.append(Interval(tmin,tmin))
#interval close to t_inf, on the upper side, but moved one frame down
intervalList.append(Interval(tinf-frame,tinf-frame+dtinfplus))
if tpulse+dtpulseplus<tmax:
itpulse=Interval(tpulse-dtpulseminus,tpulse+dtpulseplus)
frame = 1e6 / 60.
# calculate tel, tmin, tmax, tinf, tpulse
tel = 1e6 * (3.5 + 36.262) / self.e2v(energy)
tmin = tel - frame * 0.5
if tmin < 0:
tmin = 0.
tmax = tmin + frame
tinf = 1e6 * (36.262) / self.e2v(energy)
if tinf < tmin:
tinf += frame
tpulse = frame * np.floor(tmax / frame)
# check for TIB
dtib = 3500. # default length of TIB range
dtibreduced = 2500 # reduced range
dtinfminus = 500
dtinfplus = 1500
dtpulseminus = 50
dtpulseplus = 1500
# Create intervals that cannot be used for TIB. For ease,
# move everything to times lower than t_inf, make sure
# one doesn't overlap with the frame edge, then if the TIB
# interval is in the previous frame, jut move it up
intervalList = []
intervalList.append(Interval(tinf - dtinfminus, tinf)) # interval close to t_inf, on the lower side
# interval denoting frame edge. This will make sure that one cannot get an interval overlapping t_min
intervalList.append(Interval(tmin, tmin))
# interval close to t_inf, on the upper side, but moved one frame down
intervalList.append(Interval(tinf - frame, tinf - frame + dtinfplus))
if tpulse + dtpulseplus < tmax:
itpulse = Interval(tpulse - dtpulseminus, tpulse + dtpulseplus)
else:
itpulse=Interval(tpulse-dtpulseminus-frame,tpulse+dtpulseplus-frame)
itpulse = Interval(tpulse - dtpulseminus - frame, tpulse + dtpulseplus - frame)
if itpulse.overlap(Interval(tinf,tinf)):
#if the prompt pulse overlaps with t_inf move the upper part one frame down
intervalList.append(Interval(itpulse.min,tinf))
intervalList.append(Interval(tinf-frame,itpulse.max-frame))
if itpulse.overlap(Interval(tinf, tinf)):
# if the prompt pulse overlaps with t_inf move the upper part one frame down
intervalList.append(Interval(itpulse.min, tinf))
intervalList.append(Interval(tinf - frame, itpulse.max - frame))
else:
if tinf<itpulse.min:
itpulse=Interval(itpulse.min-frame,itpulse.max-frame)
if tinf < itpulse.min:
itpulse = Interval(itpulse.min - frame, itpulse.max - frame)
intervalList.append(itpulse)
#create the list of times to checked. These are the lower parts of the intervals
timestocheck=[]
# create the list of times to checked. These are the lower parts of the intervals
timestocheck = []
for i in intervalList:
if i.min>tinf-frame:
if i.min > tinf - frame:
timestocheck.append(i.min)
timestocheck.sort()
timestocheck.reverse()
for t in timestocheck:
tInterval=Interval(t-dtib,t)
if all( not inter.overlap(tInterval) for inter in intervalList ):
tibmin=tInterval.min
tibmax=tInterval.max
tInterval = Interval(t - dtib, t)
if all(not inter.overlap(tInterval) for inter in intervalList):
tibmin = tInterval.min
tibmax = tInterval.max
break
tInterval=Interval(t-dtibreduced,t)
if all( not inter.overlap(tInterval) for inter in intervalList ):
tibmin=tInterval.min
tibmax=tInterval.max
tInterval = Interval(t - dtibreduced, t)
if all(not inter.overlap(tInterval) for inter in intervalList):
tibmin = tInterval.min
tibmax = tInterval.max
break
#move to the data frame
if tibmin<tmin:
tibmin+=frame
tibmax+=frame
# move to the data frame
if tibmin < tmin:
tibmin += frame
tibmax += frame
#return the result
self.setProperty("TibMin",tibmin+50)
self.setProperty("TibMax",tibmax-50)
# return the result
self.setProperty("TibMin", tibmin + 50)
self.setProperty("TibMax", tibmax - 50)
return
AlgorithmFactory.subscribe(SuggestTibCNCS)
AlgorithmFactory.subscribe(CNCSSuggestTIB)
......@@ -5,27 +5,37 @@
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
#pylint: disable=no-init
# package imports
from mantid.api import PythonAlgorithm, AlgorithmFactory
from mantid.kernel import FloatBoundedValidator,Direction,logger
from mantid.kernel import FloatBoundedValidator, Direction, logger
from mantid.utils.deprecator import deprecated_alias
# third party imports
import numpy
class SuggestTibHYSPEC(PythonAlgorithm):
@deprecated_alias('2021-09-16')
class HYSPECSuggestTIB(PythonAlgorithm):
""" Check if certain sample logs exists on a workspace
"""
def alias(self):
r"""Alternative name to this algorithm"""
return 'SuggestTibHYSPEC'
def category(self):
""" Return category
"""
return "Inelastic\\Utility"
def seeAlso(self):
return [ "SuggestTibCNCS" ]
return ["CNCSSuggestTIB"]
def name(self):
""" Return name
"""
return "SuggestTibHYSPEC"
return "HYSPECSuggestTIB"
def summary(self):
""" Return summary
......@@ -35,15 +45,15 @@ class SuggestTibHYSPEC(PythonAlgorithm):
def PyInit(self):
""" Declare properties
"""
val=FloatBoundedValidator()
val.setBounds(3,100) #reasonable incident nergy range for HYSPEC
self.declareProperty("IncidentEnergy",0.,val,"Incident energy (3 to 100 meV)")
self.declareProperty("TibMin",0.,Direction.Output)
self.declareProperty("TibMax",0.,Direction.Output)
val = FloatBoundedValidator()
val.setBounds(3, 100) # reasonable incident nergy range for HYSPEC
self.declareProperty("IncidentEnergy", 0., val, "Incident energy (3 to 100 meV)")
self.declareProperty("TibMin", 0., Direction.Output)
self.declareProperty("TibMax", 0., Direction.Output)
return
def e2v(self,energy):
return numpy.sqrt(energy/5.227e-6)
def e2v(self, energy):
return numpy.sqrt(energy / 5.227e-6)
def PyExec(self):
""" Main execution body
......@@ -51,26 +61,26 @@ class SuggestTibHYSPEC(PythonAlgorithm):
#get parameter
energy = self.getProperty("IncidentEnergy").value
msd=1800.0
msd = 1800.0
tail_length_us = 3000.0
dist_mm = 39000.0 + msd + 4500.0
T0_moderator = 0.0
t_focEle_us = 39000.0 / self.e2v(energy) * 1000.0 + T0_moderator
t_samp_us = (dist_mm - 4500.0) / self.e2v(energy) * 1000.0 + T0_moderator
t_det_us = dist_mm /self.e2v(energy) * 1000 + T0_moderator
frame_start_us = t_det_us - 16667/2
frame_end_us = t_det_us + 16667/2
index_under_frame = int(numpy.divide(int(t_det_us),16667))
t_det_us = dist_mm / self.e2v(energy) * 1000 + T0_moderator
frame_start_us = t_det_us - 16667 / 2