-
Notifications
You must be signed in to change notification settings - Fork 308
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
[PROTO] Framework for lite "fast" formulary functionality #1098
base: main
Are you sure you want to change the base?
Conversation
…ly document imported attributes/variables
… attributes/variables are documented
…lite using add_lite; incorporate thermal_speed_lite into the thermal_speed calculation
Good timing on my part! 😉 |
Codecov Report
@@ Coverage Diff @@
## master #1098 +/- ##
==========================================
- Coverage 96.91% 96.88% -0.04%
==========================================
Files 70 70
Lines 6928 6956 +28
==========================================
+ Hits 6714 6739 +25
- Misses 214 217 +3
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal here is to develop a framework that still has our traditional formulary functionality but also an intuitive lite version for computation.
I personally hate the fact that we're still enjoying zero-ver, but already have traditions around functionality 😉
Looks great! A bunch of minor text fix suggestions, but I have no complaints about the implementation!
""" | ||
Dictionary of various coefficients used in calculating the | ||
`~plasmapy.formulary.parameters.thermal_speed`. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor rewrite:
""" | |
Dictionary of various coefficients used in calculating the | |
`~plasmapy.formulary.parameters.thermal_speed`. | |
""" | |
""" | |
Dictionary of various coefficients used for | |
`~plasmapy.formulary.parameters.thermal_speed`, with the dimensionality | |
as the key. | |
""" |
A lite weight version of `thermal_speed` intended for computational used | ||
and, thus, does not do any argument validation/conditioning. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor rewrite:
A lite weight version of `thermal_speed` intended for computational used | |
and, thus, does not do any argument validation/conditioning. | |
A lightweight version of `thermal_speed` intended for computational use | |
which does not do any argument validation/conditioning. |
**Aliases:** `vth_` | ||
**Aliases:** `~plasmapy.formulary.parameters.vth_` | ||
|
||
**Lightwieght Version:** `~plasmapy.formulary.parameters.thermal_speed_lite` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo fix
**Lightwieght Version:** `~plasmapy.formulary.parameters.thermal_speed_lite` | |
**Lightweight Version:** `~plasmapy.formulary.parameters.thermal_speed_lite` |
and wow, with the suggestions interface and the stars around it, this really jumps out at you like a coke ad.
|
||
# different methods, as per https://en.wikipedia.org/wiki/Thermal_velocity | ||
try: | ||
coef = _coefficients[ndim] | ||
coeff = thermal_speed_coefficients[ndim] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just taking the opportunity to avoid overwriting the var name later with a minor refactor
coeff = thermal_speed_coefficients[ndim] | |
coeffs_for_dim = thermal_speed_coefficients[ndim] |
except KeyError: | ||
raise ValueError("{ndim} is not a supported value for ndim in thermal_speed") | ||
try: | ||
coef = coef[method] | ||
coeff = coeff[method] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
continues minor refactor
coeff = coeff[method] | |
coeff = coeffs_for_dim[method] |
|
||
|
||
vth_ = thermal_speed | ||
""" Alias to :func:`thermal_speed`. """ | ||
""" Alias to :func:`~plasmapy.formulary.parameters.thermal_speed`. """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works nicely with #1099, too! 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I ran across this same thing when working on this PR. I actually thought I already made this change. I think we need to get in the practice of using the explicit link since `thermal_speed`
would just look for the variable in the current global()
scope.
@@ -142,3 +142,51 @@ def wrapper(*args, **kwargs): | |||
wrapper.__signature__ = inspect.signature(f) | |||
|
|||
return wrapper | |||
|
|||
|
|||
def add_lite(flite, attrs=None, scope=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe "register_lite", come to think of it? I think "register" has a pretty common meaning for decorators.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I think "register" I'm think it's registering the functionality with some kind of table/database. How about bind_lite
? That's closer to what it is actually doing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds great!
def wrapper(*args, **kwargs): | ||
return f(*args, **kwargs) | ||
|
||
wrapper_doc = inspect.cleandoc(getattr(wrapper, "__doc__", """""")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrapper_doc = inspect.cleandoc(getattr(wrapper, "__doc__", """""")) | |
wrapper_doc = inspect.cleandoc(getattr(wrapper, "__doc__", "")) |
since
> """""" == ""
True
@@ -5,6 +5,7 @@ h5py >= 2.8 | |||
lmfit >= 1.0.1 | |||
matplotlib >= 2.0 | |||
mpmath >= 1.0.0 | |||
numba |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once you start adding tests for this, tox-minimal-37 will complain since it has its own list of dependencies. I'm adding that in tox.ini
for you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we already had numba
for some reason, so I was surprised when all the tests started failing because of this. Do you have any restrictions related to which numba
version we should take?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we experimented with adding it in #953; at the time, Py3.9 was released, numba completely broke on it with its bytecode changes, and we didn't want to add version-dependent numba-or-nonumba logic. 0.53 is out now; things should be fine, as py39 will just (I think...) default to that as the only compatible dependency. Though we should look at the CI logs and check which runners get which version, just in case.
I don't have a clean lower bound on which numba versions are fine, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great @rocco8773 !! I'm so excited about this. I've suggested a few changes - let me know what you think.
The documentation looks great: the only part I don't love is that the lite versions of the functions appear alongside the main functions and the aliases in the list of functions. But, I think we should just accept that for now. When we figure out a solution for the aliases, hopefully that will improve this situation too.
thermal_speed_lite, | ||
attrs=[("coefficients", "thermal_speed_coefficients")], | ||
scope=globals(), | ||
) | ||
@check_relativistic | ||
@validate_quantities( | ||
T={"can_be_negative": False, "equivalencies": u.temperature_energy()}, | ||
mass={"can_be_negative": False, "can_be_nan": True}, | ||
) | ||
@particles.particle_input | ||
def thermal_speed( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring for thermal_speed
includes a table of the coefficients: maybe we should add a link next to that to the thermal_speed_coefficients
variable to make more clear that these contain the same information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed! I'll get to this. I haven't refined and the docstrings because I didn't want to spend the time on it until the framework was approved.
@@ -142,3 +142,51 @@ def wrapper(*args, **kwargs): | |||
wrapper.__signature__ = inspect.signature(f) | |||
|
|||
return wrapper | |||
|
|||
|
|||
def add_lite(flite, attrs=None, scope=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this might be better split into two functions, eg. register_lite
and register_attrs
. Or, maybe flite
should also be a kwarg? I'm just thinking that currently attaching an attribute without also attaching a lite function would not be supported, but that seems like it should be possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, what use case would you see for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see one now: just following the "one function one job" principle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is doing "one job" I added the ability to add other attributes than "lite", because sometimes you need additional attributes to properly use "lite". For example, for thermal_speed.lite
to properly work you also need the coefficients. So, think of add_lite
as binding all required "lite" functionality (not just the lite function) to get <func>.lite
to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, to get the documentation to come out correctly and cleanly then the lite function and required attributes needs to be added at the same time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that. I guess I'm fine with this implementation now: if we need to add attributes separately in the future I guess we can always break this function apart then.
""" | ||
if attrs is None: | ||
attrs = [] | ||
elif scope is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to not just set scope=globals() by default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect this part could be optimized out given some tinkering. Maybe sys.modules(__module__)
combined with importlib
and inspect.getmembers
in some funny way...
But at this point, if you just bluntly set globals
as a default from helpers.py
, it's going to "inherit the scope of helpers.py" rather than the local environment of the function that's being registered.
This party is trickier than it seems, I think!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it - that makes sense (as much as anything regarding scopes does)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But at this point, if you just bluntly set globals as a default from helpers.py, it's going to "inherit the scope of helpers.py" rather than the local environment of the function that's being registered.
Yes, this is what was happening when globals()
was the default value.
@preserve_signature | ||
@njit | ||
def thermal_speed_lite(T, mass, coeff): | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This docstring should still provide information on the parameters and returns so those appear in the documentation. Right now in the docs there's no indication that this function doesn't take units, or where one should look to determine coeff
@StanczakDominik Let's continue the conversation from #1099 about separately documenting lite functions here. It sounds like we have three possible solutions in mind so far:
Obligatory tag @namurphy because trade-off analysis 😃 |
See, Nick? We're learning! 😸 The con for 1 is pretty heavy. 2 - hard to tell; Sphinx is a riddle. 3 would take a quick prototype that I would absolutely do right now if I were less conscious of my current procrastination driven development binge. I would say 3 is acceptable if the lite functions appear in the index. Since they are actually separate objects, I suspect they might. But again, (:egypt: :bird: :lion: :woman:) is a riddle. |
I think this is why I've become more in favor of the date base versioning. I think plasmapy will always have a combination of mature and development functionality. That's why I consider it perpetual beta.
Great! I wanted to get everyone's opinion on the framework before spending the time to put the bells and whistles on all the functionality and docstrings. I can start refining everything now. |
I don't see that as a con.
This could be done "simply" for aliases, but I don't like it for
As I stated here, this is not an option. Not including |
Well, given #1100, let's definitely go with the automodsumm one! I would like to try my hand at adapting the solution from there to the |
I definitely agree with this! My only concern is that I think having one very long list of functions in the documentation makes the package less approachable, especially to students new to plasma physics. "Who are these crazy people and why do they have SO MANY DIFFERENT FREQUENCIES? Maybe I should transfer to a condensed matter group..." I agree the automodsum implementation a la #1100 is the ideal way of handling this. I'd like to see a list of "Functions", followed by "Lite Functions" and "Aliases" (in whatever order). Then the first thing the user sees is a more manageable list of the main functions, and they have to scroll down to see the different versions of those functions. |
As I stated in #1100 (comment), I think I have a solution for grouping lite functions, so stay tuned for a prototype. As I stated before, I think the lite functions should be document like everything else and I do agree they cause clutter on the documentation page. I like the ability to group them in a separate section that explains what they are. |
This is a prototype for developing a framework to implement lite ("lightweight") formulary functionality whose design intent is for computation. The current formulary functionality is constructed around Astropy's units for user convenience and to reduce calculation errors from improper unit use. This is great, but yields very slow executing code. The goal here is to develop a framework that still has our traditional formulary functionality but also an intuitive lite version for computation.
This prototype is developed for the
thermal_speed
function. The approach this prototype takes is...thermal_speed_lite
. The formula this function has is based on SI units, but there is no argument validations (values or units). The assumption here is the user knows what is going and can insure proper values are passed to the function. The function is also decorated withnumba.njit
.thermal_speed
function exists as it always has, but now utilizesthermal_speed_lite
within the code for calculation.add_lite
is developed to bindthermal_speed_lite
tothermal_speed
asthermal_speed.lite
, as well as updatethermal_speed
's docstrings to indicate the addition oflite
.This all works something like...
The use now looks like...
See further discussions in #1090