From bf0f4624250ae7e63a361a7acb5dcc6a6dcc6b1b Mon Sep 17 00:00:00 2001 From: EvgeniiChaikin Date: Sat, 18 May 2024 13:37:00 +0200 Subject: [PATCH 1/7] Add lower-and-upper-limits functionality to observational data object --- velociraptor/observations/objects.py | 84 +++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/velociraptor/observations/objects.py b/velociraptor/observations/objects.py index 28bf5a5..03495f8 100644 --- a/velociraptor/observations/objects.py +++ b/velociraptor/observations/objects.py @@ -8,7 +8,8 @@ """ from unyt import unyt_quantity, unyt_array -from numpy import tanh, log10 +from numpy import tanh, log10, logical_and +from numpy import sum as np_sum from matplotlib.pyplot import Axes from matplotlib import rcParams @@ -17,7 +18,6 @@ from astropy.cosmology import wCDM, FlatLambdaCDM import h5py -import json from typing import Union, Optional, List @@ -131,7 +131,7 @@ class ObservationalData(object): Attributes ---------- name: str - Name of the observation for users to identifty + Name of the observation for users to identify x_units: unyt_quantity Units for horizontal axes @@ -155,6 +155,16 @@ class ObservationalData(object): unyt_array of shape 1XN (symmetric) or 2XN (non-symmetric) such that it can be passed to plt.errorbar easily. + lolims: Union[unyt_array[bool], None] + A bool unyt_array specifying whether y data values are only lower limits + (with entries equal to True or False for each data point, with `True' standing + for the lower limits). The default is None, i.e. all values are not lower limits. + + uplims: Union[unyt_array[bool], None] + A bool unyt_array specifying whether y data values are only upper limits + (with entries equal to True or False for each data point, with `True' standing + for the upper limits). The default is None, i.e. all values are not upper limits. + x_comoving: bool Whether or not the horizontal values are comoving (True) or physical (False) @@ -220,6 +230,9 @@ class ObservationalData(object): # scatter x_scatter: Union[unyt_array, None] y_scatter: Union[unyt_array, None] + # are y data points upper/lower limits? + lower_limits: Union[unyt_array[bool], None] + upper_limits: Union[unyt_array[bool], None] # x and y are comoving? x_comoving: bool y_comoving: bool @@ -303,6 +316,27 @@ def load(self, filename: str, prefix: Optional[str] = None): except KeyError: self.y_scatter = None + try: + self.lower_limits = unyt_array.from_hdf5( + filename, dataset_name=f"{prefix}lower_limits", group_name="y" + ) + except KeyError: + self.lower_limits = None + + try: + self.upper_limits = unyt_array.from_hdf5( + filename, dataset_name=f"{prefix}upper_limits", group_name="y" + ) + except KeyError: + self.upper_limits = None + + try: + self.y_scatter = unyt_array.from_hdf5( + filename, dataset_name=f"{prefix}scatter", group_name="y" + ) + except KeyError: + self.y_scatter = None + with h5py.File(filename, "r") as handle: metadata = handle[f"{prefix}metadata"].attrs @@ -367,6 +401,16 @@ def write(self, filename: str, prefix: Optional[str] = None): filename, dataset_name=f"{prefix}scatter", group_name="y" ) + if self.lower_limits is not None: + self.lower_limits.write_hdf5( + filename, dataset_name=f"{prefix}lower_limits", group_name="y" + ) + + if self.upper_limits is not None: + self.upper_limits.write_hdf5( + filename, dataset_name=f"{prefix}upper_limits", group_name="y" + ) + with h5py.File(filename, "a") as handle: metadata = handle.create_group(f"{prefix}metadata").attrs @@ -434,6 +478,8 @@ def associate_y( scatter: Union[unyt_array, None], comoving: bool, description: str, + lolims: Union[unyt_array[bool], None] = None, + uplims: Union[unyt_array[bool], None] = None, ): """ Associate an y quantity with this observational data instance. @@ -453,6 +499,14 @@ def associate_y( description: str Short description of the data, e.g. Stellar Masses + + lolims: Union[unyt_array[bool], None] + A bool unyt_array indicating whether the y values are lower limits. + The default is `no'. + + uplims: Union[unyt_array[bool], None] + A bool unyt_array indicating whether the y values are upper limits. + The default is `no'. """ self.y = array @@ -465,6 +519,24 @@ def associate_y( else: self.y_scatter = None + if lolims is not None: + self.lower_limits = lolims + + # Check for invalid input + if uplims is not None: + if sum(logical_and(lolims, uplims)): + raise RuntimeError( + "Entries of the unyt arrays representing lower and upper limits must be " + "of 'bool' type and cannot both be 'True' for the same data points." + ) + else: + self.lower_limits = None + + if uplims is not None: + self.upper_limits = uplims + else: + self.upper_limits = None + return def associate_citation(self, citation: str, bibcode: str): @@ -653,6 +725,12 @@ def plot_on_axes(self, axes: Axes, errorbar_kwargs: Union[dict, None] = None): self.y, yerr=self.y_scatter, xerr=self.x_scatter, + lolims=self.lower_limits.value + if self.lower_limits is not None + else None, + uplims=self.upper_limits.value + if self.upper_limits is not None + else None, **kwargs, label=data_label, ) From e711bc6156b9b1c7eb4350de4d3a112f7c7fdc03 Mon Sep 17 00:00:00 2001 From: EvgeniiChaikin Date: Sat, 18 May 2024 16:30:55 +0200 Subject: [PATCH 2/7] Further improvements on the new lower/upper limits functionality --- velociraptor/observations/objects.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/velociraptor/observations/objects.py b/velociraptor/observations/objects.py index 03495f8..63c1162 100644 --- a/velociraptor/observations/objects.py +++ b/velociraptor/observations/objects.py @@ -9,7 +9,6 @@ from unyt import unyt_quantity, unyt_array from numpy import tanh, log10, logical_and -from numpy import sum as np_sum from matplotlib.pyplot import Axes from matplotlib import rcParams @@ -180,7 +179,7 @@ class ObservationalData(object): y_description: str Default label for horizontal axis (without units), also a description of the variable. - +from numpy import sum as np_sum filename: str Filename that the data was read from, or was written to. @@ -231,8 +230,8 @@ class ObservationalData(object): x_scatter: Union[unyt_array, None] y_scatter: Union[unyt_array, None] # are y data points upper/lower limits? - lower_limits: Union[unyt_array[bool], None] - upper_limits: Union[unyt_array[bool], None] + lower_limits: Union[unyt_array, None] + upper_limits: Union[unyt_array, None] # x and y are comoving? x_comoving: bool y_comoving: bool @@ -478,8 +477,8 @@ def associate_y( scatter: Union[unyt_array, None], comoving: bool, description: str, - lolims: Union[unyt_array[bool], None] = None, - uplims: Union[unyt_array[bool], None] = None, + lolims: Union[unyt_array, None] = None, + uplims: Union[unyt_array, None] = None, ): """ Associate an y quantity with this observational data instance. @@ -501,12 +500,12 @@ def associate_y( Short description of the data, e.g. Stellar Masses lolims: Union[unyt_array[bool], None] - A bool unyt_array indicating whether the y values are lower limits. - The default is `no'. + A bool unyt_array indicating whether the y values are lower limits. + The default is None, meaning no data point is a lower limit. uplims: Union[unyt_array[bool], None] A bool unyt_array indicating whether the y values are upper limits. - The default is `no'. + The default is None, meaning no data point is an upper limit. """ self.y = array From 0a4f774369745e55a91a72af795704b86d77ddf5 Mon Sep 17 00:00:00 2001 From: EvgeniiChaikin Date: Sat, 18 May 2024 16:34:39 +0200 Subject: [PATCH 3/7] Fix typo --- velociraptor/observations/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/velociraptor/observations/objects.py b/velociraptor/observations/objects.py index 63c1162..d1fe3c3 100644 --- a/velociraptor/observations/objects.py +++ b/velociraptor/observations/objects.py @@ -179,7 +179,7 @@ class ObservationalData(object): y_description: str Default label for horizontal axis (without units), also a description of the variable. -from numpy import sum as np_sum + filename: str Filename that the data was read from, or was written to. From 1fe91b4f6d0f9dc474318bb7c721b6aeb1c3b127 Mon Sep 17 00:00:00 2001 From: EvgeniiChaikin Date: Sat, 18 May 2024 16:36:55 +0200 Subject: [PATCH 4/7] Remove redundant lines --- velociraptor/observations/objects.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/velociraptor/observations/objects.py b/velociraptor/observations/objects.py index d1fe3c3..18960a6 100644 --- a/velociraptor/observations/objects.py +++ b/velociraptor/observations/objects.py @@ -329,13 +329,6 @@ def load(self, filename: str, prefix: Optional[str] = None): except KeyError: self.upper_limits = None - try: - self.y_scatter = unyt_array.from_hdf5( - filename, dataset_name=f"{prefix}scatter", group_name="y" - ) - except KeyError: - self.y_scatter = None - with h5py.File(filename, "r") as handle: metadata = handle[f"{prefix}metadata"].attrs From 4543b34aec867066ed8a250a6ef054eea475e934 Mon Sep 17 00:00:00 2001 From: EvgeniiChaikin Date: Mon, 20 May 2024 14:55:24 +0200 Subject: [PATCH 5/7] Set default scatter values to define the length of the arrows that indicate upper and lower limits --- velociraptor/observations/objects.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/velociraptor/observations/objects.py b/velociraptor/observations/objects.py index 18960a6..6e6a640 100644 --- a/velociraptor/observations/objects.py +++ b/velociraptor/observations/objects.py @@ -6,7 +6,7 @@ Includes an object container and helper functions for creating and reading files. """ - +import numpy as np from unyt import unyt_quantity, unyt_array from numpy import tanh, log10, logical_and from matplotlib.pyplot import Axes @@ -508,6 +508,8 @@ def associate_y( if scatter is not None: self.y_scatter = scatter.to(self.y_units) + elif lolims is not None or uplims is not None: + self.y_scatter = self.y * 0.0 else: self.y_scatter = None @@ -521,11 +523,26 @@ def associate_y( "Entries of the unyt arrays representing lower and upper limits must be " "of 'bool' type and cannot both be 'True' for the same data points." ) + + # In the case of upper or lower limits, the scatter values define the size of the arrows + lolims_arrow_size = self.y[self.lower_limits.value] / 3.0 + try: + self.y_scatter[self.lower_limits.value] = lolims_arrow_size + except IndexError: + self.y_scatter[:, self.lower_limits.value] = lolims_arrow_size + else: self.lower_limits = None if uplims is not None: self.upper_limits = uplims + + # In the case of upper or lower limits, the scatter values define the size of the arrows + uplims_arrow_size = self.y[self.upper_limits.value] / 3.0 + try: + self.y_scatter[self.upper_limits.value] = uplims_arrow_size + except IndexError: + self.y_scatter[:, self.upper_limits.value] = uplims_arrow_size else: self.upper_limits = None From e0fa0c88b3a89a5332569df20d9d504caea589c1 Mon Sep 17 00:00:00 2001 From: EvgeniiChaikin Date: Mon, 20 May 2024 15:00:06 +0200 Subject: [PATCH 6/7] Remove unwanted import --- velociraptor/observations/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/velociraptor/observations/objects.py b/velociraptor/observations/objects.py index 6e6a640..be31712 100644 --- a/velociraptor/observations/objects.py +++ b/velociraptor/observations/objects.py @@ -6,7 +6,7 @@ Includes an object container and helper functions for creating and reading files. """ -import numpy as np + from unyt import unyt_quantity, unyt_array from numpy import tanh, log10, logical_and from matplotlib.pyplot import Axes From 3f9a4616409eefe00251485cc0d28ab35c85f694 Mon Sep 17 00:00:00 2001 From: EvgeniiChaikin Date: Tue, 21 May 2024 18:35:16 +0200 Subject: [PATCH 7/7] Ensure that default arrow sizes are used only if the y scatter is not provided --- velociraptor/observations/objects.py | 41 ++++++++++++---------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/velociraptor/observations/objects.py b/velociraptor/observations/objects.py index be31712..efb9973 100644 --- a/velociraptor/observations/objects.py +++ b/velociraptor/observations/objects.py @@ -492,11 +492,11 @@ def associate_y( description: str Short description of the data, e.g. Stellar Masses - lolims: Union[unyt_array[bool], None] + lolims: Union[unyt_array, None] A bool unyt_array indicating whether the y values are lower limits. The default is None, meaning no data point is a lower limit. - uplims: Union[unyt_array[bool], None] + uplims: Union[unyt_array, None] A bool unyt_array indicating whether the y values are upper limits. The default is None, meaning no data point is an upper limit. """ @@ -506,13 +506,6 @@ def associate_y( self.y_comoving = comoving self.y_description = description - if scatter is not None: - self.y_scatter = scatter.to(self.y_units) - elif lolims is not None or uplims is not None: - self.y_scatter = self.y * 0.0 - else: - self.y_scatter = None - if lolims is not None: self.lower_limits = lolims @@ -524,28 +517,30 @@ def associate_y( "of 'bool' type and cannot both be 'True' for the same data points." ) - # In the case of upper or lower limits, the scatter values define the size of the arrows - lolims_arrow_size = self.y[self.lower_limits.value] / 3.0 - try: - self.y_scatter[self.lower_limits.value] = lolims_arrow_size - except IndexError: - self.y_scatter[:, self.lower_limits.value] = lolims_arrow_size - else: self.lower_limits = None if uplims is not None: self.upper_limits = uplims - - # In the case of upper or lower limits, the scatter values define the size of the arrows - uplims_arrow_size = self.y[self.upper_limits.value] / 3.0 - try: - self.y_scatter[self.upper_limits.value] = uplims_arrow_size - except IndexError: - self.y_scatter[:, self.upper_limits.value] = uplims_arrow_size else: self.upper_limits = None + if scatter is not None: + self.y_scatter = scatter.to(self.y_units) + # In the absence of provided scatter values, set default values to indicate the lower or upper limits + elif lolims is not None or uplims is not None: + self.y_scatter = self.y * 0.0 + if lolims is not None: + self.y_scatter[self.lower_limits.value] = ( + self.y[self.lower_limits.value] / 3.0 + ) + if uplims is not None: + self.y_scatter[self.upper_limits.value] = ( + self.y[self.upper_limits.value] / 3.0 + ) + else: + self.y_scatter = None + return def associate_citation(self, citation: str, bibcode: str):