From 0ce65e648c29c52a4b7a742c208bcc6e05525358 Mon Sep 17 00:00:00 2001 From: "Adam J. Jackson" Date: Thu, 31 Aug 2023 17:09:36 +0100 Subject: [PATCH 01/11] Implement spectrum copy(), *, *= methods In practice we will usually want __imul__, so makes sense to define __mul__ using it. This requires a fresh Spectrum to write into. In principle it would be more efficient to create one with an "empty" data array... ... but as there is lots of code making modified array copies, life is a lot simpler with a consistent copy() method used as a base for this stuff. If copying the data becomes a noticeable bottleneck we could add an `empty=False` argument that allows us to construct with e.g. `y_data=np.empty_like(self.y_data)*ureg(self.y_data_unit)`, but for now assume YAGNI! With the new Python/numpy/Pint versions we can use np.copy() directly on an array Quantity, which reduces unit-shuffling boilerplate. --- euphonic/spectra.py | 62 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/euphonic/spectra.py b/euphonic/spectra.py index 1c0fdc9e4..c15165b20 100644 --- a/euphonic/spectra.py +++ b/euphonic/spectra.py @@ -4,7 +4,7 @@ import itertools import math import json -from numbers import Integral +from numbers import Integral, Real from typing import (Any, Callable, Dict, List, Optional, overload, Sequence, Tuple, TypeVar, Union, Type) import warnings @@ -59,6 +59,21 @@ def y_data(self, value: Quantity) -> None: self.y_data_unit = str(value.units) self._y_data = value.to(self._internal_y_data_unit).magnitude + def __imul__(self, other: Real) -> None: + """Scale spectral data in-place""" + self._y_data *= other + + def __mul__(self: T, other: Real) -> T: + """Get a new spectrum with scaled data""" + new_spec = self.copy() + new_spec *= other + return new_spec + + @abstractmethod + def copy(self: T) -> T: + """Get an independent copy of spectrum""" + ... + @property def x_tick_labels(self) -> List[Tuple[int, str]]: return self._x_tick_labels @@ -458,6 +473,13 @@ def _split_by_indices(self: T, metadata=self.metadata) for x0, x1 in ranges] + def copy(self: T) -> T: + """Get an independent copy of spectrum""" + return type(self)(np.copy(self.x_data), + np.copy(self.y_data), + x_tick_labels=copy.copy(self.x_tick_labels), + metadata=copy.deepcopy(self.metadata)) + def to_dict(self) -> Dict[str, Any]: """ Convert to a dictionary. See Spectrum1D.from_dict for details on @@ -636,11 +658,9 @@ def broaden(self: T, x_width: Union[Quantity, CallableQuantity], else: raise TypeError("x_width must be a Quantity or Callable") - return type(self)( - np.copy(self.x_data.magnitude)*ureg(self.x_data_unit), - y_broadened, - copy.copy((self.x_tick_labels)), - copy.copy(self.metadata)) + new_spectrum = self.copy() + new_spectrum.y_data = y_broadened + return new_spectrum LineData = Sequence[Dict[str, Union[str, int]]] @@ -908,6 +928,10 @@ def _get_line_data_vals(self, *line_data_keys: str) -> np.ndarray: line_data_vals[i] = tuple([data[key] for key in line_data_keys]) return line_data_vals + def copy(self: T) -> T: + """Get an independent copy of spectrum""" + return Spectrum1D.copy(self) + def to_dict(self) -> Dict[str, Any]: """ Convert to a dictionary consistent with from_dict() @@ -1022,13 +1046,14 @@ def broaden(self: T, x_width: CallableQuantity, ) -> T: # noqa: F811 ... - def broaden(self: T, x_width: Union[Quantity, CallableQuantity], + def broaden(self: T, + x_width: Union[Quantity, CallableQuantity], shape: str = 'gauss', method: Optional[str] = None, width_lower_limit: Quantity = None, width_convention: str = 'FWHM', width_interpolation_error: float = 0.01, - width_fit: str= 'cheby-log' + width_fit: str = 'cheby-log' ) -> T: # noqa: F811 """ Individually broaden each line in y_data, returning a new @@ -1084,11 +1109,10 @@ def broaden(self: T, x_width: Union[Quantity, CallableQuantity], y_broadened[i] = self._broaden_data( yi, x_centres, x_width_calc, shape=shape, method=method) - return Spectrum1DCollection( - np.copy(self.x_data.magnitude)*ureg(self.x_data_unit), - y_broadened*ureg(self.y_data_unit), - copy.copy((self.x_tick_labels)), - copy.deepcopy(self.metadata)) + + new_spectrum = self.copy() + new_spectrum.y_data = y_broadened * ureg(self.y_data_unit) + return new_spectrum elif isinstance(x_width, Callable): return type(self).from_spectra([ @@ -1293,6 +1317,10 @@ def z_data(self, value: Quantity) -> None: self.z_data_unit = str(value.units) self._z_data = value.to(self._internal_z_data_unit).magnitude + def __imul__(self, other: Real) -> None: + """Scale spectral data in-place""" + self._z_data *= other + def __setattr__(self, name: str, value: Any) -> None: _check_unit_conversion(self, name, value, ['z_data_unit']) @@ -1475,6 +1503,14 @@ def _broaden_spectrum2d_with_function( copy.copy(spectrum.x_tick_labels), copy.copy(spectrum.metadata)) + def copy(self: T) -> T: + """Get an independent copy of spectrum""" + return type(self)(np.copy(self.x_data), + np.copy(self.y_data), + np.copy(self.z_data), + copy.copy(self.x_tick_labels), + copy.deepcopy(self.metadata)) + def get_bin_edges(self, bin_ax: str = 'x') -> Quantity: """ Get bin edges for the axis specified by bin_ax. If the size of From 2e0c320705dcf9eb2c9de87fc1cf40f392b8a918 Mon Sep 17 00:00:00 2001 From: "Adam J. Jackson" Date: Fri, 1 Sep 2023 13:52:53 +0100 Subject: [PATCH 02/11] Always deepcopy metadata dict Shallow copies may lead to surprises when elements are mutated; _usually_ it will contain literals (and the type-hinting says it is _supposed_ to only take literals) but a user might put something mutable in there. --- euphonic/spectra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/euphonic/spectra.py b/euphonic/spectra.py index c15165b20..3571eebe6 100644 --- a/euphonic/spectra.py +++ b/euphonic/spectra.py @@ -877,7 +877,7 @@ def _combine_metadata(all_metadata: Sequence[Dict[str, Union[int, str]]] # Put all other per-spectrum metadata in line_data line_data = [] for i, metadata in enumerate(all_metadata): - sdata = copy.copy(metadata) + sdata = copy.deepcopy(metadata) for key in combined_metadata.keys(): sdata.pop(key) line_data.append(sdata) @@ -1431,7 +1431,7 @@ def broaden(self: T, np.copy(self.y_data.magnitude)*ureg(self.y_data_unit), z_broadened*ureg(self.z_data_unit), copy.copy(self.x_tick_labels), - copy.copy(self.metadata)) + copy.deepcopy(self.metadata)) else: spectrum = self From b063218b8fc742b7e33f6ef4c7dfc890be0caf0c Mon Sep 17 00:00:00 2001 From: "Adam J. Jackson" Date: Fri, 1 Sep 2023 14:09:41 +0100 Subject: [PATCH 03/11] Spectrum refactoring --- euphonic/spectra.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/euphonic/spectra.py b/euphonic/spectra.py index 3571eebe6..d1e8e18cc 100644 --- a/euphonic/spectra.py +++ b/euphonic/spectra.py @@ -1165,9 +1165,11 @@ def group_by(self, *line_data_keys: str) -> T: new_y_data = new_y_data*ureg(self._internal_y_data_unit).to( self.y_data_unit) - return Spectrum1DCollection(self.x_data, new_y_data, - x_tick_labels=self.x_tick_labels, - metadata=group_metadata) + new_data = self.copy() + new_data.y_data = new_y_data + new_data.metadata = group_metadata + + return new_data def sum(self) -> Spectrum1D: """ @@ -1185,9 +1187,10 @@ def sum(self) -> Spectrum1D: metadata.update(self._combine_line_metadata()) summed_y_data = np.sum(self._y_data, axis=0)*ureg( self._internal_y_data_unit).to(self.y_data_unit) - return Spectrum1D(self.x_data, summed_y_data, - x_tick_labels=self.x_tick_labels, - metadata=metadata) + return Spectrum1D(np.copy(self.x_data), + summed_y_data, + x_tick_labels=copy.copy(self.x_tick_labels), + metadata=copy.deepcopy(metadata)) def select(self, **select_key_values: Union[ str, int, Sequence[str], Sequence[int]]) -> T: @@ -1731,21 +1734,15 @@ def apply_kinematic_constraints(spectrum: Spectrum2D, float('-Inf')] q_bounds = q_bounds.real - new_z_data = np.copy(spectrum.z_data.magnitude) - mask = np.logical_or((spectrum.get_bin_edges(bin_ax='x')[1:, np.newaxis] < q_bounds[0][np.newaxis, :]), (spectrum.get_bin_edges(bin_ax='x')[:-1, np.newaxis] > q_bounds[-1][np.newaxis, :])) - new_z_data[mask] = float('nan') + new_spectrum = spectrum.copy() + new_spectrum._z_data[mask] = float('nan') - return Spectrum2D( - np.copy(spectrum.x_data.magnitude) * ureg(spectrum.x_data_unit), - np.copy(spectrum.y_data.magnitude) * ureg(spectrum.y_data_unit), - new_z_data * ureg(spectrum.z_data_unit), - copy.copy(spectrum.x_tick_labels), - copy.deepcopy(spectrum.metadata)) + return new_spectrum def _get_cos_range(angle_range: Tuple[float]) -> Tuple[float]: From ac132fc3e257f5480889e5812ebe90a91431e13a Mon Sep 17 00:00:00 2001 From: "Adam J. Jackson" Date: Fri, 1 Sep 2023 14:38:32 +0100 Subject: [PATCH 04/11] Unit test Spectrum.copy() --- .../test/euphonic_test/test_spectrum1d.py | 27 +++++++++++++++++ .../test_spectrum1dcollection.py | 30 ++++++++++++++++++- .../test/euphonic_test/test_spectrum2d.py | 27 +++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/tests_and_analysis/test/euphonic_test/test_spectrum1d.py b/tests_and_analysis/test/euphonic_test/test_spectrum1d.py index 52878a0df..0eb981a5f 100644 --- a/tests_and_analysis/test/euphonic_test/test_spectrum1d.py +++ b/tests_and_analysis/test/euphonic_test/test_spectrum1d.py @@ -502,3 +502,30 @@ def test_add_metadata(self, spectrum_files, metadata, other_spec.metadata = metadata[1] added_spectrum = spec + other_spec assert added_spectrum.metadata == expected_metadata + + def test_copy(self): + spec = get_spectrum1d('xsq_spectrum1d.json') + + spec_copy = spec.copy() + # Copy should be same + check_spectrum1d(spec, spec_copy) + + # Until data is edited + spec_copy._y_data *= 2 + with pytest.raises(AssertionError): + check_spectrum1d(spec, spec_copy) + + spec_copy = spec.copy() + spec_copy._x_data *= 2 + with pytest.raises(AssertionError): + check_spectrum1d(spec, spec_copy) + + spec_copy = spec.copy() + spec_copy.x_tick_labels = [(1, 'different')] + with pytest.raises(AssertionError): + check_spectrum1d(spec, spec_copy) + + spec_copy = spec.copy() + spec_copy.metadata['Test'] = spec_copy.metadata['Test'].upper() + with pytest.raises(AssertionError): + check_spectrum1d(spec, spec_copy) diff --git a/tests_and_analysis/test/euphonic_test/test_spectrum1dcollection.py b/tests_and_analysis/test/euphonic_test/test_spectrum1dcollection.py index 06b5ddde6..daa7e932b 100644 --- a/tests_and_analysis/test/euphonic_test/test_spectrum1dcollection.py +++ b/tests_and_analysis/test/euphonic_test/test_spectrum1dcollection.py @@ -482,7 +482,7 @@ def test_get_bin_centres_with_invalid_data_shape_raises_value_error(self): # Check the same answer is obtained broadening Spectrum1DCollection, # and broadening each spectrum individually @pytest.mark.parametrize( - 'spectrum_file, width, shape', [ + 'spectrum_file, width, shape', [ ('methane_pdos.json', 10*ureg('1/cm'), 'lorentz'), ('quartz_dos_collection.json', 2*ureg('meV'), 'gauss'), ('quartz_dos_collection.json', 15*ureg('1/cm'), 'gauss')]) @@ -665,3 +665,31 @@ def test_add(self, spectrum_file, other_spectrum_file, expected_added_spec = get_expected_spectrum1dcollection( expected_spectrum_file) check_spectrum1dcollection(added_spec, expected_added_spec) + + def test_copy(self): + spec = get_spectrum1dcollection('gan_bands.json') + spec.metadata = {'Test': 'item', 'int': 1} + + spec_copy = spec.copy() + # Copy should be same + check_spectrum1dcollection(spec, spec_copy) + + # Until data is edited + spec_copy._y_data *= 2 + with pytest.raises(AssertionError): + check_spectrum1dcollection(spec, spec_copy) + + spec_copy = spec.copy() + spec_copy._x_data *= 2 + with pytest.raises(AssertionError): + check_spectrum1dcollection(spec, spec_copy) + + spec_copy = spec.copy() + spec_copy.x_tick_labels = [(1, 'different')] + with pytest.raises(AssertionError): + check_spectrum1dcollection(spec, spec_copy) + + spec_copy = spec.copy() + spec_copy.metadata['Test'] = spec_copy.metadata['Test'].upper() + with pytest.raises(AssertionError): + check_spectrum1dcollection(spec, spec_copy) diff --git a/tests_and_analysis/test/euphonic_test/test_spectrum2d.py b/tests_and_analysis/test/euphonic_test/test_spectrum2d.py index 2252af4ed..5ebb07efa 100644 --- a/tests_and_analysis/test/euphonic_test/test_spectrum2d.py +++ b/tests_and_analysis/test/euphonic_test/test_spectrum2d.py @@ -475,6 +475,33 @@ def test_get_bin_centres_with_invalid_data_shape_raises_value_error( with pytest.raises(ValueError): spec2d.get_bin_centres() + def test_copy(self): + spec = get_spectrum2d('example_spectrum2d.json') + + spec_copy = spec.copy() + # Copy should be same + check_spectrum2d(spec, spec_copy) + + # Until data is edited + for attr in '_x_data', '_y_data', '_z_data': + setattr(spec_copy, attr, getattr(spec, attr) * 2) + + with pytest.raises(AssertionError): + check_spectrum2d(spec, spec_copy) + + spec_copy = spec.copy() + + spec_copy = spec.copy() + spec_copy.x_tick_labels = [(1, 'different')] + with pytest.raises(AssertionError): + check_spectrum2d(spec, spec_copy) + + spec_copy = spec.copy() + spec_copy.metadata['description'] = \ + spec_copy.metadata['description'].upper() + with pytest.raises(AssertionError): + check_spectrum2d(spec, spec_copy) + class TestKinematicAngles: From f76b0da0c6c307d923e83deeca2dadf070276317 Mon Sep 17 00:00:00 2001 From: "Adam J. Jackson" Date: Fri, 1 Sep 2023 15:26:58 +0100 Subject: [PATCH 05/11] __imul__ needs return values; spectrum2D mul tests --- euphonic/spectra.py | 8 +++++--- .../test/euphonic_test/test_spectrum2d.py | 10 ++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/euphonic/spectra.py b/euphonic/spectra.py index d1e8e18cc..f6e14f369 100644 --- a/euphonic/spectra.py +++ b/euphonic/spectra.py @@ -59,9 +59,10 @@ def y_data(self, value: Quantity) -> None: self.y_data_unit = str(value.units) self._y_data = value.to(self._internal_y_data_unit).magnitude - def __imul__(self, other: Real) -> None: + def __imul__(self: T, other: Real) -> T: """Scale spectral data in-place""" self._y_data *= other + return self def __mul__(self: T, other: Real) -> T: """Get a new spectrum with scaled data""" @@ -1320,9 +1321,10 @@ def z_data(self, value: Quantity) -> None: self.z_data_unit = str(value.units) self._z_data = value.to(self._internal_z_data_unit).magnitude - def __imul__(self, other: Real) -> None: + def __imul__(self: T, other: Real) -> T: """Scale spectral data in-place""" - self._z_data *= other + self.z_data = self.z_data * other + return self def __setattr__(self, name: str, value: Any) -> None: _check_unit_conversion(self, name, value, diff --git a/tests_and_analysis/test/euphonic_test/test_spectrum2d.py b/tests_and_analysis/test/euphonic_test/test_spectrum2d.py index 5ebb07efa..9e555372c 100644 --- a/tests_and_analysis/test/euphonic_test/test_spectrum2d.py +++ b/tests_and_analysis/test/euphonic_test/test_spectrum2d.py @@ -502,6 +502,16 @@ def test_copy(self): with pytest.raises(AssertionError): check_spectrum2d(spec, spec_copy) + def test_mult(self): + spec = get_spectrum2d('example_spectrum2d.json') + + npt.assert_allclose(spec.z_data * 2., + (spec * 2.).z_data) + + check_spectrum2d(spec, (spec * 2.) * 0.5) + + with pytest.raises(AssertionError): + check_spectrum2d(spec, spec * 2.) class TestKinematicAngles: From 999b1aff1111e274202165d1d8debc6f9dd12a18 Mon Sep 17 00:00:00 2001 From: "Adam J. Jackson" Date: Fri, 1 Sep 2023 15:59:09 +0100 Subject: [PATCH 06/11] Test spectrum multiplication --- .../test/euphonic_test/test_spectrum1d.py | 11 ++++++++++ .../test_spectrum1dcollection.py | 12 +++++++++++ .../test/euphonic_test/test_spectrum2d.py | 21 ++++++++++--------- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/tests_and_analysis/test/euphonic_test/test_spectrum1d.py b/tests_and_analysis/test/euphonic_test/test_spectrum1d.py index 0eb981a5f..ba984fa07 100644 --- a/tests_and_analysis/test/euphonic_test/test_spectrum1d.py +++ b/tests_and_analysis/test/euphonic_test/test_spectrum1d.py @@ -503,6 +503,17 @@ def test_add_metadata(self, spectrum_files, metadata, added_spectrum = spec + other_spec assert added_spectrum.metadata == expected_metadata + def test_mul(self): + spec = get_spectrum1d('xsq_spectrum1d.json') + + npt.assert_allclose(spec.y_data * 2., + (spec * 2.).y_data) + + check_spectrum1d(spec, (spec * 2.) * 0.5) + + with pytest.raises(AssertionError): + check_spectrum1d(spec, spec * 2.) + def test_copy(self): spec = get_spectrum1d('xsq_spectrum1d.json') diff --git a/tests_and_analysis/test/euphonic_test/test_spectrum1dcollection.py b/tests_and_analysis/test/euphonic_test/test_spectrum1dcollection.py index daa7e932b..24aedbd5e 100644 --- a/tests_and_analysis/test/euphonic_test/test_spectrum1dcollection.py +++ b/tests_and_analysis/test/euphonic_test/test_spectrum1dcollection.py @@ -666,6 +666,18 @@ def test_add(self, spectrum_file, other_spectrum_file, expected_spectrum_file) check_spectrum1dcollection(added_spec, expected_added_spec) + def test_mul(self): + spec = get_spectrum1dcollection('gan_bands.json') + + for i, spec1d in enumerate(spec): + check_spectrum1d(spec1d * 2., + (spec * 2.)[i]) + + check_spectrum1dcollection(spec, (spec * 2.) * 0.5) + + with pytest.raises(AssertionError): + check_spectrum1dcollection(spec, spec * 2.) + def test_copy(self): spec = get_spectrum1dcollection('gan_bands.json') spec.metadata = {'Test': 'item', 'int': 1} diff --git a/tests_and_analysis/test/euphonic_test/test_spectrum2d.py b/tests_and_analysis/test/euphonic_test/test_spectrum2d.py index 9e555372c..8834ebf78 100644 --- a/tests_and_analysis/test/euphonic_test/test_spectrum2d.py +++ b/tests_and_analysis/test/euphonic_test/test_spectrum2d.py @@ -475,6 +475,17 @@ def test_get_bin_centres_with_invalid_data_shape_raises_value_error( with pytest.raises(ValueError): spec2d.get_bin_centres() + def test_mul(self): + spec = get_spectrum2d('example_spectrum2d.json') + + npt.assert_allclose(spec.z_data * 2., + (spec * 2.).z_data) + + check_spectrum2d(spec, (spec * 2.) * 0.5) + + with pytest.raises(AssertionError): + check_spectrum2d(spec, spec * 2.) + def test_copy(self): spec = get_spectrum2d('example_spectrum2d.json') @@ -502,16 +513,6 @@ def test_copy(self): with pytest.raises(AssertionError): check_spectrum2d(spec, spec_copy) - def test_mult(self): - spec = get_spectrum2d('example_spectrum2d.json') - - npt.assert_allclose(spec.z_data * 2., - (spec * 2.).z_data) - - check_spectrum2d(spec, (spec * 2.) * 0.5) - - with pytest.raises(AssertionError): - check_spectrum2d(spec, spec * 2.) class TestKinematicAngles: From 41b81be8d1a562e3de185ea0514b91e2e2a4f94e Mon Sep 17 00:00:00 2001 From: "Adam J. Jackson" Date: Fri, 1 Sep 2023 16:20:33 +0100 Subject: [PATCH 07/11] Implement `--scale` option for dos, intensity-map and powder-map --- euphonic/cli/dos.py | 6 +++++- euphonic/cli/intensity_map.py | 5 ++++- euphonic/cli/powder_map.py | 5 ++++- euphonic/cli/utils.py | 4 ++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/euphonic/cli/dos.py b/euphonic/cli/dos.py index 1710b8b6b..793393214 100644 --- a/euphonic/cli/dos.py +++ b/euphonic/cli/dos.py @@ -125,6 +125,9 @@ def energy_broadening_func(x): plot_label_kwargs = _plot_label_kwargs( args, default_xlabel=f"Energy / {dos.x_data.units:~P}") + if args.scale is not None: + dos *= args.scale + if args.save_json: dos.to_json_file(args.save_json) style = _compose_style(user_args=args, base=[base_style]) @@ -137,7 +140,8 @@ def get_parser() -> ArgumentParser: parser, _ = _get_cli_parser(features={'read-fc', 'read-modes', 'mp-grid', 'plotting', 'ebins', 'adaptive-broadening', - 'pdos-weighting'}) + 'pdos-weighting', + 'scaling'}) parser.description = ( 'Plots a DOS from the file provided. If a force ' 'constants file is provided, a DOS is generated on the Monkhorst-Pack ' diff --git a/euphonic/cli/intensity_map.py b/euphonic/cli/intensity_map.py index 3ef7ee4a9..325648907 100755 --- a/euphonic/cli/intensity_map.py +++ b/euphonic/cli/intensity_map.py @@ -85,6 +85,9 @@ def main(params: Optional[List[str]] = None) -> None: if x_tick_labels: spectrum.x_tick_labels = x_tick_labels + if args.scale is not None: + spectrum *= args.scale + spectra = spectrum.split(**split_args) # type: List[Spectrum2D] if len(spectra) > 1: print(f"Found {len(spectra)} regions in q-point path") @@ -104,7 +107,7 @@ def main(params: Optional[List[str]] = None) -> None: def get_parser() -> ArgumentParser: parser, sections = _get_cli_parser( features={'read-fc', 'read-modes', 'q-e', 'map', 'btol', 'ebins', - 'ins-weighting', 'plotting'}) + 'ins-weighting', 'plotting', 'scaling'}) parser.description = ( 'Plots a 2D intensity map from the file provided. If a force ' 'constants file is provided, a band structure path is ' diff --git a/euphonic/cli/powder_map.py b/euphonic/cli/powder_map.py index ad03b3714..efe5f0568 100755 --- a/euphonic/cli/powder_map.py +++ b/euphonic/cli/powder_map.py @@ -39,7 +39,7 @@ def get_parser() -> ArgumentParser: parser, sections = _get_cli_parser( features={'read-fc', 'pdos-weighting', 'ins-weighting', 'powder', 'plotting', 'ebins', 'q-e', 'map', - 'brille', 'kinematic'}) + 'brille', 'kinematic', 'scaling'}) sections['q'].description = ( '"GRID" options relate to Monkhorst-Pack sampling for the ' @@ -237,6 +237,9 @@ def main(params: Optional[List[str]] = None) -> None: spectrum = apply_kinematic_constraints( spectrum, e_i=e_i, e_f=e_f, angle_range=args.angle_range) + if args.scale is not None: + spectrum *= args.scale + print(f"Plotting figure: max intensity " f"{np.nanmax(spectrum.z_data.magnitude) * spectrum.z_data.units:~P}") plot_label_kwargs = _plot_label_kwargs( diff --git a/euphonic/cli/utils.py b/euphonic/cli/utils.py index 01443c941..595f5623a 100644 --- a/euphonic/cli/utils.py +++ b/euphonic/cli/utils.py @@ -622,6 +622,10 @@ def __call__(self, parser, args, values, option_string=None): '--grid-spacing', type=float, default=0.1, dest='grid_spacing', help=('q-point spacing of Monkhorst-Pack grid.')) + if 'scaling' in features: + sections['property'].add_argument( + '--scale', type=float, help='Intensity scale factor', default=None) + if 'powder' in features: _sampling_choices = {'golden', 'sphere-projected-grid', 'spherical-polar-grid', From 3ae621e4fa5fbe737f72f0f3e1580dc9c9cdd5f9 Mon Sep 17 00:00:00 2001 From: "Adam J. Jackson" Date: Fri, 1 Sep 2023 16:29:34 +0100 Subject: [PATCH 08/11] Add dos script test for --scale --- .../test/data/script_data/dos.json | 226 ++++++++++++++++++ .../test/script_tests/test_dos.py | 1 + 2 files changed, 227 insertions(+) diff --git a/tests_and_analysis/test/data/script_data/dos.json b/tests_and_analysis/test/data/script_data/dos.json index 0377efa35..9ee1c71ba 100644 --- a/tests_and_analysis/test/data/script_data/dos.json +++ b/tests_and_analysis/test/data/script_data/dos.json @@ -9447,5 +9447,231 @@ ] ] ] + }, + "NaH NaH.phonon --ebins=100 --scale=1e2": { + "x_ticklabels": [ + [ + "0", + "20", + "40", + "60", + "80", + "100", + "120" + ] + ], + "x_label": [ + "Energy / meV" + ], + "y_label": [], + "title": "", + "xy_data": [ + [ + [ + 0.5754555625823561, + 1.7263666877470683, + 2.8772778129117804, + 4.028188938076493, + 5.179100063241204, + 6.330011188405917, + 7.4809223135706295, + 8.631833438735342, + 9.782744563900053, + 10.933655689064764, + 12.084566814229479, + 13.23547793939419, + 14.386389064558902, + 15.537300189723615, + 16.688211314888328, + 17.83912244005304, + 18.99003356521775, + 20.14094469038246, + 21.291855815547173, + 22.442766940711888, + 23.5936780658766, + 24.744589191041314, + 25.895500316206025, + 27.046411441370736, + 28.197322566535448, + 29.34823369170016, + 30.49914481686487, + 31.65005594202959, + 32.8009670671943, + 33.95187819235901, + 35.10278931752372, + 36.253700442688434, + 37.404611567853145, + 38.555522693017856, + 39.70643381818257, + 40.85734494334728, + 42.008256068512, + 43.15916719367671, + 44.31007831884142, + 45.46098944400613, + 46.61190056917084, + 47.762811694335554, + 48.913722819500265, + 50.064633944664976, + 51.215545069829695, + 52.366456194994406, + 53.51736732015912, + 54.66827844532383, + 55.81918957048854, + 56.97010069565325, + 58.12101182081796, + 59.27192294598267, + 60.42283407114739, + 61.5737451963121, + 62.724656321476814, + 63.875567446641526, + 65.02647857180625, + 66.17738969697095, + 67.32830082213567, + 68.47921194730037, + 69.6301230724651, + 70.7810341976298, + 71.93194532279452, + 73.08285644795922, + 74.23376757312394, + 75.38467869828864, + 76.53558982345336, + 77.68650094861806, + 78.83741207378279, + 79.98832319894748, + 81.13923432411221, + 82.2901454492769, + 83.44105657444163, + 84.59196769960636, + 85.74287882477105, + 86.89378994993578, + 88.04470107510048, + 89.1956122002652, + 90.3465233254299, + 91.49743445059462, + 92.64834557575932, + 93.79925670092405, + 94.95016782608874, + 96.10107895125347, + 97.25199007641817, + 98.40290120158289, + 99.55381232674759, + 100.70472345191232, + 101.85563457707704, + 103.00654570224174, + 104.15745682740646, + 105.30836795257116, + 106.45927907773589, + 107.61019020290058, + 108.76110132806531, + 109.91201245323, + 111.06292357839473, + 112.21383470355943, + 113.36474582872415, + 114.51565695388885 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 21.721920531806596, + 0.0, + 0.0, + 0.0, + 0.0, + 32.58288079770989, + 0.0, + 32.582880797709976, + 10.860960265903298, + 0.0, + 0.0, + 0.0, + 0.0, + 32.582880797709976, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 21.72192053180654, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 32.582880797709805, + 0.0, + 0.0, + 0.0, + 0.0, + 32.582880797709805, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 32.58288079771014, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 10.860960265903381, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ] + ] } } \ No newline at end of file diff --git a/tests_and_analysis/test/script_tests/test_dos.py b/tests_and_analysis/test/script_tests/test_dos.py index 559a3c06a..575388466 100644 --- a/tests_and_analysis/test/script_tests/test_dos.py +++ b/tests_and_analysis/test/script_tests/test_dos.py @@ -32,6 +32,7 @@ [nah_phonon_file], [nah_phonon_file, '--energy-broadening=2'], [nah_phonon_file, '--ebins=100'], + [nah_phonon_file, '--ebins=100', '--scale=1e2'], [nah_phonon_file, '--energy-broadening=2.3', '--ebins=50'], [nah_phonon_file, '--instrument-broadening', '1.', '0.1', '-0.001', '--ebins=100', '--shape', 'gauss'], From fff78540d55bfdc722207681e138ea3db900a5cd Mon Sep 17 00:00:00 2001 From: "Adam J. Jackson" Date: Fri, 1 Sep 2023 16:35:35 +0100 Subject: [PATCH 09/11] Add intensity-map scipt test for --scale --- .../test/data/script_data/intensity-map.json | 574 ++++++++++++++++++ .../test/script_tests/test_intensity_map.py | 1 + 2 files changed, 575 insertions(+) diff --git a/tests_and_analysis/test/data/script_data/intensity-map.json b/tests_and_analysis/test/data/script_data/intensity-map.json index ca1e2674f..6186f9887 100644 --- a/tests_and_analysis/test/data/script_data/intensity-map.json +++ b/tests_and_analysis/test/data/script_data/intensity-map.json @@ -8411,5 +8411,579 @@ 0.0 ], "x_label": [] + }, + "graphite graphite.castep_bin --scale=-0.1": { + "x_ticklabels": [ + [ + "$\\Gamma$", + "M", + "K", + "$\\Gamma$", + "A", + "L", + "H", + "A" + ], + [ + "L", + "M" + ], + [ + "H", + "K" + ] + ], + "x_label": [], + "y_label": [ + "Energy / meV" + ], + "title": "", + "cmap": "viridis", + "extent": [ + 0.0, + 8.515360832214355, + 0.11658504605293274, + 208.408203125 + ], + "size": [ + 200, + 332 + ], + "data_1": [ + -0.0, + -0.0, + -0.047769564468807785, + -0.0, + -0.04776956446880777, + -0.023884782234403903, + -0.0, + -0.0, + -0.0, + -0.0, + -0.023884782234403885, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.02388478223440439, + -0.02388478223440385, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.09553912893761325, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0 + ], + "data_2": [ + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.02388478223440385, + -0.0477695644688077, + -0.0477695644688077, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0477695644688077, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.02388478223440385, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.02388478223440385, + -0.0477695644688077, + -0.0477695644688077, + -0.02388478223440385, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0477695644688077, + -0.0477695644688077, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0477695644688077, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0477695644688077, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0477695644688077, + -0.0477695644688077, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0, + -0.0 + ] } } \ No newline at end of file diff --git a/tests_and_analysis/test/script_tests/test_intensity_map.py b/tests_and_analysis/test/script_tests/test_intensity_map.py index b5945c9ee..f6ad384de 100644 --- a/tests_and_analysis/test/script_tests/test_intensity_map.py +++ b/tests_and_analysis/test/script_tests/test_intensity_map.py @@ -30,6 +30,7 @@ intensity_map_output_file = get_script_test_data_path('intensity-map.json') intensity_map_params = [ [graphite_fc_file], + [graphite_fc_file, '--scale=-0.1'], [graphite_fc_file, '--vmin=0', '--vmax=1e-10'], [graphite_fc_file, '--energy-unit=meV'], [graphite_fc_file, '-w', 'dos', '--ylabel=DOS', '--title=DOS TITLE'], From 57660c15057167a7e714272405dadb001d7e6422 Mon Sep 17 00:00:00 2001 From: "Adam J. Jackson" Date: Fri, 1 Sep 2023 16:49:25 +0100 Subject: [PATCH 10/11] Add script test for powder-map --scale --- .../test/data/script_data/powder-map.json | 242 +++++++++++++++++- .../test/script_tests/test_powder_map.py | 2 + 2 files changed, 243 insertions(+), 1 deletion(-) diff --git a/tests_and_analysis/test/data/script_data/powder-map.json b/tests_and_analysis/test/data/script_data/powder-map.json index 5c7582541..918a7c399 100644 --- a/tests_and_analysis/test/data/script_data/powder-map.json +++ b/tests_and_analysis/test/data/script_data/powder-map.json @@ -5096,5 +5096,245 @@ 1.7267336291597234e-07, 2.84690209900474e-07 ] + }, + "graphite graphite.castep_bin -w dos --y-label=DOS --scale=10 --npts=10 --npts-min=10 --q-spacing=1": { + "x_ticklabels": [ + [ + "0.0", + "0.5", + "1.0", + "1.5", + "2.0", + "2.5", + "3.0" + ], + [], + [] + ], + "x_label": [ + "|q| / 1/\u00c5" + ], + "y_label": [ + "DOS" + ], + "title": "", + "cmap": "viridis", + "extent": [ + 0.5, + 2.5, + 0.5844312310218811, + 233.18804931640625 + ], + "size": [ + 200, + 3 + ], + "data_1": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.427766336124642, + 0.427766336124642, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.427766336124642, + 0.42776633612464415, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.4277663361246464, + 0.0, + 0.0, + 0.6416495041869662, + 0.213883168062321, + 0.0, + 0.427766336124642, + 0.855532672249284, + 0.427766336124642, + 0.427766336124642, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.427766336124642, + 0.427766336124642, + 1.711065344498568, + 0.855532672249284, + 0.0, + 0.0, + 0.0, + 0.855532672249284, + 0.427766336124642, + 0.0, + 0.427766336124642, + 0.0, + 0.0, + 0.0, + 0.427766336124642, + 0.427766336124642, + 0.427766336124642, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.283299008373926, + 0.4277663361246376, + 0.4277663361246506, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.4277663361246334, + 0.4277663361246506, + 0.0, + 0.0, + 0.0, + 0.4277663361246334, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.427766336124642, + 0.0, + 0.0, + 0.0, + 0.0, + 0.8555326722492668, + 0.0, + 0.4277663361246334, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.427766336124642, + 0.427766336124642, + 0.0, + 0.427766336124642, + 0.855532672249284, + 1.069415840311605, + 0.2138831680623253, + 0.0, + 0.4277663361246506, + 1.0694158403115834, + 0.2138831680623253, + 0.8555326722492668, + 0.4277663361246506, + 0.0, + 0.0, + 0.0, + 0.0, + 0.427766336124642, + 0.427766336124642, + 0.0, + 0.641649504186963, + 0.213883168062321, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.8555326722493012, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "data_2": [ + 0.0, + 0.0, + 0.0 + ] } -} +} \ No newline at end of file diff --git a/tests_and_analysis/test/script_tests/test_powder_map.py b/tests_and_analysis/test/script_tests/test_powder_map.py index ea5c30ad6..5d5c9399f 100644 --- a/tests_and_analysis/test/script_tests/test_powder_map.py +++ b/tests_and_analysis/test/script_tests/test_powder_map.py @@ -28,6 +28,8 @@ powder_map_params = [ [graphite_fc_file, '-w', 'dos', '--y-label=DOS', '--title=DOS TITLE', *quick_calc_params], + [graphite_fc_file, '-w', 'dos', '--y-label=DOS', '--scale=10', + *quick_calc_params], [graphite_fc_file, '--e-min=50', '-u=cm^-1', '--x-label=wavenumber', *quick_calc_params, '-w=coherent-dos'], [graphite_fc_file, '--e-min=-100', '--e-max=1000', '--ebins=100', From 23c0ae33320e0beaca5fa329c7726434a79c2aba Mon Sep 17 00:00:00 2001 From: "Adam J. Jackson" Date: Fri, 1 Sep 2023 17:20:11 +0100 Subject: [PATCH 11/11] Update CHANGELOG.rst --- CHANGELOG.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4aea95874..1217d82ae 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,6 +23,20 @@ interpolation method already available for adaptive broadening of DOS. + - Added features to Spectrum classes + + - Added ``copy()`` methods returning an independent duplicate of data + + - Added ``__mul__`` and ``__imul__`` methods to Spectrum + classes. This allows results to be conveniently scaled with + infix notation ``*`` or ``*=`` + + - Added `--scale` parameter to ``euphonic-dos``, + ``euphonic-intensity-map``, ``euphonic-powder-map`` to allow + arbitrary scaling of results from command-line. (e.g. for + comparison with experiment, or changing DOS normalisation from 1 + to 3N.) + - Bug Fixes: - Changed the masking logic for kinematic constraints: instead of