Skip to content

Commit 1173f48

Browse files
Make it easy to fix and free energy offset
1 parent 8d97ad8 commit 1173f48

6 files changed

Lines changed: 237 additions & 20 deletions

File tree

src/easydynamics/analysis/analysis.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,38 @@ def plot_parameters(
422422
)
423423
return fig
424424

425+
def fix_energy_offset(self, Q_index: int | None = None) -> None:
426+
"""Fix the energy offset parameter(s) for a specific Q index, or
427+
for all Q indices if Q_index is None.
428+
429+
Args:
430+
Q_index (int | None, default=None): Index of the Q value to
431+
fix the energy offset for. If None, fixes the energy
432+
offset for all Q values. Default is None.
433+
"""
434+
if Q_index is not None:
435+
Q_index = self._verify_Q_index(Q_index)
436+
self.analysis_list[Q_index].fix_energy_offset()
437+
else:
438+
for analysis in self.analysis_list:
439+
analysis.fix_energy_offset()
440+
441+
def free_energy_offset(self, Q_index: int | None = None) -> None:
442+
"""Free the energy offset parameter(s) for a specific Q index,
443+
or for all Q indices if Q_index is None.
444+
445+
Args:
446+
Q_index (int | None, default=None): Index of the Q value to
447+
free the energy offset for. If None, frees the energy
448+
offset for all Q values. Default is None.
449+
"""
450+
if Q_index is not None:
451+
Q_index = self._verify_Q_index(Q_index)
452+
self.analysis_list[Q_index].free_energy_offset()
453+
else:
454+
for analysis in self.analysis_list:
455+
analysis.free_energy_offset()
456+
425457
#############
426458
# Private methods - updating models when things change
427459
#############
@@ -430,7 +462,7 @@ def _on_experiment_changed(self) -> None:
430462
"""Update the Q values in the sample and instrument models when
431463
the experiment changes.
432464
433-
Also update all the Analysi1d objects with the new experiment.
465+
Also update all the Analysis1d objects with the new experiment.
434466
"""
435467
if self._call_updaters:
436468
super()._on_experiment_changed()
@@ -441,7 +473,8 @@ def _on_sample_model_changed(self) -> None:
441473
"""Update the Q values in the sample model when the sample model
442474
changes.
443475
444-
Also update all the Analysi1d objects with the new sample model.
476+
Also update all the Analysis1d objects with the new sample
477+
model.
445478
"""
446479
if self._call_updaters:
447480
super()._on_sample_model_changed()
@@ -452,7 +485,7 @@ def _on_instrument_model_changed(self) -> None:
452485
"""Update the Q values in the instrument model when the
453486
instrument model changes.
454487
455-
Also update all the Analysi1d objects with the new instrument
488+
Also update all the Analysis1d objects with the new instrument
456489
model.
457490
"""
458491
if self._call_updaters:

src/easydynamics/analysis/analysis1d.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,14 @@ def plot_data_and_model(
305305
)
306306
return fig
307307

