Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add polarisation spin state to ISIS ORSO file metadata #38468

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5b55f50
Retrieve spin state form sample logs and add to ReflectometryDataSet …
adriazalvarez Nov 28, 2024
5b935cb
Add instrument settings header file if polarization is present, also …
adriazalvarez Nov 28, 2024
1f939f4
Add spin state to logs in polarization efficiencies correction algorithm
adriazalvarez Nov 28, 2024
b4e1fe7
Add test for checking polarization state is correctly saved on orso file
adriazalvarez Nov 28, 2024
40effd8
Add release notes
adriazalvarez Dec 2, 2024
a110dd9
Add test for checking dataset names with different polarization/angles
adriazalvarez Dec 2, 2024
ff774f8
Update SaveISISReflectomtryORSO entry on docs
adriazalvarez Dec 2, 2024
aa1bd77
Add polarization in orso_helper_test
adriazalvarez Dec 3, 2024
9127757
Don' print ws name when data is polarized
adriazalvarez Dec 4, 2024
7b7888e
Fix tests printing output name when data is polarized
adriazalvarez Dec 4, 2024
97ae101
Update system tests data files for save orso
adriazalvarez Dec 4, 2024
568667b
Add python module for reflectometry constansts
adriazalvarez Dec 9, 2024
212a7be
Add dependency on the interface:
adriazalvarez Dec 9, 2024
74a7e37
change python destination path
adriazalvarez Dec 10, 2024
ce8c598
Add tests to check that ReflectometryReductionAuto is adding logs if …
adriazalvarez Dec 10, 2024
4654bed
Add polarized parameter as is_polarized and as optional
adriazalvarez Dec 10, 2024
11b29e6
Correct unique dataset names for Stitched workspaces
adriazalvarez Dec 10, 2024
9b8e580
Add constants from cpp PolarizationCorrectionHelpers
adriazalvarez Dec 10, 2024
14c696f
Add constants from cpp PolarizationCorrectionHelpers
adriazalvarez Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Framework/PythonInterface/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ add_dependencies(
PythonAPIModule
PythonDataObjectsModule
PythonCurveFittingModule
PythonReflHelpersModule
)

# Clear any leftover bin/$(Configuration)/mantid/ folder, from when PythonInterface was being copied over. The last
Expand Down
1 change: 1 addition & 0 deletions Framework/PythonInterface/mantid/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ add_subdirectory(geometry)
add_subdirectory(api)
add_subdirectory(dataobjects)
add_subdirectory(_plugins)
add_subdirectory(utils/reflectometry)

# Defines the target the will cause the Python bundle to be copied
set(PYBUNDLE_POST_TARGET PythonModule)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# _polarization_helpers Python module

set(MODULE_TEMPLATE src/polarization_helpers.cpp.in)

# Files containing export definitions, these are automatically processed -- Do NOT sort this list. The order defines the
# order in which the export definitions occur and some depend on their base classes being exported first --
set(EXPORT_FILES src/Exports/PolarizationCorrectionHelpers.cpp)

set(MODULE_DEFINITION ${CMAKE_CURRENT_BINARY_DIR}/polarization_helpers.cpp)
create_module(${MODULE_TEMPLATE} ${MODULE_DEFINITION} ${EXPORT_FILES})

# Create the target for this directory
add_library(PythonReflHelpersModule ${MODULE_DEFINITION} ${EXPORT_FILES} ${PYTHON_INSTALL_FILES})
add_library(PythonReflectometryHelpersModule ALIAS PythonReflHelpersModule)

set_python_properties(PythonReflHelpersModule _polarization_helpers)

# Add the required dependencies
target_link_libraries(PythonReflHelpersModule PRIVATE Mantid::Algorithms Mantid::PythonInterfaceCore)

# Installation settings
set_target_properties(PythonReflHelpersModule PROPERTIES INSTALL_RPATH "${EXT_INSTALL_RPATH}")
mtd_install_shared_library(
TARGETS PythonReflHelpersModule DESTINATION ${Python_SITELIB_RELPATH}/mantid/utils/reflectometry
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright © 2023 ISIS Rutherford Appleton Laboratory UKRI,
# Copyright © 2024 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 +

###############################################################################
# Load the C++ library and register the C++ class exports
###############################################################################
from mantid.utils import import_mantid_cext

import_mantid_cext("._polarization_helpers", "mantid.utils.reflectometry", globals())
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from datetime import datetime, timezone
from typing import Optional, Union, List
import re
from orsopy.fileio.data_source import DataSource, Person, Experiment, Sample, Measurement
from orsopy.fileio.data_source import DataSource, Person, Experiment, Sample, Measurement, Polarization, InstrumentSettings
from orsopy.fileio import Reduction, Software
from orsopy.fileio.orso import Orso, OrsoDataset, save_orso, save_nexus
from orsopy.fileio.base import Column, ErrorColumn, File
Expand Down Expand Up @@ -62,11 +62,12 @@ def __init__(
reduction_timestamp: datetime,
creator_name: str,
creator_affiliation: str,
is_polarized_dataset: bool,
) -> None:
self._data_columns = data_columns
self._header = None

self._create_mandatory_header(ws, dataset_name, reduction_timestamp, creator_name, creator_affiliation)
self._create_mandatory_header(ws, dataset_name, reduction_timestamp, creator_name, creator_affiliation, is_polarized_dataset)

@property
def dataset(self) -> OrsoDataset:
Expand All @@ -81,6 +82,10 @@ def set_proposal_id(self, proposal_id: str) -> None:
def set_doi(self, doi: str) -> None:
self._header.data_source.experiment.doi = doi

def set_polarization(self, pol: str) -> None:
if self._header.data_source.measurement.instrument_settings is not None:
self._header.data_source.measurement.instrument_settings.polarization = Polarization(pol)

def set_reduction_call(self, call: str) -> None:
self._header.reduction.call = call

Expand All @@ -97,7 +102,13 @@ def _create_file(filename: str, timestamp: Optional[datetime] = None, comment: O
return File(filename, timestamp, comment)

def _create_mandatory_header(
self, ws, dataset_name: str, reduction_timestamp: datetime, creator_name: str, creator_affiliation: str
self,
ws,
dataset_name: str,
reduction_timestamp: datetime,
creator_name: str,
creator_affiliation: str,
is_polarized_dataset: Optional[bool] = None,
) -> None:
owner = Person(name=None, affiliation=None)

Expand All @@ -111,7 +122,9 @@ def _create_mandatory_header(

sample = Sample(name=ws.getTitle())

measurement = Measurement(instrument_settings=None, data_files=[])
# This will initially only consider polarization
instrument_settings = InstrumentSettings(None, None) if is_polarized_dataset else None
measurement = Measurement(instrument_settings=instrument_settings, data_files=[])

data_source = DataSource(owner=owner, experiment=experiment, sample=sample, measurement=measurement)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2024 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 +

#include "MantidAlgorithms/PolarizationCorrections/PolarizationCorrectionsHelpers.h"
#include <boost/python/class.hpp>

namespace {
class SpinStatesORSO {};
} // namespace

void export_SpinStatesORSO() {
using namespace boost::python;

class_<SpinStatesORSO>("SpinStatesORSO")
.def_readonly("PP", &Mantid::Algorithms::SpinStatesORSO::PP)
.def_readonly("PM", &Mantid::Algorithms::SpinStatesORSO::PM)
.def_readonly("MP", &Mantid::Algorithms::SpinStatesORSO::MP)
.def_readonly("MM", &Mantid::Algorithms::SpinStatesORSO::MM)
.def_readonly("PO", &Mantid::Algorithms::SpinStatesORSO::PO)
.def_readonly("MO", &Mantid::Algorithms::SpinStatesORSO::MO)
.def_readonly("LOG_NAME", &Mantid::Algorithms::SpinStatesORSO::LOG_NAME);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright &copy; 2024 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 +

@AUTO_GENERATE_WARNING@
/********** Source = polarization_helpers.cpp.in **********************************************************/

#include <boost/python/def.hpp>
#include <boost/python/module.hpp>

// Forward declare
@EXPORT_DECLARE@

BOOST_PYTHON_MODULE(_polarization_helpers)
{
@EXPORT_FUNCTIONS@
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ def __init__(self, ws, is_ws_grp_member: bool):
self._stitch_history = None
self._q_conversion_history = None
self._q_conversion_theta: Optional[float] = None
self._spin_state: str = ""

self._populate_histories()
self._populate_q_conversion_info()
self._set_spin_state_from_logs()
self._set_name()

@property
Expand All @@ -69,6 +71,10 @@ def is_ws_grp_member(self) -> bool:
def instrument_name(self) -> str:
return self._ws.getInstrument().getName()

@property
def spin_state(self) -> str:
return self._spin_state

@property
def reduction_history(self):
return self._reduction_history
Expand All @@ -85,6 +91,10 @@ def stitch_history(self):
def is_stitched(self) -> bool:
return self._stitch_history is not None

@property
def is_polarized(self) -> bool:
return len(self._spin_state) > 0

@property
def q_conversion_history(self):
return self._q_conversion_history
Expand Down Expand Up @@ -141,15 +151,22 @@ def _set_name(self):
if self.is_stitched:
self._name = "Stitched"
elif self._q_conversion_theta is not None:
self._name = str(self._q_conversion_theta)
self._name = f"{self._q_conversion_theta:.3f}"
else:
self._name = self._ws.name()

if self.is_polarized:
self._name = f"{self._name} {self._spin_state}"
return

Comment on lines +158 to +161
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.is_polarized:
self._name = f"{self._name} {self._spin_state}"
return
if self.is_polarized:
self._name = f"{self._name} {self._spin_state}"
return

Here I have decided to return early because if the dataset is polarized is not necessary to prepend the ws name for unique datasets and in some cases the ws name would be repeated.

# Ensure unique dataset names for workspace group members.
# Eventually we will specify the polarization spin state to provide the unique names for polarised datasets.
if self._is_ws_grp_member and self._name != self._ws.name():
if self._is_ws_grp_member and self._ws.name() != self._name:
self._name = f"{self._ws.name()} {self._name}"

def _set_spin_state_from_logs(self) -> None:
if self._ws.getRun().hasProperty("spin_state_ORSO"):
adriazalvarez marked this conversation as resolved.
Show resolved Hide resolved
self._spin_state = self._ws.getRun().getLogData("spin_state_ORSO").value


class SaveISISReflectometryORSO(PythonAlgorithm):
"""
Expand Down Expand Up @@ -395,6 +412,7 @@ def _create_dataset_with_mandatory_header(
reduction_timestamp=self._get_reduction_timestamp(refl_dataset.reduction_history),
creator_name=self.name(),
creator_affiliation=MantidORSODataset.SOFTWARE_NAME,
is_polarized_dataset=refl_dataset.is_polarized,
)

def _add_optional_header_info(self, dataset: MantidORSODataset, refl_dataset: ReflectometryDataset) -> None:
Expand All @@ -407,6 +425,8 @@ def _add_optional_header_info(self, dataset: MantidORSODataset, refl_dataset: Re
dataset.set_proposal_id(rb_number)
dataset.set_doi(doi)
dataset.set_reduction_call(self._get_reduction_script(refl_dataset))
if refl_dataset.is_polarized:
dataset.set_polarization(refl_dataset.spin_state)

reduction_workflow_histories = refl_dataset.reduction_workflow_histories
if not refl_dataset.reduction_workflow_histories:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from mantid.simpleapi import CreateSampleWorkspace
from mantid.utils.reflectometry.orso_helper import MantidORSODataColumns, MantidORSODataset, MantidORSOSaver
from mantid.utils.reflectometry import SpinStatesORSO
from orsopy.fileio.base import Column, ErrorColumn


Expand Down Expand Up @@ -225,7 +226,9 @@ def test_create_mantid_orso_dataset(self):
reduction_timestamp = datetime.now()
creator_name = "Creator Name"
creator_affiliation = "Creator Affiliation"
dataset = MantidORSODataset(dataset_name, self._data_columns, self._ws, reduction_timestamp, creator_name, creator_affiliation)
dataset = MantidORSODataset(
dataset_name, self._data_columns, self._ws, reduction_timestamp, creator_name, creator_affiliation, is_polarized_dataset=False
)

orso_dataset = dataset.dataset
self.assertIsNotNone(orso_dataset)
Expand Down Expand Up @@ -253,6 +256,12 @@ def test_set_doi_on_mantid_orso_dataset(self):

self.assertEqual(doi, dataset.dataset.info.data_source.experiment.doi)

def test_set_polarization_on_mantid_orso_dataset(self):
dataset = self._create_test_dataset(polarized=True)
dataset.set_polarization(SpinStatesORSO.PP)

self.assertEqual(SpinStatesORSO.PP, dataset.dataset.info.data_source.measurement.instrument_settings.polarization)

def test_set_reduction_call_on_mantid_orso_dataset(self):
dataset = self._create_test_dataset()
call = "ReflectometryReductionCall"
Expand Down Expand Up @@ -353,8 +362,8 @@ def test_add_multiple_additional_files_to_mantid_orso_dataset(self):

self._check_files_are_created(dataset, filenames, 0, len(filenames), False)

def _create_test_dataset(self):
return MantidORSODataset("Test dataset", self._data_columns, self._ws, datetime.now(), "", "")
def _create_test_dataset(self, polarized=False):
return MantidORSODataset("Test dataset", self._data_columns, self._ws, datetime.now(), "", "", is_polarized_dataset=polarized)

def _check_mantid_default_header(self, orso_dataset, dataset_name, ws, reduction_timestamp, creator_name, creator_affiliation):
"""Check that the default header contains only the information that can be shared
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@
from unittest.mock import Mock, patch
from pathlib import Path
from datetime import datetime, timezone, date, time
from collections import namedtuple

from mantid import config
from mantid.simpleapi import (
CreateSampleWorkspace,
SaveISISReflectometryORSO,
ConvertToPointData,
GroupWorkspaces,
)
from mantid.simpleapi import CreateSampleWorkspace, SaveISISReflectometryORSO, ConvertToPointData, GroupWorkspaces, AddSampleLog
from mantid.api import AnalysisDataService
from mantid.kernel import version, DateAndTime
from mantid.utils.reflectometry.orso_helper import MantidORSODataset, MantidORSOSaver
from mantid.utils.reflectometry import SpinStatesORSO


class SaveISISReflectometryORSOTest(unittest.TestCase):
Expand Down Expand Up @@ -484,7 +481,7 @@ def test_stitched_data_has_correct_dataset_name(self, mock_alg_histories):

@patch("mantid.api.WorkspaceHistory.getAlgorithmHistories")
def test_unstitched_data_gets_dataset_name_from_ref_roi(self, mock_alg_histories):
angle = "2.3"
angle = "2.300"
adriazalvarez marked this conversation as resolved.
Show resolved Hide resolved
ws = self._create_sample_workspace()
self._configure_q_conversion_alg_mock_history(mock_alg_histories, self._REF_ROI, {"ScatteringAngle": angle})

Expand All @@ -499,7 +496,7 @@ def test_unstitched_data_gets_dataset_name_from_convert_units(self, mock_alg_his

self._run_save_alg(ws, write_resolution=False)

self._check_file_header([self._get_dataset_name_entry(f"'{self._get_theta_value(ws)}'")])
self._check_file_header([self._get_dataset_name_entry(f"'{self._get_theta_value(ws):.3f}'")])

@patch("mantid.api.WorkspaceHistory.getAlgorithmHistories")
def test_unstitched_data_sets_dataset_name_to_ws_name_as_default(self, mock_alg_histories):
Expand Down Expand Up @@ -580,6 +577,43 @@ def test_saved_as_nexus_if_relevant_filename_extension(self, mock_save_orso_nexu
mock_save_orso_nexus.assert_called_once()
mock_save_orso_ascii.assert_not_called()

@patch("mantid.api.WorkspaceHistory.getAlgorithmHistories")
def test_dataset_name_is_correctly_generated_for_different_angle_polarization_settings_in_ws_groups(self, mock_alg_histories):
theta = 0.5
w_name = "ws"
angle_pol = namedtuple("angle_pol", "angle polarization")
angle_polarization_inputs = [
angle_pol(None, SpinStatesORSO.PP),
angle_pol(theta, ""),
angle_pol(theta, SpinStatesORSO.PP),
angle_pol(None, ""),
]
dataset_name_outputs = [f"{w_name} {SpinStatesORSO.PP}", f"{w_name} {theta:.3f}", f"{theta:.3f} {SpinStatesORSO.PP}", w_name]
dataset_name_outputs = [f"{w_name} {SpinStatesORSO.PP}", f"{w_name} {theta:.3f}", f"{theta:.3f} {SpinStatesORSO.PP}", w_name]
for in_params, out_dataset_name in zip(angle_polarization_inputs, dataset_name_outputs):
with self.subTest(test_case=in_params):
ws = self._create_sample_workspace()
AddSampleLog(Workspace=ws, LogName=SpinStatesORSO.LOG_NAME, LogText=in_params.polarization)
if in_params.angle is not None:
self._configure_q_conversion_alg_mock_history(mock_alg_histories, self._REF_ROI, {"ScatteringAngle": in_params.angle})
GroupWorkspaces(InputWorkspaces=[w_name], OutputWorkspace=f"{w_name}_group")

self._run_save_alg(f"{w_name}_group")

self._check_file_header([self._get_dataset_name_entry(out_dataset_name)])
mock_alg_histories.reset_mock(return_value=True)

def test_data_with_spin_state_logs_adds_polarization_metadata_in_instrument_settings_header(self):
spin_states = [SpinStatesORSO.PP, SpinStatesORSO.MM, SpinStatesORSO.MP, SpinStatesORSO.PM]
ws_grp = self._create_sample_workspace_group_with_spin_state(spin_states)

self._run_save_alg(ws_grp, write_resolution=False, include_extra_cols=False)

self._check_file_header(["instrument_settings:\n" "# incident_angle: null\n" "# wavelength: null"])
for state in spin_states:
with self.subTest(test_case=state):
self._check_file_header([f"# polarization: {state}"])

def _create_sample_workspace(self, rb_num_log_name=_LOG_RB_NUMBER, instrument_name="", ws_name="ws"):
# Create a single spectrum workspace in units of momentum transfer
ws = CreateSampleWorkspace(
Expand All @@ -594,6 +628,15 @@ def _create_sample_workspace_group(self, member_ws_names, group_name="sample_gro
self._create_sample_workspace(ws_name=ws_name, instrument_name=instrument_name)
return GroupWorkspaces(InputWorkspaces=",".join(member_ws_names), OutputWorkspace=group_name)

def _create_sample_workspace_group_with_spin_state(self, spin_states):
group_member_names = []
for state in spin_states:
ws_name = "ws_" + state
group_member_names.append(ws_name)
self._create_sample_workspace(ws_name=ws_name)
AddSampleLog(Workspace=ws_name, LogName=SpinStatesORSO.LOG_NAME, LogText=state)
return GroupWorkspaces(InputWorkspaces=",".join(group_member_names), OutputWorkspace="group_pol")

def _get_expected_data_file_metadata(self, expected_entries, expected_section_end):
files_entry = [f"{self._DATA_FILES_HEADING}\n"]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,7 @@ void ReflectometryReductionOneAuto3::applyPolarizationCorrection(const std::stri
polAlg->setProperty("Efficiencies", efficiencies);
polAlg->setProperty("CorrectionMethod", correctionMethod);
polAlg->setProperty(CorrectionMethod::OPTION_NAME.at(correctionMethod), correctionOption);
polAlg->setProperty("AddSpinStateToLog", true);
adriazalvarez marked this conversation as resolved.
Show resolved Hide resolved

if (correctionMethod == "Fredrikze") {
polAlg->setProperty("InputWorkspaceGroup", outputIvsLam);
Expand Down
Loading
Loading