-
Notifications
You must be signed in to change notification settings - Fork 19
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
Comparing Flavors #309
Comments
(edited first post, just adding the syntax highlight) |
Thanks for bringing it up! I wanted to separate the Flavor changes from #308 and to have a discussion on separate PR, but discussing it in an issue is more productive. My thoughts on the suggested solution: Conversion of |
As the flavor classes are used practically everywhere throughout the code, I suggest we summarize their desired behavior, before we discuss actual implementation. My view of the problem:Now we have several Flavor schemes. Whether they're Enums, or subranges or a single Enum, or general classes. Classes containing the Flavor information
I'm not sure if a every Also I don't think the Use casesThese Flavor schemes are to be accessed by user in two main cases: 1. Loop over all flavors in a given object:for flavor in flux:
plot_flavor(flux.energy, flux[flavor], label=flavor.to_tex()) 2. Access the given flavor or flavors range:prob_em = prob_matrix[Flavor.NU_E, Flavor.NU_MU]
flux_nux = flux[Flavor.NU_X] and 3. Implicitly used when applying the transformation matrices, or making a flavor scheme conversion:flux_2f = conversion_2f3f @ flux_3f #using an external conversion matrix
flux_oscillated = transformation_matrix @ flux_unoscillated RequirementsCase 1:We don't have any problems with using the wrong flavor scheme as long as it is stored in the object containers (and not just a global ❗ Requirement: Case 2:Here we want to avoid accessing the wrong flavor. So if user provides a wrong flavor we can either
❗ Requirement: Case 3:We need to check if the flavor schemes of the objects are compatible - similar as above. ❗ Requirement: |
I'm leaning towards making the conversion in limited circumstances when the flavor is being used as a key and the numerical value is immaterial. The circumstance where the conversion should be made is A) when there are equivalent keys in the different schemes (e.g. NU_E_BAR since it is common to all of them) and B) when the conversion is unambiguous e.g. the user passed in the flavor NU_MU from the ThreeFlavor scheme but the container uses TwoFlavor so the conversion is NU_MU -> NU_X. I haven't thought about whether this solution means a lot of new code but if it does, raise the exception. |
Before I look at the proposed implementation in #324, I wanted to write up my thinking at a very high level. The key feature of snewpy is providing access to lots of different supernova models (and flavor transformations) with a single consistent interface:
Now, whether any particular simulation distinguished between NU_MU and NU_TAU or lumped them both together as NU_X is also an implementation detail. Users should not have to worry about that; and anytime some user code works for a TwoFlavor scheme but not for a ThreeFlavor scheme (or vice versa), I would consider that to be a serious bug. In contrast, whether or not a simulation (or flavour transformation) includes sterile neutrinos is an actual physics difference; it is not an implementation detail. Ideally, we should still try to find a solution that ensures that code written for Two/ThreeFlavor schemes also works for FourFlavor schemes (and vice versa); but if that is not possible in some edge cases (or only with significant tradeoffs), I would be open to having some API differences here. |
I mostly agree about the final user perspective. Except for this statement:
I think that's too extreme. flux = model.get_flux(...)>>ThreeFlavor
#now we know flux has 3 flavors Note that it is somewhat similar to using energy = get_energy(...)<<u.GeV
#now we know energy is in GeV Also there is another side: the implementation of flavor schemes should make our lives as developers easy - by making the code flexible and easier to extend. So in summary I think:
|
[Note: I’m deliberately focussing on TwoFlavor vs. ThreeFlavor here. If models/transformations involve sterile neutrinos, that is a clear physical difference and I think it’s reasonable to require the user to explicitly keep track of that. The conversion matrices look like a good approach in that case. My point here is about the most common case, without steriles, and the best user interface for that.] ThreeFlavor is preferred over TwoFlavor and should be the defaultThe TwoFlavor case an approximation that’s useful to save computing resources when running SN simulations; but it’s not physically meaningful. And of course different papers/codes have different conventions on whether NU_X means (a) NU_MU or NU_TAU, (b) the sum of both, or (c) the sum of those two and their respective antineutrinos. That alone clearly indicates to me that we all think in three flavours by default (and then map our thinking onto two flavours only if forced to). SNOwGLoBES already enforces a ThreeFlavor scheme, with separate interaction channels (and associated cross section, etc. files) for NU_MU and NU_TAU.
Users may “expect” a TwoFlavor case because that was the convention in some particular model paper, sure. But in that same sense, they might “expect” a “OnePointFiveFlavor” scheme (NU_E, NU_E_BAR and NU_X only), too. Or they might expect NU_X to mean either (b) or (c), while we use (a). So we will almost invariably break some initial user expectation. The best thing we can do to help users recover from breaking these initial expectations is to provide a consistent interface. And as explained above, the ThreeFlavor scheme If the TwoFlavor case is all we have, that’s one thing; but once we have a ThreeFlavor case, I see no reason why users would explicitly want to go back to the TwoFlavor case; just like there was no one who ever requested to go back to the OnePointFiveFlavor case. Having optional support for TwoFlavor is harmfulI think there are several good reasons not to support the TwoFlavor case:
Note on implementation
I agree with your point about wanting to minimize changes to the existing code; but am drawing a different conclusion from that: Generally, a best practice when using external data is to clean it up immediately after ingesting it, so all code that needs to understand details of the data format is in one place, and all downstream code is isolated from those details. My preference would therefore be to convert to ThreeFlavours in the |
In general I agree that the TwoFlavor scheme is misleading, and should be discouraged, at least in the usage examples. However imposing a restriction means less freedom also for the developer side. 1) Our models are defined in TwoFlavor scheme
The inner data representation is different for each model (loader), so that means writing different conversion code in each of those. While this can be done, I think this is a waste of time and effort. We have code that is working and tested, and we can operate on its result, to get the desired behavior: like make conversion in 2) Our flavor transformations are defined in TwoFlavor schemeWhile most of them can and should be switched to ThreeFlavor, as in #308, in some cases it can be more natural to use TwoFlavor (I'm no expert in this though). Right now we have the freedom to define any kind of neutrino basis, and conversion matrices, which might become useful to write implementation pretty close to the formulas in the papers, which means much easier implementation and cross-check. |
Sorry for joining the discussion late, I've been out of the office the past couple of days. "Generally, a best practice when using external data is to clean it up immediately after ingesting it, so all code that needs to understand details of the data format is in one place, and all downstream code is isolated from those details. My preference would therefore be to convert to ThreeFlavours in the Model.init function" This is my preference also and I did that in #308 for the supernova models which inherit from PinchedModel. The init function for each model (e.g. Nakazato_2013) loads the data then the parent class (PinchedModel) checks whether turns the input data into three flavors. I split it this way to avoid duplicating the code to convert to three flavors. However I was lazy and didn't do the TwoFlavor>>ThreeFlavor conversion for either the Fornax models or the pre-supernova models so these require TwoFlavor >> ThreeFlavor conversion code in their get_initial_spectra functions. This can be easily fixed by copying the relevant block from PinchedModel. I would prefer not to add a method to SupernovaModel so that it remains an ABC. I dislike TwoFlavor schemes for exactly the reasons indicated, you always have to check exactly what is meant by it. I don't have a strong preference for >> over << but I am reluctant to include the conversion from TwoFlavor to ThreeFlavor because, it is not unambiguous what this means: "In the face of ambiguity, refuse the temptation to guess." My preference is that if the user wants to do this conversion, they should specify it themselves. Even ThreeFlavor>>FourFlavor makes me shiver. The only conversion matrix we require for SNEWPY2 is FourFlavor>>ThreeFlavor which I buried inside neutrino.py and called Remove_Steriles. |
Doing the conversion only in I’m currently working on a demo showing how the changes to the
I don’t have an issue with this: Every ThreeFlavor flux implicitly has zero sterile neutrinos; so explicitly adding this zeroed-out flux component feels very straightforward and intuitive to me. In this case, there are no conflicting conventions causing confusion. |
Yes, we need to add the TwoFlavor>>ThreeFlavor conversion in the init functions for Fornax and preSN models. As I said, I was lazy (the init functions for them are big and messy). |
I think this is rather extreme.
IMO copying code is bad in general - it makes the software really hard to maintain and change. Copying code N times might be an easy fix, but it makes N times more probable for mistakes (and N times more time to check everything each time you need to touch it).
If the model didn't have sterile neutrinos from the start, it will provide a ThreeFlavor output, and then you'll have to convert it to ThreeFlavor>>FourFlavor if you add the steriles in the FlavorTransformation. This requires conversion (as trivial as it is) - because it moves our arrays to different flavor basis. Do you suggest to manually add missing rows and columns instead of using matrix with defined and tested behaviour? Also if we add more sterile flavors - will we, again, do more conversions by hand? |
|
More importantly: #335 is the promised proof-of-concept PR to show how the |
It is defined only in the PinchedModel. Most of our models inherit from it, but not all. If it is part of the user interface, it should have been part of the SupernovaModel class. This is a problem with our interface in general: there is no definition of the parts which are exposed to the user, and that makes our requirements of "backward compatibility" especially painful. |
self.luminosity should not be part of the SupernovaModel class but I'm okay with it being part of PinchedModel. "This is a problem with our interface in general: there is no definition of the parts which are exposed to the user, and that makes our requirements of "backward compatibility" especially painful." There's not much we can do about this - each model has different data. We can make it easier to find out what data from the model is available to the user. |
(But I agree—sentences like “I think it’s effectively official” indicate that we messed up and haven’t been as clear as we should’ve.) |
Why?
We can and should define the user interface only in the base class. That's what the abstract base classes are for: describe the stuff which must be implemented, and anything else is implementation detail and might change from model to model. This way:
It doesn't make sense. If it's part of the interface, it should be in the base class - and tests. If it's not in the base class, but you manually add it in the same way in all other classes - it's the implementation detail, which should not be used in the user code. The problem is that the "official" interface is defined in your head, and not some documented place, so when I propose a solution, you reject it because it contradicts with something you have in your mind. SummaryWe are a distributed team, and in order to work effectively we should try to communicate and define the requirements. Otherwise it becomes a mess. We need to define the interface in base class and tests. Examples do not count: I don't have to check all the examples when developing, it's what the tests are for. I am OK with either adding luminosity to base class, or removing it from the examples. |
@Sheshuk Honestly, until I double-checked yesterday evening, I didn’t realize either that I agree with you that the interface should be obvious from the base class. I think having the luminosity available easily is useful; so I’d err on the side of adding it to the base class. |
"self.luminosity should not be part of the SupernovaModel class" It is not required that a model have a data member called luminosity and there will be cases where a model does not e.g. if the spectra are given by an analytic formula. If I remember correctly, there was a version of AnalyticModel which worked this way. The only thing required of a model class is that it has a function to return an initial and a transformed neutrino spectrum given a time, energy, and flavor prescription. Requiring a model have a data member called luminosity eliminates regions of model space. Even the requirement of get_initial_spectra is too much because we have models for Type Ia and Pair-Instability supernovae where the flavor transformation has already been applied and there is no initial spectra to transform. I have already-transformed models like this for core-collapse too. This makes them harder to use with the rest of SNEWPY and we have to hack the generate_* functions to work with them. self.luminosity is used in the notebooks which allow users to look at the data in the models - slightly manipulated if we have transformed it from 1.5 or two flavors to three flavors. Thus it appears the only reason to require |
I've been thinking about the problem Andrey identified with the new TwoFlavor, ThreeFlavor and FourFalvor classes we want to introduce in version 2.0. The issues is with comparing `flavors' across the cases with different numbers i.e. how do you compare NU_E_BAR from TwoFlavor with NU_E_BAR from ThreeFlavor or compare NU_X from TwoFlavor with NU_MU from FourFlavor, and return True in both cases even though the numerical values of the enumerations can be different. The solution I came up with is to overload the comparison operators for the *Flavor classes. To reduce the size of the code I re-assigned the numerical values and added a couple of extra short methods. Overloading the comparison operators allows a user to compare flavors from different schemes but it doesn't solve the problem that the numerical values are different. What do people think? Is this is a problem we need to fix? Anyway, the solution is below. Now this code returns True for all cases
Here's the relevant parts of neutrino.py
The text was updated successfully, but these errors were encountered: