Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

split mflike #186

Merged
merged 18 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ jobs:

- name: oldest supported versions
os: ubuntu-latest
python-version: '3.8'
toxenv: py38-test-oldest
python-version: '3.9'
toxenv: py39-test-oldest

- name: macOS latest
os: macos-latest
Expand Down
11 changes: 0 additions & 11 deletions docs/bandpass.rst

This file was deleted.

41 changes: 14 additions & 27 deletions docs/developers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,8 @@ Basic ingredients
^^^^^^^^^^^^^^^^^
Your theory calculator must inherit from the cobaya theory class. It must have 3 main blocks of functions: inizialization (``initialize``); requirements (``get_requirement``, ``must_provide``); calculations (``get_X``, ``calculate``).

In what follows, we will use the structure of ``mflike`` as a concrete example of how to build these 3 blocks. The new version of ``mflike`` in SOLikeT splits the original mflike in 4 blocks: one cobaya-likelihood component (``mflike``); three cobaya-theory components.

The three theory components are:
1. ``TheoryForge``: this is where raw theory (CMB spectra) is mixed and modified with instrumental and non-cosmological effects
2. ``Foreground``: this is where the foreground (fg) spectra are computed
3. ``BandPass``: this is where bandpasses are built (either analytically or read from file)

In what follows, we will use the structure of ``mflike`` as a concrete example of how to build these 3 blocks. mflike splits the original mflike into 2 blocks:
one cobaya-likelihood component (``mflike``), and a cobaya-theory component ``BandpowerForeground`` to calculate the foreground model.

Initialization
^^^^^^^^^^^^^^
Expand All @@ -81,37 +76,29 @@ You can either assign params in initialize or do that via a dedicated yaml. You
Requirements
^^^^^^^^^^^^
Here you need to write what external elements are needed by your theory block to perform its duties. These external elements will be computed and provided by some other external module (e.g., another Theory class).
In our case, ``mflike`` must tell us that it needs a dictionary of cmb+fg spectra. This is done by letting the get_requirement function return a dictionary which has the name of the needed element as a key. For example, if the cmb+fg spectra dict is called ``cmbfg_dict``, the get_requirement function should::
In our case, ``mflike`` must tell us that it needs a dictionary of cmb and fg spectra. This is done by letting the get_requirement function return a dictionary which has the name of the needed element as a key. For example, if the cmb+fg spectra dict is called ``fg_totals``, the get_requirement function should be::

return {"cmbfg_dict":{}}
return {"fg_totals":{}}

The key is a dict itself. It can be empty, if no params need to be passed to the external Theory in charge of computing cmbfg_dict.
It might be possible that, in order to compute ``cmbfg_dict``, we should pass to the specific Theory component some params known by ``mflike`` (e.g., frequency channel). This is done by filling the above empty dict::
The key is a dict itself. It can be empty, if no params need to be passed to the external Theory in charge of computing fg_totals.
It might be possible that, in order to compute ``fg_totals``, we should pass to the specific Theory component some params known by ``mflike`` (e.g., frequency channel). This is done by filling the above empty dict::

{"cmbfg_dict": {"param1": param1_value, "param2": param2_value, etc}}
{"fg_totals": {"param1": param1_value, "param2": param2_value, etc}}

If this happens, then the external Theory block (in this example, ``TheoryForge``) must have a ``must_provide`` function. ``must_provide`` tells the code:
If this happens, then the external Theory block (in this example, ``BandpowerForeground``) must have a ``must_provide`` function. ``must_provide`` tells the code:

1. The values which should be assigned to the parameters needed to compute the element required from the Theory block. The required elements are stored in the
``**requirements`` dictionary which is the input of ``must_provide``.
In our example, ``TheoryForge`` will assign to ``param1`` the ``param1_value`` passed from ``mflike`` via the ``get_requirement`` in ``mflike`` (and so on). For example:
In our example, ``BandpowerForeground`` will assign to ``param1`` the ``param1_value`` passed from ``mflike`` via the ``get_requirement`` in ``mflike`` (and so on). For example:
::

must_provide(self, **requirements):
if "cmbfg_dict" in requirements:
self.param1 = requirements["cmbfg_dict"]["param1"]
if "fg_totals" in requirements:
self.param1 = requirements["fg_totals"]["param1"]

if this is the only job of ``must_provide``, then the function will not return anything

2. If required, what external elements are needed by this specific theory block to perform its duties. In this case, the function will return a dictionary of dictionaries which are the requirements of the specific theory block. These dictionaries do not have to necessarily contain content (they can be empty instances of the dictionary), but must be included if expected. Note this can be also done via ``get_requirement``. However, if you need to pass some params read from the block above to the new requirements, this can only be done with ``must_provide``. For example, ``TheoryForge`` needs ``Foreground`` to compute the fg spectra, which we store in a dict called ``fg_dict``. We also want ``TheoryForge`` to pass to ``Foreground`` ``self.param1``. This is done as follows:
::

must_provide(self, **requirements):
if “cmbfg_dict” etc etc
...
return {“fg_dict”: {“param1_fg”: self.param1}}

Of course, ``Foreground`` will have a similar call to ``must_provide``, where we assign to ``self.param1_fg`` the value passed from ``TheoryForge`` to ``Foreground``.
2. If required, what external elements are needed by this specific theory block to perform its duties. In this case, the function will return a dictionary of dictionaries which are the requirements of the specific theory block. These dictionaries do not have to necessarily contain content (they can be empty instances of the dictionary), but must be included if expected. Note this can be also done via ``get_requirement``. However, if you need to pass some params read from the block above to the new requirements, this can only be done with ``must_provide``.

Calculation
^^^^^^^^^^^
Expand All @@ -123,7 +110,7 @@ In each Theory class, you need at least 2 functions:
get_X(self, any_other_param):
return self.current_state[“X”]

where "X" is the name of the requirement computed by that class (in our case, it is ``cmbfg_dict`` in ``TheoryForge``, ``fg_dict`` in ``Foreground``). ``any_other_param`` is an optional param that you may want to apply to ``current_state["X"]`` before returning it. E.g., it could be a rescaling amplitude. This function is called by the Likelihood or Theory class that has ``X`` as its requirement, via the ``self.provider.get_X(any_other_param)`` call.
where "X" is the name of the requirement computed by that class (in our case, it is ``fg_totals`` in ``BandpowerForeground``). ``any_other_param`` is an optional param that you may want to apply to ``current_state["X"]`` before returning it. E.g., it could be a rescaling amplitude. This function is called by the Likelihood or Theory class that has ``X`` as its requirement, via the ``self.provider.get_X(any_other_param)`` call.

2. A calculate function:
::
Expand Down Expand Up @@ -182,7 +169,7 @@ To see if codes you have written when developing SOLikeT are valid and will pass

If you are using conda, the easiest way to run tests (and the way we run them) is to use tox-conda::

pip install tox-conda
pip install tox
tox -e test

This will create a fresh virtual environment replicating the one which is used for CI then run the tests (i.e. without touching your current environment). Note that any args after a '--' string will be passed to pytest, so::
Expand Down
12 changes: 0 additions & 12 deletions docs/foreground.rst

This file was deleted.

25 changes: 1 addition & 24 deletions docs/mflike.rst
Original file line number Diff line number Diff line change
@@ -1,27 +1,4 @@
MFLike (Primary CMB)
======

.. automodule:: soliket.mflike.mflike

Multi Frequency Likelihood
--------------------------

.. autoclass:: soliket.mflike.MFLike
:exclude-members: initialize
:members:
:private-members:
:show-inheritance:

Application of foregrounds and systematics
------------------------------------------

.. automodule:: soliket.mflike.theoryforge_MFLike

TheoryForge_MFLike
------------------

.. autoclass:: soliket.mflike.TheoryForge_MFLike
:exclude-members: initialize
:members:
:show-inheritance:
:private-members:
Install the "mflike" package to use the SO primary CMB likelihoods.
2 changes: 1 addition & 1 deletion docs/soliket-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ channels:
- conda-forge
- nodefaults
dependencies:
- python>=3.8,<3.12
- python>=3.9
- pip
- pytest-cov
- compilers
Expand Down
66 changes: 24 additions & 42 deletions docs/theory-component-guidelines.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,81 +8,63 @@ Basic ingredients
^^^^^^^^^^^^^^^^^
Your theory calculator must inherit from the cobaya theory class. It must have 3 main blocks of functions:

inizialization (``inizialize``);
initialization (``initialize``);

requirements (``get_requirement``, ``must_provide``);
requirements (``get_requirements``, ``must_provide``);

calculations (``get_X``, ``calculate``).

