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

Add gradient and derivatives for irregular grids #692

Merged
merged 13 commits into from
Jan 4, 2018

Conversation

dopplershift
Copy link
Member

@dopplershift dopplershift commented Dec 18, 2017

This should be good to go now. This adds 1st and 2nd derivatives, as well as gradient and laplacian that work with irregular grids. This also updates the kinematics functions to use them rather than wrappers for numpy's gradient function.

Closes #174 .



def first_derivative():
a = 'foobar'

Choose a reason for hiding this comment

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

F841 local variable 'a' is assigned to but never used


def first_derivative():
a = 'foobar'
return b

Choose a reason for hiding this comment

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

F821 undefined name 'b'
W292 no newline at end of file

It doesn't break Sphinx, but it does break PyCharm. Just get rid of it
since it's not necessary.
For some reason pyplot needs to be imported explicitly. Easy enough just
to fix.
They were assuming they always got Quantities.
"""Test lapacian with full 2D arrays."""
laplac_true = 2 * (np.ones_like(deriv_2d_data.f) * (deriv_2d_data.a + deriv_2d_data.b))
laplac = laplacian(deriv_2d_data.f, x=(deriv_2d_data.y, deriv_2d_data.x))
assert_array_almost_equal(laplac, laplac_true , 5)

Choose a reason for hiding this comment

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

E203 whitespace before ','

