Skip to content

Fix halo inconsistencies v3#1069

Merged
nikfilippas merged 77 commits intov3_wipfrom
halos_inconsistencies_v3
Apr 21, 2023
Merged

Fix halo inconsistencies v3#1069
nikfilippas merged 77 commits intov3_wipfrom
halos_inconsistencies_v3

Conversation

@nikfilippas
Copy link
Contributor

This PR contains fixes to some design flaws in halos which can ultimately cause memory leaks and model inconsistencies.
Note: at the moment it breaks API (to make review easier), but once review is complete I will clone it into a non API-breaking one.

(5' read)

Problem 1

When MassDef is instantiated with a concentration it inadvertently contains a reference to itself:

mass_def is mass_def.concentration.mass_def is mass_def.[...].mass_def

This causes a memory leak because when the object is deleted, the garbage collector doesn't know the order of deletion so the instances hang in there for the rest of the runtime. This can easily be demonstrated:

import pyccl as ccl


class Concentration:

    def __init__(self, mass_def):
        self.mass_def = mass_def
        self.id = hex(id(self))[-5:]
        print(f"Concentration {self.id} created.")

    def __del__(self):
        print(f"Concentration {self.id} destroyed.")


class MassDef:

    def __init__(self):
        self.concentration = Concentration(self)  # line that causes the problem
        self.id = hex(id(self))[-5:]
        print(f"MassDef {self.id} created.")

    def __del__(self):
        print(f"MassDef {self.id} destroyed.")


mass_def = MassDef()
del mass_def  # doesn't do anything

print("program ends")

which outputs

Concentration 36050 created.
MassDef 3af50 created.
program ends
MassDef 3af50 destroyed.
Concentration 36050 destroyed.

An easy way to fix that is to use a weakref proxy of self when passing it to the Concentration constructor:
import weakref ... self.concentration = Concentration(weakref.proxy(self)). This allows the reference count of mass_def to go to zero and the gbc to purge it. This tactic is not generally advised though.

However, there is a deeper design flaw with the interplay of MassDef and Concentration.

Problem 2

Matter profiles require a Concentration to initialize. However, this isn't really a parameter; it is akin to setting up the model. As is mass_def (which is why we agreed to deprecate it from calls to subclasses of HMIngredients). This is how these two are used in the code, in every matter profile:

    def _real(self, cosmo, r, M, a, mass_def):
        # Comoving virial radius
        R_M = mass_def.get_radius(cosmo, M, a) / a
        c_M = self.concentration(cosmo, M, a)
        R_s = R_M / c_M

so they aren't used in anything other than finding the comoving virial radius, given M. These two work together; they should be together, but one is passed at initialization and the other one at calls. What's more, this creates an inconsistency: the mass_def of concentration may not be the same as the mass_def passed in _real. Luckily, most implemented concentrations are mass_def-agnostic, and also users have (hopefully) been passing the same one, but this can easily create problems.

Solution

  • One way is to add a check every time e.g. _real is called to verify that the mass definitions are consistent. That's a bad solution, no explanation needed.
  • Another way is to deprecate concentration in __init__ and pass it as an extra argument in e.g. _real. That's a bad solution because not all profiles use a Concentration, so we'll end up unnecessarily complicating the code.
  • Another way is to move mass_def to __init__ and deprecate it from e.g. _real. That's okay, but the non-matter profiles still use mass_def to compute the radius, given the mass. That means, in order to be consistent, we'd have to introduce mass_def as an __init__ argument in all profiles.
  • I propose the following. Since MassDef and Concentration are so strongly coupled, it makes sense to combine them into one single object. Then, every profile will accept a MassConcentration object in __init__ which contains all the info it will ever need: mass definition and an optional concentration. This will simplify things.

In practice, the new class would contain self.mass_def and self.concentration and would avoid self-references and other caveats. It would initialize both the mass definition and the concentration prescription at the same time.

This is what it could look like:

class MassConcentration(MassDef, Concentration):
    def __init__(self, *, mass_def, concentration):
        # careful with initialization: we don't want cyclic references like in MassDef
        self.mass_def = mass_def
        self.concentration = concentration

and then, all halo profiles would have:

class HaloProfileNFW(HaloProfile):
    def __init__(self, mass_concentration, ...):
        self.mass_concentration = mass_concentration
        ...

    def _real(self, cosmo, r, M, a):
        R_M = self.mass_concentration.get_radius(cosmo, M, a) / a
        c_M = self.mass_concentration.concentration(cosmo, M, a)
        R_s = R_M / cM

We could even go as far as implementing a get_comoving_virial_radius method, to do all of that in a single step and avoid having to repeat that in all matter profiles.

It would also make physical sense because the parameters passed in __init__ would uniquely define a profile 'entity', which would then be called via its methods and (r, M, a) (and cosmo which is an outlier as we've already discussed).

For v2.final we can leave things as they are, but add warnings that these will be deprecated in the future, and point users towards using MassConcentration objects instead of separate MassDef and Concentration.

The solution would simplify the code - for example, in benchmarks/test_haloprofile.py we do

    mdef = ccl.halos.MassDef(mDelta, 'matter')
    c = ccl.halos.ConcentrationConstant(c=concentration, mdef=mdef)
    mdef = ccl.halos.MassDef(mDelta, 'matter', concentration=c)
    p = ccl.halos.HaloProfileEinasto(c, truncated=False)

so there are two instantiations of a mass definition to get the one we want to use. With the new implementation we won't need that - it would be a single step.

This proposal is a widely used design pattern in OOP called "dependency injection", and is a technique where an object is passed as a dependency to another object, rather than having the dependent object create the dependency itself. It helps to reduce coupling between different objects, and makes the code more modular and testable.

Base automatically changed from additional_halos_features to halos_v3 April 17, 2023 17:46
Base automatically changed from halos_v3 to v3_wip April 17, 2023 18:09
Copy link
Collaborator

@damonge damonge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This I can live with (just a couple spurious comments). Ping me for review when it's fully implemented

@nikfilippas nikfilippas mentioned this pull request Apr 20, 2023
34 tasks
Copy link
Collaborator

@damonge damonge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor comments

@damonge
Copy link
Collaborator

damonge commented Apr 21, 2023

Also, there seem to be conflicts

@nikfilippas
Copy link
Contributor Author

@damonge back to you

@nikfilippas nikfilippas merged commit a19829e into v3_wip Apr 21, 2023
@nikfilippas nikfilippas deleted the halos_inconsistencies_v3 branch April 21, 2023 15:57
damonge added a commit that referenced this pull request May 3, 2023
* halo profiles in their own directory

* concentrations in their own directory

* mass functions & halo biases in their own directory

* moved CIB stuff to profiles & 2pts

* split halo model into 1/2/4-pt

* fix imports

* remove empty module

* fix typo

* Baryons in v3 (#1039)

* Added new baryons module that will deprecate old BCM

* Tracers v3 (#1040)

* Tracers in V3

* Background, boltzmann, and cls in v3 (#1041)

* Background, boltzmann and cls in v3

* Covariances in v3 (#1046)

* Covariances in V3

* Correlations v3 (#1042)

* Correlations in v3

* split base.py and restructured into its own dir to simplify

* Neutrinos, parameters, power in v3 (#1047)

* done

* Make `T_ncdm` a cosmological parameter + (v3) (#1049)

* first commit

* rename TNCDM to T_ncdm but preserve API

* T_CMB directly into cosmo struct

* Omega_g in ccl_parameters_create; no split between C/Python

* physical_constants.T_CMB doesn't mutate anymore!

* force mutate T_CMB in benchmarks

* define defaults on instantiation just in case constants mutate

* A_s & sigma8: don't play with numbers

* simplify

* remove leftover mallocs

* temporarily preserve API for CCLv3

* Unfreeze option for `physical_constants` (#1050)

* first commit

* update rtd

* addressed comments 1

* Remove `T_CMB` and `T_ncdm` as constants (reloaded) (#1058)

* first commit

* OmNuh2 fix

* addressed comments

* keep only massive

* first step

* temp commit

* debug neutrinos & deprecate Omnuh2

* ccl_omega_x & ccl_Omeganuh2 consistency

---------

Co-authored-by: David Alonso <dam.phys@gmail.com>

* RTD: Removed The Docs

---------

Co-authored-by: David Alonso <dam.phys@gmail.com>

* flaked

---------

Co-authored-by: Nick Koukoufilippas <nikfilippas@gmail.com>

* Pk2D and Tk3D in v3 (#1048)

* tk3d and pk2d in v3

---------

Co-authored-by: Nick Koukoufilippas <nikfilippas@gmail.com>

* PT biases in v3 (#1056)

* New PT bias framework

* Halos v3 (#1043)

* ported changes in halos/profiles

* changes from star in halos/ (no tests)

* refactor concentration, mass_function, halo_bias; improvements in HMCalculator

* HaloProfile subclasses & HaloProfile.normprof attribute

* simplify imports

* remove HaloProfile.name

* cleaned up pk_Npt

* add extrap_pk argument

* FFTLogParams class instead of dictionary in HaloProfile

* simplified code

* removed mass_def from HMIngredients; comprehensive code review 1

* re-implementation, bugfixes, tests

* c_m_relation >> concentration; code improvements; A_SPLINE_MAX in Python

* HMIngredients initializers - no repeated code

* rogue file

* patch for new changes

* updated Build Status badge website

* fix websites

* added a DOI badge & links

* added nonbreaking space

* removed extra space

* FancyRepr out of CCLObject to simplify

* update tests to match changes

* coverage

* simple test for tkkssc

* more tests for tkkssc

* addressed comments 1

* addressed comments 2: mass_def defaults are name strings etc.

* deprecate Gaussian & PowerLaw profiles

* bugfix in master: sometimes mass_def_strict=False fails unexpectedly

* typo

* addressed comments

* New feature: Arbitrary function for profile normalization

* gain efficiency

* comments + deprecate k_min from HMCalculator

* renamed initialize_from_input --> create_instance

* brought in CCLNamedClass from docs_v3

* brought in from docs_v3: directly callable HMIngredients

* prep for Davids input

* renamed HMCalculator --> HaloModel

* fix typo

* brought in from docs_v3: initialize mass_def from any string (e.g. 400c)

* Revert "renamed HMCalculator --> HaloModel"

This reverts commit 43591ce.

* counterterms func inside of 4pt func

* replace deprecated abstractproperty

* abstract linked methods for HaloProfile, MassFunc, HaloBias, Concentration

* fully working implementation

* renamed lM --> log10M etc.

* minor cosmetic fix

* alternative way to include linked abstract methods and declare the template methods

* reorder

* even simpler

* bring back eq_attrs

* r -> r_t

* Additional halos features (#1068)

* first commit

* enclose classmethods in the class

* brought in minor improvements to halos

* coverage

* Tests v3 (#1059)

* ported changes in halos/profiles

* changes from star in halos/ (no tests)

* refactor concentration, mass_function, halo_bias; improvements in HMCalculator

* HaloProfile subclasses & HaloProfile.normprof attribute

* simplify imports

* remove HaloProfile.name

* cleaned up pk_Npt

* add extrap_pk argument

* FFTLogParams class instead of dictionary in HaloProfile

* simplified code

* removed mass_def from HMIngredients; comprehensive code review 1

* re-implementation, bugfixes, tests

* c_m_relation >> concentration; code improvements; A_SPLINE_MAX in Python

* HMIngredients initializers - no repeated code

* rogue file

* patch for new changes

* updated Build Status badge website

* fix websites

* added a DOI badge & links

* added nonbreaking space

* removed extra space

* FancyRepr out of CCLObject to simplify

* update tests to match changes

* coverage

* simple test for tkkssc

* more tests for tkkssc

* addressed comments 1

* addressed comments 2: mass_def defaults are name strings etc.

* deprecate Gaussian & PowerLaw profiles

* remove all warnings in testing

* use pytest as per docs instead of numpy.testing and pyutils.assert_warns

* bugfix in master: sometimes mass_def_strict=False fails unexpectedly

* typo

* addressed comments

* New feature: Arbitrary function for profile normalization

* gain efficiency

* comments + deprecate k_min from HMCalculator

* renamed initialize_from_input --> create_instance

* brought in CCLNamedClass from docs_v3

* brought in from docs_v3: directly callable HMIngredients

* prep for Davids input

* renamed HMCalculator --> HaloModel

* fix typo

* brought in from docs_v3: initialize mass_def from any string (e.g. 400c)

* Revert "renamed HMCalculator --> HaloModel"

This reverts commit 43591ce.

* comments

* comments

* shortcut for __eq__ methods

* Eq. for EulerianPT calculator (#1073)

* __eq_attrs__ for nl_pt

* No normprof (#1072)

* extricated normprof

* Cosmology v3 (#1071)

* rename core --> Cosmology

* renamed core --> cosmology in docs/imports except docstrings

* cosmology v2

* first pass

* neutrinosneutrinosneutrinos

* simplified

* yamlyamlyaml

* fully fix neutrino mayhem

* new arg names in tests

* comments

* comments on comments on comments

* removed tests

* no warn

---------

Co-authored-by: David Alonso <dam.phys@gmail.com>

* avoid warning due to zeros (#1076)

* Fix halo inconsistencies v3 (#1069)

* ported changes in halos/profiles

* changes from star in halos/ (no tests)

* refactor concentration, mass_function, halo_bias; improvements in HMCalculator

* HaloProfile subclasses & HaloProfile.normprof attribute

* simplify imports

* remove HaloProfile.name

* cleaned up pk_Npt

* add extrap_pk argument

* FFTLogParams class instead of dictionary in HaloProfile

* simplified code

* removed mass_def from HMIngredients; comprehensive code review 1

* re-implementation, bugfixes, tests

* c_m_relation >> concentration; code improvements; A_SPLINE_MAX in Python

* HMIngredients initializers - no repeated code

* rogue file

* patch for new changes

* updated Build Status badge website

* fix websites

* added a DOI badge & links

* added nonbreaking space

* removed extra space

* FancyRepr out of CCLObject to simplify

* update tests to match changes

* coverage

* simple test for tkkssc

* more tests for tkkssc

* addressed comments 1

* addressed comments 2: mass_def defaults are name strings etc.

* deprecate Gaussian & PowerLaw profiles

* bugfix in master: sometimes mass_def_strict=False fails unexpectedly

* typo

* addressed comments

* New feature: Arbitrary function for profile normalization

* gain efficiency

* comments + deprecate k_min from HMCalculator

* renamed initialize_from_input --> create_instance

* brought in CCLNamedClass from docs_v3

* brought in from docs_v3: directly callable HMIngredients

* prep for Davids input

* renamed HMCalculator --> HaloModel

* fix typo

* brought in from docs_v3: initialize mass_def from any string (e.g. 400c)

* Revert "renamed HMCalculator --> HaloModel"

This reverts commit 43591ce.

* counterterms func inside of 4pt func

* replace deprecated abstractproperty

* abstract linked methods for HaloProfile, MassFunc, HaloBias, Concentration

* fully working implementation

* renamed lM --> log10M etc.

* minor cosmetic fix

* first commit

* alternative way to include linked abstract methods and declare the template methods

* reorder

* even simpler

* enclose classmethods in the class

* brought in minor improvements to halos

* clone of 1064

* bring back eq_attrs

* r -> r_t

* Additional halos features (#1068)

* first commit

* enclose classmethods in the class

* brought in minor improvements to halos

* coverage

* removed MassConcentration

* full implementation (v3 only)

* comments

* single-liner

* coverage

* Final checklist v3 (#1075)

* imports

* homogenize all imports as per PEP8 and the Python Import System

* homogenize all imports as per PEP8 and the Python Import System

* instance checks

* all exceptions raised correctly

* comments

* add aliases

* reverted changes and introduced proper enums

* Preserve API in v2.final (#1077)

* first commit

* Einasto mass translator + MassDef concentration API

* universal api no-break

* comments

* MassDef vars

* Coverage v3 (#1078)

* coverage

* comments

---------

Co-authored-by: David Alonso <dam.phys@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants