Downloading Waveforms from Different Catalogs¶
This tutorial demonstrates how to download and work with waveforms from various numerical relativity catalogs using PyART. PyART provides a unified interface to access data from multiple catalogs including:
SXS (Simulating eXtreme Spacetimes)
RIT (Rochester Institute of Technology)
CoRe (Computational Relativity)
GRA (GR-Athena++)
ICCUB (Institute of Cosmos Sciences)
SACRA
Setup¶
First, let’s import the necessary modules:
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
from PyART.catalogs import sxs, rit, core, icc_public
from PyART.catalogs.cataloger import Cataloger
/opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/PyART/analysis/match.py:15: UserWarning: Wswiglal-redir-stdio:
SWIGLAL standard output/error redirection is enabled in IPython.
This may lead to performance penalties. To disable locally, use:
with lal.no_swig_redirect_standard_output_error():
...
To disable globally, use:
lal.swig_redirect_standard_output_error(False)
Note however that this will likely lead to error messages from
LAL functions being either misdirected or lost when called from
Jupyter notebooks.
To suppress this warning, use:
import warnings
warnings.filterwarnings("ignore", "Wswiglal-redir-stdio")
import lal
import lal
WARNING: TEOBResumS not installed.
Downloading from a Single Catalog¶
Let’s start by downloading waveforms from the SXS catalog. We specify:
The catalog name
The simulation ID
Whether to download if not locally available
SXS¶
# Download and load an SXS waveform
# Note: You may need to adjust the path or set download=True
sxs_id = '0180'
wf_sxs = sxs.Waveform_SXS(path='./', ID=sxs_id, download=True, cut_N=300, ignore_deprecation=True)
print(f"SXS Waveform {sxs_id} loaded successfully")
print(f"Mass ratio q = {wf_sxs.metadata['q']:.3f}")
print(f"Total mass M = {wf_sxs.metadata['M']:.3f}")
print(f"Chi1z = {wf_sxs.metadata['chi1z']:.3f}")
print(f"Chi2z = {wf_sxs.metadata['chi2z']:.3f}")
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[2], line 4
1 # Download and load an SXS waveform
2 # Note: You may need to adjust the path or set download=True
3 sxs_id = '0180'
----> 4 wf_sxs = sxs.Waveform_SXS(path='./', ID=sxs_id, download=True, cut_N=300, ignore_deprecation=True)
6 print(f"SXS Waveform {sxs_id} loaded successfully")
7 print(f"Mass ratio q = {wf_sxs.metadata['q']:.3f}")
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/PyART/catalogs/sxs.py:135, in Waveform_SXS.__init__(self, path, ID, order, level, cut_N, cut_U, ellmax, load, download, downloads, load_m0, nu_rescale, src, ignore_deprecation, basename)
131 logging.info(
132 f"The path {self.sxs_data_path} does not exist or contains no 'Lev*' directory."
133 )
134 logging.info("Downloading the simulation from the SXS catalog.")
--> 135 self.download_simulation(
136 ID=self.ID,
137 path=path,
138 downloads=downloads,
139 level=self.level,
140 ignore_deprecation=ignore_deprecation,
141 extrapolation_order=order,
142 )
143 else:
144 logging.warning(
145 "Use download=True to download the simulation from the SXS catalog."
146 )
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/PyART/catalogs/sxs.py:290, in Waveform_SXS.download_simulation(self, ID, path, downloads, level, ignore_deprecation, extrapolation_order)
288 sys.stdout = open(os.devnull, "w")
289 sys.stderr = open(os.devnull, "w")
--> 290 sxs_sim = sxsmod.load(
291 name_level,
292 extrapolation_order=extrapolation_order,
293 ignore_deprecation=ignore_deprecation,
294 progress=True,
295 )
296 logging.info(f"Loaded SXS simulation {name_level}.")
298 # Set Level if not already set
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/sxs/handlers.py:288, in load(location, download, cache, progress, truepath, **kwargs)
285 return sxscatalog.load(location, download=download, **kwargs)
287 elif sxs_id_version_lev_exact_re.match(location):
--> 288 return Simulation(location, download=download, cache=cache, progress=progress, **kwargs)
290 else:
291 # Try to find an appropriate SXS file in the simulations
292 simulations = Simulations.load(
293 download=download,
294 local=kwargs.get("local", False),
295 annex_dir=kwargs.get("annex_dir", None)
296 )
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/sxs/simulations/simulation.py:155, in Simulation(location, *args, **kwargs)
153 # Attach metadata to this object
154 metadata = Metadata(simulations[simulation_id])
--> 155 series = simulations.dataframe.loc[simulation_id]
157 # If input_version is not the default, remove "files" from metadata
158 version_is_not_default = (
159 input_version
160 and input_version != max(metadata.get("DOI_versions", []), default="")
161 )
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/sxscatalog/simulations/simulations.py:629, in Simulations.dataframe(self)
564 gotten = gotten.map(mapper)
565 return gotten.rename(new_name)
567 sims_df = SimulationsDataFrame(pd.concat((
568 get(simulations, "reference_mass_ratio", floater),
569 get(simulations, "reference_chi_eff", floater),
570 get(simulations, "reference_chi1_perp", floater),
571 get(simulations, "reference_chi2_perp", floater),
572 get(simulations, "reference_eccentricity", floater),
573 get(simulations, "reference_eccentricity", floaterbound, new_name="reference_eccentricity_bound"),
574 get(simulations, "reference_time", floater),
575 three_vector_dataframe(simulations, "reference_dimensionless_spin1"),
576 three_vector_dataframe(simulations, "reference_dimensionless_spin2"),
577 get(simulations, "reference_mean_anomaly", floater),
578 three_vector_dataframe(simulations, "reference_orbital_frequency"),
579 (
580 get(simulations, "reference_position1", three_vec)
581 - get(simulations, "reference_position2", three_vec)
582 ).map(norm).rename("reference_separation"),
583 get(simulations, "reference_position1", three_vec),
584 get(simulations, "reference_position2", three_vec),
585 get(simulations, "reference_mass1", floater),
586 get(simulations, "reference_mass2", floater),
587 get(simulations, "reference_dimensionless_spin1", norm, new_name="reference_chi1_mag"),
588 get(simulations, "reference_dimensionless_spin2", norm, new_name="reference_chi2_mag"),
589 get(simulations, "relaxation_time", floater),
590 # get(simulations, "merger_time", floater),
591 get(simulations, "common_horizon_time", floater),
592 get(simulations, "remnant_mass", floater),
593 three_vector_dataframe(simulations, "remnant_dimensionless_spin"),
594 three_vector_dataframe(simulations, "remnant_velocity"),
595 # get(simulations, "final_time", floater),
596 get(simulations, "EOS", np.nan).fillna(get(simulations, "eos", np.nan)),
597 get(simulations, "disk_mass", floater),
598 get(simulations, "ejecta_mass", floater),
599 get(simulations, "object_types", "").astype("category"),
600 get(simulations, "initial_data_type", "").astype("category"),
601 get(simulations, "initial_separation", floater),
602 get(simulations, "initial_orbital_frequency", floater),
603 get(simulations, "initial_adot", floater),
604 get(simulations, "initial_ADM_energy", floater),
605 three_vector_dataframe(simulations, "initial_ADM_linear_momentum"),
606 three_vector_dataframe(simulations, "initial_ADM_angular_momentum"),
607 get(simulations, "initial_mass1", floater),
608 get(simulations, "initial_mass2", floater),
609 get(simulations, "initial_mass_ratio", floater),
610 three_vector_dataframe(simulations, "initial_dimensionless_spin1"),
611 three_vector_dataframe(simulations, "initial_dimensionless_spin2"),
612 get(simulations, "initial_position1", three_vec),
613 get(simulations, "initial_position2", three_vec),
614 # get(simulations, "object1", "").astype("category"),
615 # get(simulations, "object2", "").astype("category"),
616 # get(simulations, "url", ""),
617 # get(simulations, "simulation_name", ""),
618 # get(simulations, "alternative_names", []),
619 # get(simulations, "metadata_path", ""),
620 # get(simulations, "end_of_trajectory_time", floater),
621 # get(simulations, "merger_time", floater),
622 get(simulations, "number_of_orbits", floater),
623 get(simulations, "number_of_orbits_from_start", floater),
624 get(simulations, "number_of_orbits_from_reference_time", floater),
625 get(simulations, "DOI_versions", []),
626 get(simulations, "keywords", []),
627 get(simulations, "date_link_earliest", datetime_from_string),
628 get(simulations, "date_run_earliest", datetime_from_string),
--> 629 get(simulations, "date_run_latest", datetime_from_string),
630 get(simulations, "date_postprocessing", datetime_from_string),
631 ), axis=1))
633 # If `tag` or `published_at` are present, add them as attributes
634 if hasattr(self, "tag"):
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/sxscatalog/simulations/simulations.py:564, in Simulations.dataframe.<locals>.get(df, col, mapper, new_name)
562 gotten = df.get(col, default_series)
563 if use_mapper:
--> 564 gotten = gotten.map(mapper)
565 return gotten.rename(new_name)
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/pandas/core/series.py:4675, in Series.map(self, func, na_action, engine, **kwargs)
4673 if callable(func):
4674 func = functools.partial(func, **kwargs)
-> 4675 new_values = self._map_values(func, na_action=na_action)
4676 return self._constructor(new_values, index=self.index, copy=False).__finalize__(
4677 self, method="map"
4678 )
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/pandas/core/base.py:1020, in IndexOpsMixin._map_values(self, mapper, na_action)
1017 arr = self._values
1019 if isinstance(arr, ExtensionArray):
-> 1020 return arr.map(mapper, na_action=na_action)
1022 return algorithms.map_array(arr, mapper, na_action=na_action)
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/pandas/core/arrays/base.py:2692, in ExtensionArray.map(self, mapper, na_action)
2672 def map(self, mapper, na_action: Literal["ignore"] | None = None):
2673 """
2674 Map values using an input mapping or function.
2675
(...) 2690 a MultiIndex will be returned.
2691 """
-> 2692 return map_array(self, mapper, na_action=na_action)
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/pandas/core/algorithms.py:1710, in map_array(arr, mapper, na_action)
1708 values = arr.astype(object, copy=False)
1709 if na_action is None:
-> 1710 return lib.map_infer(values, mapper)
1711 else:
1712 return lib.map_infer_mask(values, mapper, mask=isna(values).view(np.uint8))
File pandas/_libs/lib.pyx:3071, in pandas._libs.lib.map_infer()
File /opt/hostedtoolcache/Python/3.11.14/x64/lib/python3.11/site-packages/sxscatalog/utilities/string_converters.py:50, in datetime_from_string(x)
48 except:
49 pass # No timezone information present; assuming UTC
---> 50 return dt.tz_localize(None)
KeyboardInterrupt:
RIT¶
The RIT catalog waveforms are downloaded via wget from the RIT website, rather than from a dedicated API. Horizon data is not available for RIT waveforms.
# Note: This requires having a RIT data path set up
# or will download the data if download=True
# For this example, we'll show the structure without executing
rit_id = 1096
wf_rit = rit.Waveform_RIT(path='./', ID=rit_id,
download=True, nu_rescale=True)
print(f"RIT Waveform {rit_id} loaded")
print(f"Mass ratio q = {wf_rit.metadata['q']:.3f}")
print(f"Total mass M = {wf_rit.metadata['M']:.3f}")
print(f"Chi1z = {wf_rit.metadata['chi1z']:.3f}")
print(f"Chi2z = {wf_rit.metadata['chi2z']:.3f}")
CoRe database¶
CoRe database waveforms are downloaded from the gitlab public repository, using git-lfs.
For the download of THC waveforms, modify the code argument accordingly.
core_id = '0001'
wf_core = core.Waveform_CoRe(path='./', ID=core_id, download=True, code='BAM')
print(f"CoRe Waveform {core_id} loaded")
print(f"Mass ratio q = {wf_core.metadata['q']:.3f}")
print(f"Total mass M = {wf_core.metadata['M']:.3f}")
print(f"Chi1z = {wf_core.metadata['chi1z']:.3f}")
print(f"Chi2z = {wf_core.metadata['chi2z']:.3f}")
ICCUB (public repository)¶
wf_icc = icc_public.Waveform_ICC(path='./', ID='0001', download=True, ellmax=4)
print(f"ICCUB Waveform 0001 loaded")
print(f"Mass ratio q = {wf_icc.metadata['q']:.3f}")
print(f"Total mass M = {wf_icc.metadata['M']:.3f}")
print(f"Chi1z = {wf_icc.metadata['chi1z']:.3f}")
print(f"Chi2z = {wf_icc.metadata['chi2z']:.3f}")
GR-Athena++¶
TODO: implement and demonstrate
Using the Cataloger for Bulk Operations¶
The Cataloger class provides a convenient way to work with multiple simulations from a catalog at once. This is particularly useful for computing mismatches or other comparative analyses across a set of simulations.
Here’s an example structure:
# Example: Working with multiple RIT simulations
sim_list = list(range(1096, 1100)) # List of simulation IDs
cat = Cataloger(
path='./local_data/rit/',
catalog='rit',
sim_list=sim_list,
add_opts={'download': True, 'nu_rescale': True}
)
# Plot all waveforms in the catalog
cat.plot_waves()
Comparing Waveforms Between Catalogs¶
One of PyART’s strengths is the ability to easily compare waveforms from different catalogs. Since all catalogs use the same interface, you can load waveforms from different sources and compare them directly.
For detailed mismatch calculations between catalogs, see the Mode-by-mode Mismatch tutorial.
Summary¶
This tutorial covered:
How to download waveforms from the SXS catalog
How to access waveform metadata
How to compute and plot waveform modes
The structure for working with other catalogs (RIT, CoRe, etc.)
Using the
Catalogerclass for bulk operations
Next Steps¶
Explore the Intro to Waveforms tutorial for more details on waveform manipulation
Learn about Phase Alignment techniques
Calculate Mode-by-mode Mismatches between waveforms