Skip to content

Commit 71eccc3

Browse files
authored
Merge pull request #441 from neutrons/EWM6091_reduction_output_workspace_names
reduction output-workspace names
2 parents db5e453 + eebbe95 commit 71eccc3

37 files changed

+2391
-548
lines changed

src/snapred/backend/dao/ingredients/CalibrationMetricsWorkspaceIngredients.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ class CalibrationMetricsWorkspaceIngredients(BaseModel):
1616
"""
1717

1818
calibrationRecord: CalibrationRecord
19-
timestamp: Optional[int] = None
19+
timestamp: Optional[float] = None

src/snapred/backend/dao/ingredients/ReductionIngredients.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
from snapred.backend.dao.ingredients.PreprocessReductionIngredients import PreprocessReductionIngredients
1212
from snapred.backend.dao.ingredients.ReductionGroupProcessingIngredients import ReductionGroupProcessingIngredients
1313
from snapred.backend.dao.state.PixelGroup import PixelGroup
14-
from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceName
1514

1615

1716
class ReductionIngredients(BaseModel):
1817
"""Data class to hold the ingredients for each subrecipe of reduction and itself"""
1918

20-
maskList: List[WorkspaceName]
19+
runNumber: str
20+
useLiteMode: bool
21+
timestamp: float
22+
2123
pixelGroups: List[PixelGroup]
2224
detectorPeaksMany: List[List[GroupPeakList]]
2325

@@ -31,7 +33,7 @@ class ReductionIngredients(BaseModel):
3133
#
3234
def preprocess(self) -> PreprocessReductionIngredients:
3335
# Note: at present, there are no required parameters.
34-
return PreprocessReductionIngredients(maskList=None)
36+
return PreprocessReductionIngredients()
3537

3638
def groupProcessing(self, groupingIndex: int) -> ReductionGroupProcessingIngredients:
3739
return ReductionGroupProcessingIngredients(pixelGroup=self.pixelGroups[groupingIndex])
@@ -50,6 +52,4 @@ def applyNormalization(self, groupingIndex: int) -> ApplyNormalizationIngredient
5052

5153
model_config = ConfigDict(
5254
extra="forbid",
53-
# required in order to use 'WorkspaceName'
54-
arbitrary_types_allowed=True,
5555
)

src/snapred/backend/dao/reduction/ReductionRecord.py

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
from typing import Any, Dict, List
1+
from typing import Dict, List
22

3-
from pydantic import BaseModel, ConfigDict, ValidationError, field_validator, model_validator
3+
from pydantic import BaseModel, ConfigDict, Field
44

55
from snapred.backend.dao.calibration.CalibrationRecord import CalibrationRecord
66
from snapred.backend.dao.normalization.NormalizationRecord import NormalizationRecord
7-
from snapred.backend.dao.ObjectSHA import ObjectSHA
87
from snapred.backend.dao.state.PixelGroupingParameters import PixelGroupingParameters
98
from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceName
109

@@ -17,35 +16,19 @@ class ReductionRecord(BaseModel):
1716
calibration, normalization, and pixel grouping details.
1817
"""
1918

20-
runNumbers: List[str]
19+
# Reduction has distinct registration attributes from Calibration and Normalization.
20+
# for this reason, this class is not derived from 'Record',
21+
# and does not include a 'CalculationParameters' instance.
2122
useLiteMode: bool
23+
timestamp: float = Field(frozen=True, default=None)
24+
25+
# specific to reduction records
26+
runNumber: str
2227
calibration: CalibrationRecord
2328
normalization: NormalizationRecord
2429
pixelGroupingParameters: Dict[str, List[PixelGroupingParameters]]
25-
version: int
26-
stateId: ObjectSHA
27-
workspaceNames: List[WorkspaceName]
28-
29-
@field_validator("stateId", mode="before")
30-
@classmethod
31-
def str_to_ObjectSHA(cls, v: Any) -> Any:
32-
# ObjectSHA to be stored in JSON as _only_ a single hex string, for the hex digest itself
33-
if isinstance(v, str):
34-
return ObjectSHA(hex=v, decodedKey=None)
35-
return v
3630

37-
@model_validator(mode="after")
38-
def checkStateId(self):
39-
cal = self.calibration
40-
norm = self.normalization
41-
redStateId = self.stateId
42-
43-
calStateId = cal.calibrationFittingIngredients.instrumentState.id
44-
normStateId = norm.calibration.instrumentState.id
45-
if not (calStateId == normStateId == redStateId):
46-
raise ValidationError("Calibration, normalization, and reduction records are not from the same state.")
47-
48-
return self
31+
workspaceNames: List[WorkspaceName]
4932

