Skip to content

Commit a26f428

Browse files
Merge pull request #108 from SWIFTSIM/add-lo-up-lims-functionality
Functionality to display lower and upper limits of observational data
2 parents 69e488b + 3f9a461 commit a26f428

File tree

1 file changed

+85
-3
lines changed

1 file changed

+85
-3
lines changed

velociraptor/observations/objects.py

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"""
99

1010
from unyt import unyt_quantity, unyt_array
11-
from numpy import tanh, log10
11+
from numpy import tanh, log10, logical_and
1212
from matplotlib.pyplot import Axes
1313
from matplotlib import rcParams
1414

@@ -17,7 +17,6 @@
1717
from astropy.cosmology import wCDM, FlatLambdaCDM
1818

1919
import h5py
20-
import json
2120

2221
from typing import Union, Optional, List
2322

@@ -131,7 +130,7 @@ class ObservationalData(object):
131130
Attributes
132131
----------
133132
name: str
134-
Name of the observation for users to identifty
133+
Name of the observation for users to identify
135134
136135
x_units: unyt_quantity
137136
Units for horizontal axes
@@ -155,6 +154,16 @@ class ObservationalData(object):
155154
unyt_array of shape 1XN (symmetric) or 2XN (non-symmetric)
156155
such that it can be passed to plt.errorbar easily.
157156
157+
lolims: Union[unyt_array[bool], None]
158+
A bool unyt_array specifying whether y data values are only lower limits
159+
(with entries equal to True or False for each data point, with `True' standing
160+
for the lower limits). The default is None, i.e. all values are not lower limits.
161+
162+
uplims: Union[unyt_array[bool], None]
163+
A bool unyt_array specifying whether y data values are only upper limits
164+
(with entries equal to True or False for each data point, with `True' standing
165+
for the upper limits). The default is None, i.e. all values are not upper limits.
166+
158167
x_comoving: bool
159168
Whether or not the horizontal values are comoving (True)
160169
or physical (False)
@@ -220,6 +229,9 @@ class ObservationalData(object):
220229
# scatter
221230
x_scatter: Union[unyt_array, None]
222231
y_scatter: Union[unyt_array, None]
232+
# are y data points upper/lower limits?
233+
lower_limits: Union[unyt_array, None]
234+
upper_limits: Union[unyt_array, None]
223235
# x and y are comoving?
224236
x_comoving: bool
225237
y_comoving: bool
@@ -303,6 +315,20 @@ def load(self, filename: str, prefix: Optional[str] = None):
303315
except KeyError:
304316
self.y_scatter = None
305317

318+
try:
319+
self.lower_limits = unyt_array.from_hdf5(
320+
filename, dataset_name=f"{prefix}lower_limits", group_name="y"
321+
)
322+
except KeyError:
323+
self.lower_limits = None
324+
325+
try:
326+
self.upper_limits = unyt_array.from_hdf5(
327+
filename, dataset_name=f"{prefix}upper_limits", group_name="y"
328+
)
329+
except KeyError:
330+
self.upper_limits = None
331+
306332
with h5py.File(filename, "r") as handle:
307333
metadata = handle[f"{prefix}metadata"].attrs
308334

@@ -367,6 +393,16 @@ def write(self, filename: str, prefix: Optional[str] = None):
367393
filename, dataset_name=f"{prefix}scatter", group_name="y"
368394
)
369395

396+
if self.lower_limits is not None:
397+
self.lower_limits.write_hdf5(
398+
filename, dataset_name=f"{prefix}lower_limits", group_name="y"
399+
)
400+
401+
if self.upper_limits is not None:
402+
self.upper_limits.write_hdf5(
403+
filename, dataset_name=f"{prefix}upper_limits", group_name="y"
404+
)
405+
370406
with h5py.File(filename, "a") as handle:
371407
metadata = handle.create_group(f"{prefix}metadata").attrs
372408

@@ -434,6 +470,8 @@ def associate_y(
434470
scatter: Union[unyt_array, None],
435471
comoving: bool,
436472
description: str,
473+
lolims: Union[unyt_array, None] = None,
474+
uplims: Union[unyt_array, None] = None,
437475
):
438476
"""
439477
Associate an y quantity with this observational data instance.
@@ -453,15 +491,53 @@ def associate_y(
453491
454492
description: str
455493
Short description of the data, e.g. Stellar Masses
494+
495+
lolims: Union[unyt_array, None]
496+
A bool unyt_array indicating whether the y values are lower limits.
497+
The default is None, meaning no data point is a lower limit.
498+
499+
uplims: Union[unyt_array, None]
500+
A bool unyt_array indicating whether the y values are upper limits.
501+
The default is None, meaning no data point is an upper limit.
456502
"""
457503

458504
self.y = array
459505
self.y_units = array.units
460506
self.y_comoving = comoving
461507
self.y_description = description
462508

509+
if lolims is not None:
510+
self.lower_limits = lolims
511+
512+
# Check for invalid input
513+
if uplims is not None:
514+
if sum(logical_and(lolims, uplims)):
515+
raise RuntimeError(
516+
"Entries of the unyt arrays representing lower and upper limits must be "
517+
"of 'bool' type and cannot both be 'True' for the same data points."
518+
)
519+
520+
else:
521+
self.lower_limits = None
522+
523+
if uplims is not None:
524+
self.upper_limits = uplims
525+
else:
526+
self.upper_limits = None
527+
463528
if scatter is not None:
464529
self.y_scatter = scatter.to(self.y_units)
530+
# In the absence of provided scatter values, set default values to indicate the lower or upper limits
531+
elif lolims is not None or uplims is not None:
532+
self.y_scatter = self.y * 0.0
533+
if lolims is not None:
534+
self.y_scatter[self.lower_limits.value] = (
535+
self.y[self.lower_limits.value] / 3.0
536+
)
537+
if uplims is not None:
538+
self.y_scatter[self.upper_limits.value] = (
539+
self.y[self.upper_limits.value] / 3.0
540+
)
465541
else:
466542
self.y_scatter = None
467543

@@ -653,6 +729,12 @@ def plot_on_axes(self, axes: Axes, errorbar_kwargs: Union[dict, None] = None):
653729
self.y,
654730
yerr=self.y_scatter,
655731
xerr=self.x_scatter,
732+
lolims=self.lower_limits.value
733+
if self.lower_limits is not None
734+
else None,
735+
uplims=self.upper_limits.value
736+
if self.upper_limits is not None
737+
else None,
656738
**kwargs,
657739
label=data_label,
658740
)

0 commit comments

Comments
 (0)