In what follows, we will use the structure of ``mflike`` as a concrete example of how to build these 3 blocks. The new version of ``mflike`` in SOLikeT splits the original mflike in 4 blocks: one cobaya-likelihood component (``mflike``); three cobaya-theory components.

The 3 theory components are:
1. ``TheoryForge``: this is where raw theory (CMB spectra) is mixed and modified with instrumental and non-cosmological effects
2. ``Foreground``: this is where the foreground (fg) spectra are computed
3. ``BandPass``: this is where bandpasses are built (either analytically or read from file)
In what follows, we will use the structure of ``mflike`` as a concrete example of how to build these 3 blocks. mflike splits the original mflike into 2 blocks:
one cobaya-likelihood component (``mflike``), and a cobaya-theory component ``BandpowerForeground`` to calculate the foreground model.


Initialization
^^^^^^^^^^^^^^
You can either assign params in inizialize or do that via a dedicated yaml. You can in general do all the calculations that need to be done once for all.
You can either assign params in initialize or do that via a dedicated yaml. You can in general do all the calculations that need to be done once for all.

Requirements
^^^^^^^^^^^^
Here you need to write what external elements are needed by your theory block to perform its duties. These external elements will be computed and provided by some other external module (e.g., another Theory class).
In our case, ``mflike`` must tell us that it needs a dictionary of cmb+fg spectra. This is done by letting the get_requirement function return a dictionary which has the name of the needed element as a key. For example, if the cmb+fg spectra dict is called ``cmbfg_dict``, the get_requirement function should

::
In our case, ``mflike`` must tell us that it needs a dictionary of cmb and fg spectra. This is done by letting the get_requirement function return a dictionary which has the name of the needed element as a key. For example, if the cmb+fg spectra dict is called ``fg_totals``, the get_requirement function should be::

return {"cmbfg_dict":{}}
return {"fg_totals":{}}

The key is a dict itself. It can be empty, if no params need to be passed to the external Theory in charge of computing cmbfg_dict.
It might be possible that, in order to compute ``cmbfg_dict``, we should pass to the specific Theory component some params known by ``mflike`` (e.g., frequency channel). This is done by filling the above empty dict:
The key is a dict itself. It can be empty, if no params need to be passed to the external Theory in charge of computing fg_totals.
It might be possible that, in order to compute ``fg_totals``, we should pass to the specific Theory component some params known by ``mflike`` (e.g., frequency channel). This is done by filling the above empty dict::

::
{"fg_totals": {"param1": param1_value, "param2": param2_value, etc}}

{"cmbfg_dict": {"param1": param1_value, "param2": param2_value, etc}}
If this happens, then the external Theory block (in this example, ``BandpowerForeground``) must have a ``must_provide`` function. ``must_provide`` tells the code:

If this happens, then the external Theory block (in this example, ``TheoryForge``) must have a ``must_provide`` function.
``must_provide`` tells the code
1. what values should be assigned to the parameters needed to compute the element required from the Theory block. The required elements are stored in the ``**requirements`` dictionary which is the input of ``must_provide``.
In our example, ``TheoryForge`` will assign to ``param1`` the ``param1_value`` passed from ``mflike`` via the ``get_requirement`` in ``mflike`` (and so on). For example:
1. The values which should be assigned to the parameters needed to compute the element required from the Theory block. The required elements are stored in the
``**requirements`` dictionary which is the input of ``must_provide``.
In our example, ``BandpowerForeground`` will assign to ``param1`` the ``param1_value`` passed from ``mflike`` via the ``get_requirement`` in ``mflike`` (and so on). For example:
::

must_provide(self, **requirements):
if "cmbfg_dict" in requirements:
self.param1 = requirements["cmbfg_dict"]["param1"]
must_provide(self, **requirements):
if "fg_totals" in requirements:
self.param1 = requirements["fg_totals"]["param1"]

if this is the only job of ``must_provide``, then the function will not return anything


2. if needed, what external elements are needed by this specific theory block to perform its duties. In this case, the function will return a dictionary of (empty or not) dictionaries which are the requirements of the specific theory block. Note this can be also done via ``get_requirement``. However, if you need to pass some params read from the block above to the new requirements, this can only be done with ``must_provide``. For example, ``TheoryForge`` needs ``Foreground`` to compute the fg spectra, which we store in a dict called ``fg_dict``. We also want ``TheoryForge`` to pass to ``Foreground`` ``self.param1``. This is done as follows:
::