5033
model_config = ConfigDict(
5134
# required in order to use 'WorkspaceName'

src/snapred/backend/dao/request/FarmFreshIngredients.py

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
from typing import Any, List, Optional, Union
1+
from typing import Any, List, NamedTuple, Optional
22

3-
from pydantic import BaseModel, ConfigDict, field_validator
3+
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
44

55
from snapred.backend.dao.Limit import Limit, Pair
66
from snapred.backend.dao.state import FocusGroup
77
from snapred.meta.Config import Config
88
from snapred.meta.mantid.AllowedPeakTypes import SymmetricPeakEnum
99

10+
# TODO: this declaration is duplicated in `ReductionRequest`.
11+
Versions = NamedTuple("Versions", [("calibration", Optional[int]), ("normalization", Optional[int])])
12+
1013

1114
class FarmFreshIngredients(BaseModel, extra="forbid"):
1215
"""
@@ -17,9 +20,25 @@ class FarmFreshIngredients(BaseModel, extra="forbid"):
1720
# Do NOT use inside ingredients for algorithms
1821

1922
runNumber: str
20-
version: Optional[str] = None
23+
24+
versions: Versions = Versions(None, None)
25+
# allow 'versions' to be accessed as a single version,
26+
# or, to be accessed ambiguously
27+
28+
@property
29+
def version(self) -> Optional[int]:
30+
if self.versions.calibration is not None and self.versions.normalization is not None:
31+
raise RuntimeError("accessing 'version' property when 'versions' is non-singular")
32+
return self.versions[0]
33+
34+
@version.setter
35+
def version(self, v: Optional[int]):
36+
self.versions = (v, None)
37+
2138
useLiteMode: bool
22-
focusGroup: Union[FocusGroup, List[FocusGroup]]
39+
40+
## mandatory for reduction
41+
timestamp: Optional[float] = None
2342

2443
## needs to be mandatory for diffcal
2544
cifPath: Optional[str] = None
@@ -40,6 +59,28 @@ class FarmFreshIngredients(BaseModel, extra="forbid"):
4059
fwhmMultipliers: Pair[float] = Pair.model_validate(Config["calibration.parameters.default.FWHMMultiplier"])
4160
maxChiSq: Optional[float] = Config["constants.GroupDiffractionCalibration.MaxChiSq"]
4261

62+
focusGroups: Optional[List[FocusGroup]] = None
63+
64+
# Allow 'focusGroups' to be accessed as a single 'focusGroup'
65+
@property
66+
def focusGroup(self) -> FocusGroup:
67+
if len(self.focusGroups) > 1:
68+
raise RuntimeError("accessing 'focusGroup' property when 'focusGroups' is non-singular")
69+
return self.focusGroups[0]
70+
71+
@focusGroup.setter
72+
def focusGroup(self, fg: FocusGroup):
73+
if fg is None:
74+
raise ValueError("'focusGroup' is None")
75+
self.focusGroups = [fg]
76+
77+
@field_validator("versions")
78+
@classmethod
79+
def validate_versions(cls, v) -> Versions:
80+
if not isinstance(v, Versions):
81+
v = Versions(v)
82+
return v
83+
4384
@field_validator("crystalDBounds", mode="before")
4485
@classmethod
4586
def validate_crystalDBounds(cls, v: Any) -> Limit[float]:
@@ -60,4 +101,18 @@ def validate_fwhmMultipliers(cls, v: Any) -> Pair[float]:
60101
v = Pair[float](**v.dict())
61102
return v
62103

104+
@model_validator(mode="before")
105+
@classmethod
106+
def validate_focusGroups(cls, v: Any):
107+
if isinstance(v, dict):
108+
if "focusGroup" in v:
109+
if "focusGroups" in v:
110+
raise ValueError("initialization with both 'focusGroup' and 'focusGroups' properties")
111+
fg = v["focusGroup"]
112+
if fg is None:
113+
raise ValueError("'focusGroup' is None")
114+
v["focusGroups"] = [fg]
115+
del v["focusGroup"]
116+
return v
117+
63118
model_config = ConfigDict(extra="forbid")

src/snapred/backend/dao/request/ReductionRequest.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
from typing import List, Literal, Optional, Union
1+
from typing import List, NamedTuple, Optional, Union
22

33
from pydantic import BaseModel, ConfigDict
44

55
from snapred.backend.dao.state.FocusGroup import FocusGroup
66
from snapred.backend.error.ContinueWarning import ContinueWarning
77

8+
Versions = NamedTuple("Versions", [("calibration", Optional[int]), ("normalization", Optional[int])])
9+
810

911
class ReductionRequest(BaseModel):
1012
runNumber: Union[str, List[str]]
1113
useLiteMode: bool
14+
timestamp: Optional[float] = None
1215
focusGroups: List[FocusGroup] = []
13-
userSelectedMaskPath: Optional[str] = None
14-
version: Union[int, Literal["*"]] = "*"
16+
17+
# Calibration and normalization versions:
18+
# `None` => <use latest version>
19+
versions: Versions = Versions(None, None)
1520

1621
# TODO: Move to SNAPRequest
1722
continueFlags: Optional[ContinueWarning.Type] = None

src/snapred/backend/data/DataExportService.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import time
12
from pathlib import Path
2-
from typing import Optional
33

44
from pydantic import validate_call
55

@@ -41,6 +41,9 @@ def getFullLiteDataFilePath(self, runNumber: str) -> Path:
4141
fileName = Config[pre] + str(runNumber) + Config[ext]
4242
return Path(path, fileName)
4343

44+
def getUniqueTimestamp(self) -> time.struct_time:
45+
return self.dataService.getUniqueTimestamp()
46+
4447
##### REDUCTION METHODS #####
4548

4649
# NOTE will be added shortly
@@ -79,11 +82,11 @@ def exportNormalizationState(self, normalization: Normalization):
7982

8083
##### REDUCTION METHODS #####
8184

82-
def exportReductionRecord(self, record: ReductionRecord, version: Optional[int] = None) -> ReductionRecord:
83-
return self.dataService.writeReductionRecord(record, version)
85+
def exportReductionRecord(self, record: ReductionRecord):
86+
self.dataService.writeReductionRecord(record)
8487

8588
def exportReductionData(self, record: ReductionRecord):
86-
return self.dataService.writeReductionData(record)
89+
self.dataService.writeReductionData(record)
8790

8891
##### WORKSPACE METHODS #####
8992

0 commit comments

Comments
 (0)