Skip to content

Conversation

@Sheshuk
Copy link
Contributor

@Sheshuk Sheshuk commented Aug 1, 2024

To be merged after #350

In this PR I do the following:

  • Take the FlavorTransformation classes, implemented by @jpkneller in Jpkneller version 2.0 #308
  • Switch to using the FlavorMatrix class everywhere in get_probabilities
  • Move the Three/FourFlavorTransformation.Pmf_HighDensityLimit methods to Three/FourFlavorMixingParameters.Pmf_HighDensityLimit - I think they belong next there, next to VacuumMixingMatrix
  • refactor the class hierarchy
  • Finalize EarthMatter transform (based on BEMEWS)
  • Finalize MSWEffect transform (based on SNOSHEWS)
  • Update tests

@sybenzvi
Copy link
Contributor

Comment from @Sheshuk (2024-08-13): some classes are implementing all flavor transformations. Others are handling transformations only in certain environments, e.g., in Earth or in vacuum. As a result, the matrix outputs are not consistent or equivalent across all classes (e.g., flavor-to-flavor, flavor-to-mass, mass-to-mass, etc.).

This needs to be checked carefully in the code to ensure that mixing operations between the classes is not allowed if physically the transformations should not be mixed. All classes inherit from FlavorTransformation and the class hierarchy needs to be refactored to prevent unphysical combinations.

@sybenzvi
Copy link
Contributor

A dumb observation @Sheshuk: while approving PR #350, I noticed the snewpy.utils submodule has just one function. While it is called in flux.py and flavor.py, but really it's quite specific to flavor transformations.

While I could live with it, I don't love having a generic "utils" module that is destined to be a dumping ground for random calculations. Can we move the function into expand_dimensions_to into flavor.py and delete utils.py?

@sybenzvi
Copy link
Contributor

sybenzvi commented Aug 15, 2024

@Sheshuk, I added support for BEMEWS to snewpy.flavor_transformation. It does work but I noticed a few other problems with the classes in this submodule:

  • Most of the classes assumed the MixingParameters objects had a property called mix_params but it's apparently mix_pars now.
  • Most of the SNTransformation and EarthTransformation children expect a private mix_pars member. This was not being defined so I added a mix_params positional argument to the parent class constructor.
  • Nearly all of the unit tests are broken because of attempted conversions of ThreeFlavor_MassBasis to TwoFlavor which are invalid. Presumably this is a holdover from an older version of the code that did not handle the mass and flavor bases separately.

@Sheshuk
Copy link
Contributor Author

Sheshuk commented Oct 24, 2024

Finally I think this is ready for review.
Here is a summary so we can discuss it.

What I did

  1. Separated "concrete" flavor transformation classes into a tree, so that SN, Vacuum and Earth transformations are different classes, and distinct from the "full" transformations (children of FlavorTransformation class)
    classes
  2. Made flavor_transformation a nested package instead of single file (so in_sn,in_vacuum and in_earth contain corresponding transforms)
  3. Made each transformation produce a FlavorMatrix object, which is very convenient for accessing, multiplication etc. instead of a plain np.array
  4. Updated the tests: convert the transformation matrix TwoFlavor>>P_ff>>TwoFlavor and use the same 2Flavor formulas in the tests as before (they still need update in the future)
  5. Added new tests: using the FlavorMatrix we can more easily check the matrix form against the equations in snewpy v2 draft, for example:
    P_SN = xform.in_sn.P_mf(t,E)
    #check with eq (A54) from snewpy_v2.0 paper
    assert np.allclose(P_SN['NU','NU'],
    [[0, U2['mu','1'], U2['tau','1'],0],
    [1, 0, 0, 0],
    [0, U2['mu','3'], U2['tau','3'],0],
    [0, 0, 0, 1]]
    )

    image
  6. User interface: since now we have all the concrete classes like AdiabaticMSW as building blocks of the TransformationChain the user would have to build the desired chain from scratch... So for backward compatibility I declared the pre-defined TransformationChains with the same names, as their main components:
    #define default values for backward compatibility
    AdiabaticMSW = TransformationChain(in_sn.AdiabaticMSW())
    NonAdiabaticMSWH = TransformationChain(in_sn.NonAdiabaticMSWH())
    AdiabaticMSWes = TransformationChain(in_sn.AdiabaticMSWes())
    NonAdiabaticMSWes = TransformationChain(in_sn.NonAdiabaticMSWes())
    TwoFlavorDecoherence = TransformationChain(in_sn.TwoFlavorDecoherence())
    NeutrinoDecay = TransformationChain(in_sn.AdiabaticMSW(), in_vacuum.NeutrinoDecay())
    QuantumDecoherence = TransformationChain(in_sn.AdiabaticMSW(), in_vacuum.QuantumDecoherence())

    I'm not sure this is the best way to go. We will probably need to revisit this.
  7. Added some basic documentation, we'll need to revisit it as well, once the interface is finalized.

