From 25073361aad4e42c0ddbe017b9d7c0c5d60043e4 Mon Sep 17 00:00:00 2001 From: rboston628 Date: Fri, 10 Jan 2025 16:58:37 -0500 Subject: [PATCH] safety measures --- environment.yml | 2 +- .../CalculateDiffCalResidualIngredients.py | 14 ---- .../dao/request/CalculateResidualRequest.py | 2 +- .../recipe/ApplyNormalizationRecipe.py | 7 +- .../recipe/CalculateDiffCalResidualRecipe.py | 50 ++++++------- .../recipe/EffectiveInstrumentRecipe.py | 8 ++- .../recipe/GenerateFocussedVanadiumRecipe.py | 8 ++- .../backend/recipe/GroupDiffCalRecipe.py | 30 ++++---- .../backend/recipe/PixelDiffCalRecipe.py | 12 ++++ .../recipe/PreprocessReductionRecipe.py | 21 ++++-- .../backend/recipe/ReadWorkspaceMetadata.py | 5 +- .../recipe/RebinFocussedGroupDataRecipe.py | 7 +- src/snapred/backend/recipe/Recipe.py | 22 ++++-- .../recipe/ReductionGroupProcessingRecipe.py | 19 +++-- src/snapred/backend/recipe/ReductionRecipe.py | 55 ++++++++------ .../backend/recipe/WriteWorkspaceMetadata.py | 6 ++ .../backend/service/CalibrationService.py | 10 +-- .../backend/service/NormalizationService.py | 8 ++- src/snapred/ui/workflow/DiffCalWorkflow.py | 2 +- tests/resources/application.yml | 4 +- .../test_CalculateDiffCalResidualRecipe.py | 43 +++++++++++ .../recipe/test_EffectiveInstrumentRecipe.py | 24 ++++--- .../backend/recipe/test_GroupDiffCalRecipe.py | 2 +- .../recipe/test_ReadWorkspaceMetadata.py | 8 +++ tests/unit/backend/recipe/test_Recipe.py | 38 +++++++++- .../test_ReductionGroupProcessingRecipe.py | 6 -- .../backend/recipe/test_ReductionRecipe.py | 72 ++++++++++++++----- .../recipe/test_WriteWorkspaceMetadata.py | 4 ++ .../service/test_CalibrationService.py | 11 ++- 29 files changed, 343 insertions(+), 157 deletions(-) delete mode 100644 src/snapred/backend/dao/ingredients/CalculateDiffCalResidualIngredients.py create mode 100644 tests/unit/backend/recipe/test_CalculateDiffCalResidualRecipe.py diff --git a/environment.yml b/environment.yml index 25feddb2b..5d6f6918e 100644 --- a/environment.yml +++ b/environment.yml @@ -7,7 +7,7 @@ dependencies: - python=3.10 - pip - pydantic>=2.7.3,<3 -- mantidworkbench>=6.11.20250101 +- mantidworkbench>=6.11.20250107.1932 - qtpy - pre-commit - pytest diff --git a/src/snapred/backend/dao/ingredients/CalculateDiffCalResidualIngredients.py b/src/snapred/backend/dao/ingredients/CalculateDiffCalResidualIngredients.py deleted file mode 100644 index 8c24b4e1e..000000000 --- a/src/snapred/backend/dao/ingredients/CalculateDiffCalResidualIngredients.py +++ /dev/null @@ -1,14 +0,0 @@ -from pydantic import BaseModel, ConfigDict - -from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceName - - -class CalculateDiffCalResidualIngredients(BaseModel): - inputWorkspace: WorkspaceName - outputWorkspace: WorkspaceName - fitPeaksDiagnosticWorkspace: WorkspaceName - - model_config = ConfigDict( - # required in order to use 'WorkspaceName' - arbitrary_types_allowed=True, - ) diff --git a/src/snapred/backend/dao/request/CalculateResidualRequest.py b/src/snapred/backend/dao/request/CalculateResidualRequest.py index 144f4ab1f..573d85d27 100644 --- a/src/snapred/backend/dao/request/CalculateResidualRequest.py +++ b/src/snapred/backend/dao/request/CalculateResidualRequest.py @@ -6,7 +6,7 @@ class CalculateResidualRequest(BaseModel): inputWorkspace: WorkspaceName outputWorkspace: WorkspaceName - fitPeaksDiagnostic: WorkspaceName + fitPeaksDiagnosticWorkspace: WorkspaceName model_config = ConfigDict( # required in order to use 'WorkspaceName' diff --git a/src/snapred/backend/recipe/ApplyNormalizationRecipe.py b/src/snapred/backend/recipe/ApplyNormalizationRecipe.py index 25c56adc9..2db3ef778 100644 --- a/src/snapred/backend/recipe/ApplyNormalizationRecipe.py +++ b/src/snapred/backend/recipe/ApplyNormalizationRecipe.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Tuple +from typing import Any, Dict, List, Set, Tuple from snapred.backend.dao.ingredients import ApplyNormalizationIngredients as Ingredients from snapred.backend.log.logger import snapredLogger @@ -18,7 +18,10 @@ class ApplyNormalizationRecipe(Recipe[Ingredients]): NUM_BINS = Config["constants.ResampleX.NumberBins"] LOG_BINNING = True - def mandatoryInputWorkspaces(self): + def allGroceryKeys(self) -> Set[str]: + return {"inputWorkspace", "normalizationWorkspace", "backgroundWorkspace"} + + def mandatoryInputWorkspaces(self) -> Set[str]: return {"inputWorkspace"} def chopIngredients(self, ingredients: Ingredients): diff --git a/src/snapred/backend/recipe/CalculateDiffCalResidualRecipe.py b/src/snapred/backend/recipe/CalculateDiffCalResidualRecipe.py index 8f1bbcc41..2ce9a2d7f 100644 --- a/src/snapred/backend/recipe/CalculateDiffCalResidualRecipe.py +++ b/src/snapred/backend/recipe/CalculateDiffCalResidualRecipe.py @@ -1,11 +1,9 @@ -from typing import Dict +from types import NoneType +from typing import Dict, Set import numpy as np from pydantic import BaseModel -from snapred.backend.dao.ingredients.CalculateDiffCalResidualIngredients import ( - CalculateDiffCalResidualIngredients as Ingredients, -) from snapred.backend.log.logger import snapredLogger from snapred.backend.recipe.algorithm.Utensils import Utensils from snapred.backend.recipe.Recipe import Recipe @@ -18,7 +16,7 @@ class CalculateDiffCalServing(BaseModel): outputWorkspace: str -class CalculateDiffCalResidualRecipe(Recipe[Ingredients]): +class CalculateDiffCalResidualRecipe(Recipe[None]): def __init__(self, utensils: Utensils = None): if utensils is None: utensils = Utensils() @@ -29,29 +27,31 @@ def __init__(self, utensils: Utensils = None): def logger(self): return logger - def validateInputs(self, ingredients: Ingredients, groceries: Dict[str, WorkspaceName]): - super().validateInputs(ingredients, groceries) + def allGroceryKeys(self) -> Set[str]: + return {"inputWorkspace", "outputWorkspace", "fitPeaksDiagnosticWorkspace"} - def chopIngredients(self, ingredients: Ingredients) -> None: - """Receive the ingredients from the recipe.""" - self.inputWorkspaceName = ingredients.inputWorkspace - self.outputWorkspaceName = ingredients.outputWorkspace - inputGroupWorkspace = ingredients.fitPeaksDiagnosticWorkspace - - fitPeaksGroupWorkspace = self.mantidSnapper.mtd[inputGroupWorkspace] - lastWorkspaceName = fitPeaksGroupWorkspace.getNames()[-1] - self.fitPeaksDiagnosticWorkSpaceName = lastWorkspaceName + def mandatoryInputWorkspaces(self) -> Set[str]: + return {"inputWorkspace", "fitPeaksDiagnosticWorkspace"} - def unbagGroceries(self): + def chopIngredients(self, ingredients: NoneType = None) -> None: + """Receive the ingredients from the recipe.""" pass - def prep(self, ingredients: Ingredients): + def unbagGroceries(self, groceries: Dict[str, WorkspaceName]): + self.inputWorkspaceName = groceries["inputWorkspace"] + self.outputWorkspaceName = groceries["outputWorkspace"] + diagnosticWSname = groceries["fitPeaksDiagnosticWorkspace"] + diagnosticWorkspace = self.mantidSnapper.mtd[diagnosticWSname] + lastWorkspaceName = diagnosticWorkspace.getNames()[-1] + self.fitPeaksDiagnosticWorkspaceName = lastWorkspaceName + + def prep(self, ingredients: NoneType, groceries: Dict[str, WorkspaceName]): """ Convenience method to prepare the recipe for execution. """ - self.validateInputs(ingredients, groceries=None) + self.validateInputs(ingredients, groceries) self.chopIngredients(ingredients) - self.unbagGroceries() + self.unbagGroceries(groceries) self.stirInputs() self.queueAlgos() @@ -64,14 +64,14 @@ def queueAlgos(self): ) # Step 2: Check for overlapping spectra and manage them - fitPeaksWorkspace = self.mantidSnapper.mtd[self.fitPeaksDiagnosticWorkSpaceName] + fitPeaksWorkspace = self.mantidSnapper.mtd[self.fitPeaksDiagnosticWorkspaceName] numHistograms = fitPeaksWorkspace.getNumberHistograms() processedSpectra = [] spectrumDict = {} for i in range(numHistograms): spectrumId = fitPeaksWorkspace.getSpectrum(i).getSpectrumNo() - singleSpectrumName = f"{self.fitPeaksDiagnosticWorkSpaceName}_spectrum_{spectrumId}" + singleSpectrumName = f"{self.fitPeaksDiagnosticWorkspaceName}_spectrum_{spectrumId}" # If this spectrum number is already processed, average with existing if spectrumId in spectrumDict: @@ -86,7 +86,7 @@ def queueAlgos(self): # Extract spectrum by position self.mantidSnapper.ExtractSingleSpectrum( f"Extracting spectrum with SpectrumNumber {spectrumId}...", - InputWorkspace=self.fitPeaksDiagnosticWorkSpaceName, + InputWorkspace=self.fitPeaksDiagnosticWorkspaceName, OutputWorkspace=singleSpectrumName, WorkspaceIndex=i, ) @@ -128,8 +128,8 @@ def execute(self): # Set the output property to the final residual workspace self.outputWorkspace = self.mantidSnapper.mtd[self.outputWorkspaceName] - def cook(self, ingredients: Ingredients): - self.prep(ingredients) + def cook(self, ingredients: NoneType, groceries: Dict[str, WorkspaceName]): # noqa ARG002 + self.prep(None, groceries) self.execute() return CalculateDiffCalServing( outputWorkspace=self.outputWorkspaceName, diff --git a/src/snapred/backend/recipe/EffectiveInstrumentRecipe.py b/src/snapred/backend/recipe/EffectiveInstrumentRecipe.py index cd79bec05..57793171c 100644 --- a/src/snapred/backend/recipe/EffectiveInstrumentRecipe.py +++ b/src/snapred/backend/recipe/EffectiveInstrumentRecipe.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Tuple +from typing import Any, Dict, List, Set, Tuple import numpy as np @@ -16,6 +16,12 @@ @Singleton class EffectiveInstrumentRecipe(Recipe[Ingredients]): + def allGroceryKeys(self) -> Set[str]: + return {"inputWorkspace", "outputWorkspace"} + + def mandatoryInputWorkspaces(self) -> Set[str]: + return {"inputWorkspace"} + def unbagGroceries(self, groceries: Dict[str, Any]): self.inputWS = groceries["inputWorkspace"] self.outputWS = groceries.get("outputWorkspace", groceries["inputWorkspace"]) diff --git a/src/snapred/backend/recipe/GenerateFocussedVanadiumRecipe.py b/src/snapred/backend/recipe/GenerateFocussedVanadiumRecipe.py index b29780b19..853057c5c 100644 --- a/src/snapred/backend/recipe/GenerateFocussedVanadiumRecipe.py +++ b/src/snapred/backend/recipe/GenerateFocussedVanadiumRecipe.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Tuple +from typing import Any, Dict, List, Set, Tuple from snapred.backend.dao.ingredients import GenerateFocussedVanadiumIngredients as Ingredients from snapred.backend.log.logger import snapredLogger @@ -23,6 +23,12 @@ class GenerateFocussedVanadiumRecipe(Recipe[Ingredients]): """ + def allGroceryKeys(self) -> Set[str]: + return {"inputWorkspace", "outputWorkspace"} + + def mandatoryInputWorkspaces(self) -> Set[str]: + return {"inputWorkspace"} + def chopIngredients(self, ingredients: Ingredients): self.smoothingParameter = ingredients.smoothingParameter self.detectorPeaks = ingredients.detectorPeaks diff --git a/src/snapred/backend/recipe/GroupDiffCalRecipe.py b/src/snapred/backend/recipe/GroupDiffCalRecipe.py index 2a93b8fef..f9d0c8e3e 100644 --- a/src/snapred/backend/recipe/GroupDiffCalRecipe.py +++ b/src/snapred/backend/recipe/GroupDiffCalRecipe.py @@ -30,17 +30,6 @@ class GroupDiffCalRecipe(Recipe[Ingredients]): NOYZE_2_MIN = Config["calibration.fitting.minSignal2Noise"] MAX_CHI_SQ = Config["constants.GroupDiffractionCalibration.MaxChiSq"] - GROCERIES = { - # NOTE this would be better as a StrEnum, which requires python 3.11 - "inputWorkspace", - "groupingWorkspace", - "maskWorkspace", - "outputWorkspace", - "diagnosticWorkspace", - "previousCalibration", - "calibrationTable", - } - def __init__(self, utensils: Utensils = None): if utensils is None: utensils = Utensils() @@ -51,18 +40,23 @@ def __init__(self, utensils: Utensils = None): def logger(self): return logger - def mandatoryInputWorkspaces(self) -> Set[WorkspaceName]: + def allGroceryKeys(self) -> Set[str]: + return { + "inputWorkspace", + "groupingWorkspace", + "maskWorkspace", + "outputWorkspace", + "diagnosticWorkspace", + "previousCalibration", + "calibrationTable", + } + + def mandatoryInputWorkspaces(self) -> Set[str]: return {"inputWorkspace", "groupingWorkspace"} def validateInputs(self, ingredients: Ingredients, groceries: Dict[str, WorkspaceName]): super().validateInputs(ingredients, groceries) - # make sure no invalid keys were passed - # NOTE this is for safer refactor, but not necessary for proper functioning - diff = set(groceries.keys()).difference(self.GROCERIES) - if bool(diff): - raise RuntimeError(f"The following invalid keys were found in the input groceries: {diff}") - pixelGroupIDs = ingredients.pixelGroup.groupIDs groupIDs = [peakList.groupID for peakList in ingredients.groupedPeakLists] if groupIDs != pixelGroupIDs: diff --git a/src/snapred/backend/recipe/PixelDiffCalRecipe.py b/src/snapred/backend/recipe/PixelDiffCalRecipe.py index 091746c72..ed12192a4 100644 --- a/src/snapred/backend/recipe/PixelDiffCalRecipe.py +++ b/src/snapred/backend/recipe/PixelDiffCalRecipe.py @@ -43,6 +43,18 @@ def __init__(self, utensils: Utensils = None): def logger(self): return logger + def allGroceryKeys(self) -> Set[str]: + return { + "inputWorkspace", + "groupingWorkspace", + "calibrationTable", + "maskWorkspace", + "previousCalibration", + # NOTE these are used only in the entire diff cal workflow + "diagnosticWorkspace", + "outputWorkspace", + } + def mandatoryInputWorkspaces(self) -> Set[str]: return {"inputWorkspace", "groupingWorkspace"} diff --git a/src/snapred/backend/recipe/PreprocessReductionRecipe.py b/src/snapred/backend/recipe/PreprocessReductionRecipe.py index 76da2a20f..b57da5cf4 100644 --- a/src/snapred/backend/recipe/PreprocessReductionRecipe.py +++ b/src/snapred/backend/recipe/PreprocessReductionRecipe.py @@ -4,7 +4,6 @@ from snapred.backend.log.logger import snapredLogger from snapred.backend.recipe.Recipe import Recipe from snapred.meta.decorators.Singleton import Singleton -from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceName logger = snapredLogger.getLogger(__name__) @@ -19,7 +18,17 @@ def chopIngredients(self, ingredients: Ingredients): """ pass - def mandatoryInputWorkspaces(self) -> Set[WorkspaceName]: + def allGroceryKeys(self) -> Set[str]: + return { + "inputWorkspace", + "backgroundWorkspace", + "groupingWorkspace", + "diffcalWorkspace", + "maskWorkspace", + "outputWorkspace", + } + + def mandatoryInputWorkspaces(self) -> Set[str]: return {"inputWorkspace"} def unbagGroceries(self, groceries: Dict[str, Any]): @@ -46,16 +55,18 @@ def queueAlgos(self): OutputWorkspace=self.outputWs, ) - if self.maskWs: + if self.maskWs != "": self.mantidSnapper.MaskDetectorFlags( "Applying pixel mask...", MaskWorkspace=self.maskWs, OutputWorkspace=self.outputWs, ) - if self.diffcalWs: + if self.diffcalWs != "": self.mantidSnapper.ApplyDiffCal( - "Applying diffcal..", InstrumentWorkspace=self.outputWs, CalibrationWorkspace=self.diffcalWs + "Applying diffcal..", + InstrumentWorkspace=self.outputWs, + CalibrationWorkspace=self.diffcalWs, ) # convert to tof if needed diff --git a/src/snapred/backend/recipe/ReadWorkspaceMetadata.py b/src/snapred/backend/recipe/ReadWorkspaceMetadata.py index fb91ee3c8..e6a946896 100644 --- a/src/snapred/backend/recipe/ReadWorkspaceMetadata.py +++ b/src/snapred/backend/recipe/ReadWorkspaceMetadata.py @@ -16,7 +16,10 @@ class ReadWorkspaceMetadata(Recipe[WorkspaceMetadata]): TAG_PREFIX = Config["metadata.tagPrefix"] - def mandatoryInputWorkspaces(self) -> Set[WorkspaceName]: + def allGroceryKeys(self) -> Set[str]: + return {"workspace"} + + def mandatoryInputWorkspaces(self) -> Set[str]: return {"workspace"} def chopIngredients(self, ingredients): # noqa ARG002 diff --git a/src/snapred/backend/recipe/RebinFocussedGroupDataRecipe.py b/src/snapred/backend/recipe/RebinFocussedGroupDataRecipe.py index f75663d1b..8c75ff3a0 100644 --- a/src/snapred/backend/recipe/RebinFocussedGroupDataRecipe.py +++ b/src/snapred/backend/recipe/RebinFocussedGroupDataRecipe.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Tuple +from typing import Any, Dict, Set, Tuple from snapred.backend.dao.ingredients import RebinFocussedGroupDataIngredients as Ingredients from snapred.backend.log.logger import snapredLogger @@ -17,7 +17,10 @@ class RebinFocussedGroupDataRecipe(Recipe[Ingredients]): NUM_BINS = Config["constants.ResampleX.NumberBins"] LOG_BINNING = True - def mandatoryInputWorkspaces(self): + def allGroceryKeys(self) -> Set[str]: + return {"inputWorkspace"} + + def mandatoryInputWorkspaces(self) -> Set[str]: return {"inputWorkspace"} def chopIngredients(self, ingredients: Ingredients): diff --git a/src/snapred/backend/recipe/Recipe.py b/src/snapred/backend/recipe/Recipe.py index 9765d1e09..7790a453a 100644 --- a/src/snapred/backend/recipe/Recipe.py +++ b/src/snapred/backend/recipe/Recipe.py @@ -52,13 +52,20 @@ def queueAlgos(self): Requires: unbagged groceries and chopped ingredients. """ + @abstractmethod + def allGroceryKeys(self) -> Set[str]: + """ + A set of all possible keys which may be in the grocery dictionary + """ + return set() + # methods which MAY be kept as is - def mandatoryInputWorkspaces(self) -> Set[WorkspaceName]: + def mandatoryInputWorkspaces(self) -> Set[str]: """ - A list of workspace names corresponding to mandatory inputs + A set of workspace keys corresponding to mandatory inputs """ - return {} + return set() @classmethod def Ingredients(cls, **kwargs): @@ -104,7 +111,14 @@ def validateInputs(self, ingredients: Ingredients, groceries: Dict[str, Workspac else: logger.info("No ingredients given, skipping ingredient validation") pass - # ensure all of the given workspaces exist + + # make sure no invalid keys were passed + if groceries is not None: + diff = set(groceries.keys()).difference(self.allGroceryKeys()) + if bool(diff): + raise ValueError(f"The following invalid keys were found in the input groceries: {diff}") + + # ensure all of the mandatory workspaces exist # NOTE may need to be tweaked to ignore output workspaces... if groceries is not None: logger.info(f"Validating the given workspaces: {groceries.values()}") diff --git a/src/snapred/backend/recipe/ReductionGroupProcessingRecipe.py b/src/snapred/backend/recipe/ReductionGroupProcessingRecipe.py index 423dc6f43..d4cc59efd 100644 --- a/src/snapred/backend/recipe/ReductionGroupProcessingRecipe.py +++ b/src/snapred/backend/recipe/ReductionGroupProcessingRecipe.py @@ -13,11 +13,23 @@ @Singleton class ReductionGroupProcessingRecipe(Recipe[Ingredients]): + def allGroceryKeys(self): + return { + "inputWorkspace", + "groupingWorkspace", + "outputWorkspace", + # the following need to be here for consistency + "diffcalWorkspace", + "maskWorkspace", + "backgroundWorkspace", + } + + def mandatoryInputWorkspaces(self) -> Set[WorkspaceName]: + return {"inputWorkspace", "groupingWorkspace"} + def unbagGroceries(self, groceries: Dict[str, Any]): self.rawInput = groceries["inputWorkspace"] self.outputWS = groceries.get("outputWorkspace", groceries["inputWorkspace"]) - # self.geometryOutputWS = groceries["geometryOutputWorkspace"] - # self.diffFocOutputWS = groceries["diffFocOutputWorkspace"] self.groupingWS = groceries["groupingWorkspace"] def chopIngredients(self, ingredients): @@ -50,9 +62,6 @@ def queueAlgos(self): OutputWorkspace=self.outputWS, ) - def mandatoryInputWorkspaces(self) -> Set[WorkspaceName]: - return {"inputWorkspace", "groupingWorkspace"} - def execute(self): """ Final step in a recipe, executes the queued algorithms. diff --git a/src/snapred/backend/recipe/ReductionRecipe.py b/src/snapred/backend/recipe/ReductionRecipe.py index 65da198a2..32a550b64 100644 --- a/src/snapred/backend/recipe/ReductionRecipe.py +++ b/src/snapred/backend/recipe/ReductionRecipe.py @@ -43,7 +43,16 @@ class ReductionRecipe(Recipe[Ingredients]): def logger(self): return logger - def mandatoryInputWorkspaces(self) -> Set[WorkspaceName]: + def allGroceryKeys(self) -> Set[str]: + return { + "inputWorkspace", + "groupingWorkspaces", + "diffcalWorkspace", + "normalizationWorkspace", + "combinedPixelMask", + } + + def mandatoryInputWorkspaces(self) -> Set[str]: return {"inputWorkspace", "groupingWorkspaces"} def chopIngredients(self, ingredients: Ingredients): @@ -62,8 +71,9 @@ def unbagGroceries(self, groceries: Dict[str, Any]): """ self.groceries = groceries.copy() self.sampleWs = groceries["inputWorkspace"] + self.diffcalWs = groceries.get("diffcalWorkspace", "") self.normalizationWs = groceries.get("normalizationWorkspace", "") - self.maskWs = groceries.get("combinedMask", "") + self.maskWs = groceries.get("combinedPixelMask", "") self.groupingWorkspaces = groceries["groupingWorkspaces"] def _cloneWorkspace(self, inputWorkspace: str, outputWorkspace: str) -> str: @@ -143,24 +153,21 @@ def _prepareArtificialNormalization(self, inputWorkspace: str, groupIndex: int) return normalizationWorkspace def _applyRecipe(self, recipe: Type[Recipe], ingredients_, **kwargs): - if "inputWorkspace" in kwargs: - inputWorkspace = kwargs["inputWorkspace"] - if not inputWorkspace: - self.logger().debug(f"{recipe.__name__} :: Skipping recipe with default empty input workspace") - return - if "outputWorkspace" not in kwargs and "outputWorkspace" in self.groceries: - del self.groceries["outputWorkspace"] - if self.mantidSnapper.mtd.doesExist(inputWorkspace): - self.groceries.update(kwargs) - recipe().cook(ingredients_, self.groceries) - else: - raise RuntimeError( - ( - f"{recipe.__name__} ::" - " Missing non-default input workspace with groceries:" - f" {self.groceries} and kwargs: {kwargs}" - ) + inws = kwargs.get("inputWorkspace", None) + if inws is None or inws == "": + self.logger().debug(f"{recipe.__name__} :: Skipping recipe with default empty input workspace") + return + + if self.mantidSnapper.mtd.doesExist(kwargs["inputWorkspace"]): + recipe().cook(ingredients_, kwargs) + else: + raise RuntimeError( + ( + f"{recipe.__name__} ::" + " Missing non-default input workspace with groceries:" + f" {kwargs} and kwargs: {kwargs}" ) + ) def _getNormalizationWorkspaceName(self, groupingIndex: int): return f"reduced_normalization_{groupingIndex}_{wnvf.formatTimestamp(self.ingredients.timestamp)}" @@ -216,20 +223,20 @@ def execute(self): PreprocessReductionRecipe, self.ingredients.preprocess(), inputWorkspace=self.sampleWs, - **({"maskWorkspace": self.maskWs} if self.maskWs else {}), + diffcalWorkspace=self.diffcalWs, + maskWorkspace=self.maskWs, ) self._cloneIntermediateWorkspace(self.sampleWs, "sample_preprocessed") self._applyRecipe( PreprocessReductionRecipe, self.ingredients.preprocess(), inputWorkspace=self.normalizationWs, - **({"maskWorkspace": self.maskWs} if self.maskWs else {}), + diffcalWorkspace=self.diffcalWs, + maskWorkspace=self.maskWs, ) self._cloneIntermediateWorkspace(self.normalizationWs, "normalization_preprocessed") for groupingIndex, groupingWs in enumerate(self.groupingWorkspaces): - self.groceries["groupingWorkspace"] = groupingWs - if self.maskWs and self._isGroupFullyMasked(groupingWs): # Notify the user of a fully masked group, but continue with the workflow self.logger().warning( @@ -246,12 +253,14 @@ def execute(self): ReductionGroupProcessingRecipe, self.ingredients.groupProcessing(groupingIndex), inputWorkspace=sampleClone, + groupingWorkspace=groupingWs, ) self._cloneIntermediateWorkspace(sampleClone, f"sample_GroupProcessing_{groupingIndex}") self._applyRecipe( ReductionGroupProcessingRecipe, self.ingredients.groupProcessing(groupingIndex), inputWorkspace=normalizationClone, + groupingWorkspace=groupingWs, ) self._cloneIntermediateWorkspace(normalizationClone, f"normalization_GroupProcessing_{groupingIndex}") diff --git a/src/snapred/backend/recipe/WriteWorkspaceMetadata.py b/src/snapred/backend/recipe/WriteWorkspaceMetadata.py index 0d4b33e0d..e0a9e1699 100644 --- a/src/snapred/backend/recipe/WriteWorkspaceMetadata.py +++ b/src/snapred/backend/recipe/WriteWorkspaceMetadata.py @@ -17,6 +17,12 @@ class WriteWorkspaceMetadata(Recipe[WorkspaceMetadata]): TAG_PREFIX = Config["metadata.tagPrefix"] + def allGroceryKeys(self): + return {"workspace"} + + def mandatoryInputWorkspaces(self): + return {"workspace"} + def chopIngredients(self, ingredients: WorkspaceMetadata): prevMetadata = ReadWorkspaceMetadata().cook({"workspace": self.workspace}) diff --git a/src/snapred/backend/service/CalibrationService.py b/src/snapred/backend/service/CalibrationService.py index f4c75489f..693dae1e9 100644 --- a/src/snapred/backend/service/CalibrationService.py +++ b/src/snapred/backend/service/CalibrationService.py @@ -11,7 +11,6 @@ from snapred.backend.dao.indexing import IndexEntry from snapred.backend.dao.indexing.Versioning import VERSION_START, VersionState from snapred.backend.dao.ingredients import ( - CalculateDiffCalResidualIngredients, CalibrationMetricsWorkspaceIngredients, DiffractionCalibrationIngredients, GroceryListItem, @@ -258,12 +257,9 @@ def validateWritePermissions(self, request: CalibrationWritePermissionsRequest): @FromString def calculateResidual(self, request: CalculateResidualRequest): - ingredients = CalculateDiffCalResidualIngredients( - inputWorkspace=request.inputWorkspace, - outputWorkspace=request.outputWorkspace, - fitPeaksDiagnosticWorkspace=request.fitPeaksDiagnostic, - ) - return CalculateDiffCalResidualRecipe().cook(ingredients) + ingredients = None + groceries = request.model_dump() + return CalculateDiffCalResidualRecipe().cook(ingredients, groceries) @FromString def focusSpectra(self, request: FocusSpectraRequest): diff --git a/src/snapred/backend/service/NormalizationService.py b/src/snapred/backend/service/NormalizationService.py index aa7ae9718..95f5d24b1 100644 --- a/src/snapred/backend/service/NormalizationService.py +++ b/src/snapred/backend/service/NormalizationService.py @@ -152,13 +152,17 @@ def normalization(self, request: NormalizationRequest): # Apply diffcal and mask groceries["inputWorkspace"] = correctedVanadium groceries["outputWorkspace"] = focusedVanadium - PreprocessReductionRecipe().cook(PreprocessReductionRecipe.Ingredients(), groceries) + PreprocessReductionRecipe().cook( + PreprocessReductionRecipe.Ingredients(), + groceries, + ) # focus and normalize by current groceries["inputWorkspace"] = focusedVanadium groceries["outputWorkspace"] = focusedVanadium ReductionGroupProcessingRecipe().cook( - ReductionGroupProcessingRecipe.Ingredients(pixelGroup=ingredients.pixelGroup), groceries + ReductionGroupProcessingRecipe.Ingredients(pixelGroup=ingredients.pixelGroup), + groceries, ) # 2. focus diff --git a/src/snapred/ui/workflow/DiffCalWorkflow.py b/src/snapred/ui/workflow/DiffCalWorkflow.py index df2c3db2b..dccc97db4 100644 --- a/src/snapred/ui/workflow/DiffCalWorkflow.py +++ b/src/snapred/ui/workflow/DiffCalWorkflow.py @@ -406,7 +406,7 @@ def _calculateResidual(self): payload = CalculateResidualRequest( inputWorkspace=self.focusedWorkspace, outputWorkspace=self.residualWorkspace, - fitPeaksDiagnostic=self.fitPeaksDiagnostic, + fitPeaksDiagnosticWorkspace=self.fitPeaksDiagnostic, ) return self.request(path="calibration/residual", payload=payload) diff --git a/tests/resources/application.yml b/tests/resources/application.yml index 5dbfb7249..0ad5e3f4f 100644 --- a/tests/resources/application.yml +++ b/tests/resources/application.yml @@ -246,8 +246,8 @@ constants: highdSpacingCrop: 0.15 RawVanadiumCorrection: - numberOfSlices: 10 - numberOfAnnuli: 10 + numberOfSlices: 1 + numberOfAnnuli: 1 metadata: tagPrefix: testSNAPfuntime_ diff --git a/tests/unit/backend/recipe/test_CalculateDiffCalResidualRecipe.py b/tests/unit/backend/recipe/test_CalculateDiffCalResidualRecipe.py new file mode 100644 index 000000000..815e12b6f --- /dev/null +++ b/tests/unit/backend/recipe/test_CalculateDiffCalResidualRecipe.py @@ -0,0 +1,43 @@ +import unittest + +from mantid.api import WorkspaceGroup +from mantid.simpleapi import CreateSingleValuedWorkspace, mtd + +from snapred.backend.recipe.algorithm.Utensils import Utensils +from snapred.backend.recipe.CalculateDiffCalResidualRecipe import CalculateDiffCalResidualRecipe + + +class CalculateDiffCalResidualRecipeTest(unittest.TestCase): + def _make_groceries(self): + inputWS = mtd.unique_name(prefix="test_resid") + diagWS = mtd.unique_name(prefix="test_resid") + fitDiagWS = mtd.unique_name(prefix="test_resid") + outputWS = mtd.unique_name(prefix="test_resid") + CreateSingleValuedWorkspace(OutputWorkspace=inputWS) + ws = CreateSingleValuedWorkspace(OutputWorkspace=diagWS) + fitDiag = WorkspaceGroup() + fitDiag.addWorkspace(ws) + mtd.add(fitDiagWS, fitDiag) + return { + "inputWorkspace": inputWS, + "outputWorkspace": outputWS, + "fitPeaksDiagnosticWorkspace": fitDiagWS, + } + + def test_init(self): + CalculateDiffCalResidualRecipe() + + def test_init_reuseUtensils(self): + utensils = Utensils() + utensils.PyInit() + recipe = CalculateDiffCalResidualRecipe(utensils=utensils) + assert recipe.mantidSnapper == utensils.mantidSnapper + + def test_validateInputs(self): + groceries = self._make_groceries() + recipe = CalculateDiffCalResidualRecipe() + recipe.validateInputs(None, groceries) + + def test_chopIngredients(self): + recipe = CalculateDiffCalResidualRecipe() + recipe.chopIngredients() diff --git a/tests/unit/backend/recipe/test_EffectiveInstrumentRecipe.py b/tests/unit/backend/recipe/test_EffectiveInstrumentRecipe.py index 79b46e8f0..467242806 100644 --- a/tests/unit/backend/recipe/test_EffectiveInstrumentRecipe.py +++ b/tests/unit/backend/recipe/test_EffectiveInstrumentRecipe.py @@ -2,6 +2,7 @@ import numpy as np import pytest +from mantid.simpleapi import CreateSingleValuedWorkspace, mtd from util.SculleryBoy import SculleryBoy from snapred.backend.dao.ingredients import EffectiveInstrumentIngredients as Ingredients @@ -19,6 +20,9 @@ class TestEffectiveInstrumentRecipe: @pytest.fixture(autouse=True) def _setup(self): + self.inputWS = mtd.unique_name() + CreateSingleValuedWorkspace(OutputWorkspace=self.inputWS) + self.ingredients = mock.Mock( spec=Ingredients, unmaskedPixelGroup=mock.Mock( @@ -46,6 +50,10 @@ def _setup(self): # teardown follows ... pass + def test_allGroceryKeys(self): + assert {"inputWorkspace", "outputWorkspace"} == EffectiveInstrumentRecipe().allGroceryKeys() + assert {"inputWorkspace"} == EffectiveInstrumentRecipe().mandatoryInputWorkspaces() + def test_chopIngredients(self): recipe = EffectiveInstrumentRecipe() ingredients = self.ingredients @@ -54,14 +62,14 @@ def test_chopIngredients(self): def test_unbagGroceries(self): recipe = EffectiveInstrumentRecipe() - groceries = {"inputWorkspace": mock.Mock(), "outputWorkspace": mock.Mock()} + groceries = {"inputWorkspace": self.inputWS, "outputWorkspace": mock.Mock()} recipe.unbagGroceries(groceries) assert recipe.inputWS == groceries["inputWorkspace"] assert recipe.outputWS == groceries["outputWorkspace"] def test_unbagGroceries_output_default(self): recipe = EffectiveInstrumentRecipe() - groceries = {"inputWorkspace": mock.Mock()} + groceries = {"inputWorkspace": self.inputWS} recipe.unbagGroceries(groceries) assert recipe.inputWS == groceries["inputWorkspace"] assert recipe.outputWS == groceries["inputWorkspace"] @@ -69,7 +77,7 @@ def test_unbagGroceries_output_default(self): def test_queueAlgos(self): recipe = EffectiveInstrumentRecipe() ingredients = self.ingredients - groceries = {"inputWorkspace": mock.Mock(), "outputWorkspace": mock.Mock()} + groceries = {"inputWorkspace": self.inputWS, "outputWorkspace": mock.Mock()} recipe.prep(ingredients, groceries) recipe.queueAlgos() @@ -87,7 +95,7 @@ def test_queueAlgos(self): def test_queueAlgos_default(self): recipe = EffectiveInstrumentRecipe() ingredients = self.ingredients - groceries = {"inputWorkspace": mock.Mock()} + groceries = {"inputWorkspace": self.inputWS} recipe.prep(ingredients, groceries) recipe.queueAlgos() @@ -103,7 +111,7 @@ def test_cook(self): utensils.mantidSnapper = mockSnapper recipe = EffectiveInstrumentRecipe(utensils=utensils) ingredients = self.ingredients - groceries = {"inputWorkspace": mock.Mock(), "outputWorkspace": mock.Mock()} + groceries = {"inputWorkspace": self.inputWS, "outputWorkspace": mock.Mock()} output = recipe.cook(ingredients, groceries) @@ -130,7 +138,7 @@ def test_cook_default(self): utensils.mantidSnapper = mockSnapper recipe = EffectiveInstrumentRecipe(utensils=utensils) ingredients = self.ingredients - groceries = {"inputWorkspace": mock.Mock()} + groceries = {"inputWorkspace": self.inputWS} output = recipe.cook(ingredients, groceries) @@ -155,7 +163,7 @@ def test_cook_fail(self): utensils.mantidSnapper = mockSnapper recipe = EffectiveInstrumentRecipe(utensils=utensils) ingredients = self.ingredients - groceries = {"inputWorkspace": mock.Mock()} + groceries = {"inputWorkspace": self.inputWS} with pytest.raises(RuntimeError, match=r".*EditInstrumentGeometry.*"): recipe.cook(ingredients, groceries) @@ -167,7 +175,7 @@ def test_cater(self): recipe = EffectiveInstrumentRecipe(utensils=untensils) ingredientss = self.ingredientss - groceriess = [{"inputWorkspace": mock.Mock()}, {"inputWorkspace": mock.Mock()}] + groceriess = [{"inputWorkspace": self.inputWS}, {"inputWorkspace": self.inputWS}] recipe.cater(zip(ingredientss, groceriess)) diff --git a/tests/unit/backend/recipe/test_GroupDiffCalRecipe.py b/tests/unit/backend/recipe/test_GroupDiffCalRecipe.py index d0eddf3d7..5101dcb23 100644 --- a/tests/unit/backend/recipe/test_GroupDiffCalRecipe.py +++ b/tests/unit/backend/recipe/test_GroupDiffCalRecipe.py @@ -300,5 +300,5 @@ def test_validateInputs_bad_workspaces(self): "notInTheList": str(mock.sentinel.bad), } - with pytest.raises(RuntimeError, match=r".*input groceries: \{'notInTheList'\}"): + with pytest.raises(ValueError, match=r".*input groceries: \{'notInTheList'\}"): Recipe().validateInputs(self.fakeIngredients, groceries) diff --git a/tests/unit/backend/recipe/test_ReadWorkspaceMetadata.py b/tests/unit/backend/recipe/test_ReadWorkspaceMetadata.py index 480b7f6ee..b289d6143 100644 --- a/tests/unit/backend/recipe/test_ReadWorkspaceMetadata.py +++ b/tests/unit/backend/recipe/test_ReadWorkspaceMetadata.py @@ -152,6 +152,14 @@ def test_invalid_dao(self): ReadWorkspaceMetadata().cook(groceries) assert "WorkspaceMetadata" in str(e) + def test_validateInputs_bad_workspaces(self): + groceries = { + "workspace": mock.sentinel.input, + "notInTheList": mock.sentinel.bad, + } + with pytest.raises(ValueError, match=r".*input groceries: \{'notInTheList'\}"): + ReadWorkspaceMetadata().validateInputs(None, groceries) + # this at teardown removes the loggers, eliminating logger error printouts # see https://github.com/pytest-dev/pytest/issues/5502#issuecomment-647157873 diff --git a/tests/unit/backend/recipe/test_Recipe.py b/tests/unit/backend/recipe/test_Recipe.py index 7e00bbd91..e2b55b0a1 100644 --- a/tests/unit/backend/recipe/test_Recipe.py +++ b/tests/unit/backend/recipe/test_Recipe.py @@ -8,7 +8,6 @@ from snapred.backend.recipe.algorithm.Utensils import Utensils from snapred.backend.recipe.Recipe import Recipe -from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceName class Ingredients(BaseModel): @@ -26,7 +25,10 @@ class DummyRecipe(Recipe[Ingredients]): An instantiation with no abstract classes """ - def mandatoryInputWorkspaces(self) -> Set[WorkspaceName]: + def allGroceryKeys(self) -> Set[str]: + return {"ws"} + + def mandatoryInputWorkspaces(self) -> Set[str]: return {"ws"} def chopIngredients(self, ingredients: Ingredients): @@ -76,6 +78,9 @@ def test_validate_inputs(self): worseGroceries = {} with pytest.raises(RuntimeError, match="The workspace property ws was not found in the groceries"): recipe.validateInputs(ingredients, worseGroceries) + tooManyGroceries = {"ws": groceries["ws"], "another": groceries["ws"]} + with pytest.raises(ValueError, match=r".*invalid keys.*"): + recipe.validateInputs(ingredients, tooManyGroceries) def test_validate_grocery(self): groceries = self._make_groceries() @@ -117,6 +122,35 @@ def test_cater(self): ## The below tests verify the abstract methods must be initialized + def test_allGroceryKeys(self): + class RecipeNoGroceryKeys(Recipe[Ingredients]): + def unbagGroceries(self, groceries: Dict[str, str]): + pass + + def queueAlgos(self): + pass + + with pytest.raises(TypeError) as e: + RecipeNoGroceryKeys() + assert "allGroceryKeys" in str(e) + assert "unbagGroceries" not in str(e) + assert "queueAlgos" not in str(e) + + class RecipeGroceryKeys(Recipe[Ingredients]): + def allGroceryKeys(self): + return super().allGroceryKeys() + + def chopIngredients(self, ingredients): + pass + + def unbagGroceries(self, groceries): + pass + + def queueAlgos(self): + pass + + assert set() == RecipeGroceryKeys().allGroceryKeys() + def test_chopIngredients(self): class RecipeNoChopIngredients(Recipe[Ingredients]): def unbagGroceries(self, groceries: Dict[str, str]): diff --git a/tests/unit/backend/recipe/test_ReductionGroupProcessingRecipe.py b/tests/unit/backend/recipe/test_ReductionGroupProcessingRecipe.py index b915fa5e8..cbec3bc71 100644 --- a/tests/unit/backend/recipe/test_ReductionGroupProcessingRecipe.py +++ b/tests/unit/backend/recipe/test_ReductionGroupProcessingRecipe.py @@ -27,8 +27,6 @@ def test_unbagGroceries(self): groceries = { "inputWorkspace": "input", "outputWorkspace": "output", - "geometryOutputWorkspace": "geoWS", - "diffFocOutputWorkspace": "diffFocWS", "groupingWorkspace": "groupingWS", } recipe.unbagGroceries(groceries) @@ -77,8 +75,6 @@ def test_cook(self): groceries = { "inputWorkspace": "input", "outputWorkspace": "output", - "geometryOutputWorkspace": "geoWS", - "diffFocOutputWorkspace": "diffFocWS", "groupingWorkspace": "groupingWS", } @@ -102,8 +98,6 @@ def test_cater(self): groceries = { "inputWorkspace": "input", "outputWorkspace": "output", - "geometryOutputWorkspace": "geoWS", - "diffFocOutputWorkspace": "diffFocWS", "groupingWorkspace": "groupingWS", } diff --git a/tests/unit/backend/recipe/test_ReductionRecipe.py b/tests/unit/backend/recipe/test_ReductionRecipe.py index ac651f530..a6093d3e6 100644 --- a/tests/unit/backend/recipe/test_ReductionRecipe.py +++ b/tests/unit/backend/recipe/test_ReductionRecipe.py @@ -2,7 +2,7 @@ from unittest import TestCase, mock import pytest -from mantid.simpleapi import CreateSingleValuedWorkspace, mtd +from mantid.simpleapi import CreateEmptyTableWorkspace, CreateSingleValuedWorkspace, mtd from util.Config_helpers import Config_override from util.SculleryBoy import SculleryBoy @@ -24,13 +24,28 @@ class ReductionRecipeTest(TestCase): def _make_groceries(self): sampleWS = mtd.unique_name(prefix="test_applynorm") normWS = mtd.unique_name(prefix="test_applynorm") + difcWS = mtd.unique_name(prefix="test_applynorm") CreateSingleValuedWorkspace(OutputWorkspace=sampleWS) CreateSingleValuedWorkspace(OutputWorkspace=normWS) + CreateEmptyTableWorkspace(OutputWorkspace=difcWS) return { "inputWorkspace": sampleWS, "normalizationWorkspace": normWS, + "diffcalWorkspace": difcWS, } + def test_validateInputs_bad_workspaces(self): + groceries = { + "inputWorkspace": mock.sentinel.input, + "groupingWorkspaces": mock.sentinel.groupws, + "diffcalWorkspace": mock.sentinel.difc, + "normalizationWorkspace": mock.sentinel.norm, + "combinedPixelMask": mock.sentinel.mask, + "notInTheList": mock.sentinel.bad, + } + with pytest.raises(ValueError, match=r".*input groceries: \{'notInTheList'\}"): + ReductionRecipe().validateInputs(None, groceries) + def test_chopIngredients(self): recipe = ReductionRecipe() ingredients = mock.Mock() @@ -201,6 +216,7 @@ def test_keepUnfocusedData(self, mockMtd): # Set up other recipe variables recipe.sampleWs = "sample" + recipe.diffcalWs = "difc" recipe.maskWs = "mask" recipe.normalizationWs = "norm" recipe.groupingWorkspaces = ["group1", "group2"] @@ -229,8 +245,7 @@ def test_applyRecipe(self): inputWS = "input" recipe._applyRecipe(mockRecipe, recipe.ingredients, inputWorkspace=inputWS) - assert recipe.groceries["inputWorkspace"] == inputWS - mockRecipe().cook.assert_called_once_with(recipe.ingredients, recipe.groceries) + mockRecipe().cook.assert_called_once_with(recipe.ingredients, {"inputWorkspace": inputWS}) def test_applyRecipe_no_input_workspace(self): recipe = ReductionRecipe() @@ -354,10 +369,9 @@ def test_applyNormalization(self, mockApplyNormalizationRecipe): ) mockApplyNormalizationRecipe().cook.assert_called_once_with( - recipe.ingredients.applyNormalization(groupingIndex), recipe.groceries + recipe.ingredients.applyNormalization(groupingIndex), + {"inputWorkspace": "sample", "normalizationWorkspace": "norm"}, ) - assert recipe.groceries["inputWorkspace"] == "sample" - assert recipe.groceries["normalizationWorkspace"] == "norm" def test_cloneIntermediateWorkspace(self): recipe = ReductionRecipe() @@ -414,6 +428,7 @@ def test_execute(self, mockMtd): # Set up other recipe variables recipe.sampleWs = "sample" + recipe.diffcalWs = "difc" recipe.maskWs = "mask" recipe.normalizationWs = "norm" recipe.groupingWorkspaces = ["group1", "group2"] @@ -428,21 +443,30 @@ def test_execute(self, mockMtd): PreprocessReductionRecipe, recipe.ingredients.preprocess(), inputWorkspace=recipe.sampleWs, + diffcalWorkspace=recipe.diffcalWs, maskWorkspace=recipe.maskWs, ) recipe._applyRecipe.assert_any_call( PreprocessReductionRecipe, recipe.ingredients.preprocess(), inputWorkspace=recipe.normalizationWs, + diffcalWorkspace=recipe.diffcalWs, maskWorkspace=recipe.maskWs, ) - recipe._applyRecipe.assert_any_call( - ReductionGroupProcessingRecipe, recipe.ingredients.groupProcessing(0), inputWorkspace="sample_grouped" - ) - recipe._applyRecipe.assert_any_call( - ReductionGroupProcessingRecipe, recipe.ingredients.groupProcessing(1), inputWorkspace="norm_grouped" - ) + for groupingWS in recipe.groupingWorkspaces: + recipe._applyRecipe.assert_any_call( + ReductionGroupProcessingRecipe, + recipe.ingredients.groupProcessing(0), + inputWorkspace="sample_grouped", + groupingWorkspace=groupingWS, + ) + recipe._applyRecipe.assert_any_call( + ReductionGroupProcessingRecipe, + recipe.ingredients.groupProcessing(1), + inputWorkspace="norm_grouped", + groupingWorkspace=groupingWS, + ) recipe._applyRecipe.assert_any_call( GenerateFocussedVanadiumRecipe, @@ -529,6 +553,7 @@ def test_execute_useEffectiveInstrument(self, mockMtd): # Set up other recipe variables recipe.sampleWs = "sample" + recipe.diffcalWs = "difc" recipe.maskWs = "mask" recipe.normalizationWs = "norm" recipe.groupingWorkspaces = ["group1", "group2"] @@ -539,25 +564,35 @@ def test_execute_useEffectiveInstrument(self, mockMtd): result = recipe.execute() # Perform assertions + recipe._applyRecipe.assert_any_call( PreprocessReductionRecipe, recipe.ingredients.preprocess(), inputWorkspace=recipe.sampleWs, + diffcalWorkspace=recipe.diffcalWs, maskWorkspace=recipe.maskWs, ) recipe._applyRecipe.assert_any_call( PreprocessReductionRecipe, recipe.ingredients.preprocess(), inputWorkspace=recipe.normalizationWs, + diffcalWorkspace=recipe.diffcalWs, maskWorkspace=recipe.maskWs, ) - recipe._applyRecipe.assert_any_call( - ReductionGroupProcessingRecipe, recipe.ingredients.groupProcessing(0), inputWorkspace="sample_grouped" - ) - recipe._applyRecipe.assert_any_call( - ReductionGroupProcessingRecipe, recipe.ingredients.groupProcessing(1), inputWorkspace="norm_grouped" - ) + for groupingWS in recipe.groupingWorkspaces: + recipe._applyRecipe.assert_any_call( + ReductionGroupProcessingRecipe, + recipe.ingredients.groupProcessing(0), + inputWorkspace="sample_grouped", + groupingWorkspace=groupingWS, + ) + recipe._applyRecipe.assert_any_call( + ReductionGroupProcessingRecipe, + recipe.ingredients.groupProcessing(1), + inputWorkspace="norm_grouped", + groupingWorkspace=groupingWS, + ) recipe._applyRecipe.assert_any_call( GenerateFocussedVanadiumRecipe, @@ -685,6 +720,7 @@ def test_execute_with_fully_masked_group(self, mockMtd): # Set up other recipe variables recipe.sampleWs = "sample" + recipe.diffcalWs = "difc" recipe.normalizationWs = "norm" recipe.groupingWorkspaces = ["group1", "group2"] recipe.keepUnfocused = True diff --git a/tests/unit/backend/recipe/test_WriteWorkspaceMetadata.py b/tests/unit/backend/recipe/test_WriteWorkspaceMetadata.py index ffd20fc2e..96dbc9c3a 100644 --- a/tests/unit/backend/recipe/test_WriteWorkspaceMetadata.py +++ b/tests/unit/backend/recipe/test_WriteWorkspaceMetadata.py @@ -81,6 +81,10 @@ def _verify_logs(self, groceries, values): assert run.hasProperty(prop) assert value == run.getLogData(prop).value + def test_validateInputs_bad_workspaces(self): + assert {"workspace"} == WriteWorkspaceMetadata().mandatoryInputWorkspaces() + assert {"workspace"} == WriteWorkspaceMetadata().allGroceryKeys() + def test_write_metadata(self): groceries = self._make_groceries() diff --git a/tests/unit/backend/service/test_CalibrationService.py b/tests/unit/backend/service/test_CalibrationService.py index 4524a8b22..88b78d3b8 100644 --- a/tests/unit/backend/service/test_CalibrationService.py +++ b/tests/unit/backend/service/test_CalibrationService.py @@ -23,7 +23,7 @@ from util.dao import DAOFactory from util.state_helpers import state_root_redirect -from snapred.backend.dao.ingredients import CalculateDiffCalResidualIngredients, CalibrationMetricsWorkspaceIngredients +from snapred.backend.dao.ingredients import CalibrationMetricsWorkspaceIngredients from snapred.backend.dao.request import ( CalculateResidualRequest, CalibrationAssessmentRequest, @@ -1112,7 +1112,7 @@ def test_calculateResidual(self, MockCalculateDiffCalResidualRecipe): spec=CalculateResidualRequest, inputWorkspace="inputWS", outputWorkspace="outputWS", - fitPeaksDiagnostic="fitPeaksDiagWS", + fitPeaksDiagnosticWorkspace="fitPeaksDiagWS", ) # Mock the recipe instance and its return value @@ -1125,11 +1125,8 @@ def test_calculateResidual(self, MockCalculateDiffCalResidualRecipe): # Assert: check that the recipe was instantiated and cook was called with the correct ingredients MockCalculateDiffCalResidualRecipe.assert_called_once_with() mockRecipeInstance.cook.assert_called_once_with( - CalculateDiffCalResidualIngredients( - inputWorkspace="inputWS", - outputWorkspace="outputWS", - fitPeaksDiagnosticWorkspace="fitPeaksDiagWS", - ) + None, + mockRequest.model_dump(), ) # Assert that the result contains the expected output workspace