SED Recovery¶
In this section, we will discuss the process of recovering the spectral energy distribution (SED) of astronomical sources using the models generated in the previous sections. This will only work if you have a trained inference model, which can be obtained by following the inference tutorial.
For most models created by Synference using Synthesizer, information about the simulator used to generate the model grid is stored in the HDF5 file (e.g. the SPS grid, SFH and metallicity models, emission model, etc). This allows Synference to automatically reconstruct the simulator to generate SEDs for any set of parameters sampled from the posterior distribution.
First we will load in a trained inference model and the corresponding fitter object used to generate the model library.
Note that the manual recreation of the noise model here is only neccessary to get this to work in the documentation. In practice, the noise model should be loaded directly if the path to exists.
You will also need to have the correct model grid (test_grid.hdf5) available in your Synthesizer grid directory for this to work. You can download this from Box.
```python
[1]:
import os
from synthesizer import get_grids_dir
print(get_grids_dir())
available_grids = os.listdir(get_grids_dir())
if "test_grid.hdf5" not in available_grids:
cmd = f"synthesizer-download --test-grids --destination {get_grids_dir()}"
os.system(cmd)
library_path = os.path.join(get_grids_dir(), "test_grid.hdf5")
/home/runner/.local/share/Synthesizer/grids
[2]:
from synference import SBI_Fitter, load_unc_model_from_hdf5, test_data_dir
library_path = (
f"{test_data_dir}/grid_BPASS_Chab_DenseBasis_SFH_0.01_z_14_logN_2.7_Calzetti_v3_multinode.hdf5" # noqa: E501
)
fitter = SBI_Fitter.load_saved_model(
model_file=f"{test_data_dir}", library_path=library_path, device="cpu"
)
nm_path = f"{test_data_dir}/BPASS_DenseBasis_v4_final_nsf_0_params_empirical_noise_models.h5"
noise_models = load_unc_model_from_hdf5(nm_path)
fitter.feature_array_flags["empirical_noise_models"] = noise_models
/opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
2026-06-24 09:47:05,071 | synference | INFO | Loaded model from /home/runner/.local/share/Synthesizer/data/synference/BPASS_DenseBasis_v4_final_nsf_0_posterior.pkl.
2026-06-24 09:47:05,072 | synference | INFO | Device: cpu
2026-06-24 09:47:05,159 | synference | WARNING | IndexError when trying to set train/test arrays.
Now we can recreate the simulator.
[3]:
fitter.recreate_simulator_from_library(
override_library_path=library_path, override_grid_path="test_grid.hdf5"
);
2026-06-24 09:47:05,219 | synference | INFO | Overriding internal library name from provided file path.
2026-06-24 09:47:05,725 | synference | WARNING | Failed to load cosmology from HDF5. Using Planck18 instead.
---------------------------------------------------------------------------
TimeoutError Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/urllib/request.py:1348, in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
1347 try:
-> 1348 h.request(req.get_method(), req.selector, req.data, headers,
1349 encode_chunked=req.has_header('Transfer-encoding'))
1350 except OSError as err: # timeout error
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/http/client.py:1303, in HTTPConnection.request(self, method, url, body, headers, encode_chunked)
1302 """Send a complete request to the server."""
-> 1303 self._send_request(method, url, body, headers, encode_chunked)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/http/client.py:1349, in HTTPConnection._send_request(self, method, url, body, headers, encode_chunked)
1348 body = _encode(body, 'body')
-> 1349 self.endheaders(body, encode_chunked=encode_chunked)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/http/client.py:1298, in HTTPConnection.endheaders(self, message_body, encode_chunked)
1297 raise CannotSendHeader()
-> 1298 self._send_output(message_body, encode_chunked=encode_chunked)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/http/client.py:1058, in HTTPConnection._send_output(self, message_body, encode_chunked)
1057 del self._buffer[:]
-> 1058 self.send(msg)
1060 if message_body is not None:
1061
1062 # create a consistent interface to message_body
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/http/client.py:996, in HTTPConnection.send(self, data)
995 if self.auto_open:
--> 996 self.connect()
997 else:
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/http/client.py:962, in HTTPConnection.connect(self)
961 sys.audit("http.client.connect", self, self.host, self.port)
--> 962 self.sock = self._create_connection(
963 (self.host,self.port), self.timeout, self.source_address)
964 # Might fail in OSs that don't implement TCP_NODELAY
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/socket.py:857, in create_connection(address, timeout, source_address)
856 try:
--> 857 raise err
858 finally:
859 # Break explicitly a reference cycle
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/socket.py:845, in create_connection(address, timeout, source_address)
844 sock.bind(source_address)
--> 845 sock.connect(sa)
846 # Break explicitly a reference cycle
TimeoutError: [Errno 110] Connection timed out
During handling of the above exception, another exception occurred:
URLError Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/instruments/filters.py:2041, in Filter._make_svo_filter(self)
2040 try:
-> 2041 with urllib.request.urlopen(self.svo_url) as f:
2042 # Get the root of the XML tree
2043 root = ElementTree.parse(f).getroot()
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/urllib/request.py:216, in urlopen(url, data, timeout, cafile, capath, cadefault, context)
215 opener = _opener
--> 216 return opener.open(url, data, timeout)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/urllib/request.py:519, in OpenerDirector.open(self, fullurl, data, timeout)
518 sys.audit('urllib.Request', req.full_url, req.data, req.headers, req.get_method())
--> 519 response = self._open(req, data)
521 # post-process response
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/urllib/request.py:536, in OpenerDirector._open(self, req, data)
535 protocol = req.type
--> 536 result = self._call_chain(self.handle_open, protocol, protocol +
537 '_open', req)
538 if result:
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/urllib/request.py:496, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args)
495 func = getattr(handler, meth_name)
--> 496 result = func(*args)
497 if result is not None:
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/urllib/request.py:1377, in HTTPHandler.http_open(self, req)
1376 def http_open(self, req):
-> 1377 return self.do_open(http.client.HTTPConnection, req)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/urllib/request.py:1351, in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
1350 except OSError as err: # timeout error
-> 1351 raise URLError(err)
1352 r = h.getresponse()
URLError: <urlopen error [Errno 110] Connection timed out>
During handling of the above exception, another exception occurred:
SVOInaccessible Traceback (most recent call last)
Cell In[3], line 1
----> 1 fitter.recreate_simulator_from_library(
2 override_library_path=library_path, override_grid_path="test_grid.hdf5"
3 );
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synference/sbi_runner.py:5652, in SBI_Fitter.recreate_simulator_from_library(self, set_self, overwrite, override_library_path, override_grid_path, **kwargs)
5649 default_kwargs.update(kwargs)
5651 try:
-> 5652 simulator = GalaxySimulator.from_library(
5653 library_path, override_synthesizer_grid_dir=override_grid_path, **default_kwargs
5654 )
5655 except ValueError as e:
5656 logger.error(
5657 "Could not recreate simulator from grid. This model"
5658 " may not be compatible. A GalaxySimulator object can"
5659 " be provided manually to recover the SED."
5660 )
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synference/library.py:5562, in GalaxySimulator.from_library(cls, library_path, override_synthesizer_grid_dir, override_emission_model, **kwargs)
5547 dict_create = dict(
5548 sfh_model=sfh_model,
5549 zdist_model=zdist_model,
(...)
5559 fixed_params=fixed_params,
5560 )
5561 dict_create.update(kwargs)
-> 5562 return cls(**dict_create)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synference/library.py:5108, in GalaxySimulator.__init__(self, sfh_model, zdist_model, grid, instrument, emission_model, emission_model_key, emitter_params, cosmo, param_order, param_units, param_transforms, out_flux_unit, required_keys, extra_functions, normalize_method, output_type, include_phot_errors, depths, depth_sigma, noise_models, fixed_params, photometry_to_remove, ignore_params, ignore_scatter, return_type, device)
5105 self.reported_unused = False
5107 if len(photometry_to_remove) > 0:
-> 5108 self.update_photo_filters(
5109 photometry_to_remove=photometry_to_remove, photometry_to_add=None
5110 )
5112 if noise_models is not None:
5113 assert isinstance(noise_models, dict), (
5114 f"Noise models must be a dictionary. Got {type(noise_models)} instead."
5115 )
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synference/library.py:5224, in GalaxySimulator.update_photo_filters(self, photometry_to_remove, photometry_to_add)
5221 if filter_code not in new_filters:
5222 new_filters.append(filter_code)
-> 5224 self.instrument.filters = FilterCollection(filter_codes=new_filters)
5225 logger.info(f"Updated filters: {self.instrument.filters.filter_codes}")
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/utils/operation_timers.py:98, in timed.<locals>.decorator.<locals>.wrapped(*args, **kwargs)
94 tic(timer_name)
95 try:
96 # Return the wrapped function result unchanged so the decorator
97 # is transparent aside from its timing side effect.
---> 98 return func(*args, **kwargs)
99 finally:
100 # Always stop the timer, even if the wrapped function raises,
101 # so the timing stack remains balanced.
102 toc(timer_name)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/instruments/filters.py:297, in FilterCollection.__init__(self, filter_codes, tophat_dict, generic_dict, filters, path, new_lam, fill_gaps, verbose)
295 # Let's make the filters
296 if filter_codes is not None:
--> 297 self._include_svo_filters(filter_codes)
298 if tophat_dict is not None:
299 self._include_top_hat_filters(tophat_dict)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/instruments/filters.py:474, in FilterCollection._include_svo_filters(self, filter_codes)
471 # Loop over the given filter codes
472 for f in filter_codes:
473 # Get filter from SVO
--> 474 _filter = Filter(f, new_lam=self.lam)
476 # Store the filter and its code
477 self.filters[_filter.filter_code] = _filter
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/units.py:909, in accepts.<locals>.check_accepts.<locals>.wrapped(*args, **kwargs)
906 finally:
907 toc(f"accepts({func.__qualname__})")
--> 909 return func(*bound.args, **bound.kwargs)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/utils/operation_timers.py:98, in timed.<locals>.decorator.<locals>.wrapped(*args, **kwargs)
94 tic(timer_name)
95 try:
96 # Return the wrapped function result unchanged so the decorator
97 # is transparent aside from its timing side effect.
---> 98 return func(*args, **kwargs)
99 finally:
100 # Always stop the timer, even if the wrapped function raises,
101 # so the timing stack remains balanced.
102 toc(timer_name)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/instruments/filters.py:1770, in Filter.__init__(self, filter_code, transmission, lam_min, lam_max, lam_eff, lam_fwhm, new_lam, hdf)
1768 # Is this an SVO filter?
1769 elif "/" in filter_code and "." in filter_code:
-> 1770 self._make_svo_filter()
1772 # Otherwise we haven't got a valid combination of inputs.
1773 else:
1774 raise exceptions.InconsistentArguments(
1775 "Invalid combination of filter inputs. \n For a generic "
1776 "filter provide a transmission and wavelength array. "
(...)
1781 "wavelength or an effective wavelength and FWHM."
1782 )
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/instruments/filters.py:2052, in Filter._make_svo_filter(self)
2049 data = root.find(".//TABLEDATA")
2051 except URLError:
-> 2052 raise exceptions.SVOInaccessible(
2053 (
2054 f"The SVO Database at {self.svo_url} "
2055 "is not responding. Is it down?"
2056 )
2057 )
2059 # Throw an error if we didn't find the filter.
2060 if field is None:
SVOInaccessible: The SVO Database at http://svo2.cab.inta-csic.es/theory/fps/fps.php?ID=HST/ACS_WFC.F435W is not responding. Is it down?
Now let’s get some mock photometry to recover the SEDs for. We will load a set of mock observations created using the same simulator as the model library.
[4]:
import numpy as np
from IPython.display import display
path = f"{test_data_dir}/sbi_test_data_BPASS_DenseBasis_v4_final.npz"
data = np.load(path)
X_test = data["X"]
y_test = data["y"]
Now we can use the recover_SED method of the inference model to recover the SEDs for our mock photometry. This method will sample from the posterior distribution and generate SEDs for each sample using the reconstructed simulator.
[5]:
?fitter.recover_SED
Now we will loop over the recovered SEDs and plot them.
[6]:
%matplotlib inline
for xi_test, yi_test in zip(X_test, y_test):
_, _, _, _, fig = fitter.recover_SED(
X_test=xi_test, true_parameters=yi_test, num_samples=100, save_plots=False
)
# jupyter notebook display
display(fig)
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
Cell In[6], line 4
1 get_ipython().run_line_magic('matplotlib', 'inline')
3 for xi_test, yi_test in zip(X_test, y_test):
----> 4 _, _, _, _, fig = fitter.recover_SED(
5 X_test=xi_test, true_parameters=yi_test, num_samples=100, save_plots=False
6 )
8 # jupyter notebook display
9 display(fig)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synference/sbi_runner.py:5779, in SBI_Fitter.recover_SED(self, X_test, samples, num_samples, sample_method, sample_kwargs, posteriors, simulator, prior, plot, marginalized_parameters, extra_parameters, phot_unit, true_parameters, plot_name, plots_dir, sample_color, param_labels, plot_closest_draw_to, plot_sfh, plot_histograms, kde, save_plots, fig, ax, ax_sfh, verbose)
5777 if simulator is None:
5778 if not self.has_simulator:
-> 5779 self.recreate_simulator_from_library(set_self=True)
5781 if not hasattr(self, "simulator"):
5782 raise ValueError("Simulator must be provided or set in the object.")
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synference/sbi_runner.py:5652, in SBI_Fitter.recreate_simulator_from_library(self, set_self, overwrite, override_library_path, override_grid_path, **kwargs)
5649 default_kwargs.update(kwargs)
5651 try:
-> 5652 simulator = GalaxySimulator.from_library(
5653 library_path, override_synthesizer_grid_dir=override_grid_path, **default_kwargs
5654 )
5655 except ValueError as e:
5656 logger.error(
5657 "Could not recreate simulator from grid. This model"
5658 " may not be compatible. A GalaxySimulator object can"
5659 " be provided manually to recover the SED."
5660 )
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synference/library.py:5316, in GalaxySimulator.from_library(cls, library_path, override_synthesizer_grid_dir, override_emission_model, **kwargs)
5314 grid_name = os.path.basename(grid_dir).replace(".hdf5", "").replace(".h5", "")
5315 grid_dir = os.path.dirname(grid_dir)
-> 5316 grid = Grid(grid_name, grid_dir) # new_lam=lam)
5318 # Step 2. Make instrument
5319 if model_group["Instrument"].attrs.get("instrument_type", None) is None:
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/units.py:909, in accepts.<locals>.check_accepts.<locals>.wrapped(*args, **kwargs)
906 finally:
907 toc(f"accepts({func.__qualname__})")
--> 909 return func(*bound.args, **bound.kwargs)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/utils/operation_timers.py:98, in timed.<locals>.decorator.<locals>.wrapped(*args, **kwargs)
94 tic(timer_name)
95 try:
96 # Return the wrapped function result unchanged so the decorator
97 # is transparent aside from its timing side effect.
---> 98 return func(*args, **kwargs)
99 finally:
100 # Always stop the timer, even if the wrapped function raises,
101 # so the timing stack remains balanced.
102 toc(timer_name)
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/grid.py:185, in Grid.__init__(self, grid_name, grid_dir, ignore_spectra, spectra_to_read, ignore_lines, new_lam, lam_lims, use_precision)
183 self._extract_axes = []
184 self._extract_axes_values = {}
--> 185 self._get_axes()
187 # Read in the metadata
188 self._weight_var = None
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/synthesizer/grid.py:356, in Grid._get_axes(self)
354 """Get the grid axes from the HDF5 file."""
355 # Get basic info of the grid
--> 356 with h5py.File(self.grid_filename, "r") as hf:
357 # Get list of axes
358 axes = list(hf.attrs["axes"])
360 # Set the values of each axis as an attribute
361 # e.g. self.log10age == hdf["axes"]["log10age"]
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/h5py/_hl/files.py:555, in File.__init__(self, name, mode, driver, libver, userblock_size, swmr, rdcc_nslots, rdcc_nbytes, rdcc_w0, track_order, fs_strategy, fs_persist, fs_threshold, fs_page_size, page_buf_size, min_meta_keep, min_raw_keep, locking, alignment_threshold, alignment_interval, meta_block_size, track_times, **kwds)
546 fapl = make_fapl(driver, libver, rdcc_nslots, rdcc_nbytes, rdcc_w0,
547 locking, page_buf_size, min_meta_keep, min_raw_keep,
548 alignment_threshold=alignment_threshold,
549 alignment_interval=alignment_interval,
550 meta_block_size=meta_block_size,
551 **kwds)
552 fcpl = make_fcpl(track_order=track_order, track_times=track_times,
553 fs_strategy=fs_strategy, fs_persist=fs_persist,
554 fs_threshold=fs_threshold, fs_page_size=fs_page_size)
--> 555 fid = make_fid(name, mode, userblock_size, fapl, fcpl, swmr=swmr)
557 if isinstance(libver, tuple):
558 self._libver = libver
File /opt/hostedtoolcache/Python/3.10.20/x64/lib/python3.10/site-packages/h5py/_hl/files.py:232, in make_fid(name, mode, userblock_size, fapl, fcpl, swmr)
230 if swmr:
231 flags |= h5f.ACC_SWMR_READ
--> 232 fid = h5f.open(name, flags, fapl=fapl)
233 elif mode == 'r+':
234 fid = h5f.open(name, h5f.ACC_RDWR, fapl=fapl)
File h5py/_objects.pyx:54, in h5py._objects.with_phil.wrapper()
File h5py/_objects.pyx:55, in h5py._objects.with_phil.wrapper()
File h5py/h5f.pyx:106, in h5py.h5f.open()
FileNotFoundError: [Errno 2] Unable to synchronously open file (unable to open file: name = '/cosma7/data/dp276/dc-harv3/work/grids/bpass-2.2.1-bin_chabrier03-0.1,300.0_cloudy-c23.01-sps.hdf5', errno = 2, error message = 'No such file or directory', flags = 0, o_flags = 0)