308+
def fix_energy_offset(self) -> None:
309+
"""Fix the energy offset parameter for the current Q index."""
310+
self.instrument_model.fix_energy_offset(Q_index=self._require_Q_index())
311+
312+
def free_energy_offset(self) -> None:
313+
"""Free the energy offset parameter for the current Q index."""
314+
self.instrument_model.free_energy_offset(Q_index=self._require_Q_index())
315+
308316
#############
309317
# Private methods: small utilities
310318
#############
@@ -428,7 +436,7 @@ def _evaluate_components(
428436
if energy is None:
429437
energy = self._masked_energy
430438

431-
energy_offset = self.instrument_model.get_energy_offset_at_Q(Q_index)
439+
energy_offset = self.instrument_model.get_energy_offset(Q_index)
432440
energy_with_offset = self._calculate_energy_with_offset(
433441
energy=energy,
434442
energy_offset=energy_offset,
@@ -601,7 +609,7 @@ def _create_convolver(
601609
resolution_components=resolution_components,
602610
energy=energy,
603611
temperature=self.temperature,
604-
energy_offset=self.instrument_model.get_energy_offset_at_Q(Q_index),
612+
energy_offset=self.instrument_model.get_energy_offset(Q_index),
605613
)
606614
return convolver
607615

src/easydynamics/sample_model/instrument_model.py

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -351,32 +351,96 @@ def free_resolution_parameters(self) -> None:
351351
"""Free all parameters in the resolution model."""
352352
self.resolution_model.free_all_parameters()
353353

354-
def get_energy_offset_at_Q(self, Q_index: int) -> Parameter:
354+
def get_energy_offset(
355+
self,
356+
Q_index: int | None = None,
357+
) -> Parameter | list[Parameter]:
355358
"""Get the energy offset Parameter at a specific Q index.
356359
357360
Args:
358-
Q_index (int): The index of the Q value to get the energy
359-
offset for.
361+
Q_index (int | None, default=None): The index of the Q value to get the energy
362+
offset for. If None, get the energy offset for all Q values.
360363
361364
Returns:
362-
Parameter: The energy offset Parameter at the specified Q
363-
index.
365+
Parameter | list[Parameter]: The energy offset Parameter at the specified Q
366+
index, or a list of Parameters if Q_index is None.
364367
365368
Raises:
366369
ValueError: If no Q values are set in the InstrumentModel.
367370
IndexError: If Q_index is out of bounds.
371+
TypeError: If Q_index is not an int or None.
368372
"""
369373
if self._Q is None:
370374
raise ValueError('No Q values are set in the InstrumentModel.')
371375

376+
if Q_index is None:
377+
return self._energy_offsets
378+
379+
if not isinstance(Q_index, int):
380+
raise TypeError(f'Q_index must be an int or None, got {type(Q_index).__name__}')
381+
372382
if Q_index < 0 or Q_index >= len(self._Q):
373383
raise IndexError(f'Q_index {Q_index} is out of bounds for Q of length {len(self._Q)}')
374384

375385
return self._energy_offsets[Q_index]
376386

387+
def fix_energy_offset(self, Q_index: int | None = None) -> None:
388+
"""Fix energy offset parameters. If Q_index is specified, only
389+
fix the energy offset for that Q value. If Q_index is None, fix
390+
energy offsets for all Q values.
391+
392+
Args:
393+
Q_index (int | None, default=None): The index of the Q value
394+
to fix the energy offset for. If None, fix energy
395+
offsets for all Q values.
396+
"""
397+
self._fix_or_free_energy_offset(Q_index, fixed=True)
398+
399+
def free_energy_offset(self, Q_index: int | None = None) -> None:
400+
"""Free energy offset parameters. If Q_index is specified, only
401+
free the energy offset for that Q value. If Q_index is None,
402+
free energy offsets for all Q values.
403+
404+
Args:
405+
Q_index (int | None, default=None): The index of the Q value
406+
to free the energy offset for. If None, free energy
407+
offsets for all Q values.
408+
"""
409+
self._fix_or_free_energy_offset(Q_index, fixed=False)
410+
377411
# --------------------------------------------------------------
378412
# Private methods
379413
# --------------------------------------------------------------
414+
def _fix_or_free_energy_offset(self, Q_index: int | None = None, fixed: bool = True) -> None:
415+
"""Fix or free energy offset parameters. If Q_index is
416+
specified, only fix or free the energy offset for that Q value.
417+
If Q_index is None, fix or free energy offsets for all Q values.
418+
419+
Args:
420+
Q_index (int | None, default=None): The index of the Q value
421+
to fix or free the energy offset for. If None, fix or
422+
free energy offsets for all Q values.
423+
fixed (bool, default=True): Whether to fix (True) or free
424+
(False) the energy offset.
425+
426+
Raises:
427+
TypeError: If Q_index is not an int or None.
428+
IndexError: If Q_index is out of bounds for the Q values in
429+
the InstrumentModel.
430+
"""
431+
432+
if Q_index is None:
433+
for offset in self._energy_offsets:
434+
offset.fixed = fixed
435+
else:
436+
if not isinstance(Q_index, int):
437+
raise TypeError(f'Q_index must be an int or None, got {type(Q_index).__name__}')
438+
439+
if Q_index < 0 or Q_index >= len(self._Q):
440+
raise IndexError(
441+
f'Q_index {Q_index} is out of bounds for Q of length {len(self._Q)}'
442+
)
443+
self._energy_offsets[Q_index].fixed = fixed
380444

381445
def _generate_energy_offsets(self) -> None:
382446
"""Generate energy offset Parameters for each Q value."""

tests/unit/easydynamics/analysis/test_analysis.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,43 @@ def test_plot_parameters(self, analysis):
476476
# and that we return the figure
477477
assert result is fake_fig
478478

479+
def test_fix_and_free_energy_offset(self, analysis):
480+
# EXPECT
481+
offsets = analysis.instrument_model.get_energy_offset()
482+
for offset in offsets:
483+
assert offset.fixed is False
484+
485+
# THEN
486+
analysis.fix_energy_offset()
487+
488+
# EXPECT
489+
for offset in offsets:
490+
assert offset.fixed is True
491+
492+
# THEN
493+
analysis.free_energy_offset()
494+
495+
# EXPECT
496+
for offset in offsets:
497+
assert offset.fixed is False
498+
499+
# THEN
500+
analysis.fix_energy_offset(Q_index=1)
501+
502+
# EXPECT
503+
for i, offset in enumerate(offsets):
504+
if i == 1:
505+
assert offset.fixed is True
506+
else:
507+
assert offset.fixed is False
508+
509+
# THEN
510+
analysis.free_energy_offset(Q_index=1)
511+
512+
# EXPECT
513+
for offset in offsets:
514+
assert offset.fixed is False
515+
479516
def test_on_experiment_changed_similar_Q(self, analysis):
480517
# WHEN
481518
# Create a new experiment.

tests/unit/easydynamics/analysis/test_analysis1d.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,24 @@ def test_plot_calls_plopp_with_correct_arguments(self, analysis1d):
274274

275275
assert result is fake_fig
276276

277+
def test_fix_and_free_offset(self, analysis1d):
278+
# WHEN
279+
280+
# EXPECT
281+
assert analysis1d.instrument_model.get_energy_offset(Q_index=0).fixed is False
282+
283+
# THEN
284+
analysis1d.fix_energy_offset()
285+
286+
# EXPECT
287+
assert analysis1d.instrument_model.get_energy_offset(Q_index=0).fixed is True
288+
289+
# THEN
290+
analysis1d.free_energy_offset()
291+
292+
# EXPECT
293+
assert analysis1d.instrument_model.get_energy_offset(Q_index=0).fixed is False
294+
277295
#############
278296
# Private methods: small utilities
279297
#############
@@ -334,7 +352,7 @@ def test_verify_energy_raises(self, analysis1d):
334352
def test_calculate_energy_with_offset(self, analysis1d):
335353
# WHEN
336354
energy = analysis1d.experiment.energy
337-
energy_offset = analysis1d.instrument_model.get_energy_offset_at_Q(analysis1d.Q_index)
355+
energy_offset = analysis1d.instrument_model.get_energy_offset(Q_index=analysis1d.Q_index)
338356
energy_offset.value = 1.0 # override with a simple value for testing
339357

340358
# THEN
@@ -347,7 +365,7 @@ def test_calculate_energy_with_offset(self, analysis1d):
347365
def test_calculate_energy_with_offset_different_units(self, analysis1d):
348366
# WHEN
349367
energy = analysis1d.experiment.energy
350-
energy_offset = analysis1d.instrument_model.get_energy_offset_at_Q(analysis1d.Q_index)
368+
energy_offset = analysis1d.instrument_model.get_energy_offset(Q_index=analysis1d.Q_index)
351369
energy_offset.value = 1.0 # override with a simple value for testing
352370
energy_offset.convert_unit('eV')
353371

@@ -452,7 +470,7 @@ def test_evaluate_with_resolution(self, analysis1d):
452470
)
453471
)
454472

455-
energy_offset = analysis1d.instrument_model.get_energy_offset_at_Q(analysis1d.Q_index)
473+
energy_offset = analysis1d.instrument_model.get_energy_offset(analysis1d.Q_index)
456474

457475
# Extract call arguments
458476
_, kwargs = MockConvolution.call_args
@@ -576,7 +594,7 @@ def test_create_convolver(self, analysis1d):
576594
return_value=resolution_components
577595
)
578596

579-
analysis1d.instrument_model.get_energy_offset_at_Q = MagicMock(return_value=123.0)
597+
analysis1d.instrument_model.get_energy_offset = MagicMock(return_value=123.0)
580598

581599
with patch('easydynamics.analysis.analysis1d.Convolution') as MockConvolution:
582600
# THEN

0 commit comments

Comments
 (0)