must_provide(self, **requirements):
if “cmbfg_dict” etc etc
...
return {“fg_dict”: {“param1_fg”: self.param1}}

Of course, ``Foreground`` will have a similar call to ``must_provide``, where we assign to ``self.param1_fg`` the value passed from ``TheoryForge`` to ``Foreground``.
2. If required, what external elements are needed by this specific theory block to perform its duties. In this case, the function will return a dictionary of dictionaries which are the requirements of the specific theory block. These dictionaries do not have to necessarily contain content (they can be empty instances of the dictionary), but must be included if expected. Note this can be also done via ``get_requirement``. However, if you need to pass some params read from the block above to the new requirements, this can only be done with ``must_provide``.

Calculation
^^^^^^^^^^^
In each Theory class, you need at least 2 functions:

1.
1. A get function:
::

get_X(self, any_other_param):
return self.current_state[“X”]

where "X" is the name of the requirement computed by that class (in our case, it is ``cmbfg_dict`` in ``TheoryForge``, ``fg_dict`` in ``Foreground``).
"any_other_param" is an optional param that you may want to apply to ``current_state["X"]`` before returning it. E.g., it could be a rescaling amplitude.
This function is called by the Likelihood or Theory class that has "X" as its requirement, via the ``self.provider.get_X(any_other_param)`` call.
where "X" is the name of the requirement computed by that class (in our case, it is ``fg_totals`` in ``BandpowerForeground``). ``any_other_param`` is an optional param that you may want to apply to ``current_state["X"]`` before returning it. E.g., it could be a rescaling amplitude. This function is called by the Likelihood or Theory class that has ``X`` as its requirement, via the ``self.provider.get_X(any_other_param)`` call.

2.
2. A calculate function:
::

calculate(self, **state, want_derived=False/True, **params_values_dict):
do actual calculations, that could involve the use of some of the **params_value_dict, and might also compute derived params (if want_derived=True)
calculate(self, **state, want_derived=False, **params_values_dict):
state[“X”] = result of above calculations

state[“X”] = result of above calculations
which will do actual calculations, that could involve the use of some of the ``**params_value_dict``, and might also compute derived params (if ``want_derived=True``).
33 changes: 6 additions & 27 deletions examples/example_1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,17 @@ output: output/example_1

# Specify which Likelihoods we would like to use
# Any options for the Likelihood will use their default values unless specified here
# In this case the options and defaults are specified in soliket/mflike/MFLike.yaml
# Note that MFLike is a Cobaya `installable likelihood`.
# When running this yaml file, or calling `cobaya-install example_1.yaml` the required
# installable components will automatically be downloaded and installed.
# Note that for the soliket MFLike likelihood we are required to calculate:
# Note that for the mflike.MFLike likelihood we are required to calculate:
# - CMB theory power spectra (from CAMB theory below)
# - Multi-frequency bandpass calibrations (from soliket.BandPass theory below)
# - Multi-frequency foregrounds (from soliket.Foreground theory below)
# - The combination of the above components (from soliket.TheoryForge_MFLike theory below)
# - Multi-frequency passband-integrated foregrounds (from mflike.BandpowerForeground theory below)
likelihood:
soliket.MFLike:
data_folder: MFLike/v0.8
input_file: LAT_simu_sacc_00000.fits
mflike.MFLike:
package_install: pip
input_file: LAT_simu_sacc_00044.fits
cov_Bbl_file: data_sacc_w_covar_and_Bbl.fits
defaults:
polarizations: ['TT', 'TE', 'ET', 'EE']
scales:
TT: [30, 9000]
TE: [30, 9000]
ET: [30, 9000]
EE: [30, 9000]
symmetrize: False

# Specify the Theory codes which will compute observables to be compared with the data
# in the Likelihood.
Expand All @@ -55,17 +44,7 @@ theory:
share_delta_neff: True
stop_at_error: True

soliket.BandPass:
stop_at_error: True

soliket.Foreground:
stop_at_error: True

soliket.TheoryForge_MFLike:
spectra:
polarizations: ['tt', 'te', 'ee']
lmin: 2
lmax: 9050
mflike.BandpowerForeground:
stop_at_error: True

# Specify the parameter values at which to compute the likelihood
Expand Down
Loading
Loading