Next steps (to be done in separate PRs):

  • Update tests with 3flavor formulas
  • Finalize the user interface
  • Update the documentation
  • SNOSHEWS support needs to be tested separately
  • Implement 3flavor rate calculation

@Sheshuk Sheshuk marked this pull request as ready for review October 24, 2024 11:48
@Sheshuk
Copy link
Contributor Author

Sheshuk commented Oct 24, 2024

One more thing: I had to swap the formulas for NMO and IMO for NeutrinoDecay in the tests: 09735b5 I'm not sure this is correct. we need a cross-check

@JostMigenda
Copy link
Member

Thanks a lot for this work @Sheshuk! Sorry it’s taking me a while to review this; I’m still looking through the code but wanted to get at least some high level comments to you now:

  1. Separated "concrete" flavor transformation classes into a tree, so that SN, Vacuum and Earth transformations are different classes, and distinct from the "full" transformations (children of FlavorTransformation class)

This class structure looks really good to me. Just two comments:

  • I can see in_earth.NoEarthMatter and in_vacuum.NoVacuumTransformation, but no equivalent in_sn.NoSNTransformation? And that’s presumably why in_sn is a required argument to a TransformationChain, while in_vacuum and in_earth are optional? For cases like this, where the symmetry of the three transformation types is broken, we should take care to explicitly point them out in the documentation.
  • I’d like to rename MSWEffect, since that name sounds so generic that novice users would likely expect that to be the basic MSW case. Something like CustomMSW or AdvancedMSW would be clearer (and more consistent with AdiabaticMSW, NonAdiabaticMSW and their four-flavour equivalents, which all drop the “Effect” from the class name).
  1. Added new tests: using the FlavorMatrix we can more easily check the matrix form against the equations in snewpy v2 draft, for example:

    P_SN = xform.in_sn.P_mf(t,E)
    #check with eq (A54) from snewpy_v2.0 paper
    assert np.allclose(P_SN['NU','NU'],
    [[0, U2['mu','1'], U2['tau','1'],0],
    [1, 0, 0, 0],
    [0, U2['mu','3'], U2['tau','3'],0],
    [0, 0, 0, 1]]
    )

    image

This is really nice for readability!

  1. User interface: since now we have all the concrete classes like AdiabaticMSW as building blocks of the TransformationChain the user would have to build the desired chain from scratch... So for backward compatibility I declared the pre-defined TransformationChains with the same names, as their main components:

    #define default values for backward compatibility
    AdiabaticMSW = TransformationChain(in_sn.AdiabaticMSW())
    NonAdiabaticMSWH = TransformationChain(in_sn.NonAdiabaticMSWH())
    AdiabaticMSWes = TransformationChain(in_sn.AdiabaticMSWes())
    NonAdiabaticMSWes = TransformationChain(in_sn.NonAdiabaticMSWes())
    TwoFlavorDecoherence = TransformationChain(in_sn.TwoFlavorDecoherence())
    NeutrinoDecay = TransformationChain(in_sn.AdiabaticMSW(), in_vacuum.NeutrinoDecay())
    QuantumDecoherence = TransformationChain(in_sn.AdiabaticMSW(), in_vacuum.QuantumDecoherence())

    I'm not sure this is the best way to go. We will probably need to revisit this.

At first glance, that makes sense to me; but happy to revisit it later.

  1. Added some basic documentation, we'll need to revisit it as well, once the interface is finalized.

That makes sense; I’ll largely skip that during my review.


And a first comment on the code:

self.in_sn = in_sn
self.in_vacuum = in_vacuum
self.in_earth = in_earth
self.transforms = [in_sn, in_vacuum, in_earth]

The TransformationChain instances are currently saving the transforms in two different places; can we simplify that?

Copy link
Member

@JostMigenda JostMigenda left a comment

Choose a reason for hiding this comment

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

Individual comments are at the appropriate line in the code.

Regarding the code arrangement in flavor_transformation/:
In addition to the in_* submodules (which makes sense), there are three fairly generic submodules (__init__.py, base.py and transforms.py) and it’s not obvious to me what code goes where. My intuition would probably be to drop transforms.py by putting NoTransformation/CompleteExchange/ThreeFlavorDecoherence into __init__.py (alongside the backwards-compatible transformation chains) and moving FlavorTransformation and TransformationChain into base.py?

Finally, I added a commit cleaning up unused imports.

@Sheshuk
Copy link
Contributor Author

Sheshuk commented Nov 18, 2024

@JostMigenda thanks a lot for your review!

  • I can see in_earth.NoEarthMatter and in_vacuum.NoVacuumTransformation, but no equivalent in_sn.NoSNTransformation? And that’s presumably why in_sn is a required argument to a TransformationChain, while in_vacuum and in_earth are optional?

Yes, it is correct. I also don't like it being asymmetric, so if you have any idea of making it more clear for the user, please tell.
Another misleading thing is that we can't reproduce NoTransformation in TransformationChain - even if we completely ignore the SN matter and other effects, the flavor states are transformed to mass ones $P_{\alpha\to i}$ and then back $P_{i\to\beta}$, but because of the decoherence of mass states, these transformation matrices don't produce a unit matrix $I_{\alpha\beta}$.

  • I’d like to rename MSWEffect, since that name sounds so generic that novice users would likely expect that to be the basic MSW case. Something like CustomMSW or AdvancedMSW would be clearer (and more consistent with AdiabaticMSW, NonAdiabaticMSW and their four-flavour equivalents, which all drop the “Effect” from the class name).

I agree, CustomMSW would be better here.
I kept all the class names initially defined by Jim in his branch, and didn't change anything because I'm not sure I have enough understanding of underlying physics 😃
But it will be good if we can come with a more clear naming convention in a discussion.

But probably we should do the renaming in a separate PR, it will be rather straightforward.

The TransformationChain instances are currently saving the transforms in two different places; can we simplify that?

Sure. I can make in_sn, in_vacuum, in_earth properties, accessing the elements of self.transforms. Or vice versa. Would that be better?

In addition to the in_* submodules (which makes sense), there are three fairly generic submodules (__init__.py, base.py and transforms.py) and it’s not obvious to me what code goes where. My intuition would probably be to drop transforms.py by putting NoTransformation/CompleteExchange/ThreeFlavorDecoherence into __init__.py (alongside the backwards-compatible transformation chains) and moving FlavorTransformation and TransformationChain into base.py?

That makes sense, thank you, I'll do that!

The only classes standing out here are Three/FourFlavorTransformation in base.py, which are not base classes per se, but a small functionality, providing a way to say "this class should have Three(Four)FlavorMixingParameters,no matter what. If you try to change the parameters, just change the values and don't change their type".
I'm not sure this is the most elegant way to implement it. If you have any ideas, please share

@Sheshuk
Copy link
Contributor Author

Sheshuk commented Nov 18, 2024

In addition to the in_* submodules (which makes sense), there are three fairly generic submodules (__init__.py, base.py and transforms.py) and it’s not obvious to me what code goes where. My intuition would probably be to drop transforms.py by putting NoTransformation/CompleteExchange/ThreeFlavorDecoherence into __init__.py (alongside the backwards-compatible transformation chains) and moving FlavorTransformation and TransformationChain into base.py?

On second thought, this makes a circular import:
TransformationChain needs NoVacuumTransformation,NoEarthMatter for the default values (and SNTransformation,VacuumTransformation and EarthTransformation for type hints), which depend on FlavorTransformation base class. So we can't put FlavorTransformation in base.py.
We can put it together with the rest transorms in __init__.py, but that will bascially mean that we migrated our transforms.py into __init__.py. I personally don't like when there is a lot of code in __init__.py except for some small definitions of the objects to be imported etc.

@JostMigenda
Copy link
Member

  • I can see in_earth.NoEarthMatter and in_vacuum.NoVacuumTransformation, but no equivalent in_sn.NoSNTransformation? And that’s presumably why in_sn is a required argument to a TransformationChain, while in_vacuum and in_earth are optional?

Yes, it is correct. I also don't like it being asymmetric, so if you have any idea of making it more clear for the user, please tell. Another misleading thing is that we can't reproduce NoTransformation in TransformationChain - even if we completely ignore the SN matter and other effects, the flavor states are transformed to mass ones P α → i and then back P i → β , but because of the decoherence of mass states, these transformation matrices don't produce a unit matrix I α β .

Hm … good point; I can see it being very confusing if TransformationChain(NoSNTransformation, NoVacuumTransformation, NoEarthMatter) does not match NoTransformation. @jpkneller, do you have any idea how to make it clearer? Or should we just live with that asymmetry and document it well?

  • I’d like to rename MSWEffect, since that name sounds so generic that novice users would likely expect that to be the basic MSW case. Something like CustomMSW or AdvancedMSW would be clearer (and more consistent with AdiabaticMSW, NonAdiabaticMSW and their four-flavour equivalents, which all drop the “Effect” from the class name).

I agree, CustomMSW would be better here. I kept all the class names initially defined by Jim in his branch, and didn't change anything because I'm not sure I have enough understanding of underlying physics 😃 But it will be good if we can come with a more clear naming convention in a discussion.

But probably we should do the renaming in a separate PR, it will be rather straightforward.

Yep, let’s do that! I’ve created #366 to keep track of it.

The TransformationChain instances are currently saving the transforms in two different places; can we simplify that?

Sure. I can make in_sn, in_vacuum, in_earth properties, accessing the elements of self.transforms. Or vice versa. Would that be better?

It would certainly eliminate any risk of bugs where we change e.g. self.in_sn but forget to update self.transforms, yes.
After thinking about it a bit longer yesterday, however, I think making self.transforms a namedtuple would be even better?

>>> from collections import namedtuple
>>> Transformations = namedtuple('Transformations', 'in_sn, in_vacuum, in_earth')
>>> transforms = Transformations('AdiabaticMSW', 'NoVacuumTransformation', 'NoEarthMatter')
>>> for t in transforms:
...     print(t)
... 
AdiabaticMSW
NoVacuumTransformation
NoEarthMatter
>>> transforms.in_vacuum
'NoVacuumTransformation'

In particular, the current design doesn’t make it clear that iterating over self.transforms and accessing self.in_* is equivalent; whereas the namedtuple makes that obvious.
(Personally, I also think that self.transforms.in_vacuum, while a bit more verbose, is more readable than self.in_vacuum; but that’s more subjective.)

The only classes standing out here are Three/FourFlavorTransformation in base.py, which are not base classes per se, but a small functionality, providing a way to say "this class should have Three(Four)FlavorMixingParameters,no matter what. If you try to change the parameters, just change the values and don't change their type". I'm not sure this is the most elegant way to implement it. If you have any ideas, please share

I guess they are more of a mix-in than a base class; but I’m fine with that.

In addition to the in_* submodules (which makes sense), there are three fairly generic submodules (__init__.py, base.py and transforms.py) and it’s not obvious to me what code goes where. My intuition would probably be to drop transforms.py by putting NoTransformation/CompleteExchange/ThreeFlavorDecoherence into __init__.py (alongside the backwards-compatible transformation chains) and moving FlavorTransformation and TransformationChain into base.py?

On second thought, this makes a circular import:

Oh, good point!

After thinking about it for a while, I think the main issue is the TransformationChain—it’s a high-level class which depends on the SN/Vacuum/EarthTransformation classes, so it shouldn’t be lumped in with the base classes. With that in mind, I would suggest restructuring as follows:

  • One submodule for all concrete transformation presets (both phenomenological ones (NoTransformation/CompleteExchange/ThreeFlavorDecoherence) and the backwards-compatible TransformationChains)
  • One submodule for base classes and mix-ins (i.e. FlavorTransformation and Three/FourFlavorTransformation)
  • A separate submodule for the TransformationChain class

The first of these should probably be __init__.py (where the backwards-compatible TransformationChains already live); the second should be base.py; and I’d probably rename the last one from transforms.py to TransformationChain.py for clarity.
Since I already went through the whole process, I’ll post a separate PR in a minute with these changes for you to look at (and merge into this PR branch, if you’re happy with it).

@Sheshuk
Copy link
Contributor Author

Sheshuk commented Nov 22, 2024

After thinking about it a bit longer yesterday, however, I think making self.transforms a namedtuple would be even better?

Good idea, that's perfect use case for that. I also like self.transforms.in_vacuum - it's always nice when the code reads as a natural language.

I'll do it now.

@jpkneller
Copy link
Contributor

One more thing: I had to swap the formulas for NMO and IMO for NeutrinoDecay in the tests: 09735b5 I'm not sure this is correct. we need a cross-check

I'm glad you caught this. I checked the formulas, they are now correct. I must have mixed up NMO and IMO.

@JostMigenda JostMigenda added this to the v2.0 milestone Nov 26, 2024
@jpkneller
Copy link
Contributor

  • I can see in_earth.NoEarthMatter and in_vacuum.NoVacuumTransformation, but no equivalent in_sn.NoSNTransformation? And that’s presumably why in_sn is a required argument to a TransformationChain, while in_vacuum and in_earth are optional?

Yes, it is correct. I also don't like it being asymmetric, so if you have any idea of making it more clear for the user, please tell. Another misleading thing is that we can't reproduce NoTransformation in TransformationChain - even if we completely ignore the SN matter and other effects, the flavor states are transformed to mass ones P α → i and then back P i → β , but because of the decoherence of mass states, these transformation matrices don't produce a unit matrix I α β .

From a physics perspective there is no symmetry between the three parts of a transformation chain: the neutrinos have to pass through the supernova but they don't have to do anything in the vacuum (except decohere), or pass through the Earth. I would prefer that we not provide a in_sn.NoSNTransformation prescription so that the user cannot create an empty TransformationChain object that they might think would be equivalent to NoTransformation. But if you insist, the only way I can see how to do it would be to check that all three parts of the chain are None and if so, use NoTransformation.

  • I’d like to rename MSWEffect, since that name sounds so generic that novice users would likely expect that to be the basic MSW case. Something like CustomMSW or AdvancedMSW would be clearer (and more consistent with AdiabaticMSW, NonAdiabaticMSW and their four-flavour equivalents, which all drop the “Effect” from the class name).

I agree, CustomMSW would be better here. I kept all the class names initially defined by Jim in his branch, and didn't change anything because I'm not sure I have enough understanding of underlying physics 😃 But it will be good if we can come with a more clear naming convention in a discussion.

AdvancedMSW is fine with me, or we could adopt NumericalMSW

@jpkneller
Copy link
Contributor

I noticed that ThreeFlavorDecoherence is described as Phenomenological transformation and placed in init.py. It really should be a SN transformation. It's true that Earth matter can't change the decoherence but vacuum transformations could e.g. one of the mass states decays.

@JostMigenda
Copy link
Member

From a physics perspective there is no symmetry between the three parts of a transformation chain: the neutrinos have to pass through the supernova but they don't have to do anything in the vacuum (except decohere), or pass through the Earth. I would prefer that we not provide a in_sn.NoSNTransformation prescription so that the user cannot create an empty TransformationChain object that they might think would be equivalent to NoTransformation. But if you insist, the only way I can see how to do it would be to check that all three parts of the chain are None and if so, use NoTransformation.

I just wanted to make sure that this asymmetry is deliberate and not an oversight.
It clearly is and we have a good explanation for it, so I’m happy to leave it as is. (Though this might be a good point to highlight in the docs, in case any users have a similar question.)

I noticed that ThreeFlavorDecoherence is described as Phenomenological transformation and placed in __init__.py. It really should be a SN transformation. It's true that Earth matter can't change the decoherence but vacuum transformations could e.g. one of the mass states decays.

My impression was that NoTransformation, CompleteExchange and ThreeFlavorDecoherence are typically used as “black box transformations”—where researchers are interested in some other aspect and use these extreme scenarios to get a rough idea of how flavor transformations could affect studies of that other aspect. So it makes sense to me to keep these three separate?

However, if you think that any users might want to build a TransformationChain out of ThreeFlavorDecoherence in the SN plus additional transformations in vacuum/earth, then we could turn TFD into a SNTransformation.
In that case, would TransformationChain(in_sn=ThreeFlavorDecoherence()) automatically result in the same flavour equilibration that we get from the current implementation of TFD? If not, that might be very confusing.

Comment on lines 59 to 63
def __call__(self, mixing_params):
"""Convenience method: update mixing parameters and return self"""
self.set_mixing_params(mixing_params)
return self

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
def __call__(self, mixing_params):
"""Convenience method: update mixing parameters and return self"""
self.set_mixing_params(mixing_params)
return self

Hm … changing the existing transformation in place and returning it at the same time seems like a potentially confusing design to me.
I’d contrast this with list.sort() (which sorts in place and deliberately doesn’t return the list) vs. sorted(list) (which returns a new list but does not modify its argument).

For example, I’d be tempted to write something like

from snewpy.flavor_transformation import AdiabaticMSW
from snewpy.neutrino import MixingParameters

msw_nufit = AdiabaticMSW(MixingParameters(version="NuFIT6.0"))
msw_pdg = AdiabaticMSW(MixingParameters(version="PDG2024"))

but this wouldn’t work at all. (Instead, it would result in msw_nufit == msw_pdg == AdiabaticMSW.)

I’m not even sure what __call__ should do in general. In the example above, it feels intuitive to have it return a deep copy (with modified mixing params) and leave the original object unmodified; but in other contexts having it as a convenience method as it is now might make more sense?

For this PR, I think we should stick with explicit use of set_mixing_params() (which makes it clear that the current object is modified), remove __call__ and maybe open a separate issue if we want to enable use cases like in the example above.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with this suggestion. My presumption when reading a code is that member functions modify the existing class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also don't like this __call__ method - it is here only to mimic the previous behaviour, as in __init__.py we define the aliases:

# define default values for backward compatibility
AdiabaticMSW = TransformationChain(in_sn.AdiabaticMSW())
NonAdiabaticMSWH = TransformationChain(in_sn.NonAdiabaticMSWH())
AdiabaticMSWes = TransformationChain(in_sn.AdiabaticMSWes())
NonAdiabaticMSWes = TransformationChain(in_sn.NonAdiabaticMSWes())
TwoFlavorDecoherence = TransformationChain(in_sn.TwoFlavorDecoherence())
NeutrinoDecay = TransformationChain(in_sn.AdiabaticMSW(), in_vacuum.NeutrinoDecay())
QuantumDecoherence = TransformationChain(in_sn.AdiabaticMSW(), in_vacuum.QuantumDecoherence())
EarthMatter = lambda mixing_params,AltAz: TransformationChain(
in_sn.AdiabaticMSW(),
in_earth=in_earth.EarthMatter(SNAltAz=AltAz),
mixing_params=mixing_params
)

so that previous code like AdiabaticMSW(MixingParams) would work.
I'm not sure about this whole interface - so I just tried to make a minimal adapter layer, to conform to the old interface. There are many other ways to do them, but I don't see any of them as simple and elegant. Make specific classmethod constructors, and use them as aliases in init?
Maybe you have an idea?

Copy link
Contributor Author

@Sheshuk Sheshuk May 12, 2025

Choose a reason for hiding this comment

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

but this wouldn’t work at all. (Instead, it would result in msw_nufit == msw_pdg == AdiabaticMSW.)

Could you explain, why? I thought, that in each line we create an instance of AdiabaticMSW, and then set the _mixing_params attribute for each of them specifically, so that msw_pdg and msw_pdg should be AdiabaticMSW instances with corresponding mixing parameters? Or am I missing something?

Copy link
Member

Choose a reason for hiding this comment

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

I thought, that in each line we create an instance of AdiabaticMSW

Nope—we’ve turned AdiabaticMSW from a class into an instance of a TransformationChain. So AdiabaticMSW(mix_pars) no longer creates new instances; it’s just syntactic sugar for TransformationChain.__call__(AdiabaticMSW, mix_pars), which passes AdiabaticMSW in as the self argument.

Copy link
Member

Choose a reason for hiding this comment

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

so that previous code like AdiabaticMSW(MixingParams) would work.

That wasn’t quite the previous interface, though. Previously, the function signature was def __init__(self, mix_angles=None, mh=MassHierarchy.NORMAL): and we’re breaking this deliberately to pass in a MixingParameters instead.

So … I’d be okay with breaking that “even more” (at least for now—maybe we can discuss at the hackathon next month how an intuitive interface for that should look like?) and deleting the __call__ for now before it causes any further confusion.

Copy link
Member

Choose a reason for hiding this comment

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

Note from Zoom call: Replace “singletons” with lambdas

@jpkneller
Copy link
Contributor

From a physics perspective there is no symmetry between the three parts of a transformation chain: the neutrinos have to pass through the supernova but they don't have to do anything in the vacuum (except decohere), or pass through the Earth. I would prefer that we not provide a in_sn.NoSNTransformation prescription so that the user cannot create an empty TransformationChain object that they might think would be equivalent to NoTransformation. But if you insist, the only way I can see how to do it would be to check that all three parts of the chain are None and if so, use NoTransformation.

I just wanted to make sure that this asymmetry is deliberate and not an oversight. It clearly is and we have a good explanation for it, so I’m happy to leave it as is. (Though this might be a good point to highlight in the docs, in case any users have a similar question.)

Will do.

I noticed that ThreeFlavorDecoherence is described as Phenomenological transformation and placed in __init__.py. It really should be a SN transformation. It's true that Earth matter can't change the decoherence but vacuum transformations could e.g. one of the mass states decays.

My impression was that NoTransformation, CompleteExchange and ThreeFlavorDecoherence are typically used as “black box transformations”—where researchers are interested in some other aspect and use these extreme scenarios to get a rough idea of how flavor transformations could affect studies of that other aspect. So it makes sense to me to keep these three separate?