combined_delta = delta[delta_slice0] + delta[delta_slice1]
delta_diff = delta[delta_slice1] - delta[delta_slice0]
center = (- delta[delta_slice1] / (combined_delta * delta[delta_slice0]) * f[slice0]
+ delta_diff / (delta[delta_slice0] * delta[delta_slice1]) * f[slice1]

Choose a reason for hiding this comment

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

W503 line break before binary operator

delta_diff = delta[delta_slice1] - delta[delta_slice0]
center = (- delta[delta_slice1] / (combined_delta * delta[delta_slice0]) * f[slice0]
+ delta_diff / (delta[delta_slice0] * delta[delta_slice1]) * f[slice1]
+ delta[delta_slice0] / (combined_delta * delta[delta_slice1]) * f[slice2])

Choose a reason for hiding this comment

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

W503 line break before binary operator

combined_delta = delta[delta_slice0] + delta[delta_slice1]
big_delta = combined_delta + delta[delta_slice0]
left = (- big_delta / (combined_delta * delta[delta_slice0]) * f[slice0]
+ combined_delta / (delta[delta_slice0] * delta[delta_slice1]) * f[slice1]

Choose a reason for hiding this comment

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

W503 line break before binary operator

big_delta = combined_delta + delta[delta_slice0]
left = (- big_delta / (combined_delta * delta[delta_slice0]) * f[slice0]
+ combined_delta / (delta[delta_slice0] * delta[delta_slice1]) * f[slice1]
- delta[delta_slice0] / (combined_delta * delta[delta_slice1]) * f[slice2])

Choose a reason for hiding this comment

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

W503 line break before binary operator

combined_delta = delta[delta_slice0] + delta[delta_slice1]
center = 2 * (f[slice0] / (combined_delta * delta[delta_slice0])
- f[slice1] / (delta[delta_slice0] * delta[delta_slice1])
+ f[slice2] / (combined_delta * delta[delta_slice1]))

Choose a reason for hiding this comment

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

W503 line break before binary operator


combined_delta = delta[delta_slice0] + delta[delta_slice1]
left = 2 * (f[slice0] / (combined_delta * delta[delta_slice0])
- f[slice1] / (delta[delta_slice0] * delta[delta_slice1])

Choose a reason for hiding this comment

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

W503 line break before binary operator

combined_delta = delta[delta_slice0] + delta[delta_slice1]
left = 2 * (f[slice0] / (combined_delta * delta[delta_slice0])
- f[slice1] / (delta[delta_slice0] * delta[delta_slice1])
+ f[slice2] / (combined_delta * delta[delta_slice1]))

Choose a reason for hiding this comment

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

W503 line break before binary operator


combined_delta = delta[delta_slice0] + delta[delta_slice1]
right = 2 * (f[slice0] / (combined_delta * delta[delta_slice0])
- f[slice1] / (delta[delta_slice0] * delta[delta_slice1])

Choose a reason for hiding this comment

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

W503 line break before binary operator

combined_delta = delta[delta_slice0] + delta[delta_slice1]
right = 2 * (f[slice0] / (combined_delta * delta[delta_slice0])
- f[slice1] / (delta[delta_slice0] * delta[delta_slice1])
+ f[slice2] / (combined_delta * delta[delta_slice1]))

Choose a reason for hiding this comment

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

W503 line break before binary operator

@dopplershift dopplershift changed the title WIP: Add gradient Add gradient and derivatives for irregular grids Jan 3, 2018
@dopplershift dopplershift added this to the 0.7 milestone Jan 3, 2018
@dopplershift dopplershift force-pushed the gradient branch 2 times, most recently from 16c5524 to 7a6b93f Compare January 3, 2018 08:53
@dopplershift
Copy link
Member Author

Need to find some way to make Code Climate less noisy--but I think we can ignore it here.

Copy link
Contributor

@jrleeman jrleeman 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 things that should probably be at least thought about - overall a huge improvement to our functionality!

return units.Quantity(ret, orig_units[0])
return [units.Quantity(m, u) for m, u in zip(ret, orig_units)]
if orig_units[0] is not None:
return units.Quantity(ret, orig_units[0])
Copy link
Contributor

Choose a reason for hiding this comment

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

Given that we just saw some weirdness in attaching units this way in #619 - maybe go with ret * orig_units[0]

Copy link
Member Author

@dopplershift dopplershift Jan 3, 2018

Choose a reason for hiding this comment

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

Given that I don't understand what went wrong on that one, I'm not inclined to fix it proactively. I'd much prefer to have an actual test case to validate that the new way would be the right one. Either way outside the scope of this PR.

return units.Quantity(ret, orig_units[0])
else:
return ret
return [units.Quantity(m, u) if u is not None else m for m, u in zip(ret, orig_units)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Same

Copy link
Member Author

Choose a reason for hiding this comment

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

see above

return units.Quantity(ret, orig_units[0])
return [units.Quantity(m, u) for m, u in zip(ret, orig_units)]
if orig_units[0] is not None:
return units.Quantity(ret, orig_units[0])
Copy link
Contributor

Choose a reason for hiding this comment

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

same

Copy link
Member Author

Choose a reason for hiding this comment

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

again

return units.Quantity(ret, orig_units[0])
else:
return ret
return [units.Quantity(m, u) if u is not None else m for m, u in zip(ret, orig_units)]
Copy link
Contributor

Choose a reason for hiding this comment

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

same

Copy link
Member Author

Choose a reason for hiding this comment

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

again

n, axis, delta = _process_deriv_args(f, kwargs)

# create slice objects --- initially all are [:, :, ..., :]
slice0 = [slice(None)] * n
Copy link
Contributor

Choose a reason for hiding this comment

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

Given that these 10-ish lines are duplicated in the first derivative, should they be in a helper somewhere? Trivial lines, but more to maintain/bugfix and makes the functions even longer.

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about this, but they each have their own set of 5 slices that would have to be passed in--that seemed like it wouldn't simplify enough to justify.



def _process_gradient_args(kwargs):
"""Handle common processing of arguments for gradient and gradient-like functions."""
Copy link
Contributor

Choose a reason for hiding this comment

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

Should there be an exception raised when a user through accident or mis-understanding passes both instead of just grabbing deltas and ignoring x silently?

Copy link
Member Author

Choose a reason for hiding this comment

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

Fair enough.

if f.shape[axis] < 3:
raise ValueError('f must have at least 3 point along the desired axis.')

if 'delta' in kwargs:
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar question here on silently ignoring an argument if both are passed.

Copy link
Member Author

Choose a reason for hiding this comment

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

ok

return n, axis, delta


def _diff(x, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be in units like atleast_1d and be more publicly accessible? I could see it being handy.

Copy link
Member Author

Choose a reason for hiding this comment

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

Meh, ok.

return dudx - dvdy


@exporter.export
@deprecated('0.7', addendum=' Use stretching_deformation and/or shearing_deformation instead.',
Copy link
Contributor

Choose a reason for hiding this comment

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

Should also probably note in the docs like we do elsewhere:

    .. deprecated:: 0.6.0
      Function has been moved to the Siphon library and will be removed from MetPy in 0.8.0.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I missed that we did that. Will fix.

@jrleeman
Copy link
Contributor

jrleeman commented Jan 3, 2018

Also, deprecations will need to be added to #689 or #690 as appropriate.

Adds first and second derivatives, as well as gradient and laplacian
functions. These work on grids that have uneven spacing between points.
The original, actually shipped name was `h_convergence`, not
`convergence`.
With the new derivative functions, there's no performance benefit to the
combined function. Instead of adding the appropriate named version, just
deprecate the old one entirely in favor of the individual vorticity and
divergence functions.
This will no longer give us any performance benefit, so remove it.
Use our own first_derivative and gradient functions. Some tests needed
to be updated to account for changes to how values at the edge of the
grid are treated.
delta = _broadcast_to_axis(delta, axis, n)
elif 'x' in kwargs:
x = _broadcast_to_axis(kwargs['x'], axis, n)
delta = _diff(x, axis=axis)

Choose a reason for hiding this comment

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

F821 undefined name '_diff'

raise ValueError('Must specify either "x" or "delta" for value positions.')

return n, axis, delta

Choose a reason for hiding this comment

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

W391 blank line at end of file

@dopplershift
Copy link
Member Author

Other issues are already updated for deprecations.

jrleeman
jrleeman previously approved these changes Jan 3, 2018
Copy link
Contributor

@jrleeman jrleeman left a comment

Choose a reason for hiding this comment

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

Looks good!

@dopplershift dopplershift added Area: Calc Pertains to calculations Type: Enhancement Enhancement to existing functionality labels Jan 3, 2018
jrleeman
jrleeman previously approved these changes Jan 3, 2018
jrleeman
jrleeman previously approved these changes Jan 3, 2018
It's just a nice helper for better error messages, so we can just
backport as a noop.
@dopplershift dopplershift merged commit f477fdc into Unidata:master Jan 4, 2018
@dopplershift dopplershift deleted the gradient branch January 4, 2018 00:36
@eric-wieser
Copy link

It'd be great to get something like this into numpy


import functools
import warnings

import numpy as np
try:
from numpy.core.numeric import normalize_axis_index

Choose a reason for hiding this comment

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

Should really be np.core.multiarray instead

Copy link
Member Author

Choose a reason for hiding this comment

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

That makes more sense, good to know. I'll look at a PR to numpy some time when I have some cycles...

Not sure what I'd call the damned thing 😁

Choose a reason for hiding this comment

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

Refactoring np.gradient to use your _first_derivative would be a good first step

Copy link
Member Author

Choose a reason for hiding this comment

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

There's a pretty steep performance penalty (or it would seem from the complexity) for the support for irregular spacing though...

Choose a reason for hiding this comment

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

We already have irregular spacing - all we don't currently support is broadcasting of dx against the values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Calc Pertains to calculations Type: Enhancement Enhancement to existing functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants