Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions docs/docs/tutorials/delta_lorentz.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@
"metadata": {},
"outputs": [],
"source": [
"diffusion_model._A_0_list[3].value = 0.02 # noqa\n",
"diffusion_model._lorentzian_width_list[5].value = 0.1 # noqa"
"diffusion_model._A_0_list[3].value = 0.02 # ruff: ignore[private-member-access]\n",
"diffusion_model._lorentzian_width_list[5].value = 0.1 # ruff: ignore[private-member-access]"
]
},
{
Expand Down
5,923 changes: 2,938 additions & 2,985 deletions pixi.lock

Large diffs are not rendered by default.

14 changes: 9 additions & 5 deletions src/easydynamics/analysis/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def rebin(
If rebinning changes Q and ``confirm`` is not ``True``.
"""
old_Q = np.asarray(self.Q.values) if self.Q is not None else None
old_binned_data = self.experiment._binned_data # noqa: SLF001
old_binned_data = self.experiment._binned_data # ruff: ignore[private-member-access]

self.experiment.rebin(dimensions)
new_Q = np.asarray(self.Q.values) if self.Q is not None else None
Expand All @@ -212,7 +212,7 @@ def rebin(
)

if q_changed and not confirm:
self.experiment._binned_data = old_binned_data # noqa: SLF001
self.experiment._binned_data = old_binned_data # ruff: ignore[private-member-access]
raise ValueError(
'Rebinning changed Q values, which requires clearing Q from sample_model and '
'instrument_model (including resolution and background sub-models). '
Expand Down Expand Up @@ -756,7 +756,7 @@ def _fit_all_Q_simultaneously(self) -> FitResults:
ys = []
ws = []

# TODO: consider using scipp built-in masking instead of numpy boolean masks, # noqa: FIX002 TD002 TD003
# TODO: consider using scipp built-in masking instead of numpy boolean masks, # ruff: ignore[line-contains-todo, missing-todo-author, missing-todo-link]

for analysis1d in self.analysis_list:
x, y, weight, mask = self.experiment.extract_x_y_weights_only_finite(
Expand Down Expand Up @@ -812,7 +812,11 @@ def _create_model_array(self, energy: sc.Variable | None = None) -> sc.DataArray
"""
if energy is None:
energy = self.energy
model = sc.array(dims=['Q', 'energy'], values=self.calculate(energy=energy))
model = sc.array(
dims=['Q', 'energy'],
values=self.calculate(energy=energy),
unit=self.sample_model.y_unit,
)
return sc.DataArray(
data=model,
coords={'Q': self.Q, 'energy': energy},
Expand Down Expand Up @@ -859,7 +863,7 @@ def _create_components_dataset(
energy = self.energy

datasets = [
analysis1d._create_components_dataset_single_Q( # noqa: SLF001
analysis1d._create_components_dataset_single_Q( # ruff: ignore[private-member-access]
add_background=add_background, energy=energy
)
for analysis1d in self.analysis_list
Expand Down
8 changes: 5 additions & 3 deletions src/easydynamics/analysis/analysis1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ def fit(self) -> FitResults:
def as_fit_function(
self,
_x: np.ndarray | sc.Variable | None = None,
**kwargs: dict[str, Any], # noqa: ARG002
**kwargs: dict[str, Any], # ruff: ignore[unused-method-argument]
) -> callable:
"""
Return self._calculate as a fit function.
Expand All @@ -294,7 +294,7 @@ def as_fit_function(

def fit_function(
_x: np.ndarray | sc.Variable | None = None,
**kwargs: dict[str, Any], # noqa: ARG001
**kwargs: dict[str, Any], # ruff: ignore[unused-function-argument]
) -> np.ndarray:
"""Fit function."""
return self._calculate()
Expand Down Expand Up @@ -736,6 +736,8 @@ def _build_convolution(
convolution_settings=self.convolution_settings,
temperature=self.temperature,
detailed_balance_settings=self.detailed_balance_settings,
x_unit=self.sample_model.x_unit,
y_unit=self.sample_model.y_unit,
)

#############
Expand Down Expand Up @@ -854,7 +856,7 @@ def _to_scipp_array(
if energy is None:
energy = self._masked_energy
return sc.DataArray(
data=sc.array(dims=['energy'], values=values),
data=sc.array(dims=['energy'], values=values, unit=self.sample_model.y_unit),
coords={
'energy': energy,
'Q': self.Q[self.Q_index],
Expand Down
2 changes: 1 addition & 1 deletion src/easydynamics/base_classes/easydynamics_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def _validate_type(self, value: object) -> None:
if not isinstance(value, tuple(self._protected_types)):
allowed = ', '.join(t.__name__ for t in self._protected_types)
raise TypeError(
f'Value must be an instance of type: {allowed}. Got {type(value).__name__} instead.' # noqa: E501
f'Value must be an instance of type: {allowed}. Got {type(value).__name__} instead.' # ruff: ignore[line-too-long]
)

# ------------------------------------------------------------------
Expand Down
10 changes: 5 additions & 5 deletions src/easydynamics/convolution/convolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,9 @@ def _build_convolution_plan(self) -> None:
Separate sample model components into analytical pairs, delta functions, and the rest.
"""

analytical_sample_components = ComponentCollection()
delta_sample_components = ComponentCollection()
numerical_sample_components = ComponentCollection()
analytical_sample_components = ComponentCollection(x_unit=self.x_unit, y_unit=self.y_unit)
delta_sample_components = ComponentCollection(x_unit=self.x_unit, y_unit=self.y_unit)
numerical_sample_components = ComponentCollection(x_unit=self.x_unit, y_unit=self.y_unit)

for sample_component in self._sample_components:
# If delta function, put in delta sample model and go to the
Expand Down Expand Up @@ -341,9 +341,9 @@ def convert_y_unit(self, unit: str) -> None:
# The sub-convolvers share this convolver's component objects, which were already
# converted by super(); only their y-unit labels need updating.
if getattr(self, '_analytical_convolver', None) is not None:
self._analytical_convolver._relabel_y_unit(self.y_unit) # noqa: SLF001
self._analytical_convolver._relabel_y_unit(self.y_unit) # ruff: ignore[private-member-access]
if getattr(self, '_numerical_convolver', None) is not None:
self._numerical_convolver._relabel_y_unit(self.y_unit) # noqa: SLF001
self._numerical_convolver._relabel_y_unit(self.y_unit) # ruff: ignore[private-member-access]

# Update some setters so the internal sample models are updated
def __setattr__(self, name: str, value: any) -> None:
Expand Down
32 changes: 24 additions & 8 deletions src/easydynamics/convolution/convolution_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,20 +96,28 @@ def __init__(
isinstance(sample_components, (ComponentCollection, ModelComponent))
):
raise TypeError(
f'`sample_components` is an instance of {type(sample_components).__name__}, but must be a ComponentCollection or ModelComponent.' # noqa: E501
f'`sample_components` is an instance of {type(sample_components).__name__}, but must be a ComponentCollection or ModelComponent.' # ruff: ignore[line-too-long]
)
if isinstance(sample_components, ModelComponent):
sample_components = ComponentCollection(components=[sample_components])
sample_components = ComponentCollection(
components=[sample_components],
x_unit=sample_components.x_unit,
y_unit=sample_components.y_unit,
)
self._sample_components = sample_components

if resolution_components is not None and not (
isinstance(resolution_components, (ComponentCollection, ModelComponent))
):
raise TypeError(
f'`resolution_components` is an instance of {type(resolution_components).__name__}, but must be a ComponentCollection or ModelComponent.' # noqa: E501
f'`resolution_components` is an instance of {type(resolution_components).__name__}, but must be a ComponentCollection or ModelComponent.' # ruff: ignore[line-too-long]
)
if isinstance(resolution_components, ModelComponent):
resolution_components = ComponentCollection(components=[resolution_components])
resolution_components = ComponentCollection(
components=[resolution_components],
x_unit=resolution_components.x_unit,
y_unit=resolution_components.y_unit,
)
self._resolution_components = resolution_components

@property
Expand Down Expand Up @@ -314,11 +322,15 @@ def sample_components(self, sample_components: ComponentCollection | ModelCompon
"""
if not isinstance(sample_components, (ComponentCollection, ModelComponent)):
raise TypeError(
f'`sample_components` is an instance of {type(sample_components).__name__}, but must be a ComponentCollection or ModelComponent.' # noqa: E501
f'`sample_components` is an instance of {type(sample_components).__name__}, but must be a ComponentCollection or ModelComponent.' # ruff: ignore[line-too-long]
)

if isinstance(sample_components, ModelComponent):
sample_components = ComponentCollection(components=[sample_components])
sample_components = ComponentCollection(
components=[sample_components],
x_unit=sample_components.x_unit,
y_unit=sample_components.y_unit,
)
self._sample_components = sample_components

@property
Expand Down Expand Up @@ -353,9 +365,13 @@ def resolution_components(
"""
if not isinstance(resolution_components, (ComponentCollection, ModelComponent)):
raise TypeError(
f'`resolution_components` is an instance of {type(resolution_components).__name__}, but must be a ComponentCollection or ModelComponent.' # noqa: E501
f'`resolution_components` is an instance of {type(resolution_components).__name__}, but must be a ComponentCollection or ModelComponent.' # ruff: ignore[line-too-long]
)

if isinstance(resolution_components, ModelComponent):
resolution_components = ComponentCollection(components=[resolution_components])
resolution_components = ComponentCollection(
components=[resolution_components],
x_unit=resolution_components.x_unit,
y_unit=resolution_components.y_unit,
)
self._resolution_components = resolution_components
6 changes: 3 additions & 3 deletions src/easydynamics/convolution/numerical_convolution_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,11 @@ def _convolution_plan_is_current(self) -> bool:
seen_version = getattr(self, '_plan_seen_version', None)
if seen_version is None:
return False
return self.convolution_settings._plan_valid_for(seen_version) # noqa: SLF001
return self.convolution_settings._plan_valid_for(seen_version) # ruff: ignore[private-member-access]

def _mark_convolution_plan_current(self) -> None:
"""Record that this convolver's plan matches its current state and settings."""
self._plan_seen_version = self.convolution_settings._plan_version # noqa: SLF001
self._plan_seen_version = self.convolution_settings._plan_version # ruff: ignore[private-member-access]

@property
def convolution_settings(self) -> ConvolutionSettings:
Expand Down Expand Up @@ -358,7 +358,7 @@ def _create_energy_grid(
is_uniform = np.allclose(energy_diff, energy_diff[0])
if not is_uniform:
raise ValueError(
'Input array `energy` must be uniformly spaced if upsample_factor is not given.' # noqa: E501
'Input array `energy` must be uniformly spaced if upsample_factor is not given.' # ruff: ignore[line-too-long]
)
energy_dense = self.energy.values

Expand Down
4 changes: 3 additions & 1 deletion src/easydynamics/experiment/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,9 @@ def rebin(self, dimensions: dict[str, int | sc.Variable]) -> None:
)
if self._data is None:
raise ValueError('No data to rebin. Please load data first.')
binned_data = self._data.copy()
# sc.bin cannot handle multi-dimensional dense data with bin-edge
# coordinates, so convert them to bin centers first.
binned_data = self._convert_to_bin_centers(self._data.copy())
dim_copy = dimensions.copy()
for dim, value in dim_copy.items():
if not isinstance(dim, str):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,13 +474,13 @@ def _relabel_parameter_unit(self, name: str, unit: str | sc.Unit) -> None:

# Parameter.unit is read-only and convert_unit rescales the value, so a pure relabel
# has to swap the underlying scipp scalars (value and bounds) directly.
param._scalar = sc.scalar( # noqa: SLF001
param._scalar = sc.scalar( # ruff: ignore[private-member-access]
param.value,
unit=new_unit,
variance=param._scalar.variance, # noqa: SLF001
variance=param._scalar.variance, # ruff: ignore[private-member-access]
)
param._min = sc.scalar(param._min.value, unit=new_unit) # noqa: SLF001
param._max = sc.scalar(param._max.value, unit=new_unit) # noqa: SLF001
param._min = sc.scalar(param._min.value, unit=new_unit) # ruff: ignore[private-member-access]
param._max = sc.scalar(param._max.value, unit=new_unit) # ruff: ignore[private-member-access]

@classmethod
def _create_physical_constant(cls, name: str) -> DescriptorNumber:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ def create_component_collections(
y_unit=self.y_unit,
)
if self._allow_Q_variation['lorentzian_width'] is True:
lorz_component._width = self._lorentzian_width_list[i] # noqa: SLF001
lorz_component._width = self._lorentzian_width_list[i] # ruff: ignore[private-member-access]

# If the width is allowed to vary with Q it is independent.
# If the width is not allowed to vary with Q it must be made
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ def _assert_convertible_to_mev(unit: str | sc.Unit) -> None:
test.convert_unit('meV')
except Exception as e:
raise UnitError(
f'Invalid unit: {unit}. Unit must be a string or scipp Unit and convertible to meV.' # noqa: E501
f'Invalid unit: {unit}. Unit must be a string or scipp Unit and convertible to meV.' # ruff: ignore[line-too-long]
) from e

def convert_x_unit(self, unit: str | sc.Unit) -> None:
Expand Down
6 changes: 4 additions & 2 deletions src/easydynamics/sample_model/model_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def __init__(
f'got {type(components).__name__}'
)

self._components = ComponentCollection()
self._components = ComponentCollection(x_unit=self.x_unit, y_unit=self.y_unit)
self._component_collections: list[ComponentCollection] = []
self._component_collections_is_dirty = True
if isinstance(components, (ModelComponent, ComponentCollection)):
Expand Down Expand Up @@ -313,7 +313,9 @@ def _convert_axis_unit(self, unit: str | sc.Unit, axis: str) -> None:

method = f'convert_{axis}_unit'
old_unit = self.x_unit if axis == 'x' else self.y_unit
children = [*self.components, *self._component_collections]
# Convert the template collection as a whole (not its unpacked components) so its own
# unit attribute is updated too; regenerated per-Q collections copy that attribute.
children = [self._components, *self._component_collections]
convert_units_with_rollback([
(getattr(child, method), unit, old_unit) for child in children
])
Expand Down
4 changes: 2 additions & 2 deletions src/easydynamics/sample_model/sample_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def append_diffusion_model(self, diffusion_model: DiffusionModelBase) -> None:
"""
if not isinstance(diffusion_model, DiffusionModelBase):
raise TypeError(
f'diffusion_model must be a DiffusionModelBase, got {type(diffusion_model).__name__}' # noqa: E501
f'diffusion_model must be a DiffusionModelBase, got {type(diffusion_model).__name__}' # ruff: ignore[line-too-long]
)
diffusion_model.Q = self.Q
self._diffusion_models.append(diffusion_model)
Expand Down Expand Up @@ -355,7 +355,7 @@ def temperature_unit(self, _value: str | sc.Unit) -> None:

raise AttributeError(
f'Temperature_unit is read-only. Use convert_temperature_unit to change the unit '
f'between allowed types or create a new {self.__class__.__name__} with the desired unit.' # noqa: E501
f'between allowed types or create a new {self.__class__.__name__} with the desired unit.' # ruff: ignore[line-too-long]
)

def convert_temperature_unit(self, unit: str | sc.Unit) -> None:
Expand Down
24 changes: 24 additions & 0 deletions tests/unit/easydynamics/analysis/test_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,30 @@ def test_create_model_array(self, analysis):
np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]),
)

def test_create_model_array_and_residuals_with_counts_data(self):
"Regression: model arrays must carry the sample model y_unit so counts data works"
# WHEN
Q = sc.array(dims=['Q'], values=[1.0, 2.0], unit='1/Angstrom')
energy = sc.linspace('energy', -5.0, 5.0, num=20, unit='meV')
values = np.ones((2, 20))
data_array = sc.DataArray(
data=sc.array(dims=['Q', 'energy'], values=values, variances=values, unit='counts'),
coords={'Q': Q, 'energy': energy},
)
analysis = Analysis(
experiment=Experiment(data=data_array),
sample_model=SampleModel(y_unit='counts', components=Gaussian(y_unit='counts')),
instrument_model=InstrumentModel(),
)

# THEN
model_array = analysis._create_model_array()
residuals = analysis._create_residuals_array()

# EXPECT
assert model_array.unit == sc.Unit('counts')
assert residuals.unit == sc.Unit('counts')

def test_create_residuals_array(self, analysis):
# WHEN
# Mock the _create_model_array method to return a specific model array
Expand Down
Loading