However, if you think that any users might want to build a TransformationChain out of ThreeFlavorDecoherence in the SN plus additional transformations in vacuum/earth, then we could turn TFD into a SNTransformation. In that case, would TransformationChain(in_sn=ThreeFlavorDecoherence()) automatically result in the same flavour equilibration that we get from the current implementation of TFD? If not, that might be very confusing.

The use case you describe for TFD is a useful one so my suggestion is to have a Phenomenological Transformation in init.py called e.g. Equilibrate which implements this case, and to have ThreeFlavorDecoherence as prescription in in_sn.py. The ThreeFlavorDecoherence is useful for describing the effect of strong turbulence in supernovae; the TwoFlavorDecoherence describes the effect of weak turbulence.

@JostMigenda
Copy link
Member

Opened #384 for the TFD issue. Splitting it up like you suggested sounds good to me; I’d just like to do that in a follow-up PR later, since this one is already more unwieldy than I’d like.

@jpkneller
Copy link
Contributor

Which branch should I start from? We seem to have a couple running in parallel.

@JostMigenda
Copy link
Member

Probably from the branch of this PR here? (Sheshuk/FlavorTransformations_with_FlavorMatrices) This shouldn’t conflict with the changes in #363.

Does that mean you’re done reviewing this PR and happy with it? If so, then I think the only remaining issue before we can merge this PR is the __call__ question.

@jpkneller
Copy link
Contributor

Does that mean you’re done reviewing this PR and happy with it? If so, then I think the only remaining issue before we can merge this PR is the __call__ question.

I agree with your suggestion to remove the definition of call as currently written. I would make it equivalent to the apply_to method.

@JostMigenda JostMigenda force-pushed the Sheshuk/FlavorTransformations_with_FlavorMatrices branch 2 times, most recently from 402345c to 6ffae09 Compare May 12, 2025 19:43
@JostMigenda
Copy link
Member

I’ve moved Jim’s commits to a separate branch (and separate PR #385). Hopefully, keeping that discussion separate will help us get this PR here merged sooner

@jpkneller
Copy link
Contributor

Did we decide on the new name for MSWEffect?
My preference is for NumericalMSW.

@Sheshuk
Copy link
Contributor Author

Sheshuk commented May 27, 2025

  • Removed __call__ method
  • Made the AdiabaticMSW and other chain aliases in __init__ now be produced by the construct_chain function

@Sheshuk
Copy link
Contributor Author

Sheshuk commented May 29, 2025

@jpkneller, about your commit: the implementation of get_transformed_spectra is already done in #363
So this MR here avoids this on purpose

@jpkneller
Copy link
Contributor

@jpkneller, about your commit: the implementation of get_transformed_spectra is already done in #363 So this MR here avoids this on purpose

Sorry, I just saw the error and didn't check. Is there anything left to do with this PR?

Copy link
Contributor

@jpkneller jpkneller left a comment

Choose a reason for hiding this comment

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

Let's merge this branch and #363 and then deal with the remaining small issues.

@Sheshuk Sheshuk force-pushed the Sheshuk/FlavorTransformations_with_FlavorMatrices branch from 51fb540 to 27cc176 Compare May 30, 2025 14:01
@Sheshuk
Copy link
Contributor Author

Sheshuk commented May 30, 2025

@jpkneller, about your commit: the implementation of get_transformed_spectra is already done in #363 So this MR here avoids this on purpose

Sorry, I just saw the error and didn't check. Is there anything left to do with this PR?

No, I think we're good. I've just reverted your commit to avoid any kind of merge conflicts in the future

@Sheshuk Sheshuk merged commit dc4c2c7 into main May 30, 2025
3 of 24 checks passed
@JostMigenda JostMigenda deleted the Sheshuk/FlavorTransformations_with_FlavorMatrices branch June 18, 2025 10:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

5 participants