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

Method to make multiple animations in one figure #38

Closed
wants to merge 44 commits into from

Conversation

johnomotani
Copy link
Collaborator

Plot multiple variables at once, like

ds.bout.animate_list([n, V, U])

The variables need to be sliced to 1D+1 or 2D+1 first (can mix 1d and 2d in one call though).

@johnomotani johnomotani added the enhancement New feature or request label Jul 17, 2019
@pep8speaks
Copy link

pep8speaks commented Jul 17, 2019

Hello @johnomotani! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

Line 91:1: W293 blank line contains whitespace
Line 107:90: E501 line too long (90 > 89 characters)

Line 42:24: E251 unexpected spaces around keyword / parameter equals
Line 42:26: E251 unexpected spaces around keyword / parameter equals
Line 213:18: E714 test for object identity should be 'is not'

Line 24:49: E127 continuation line over-indented for visual indent

Line 69:54: E127 continuation line over-indented for visual indent
Line 80:59: E127 continuation line over-indented for visual indent
Line 91:54: E127 continuation line over-indented for visual indent
Line 92:53: E127 continuation line over-indented for visual indent
Line 103:54: E127 continuation line over-indented for visual indent
Line 104:53: E127 continuation line over-indented for visual indent
Line 115:54: E127 continuation line over-indented for visual indent
Line 116:53: E127 continuation line over-indented for visual indent
Line 128:54: E127 continuation line over-indented for visual indent
Line 129:53: E127 continuation line over-indented for visual indent
Line 140:54: E127 continuation line over-indented for visual indent
Line 141:53: E127 continuation line over-indented for visual indent
Line 153:58: E127 continuation line over-indented for visual indent
Line 154:57: E127 continuation line over-indented for visual indent
Line 186:57: E127 continuation line over-indented for visual indent
Line 187:56: E127 continuation line over-indented for visual indent
Line 198:54: E127 continuation line over-indented for visual indent
Line 199:53: E127 continuation line over-indented for visual indent
Line 211:54: E127 continuation line over-indented for visual indent
Line 212:54: E127 continuation line over-indented for visual indent
Line 223:54: E127 continuation line over-indented for visual indent
Line 224:53: E127 continuation line over-indented for visual indent
Line 235:54: E127 continuation line over-indented for visual indent
Line 236:54: E127 continuation line over-indented for visual indent
Line 247:54: E127 continuation line over-indented for visual indent
Line 248:53: E127 continuation line over-indented for visual indent
Line 259:54: E127 continuation line over-indented for visual indent
Line 260:53: E127 continuation line over-indented for visual indent
Line 271:54: E127 continuation line over-indented for visual indent
Line 272:53: E127 continuation line over-indented for visual indent
Line 283:54: E127 continuation line over-indented for visual indent
Line 284:53: E127 continuation line over-indented for visual indent

Comment last updated at 2019-12-10 12:37:44 UTC

@codecov-io
Copy link

codecov-io commented Jul 17, 2019

Codecov Report

Merging #38 into master will increase coverage by 4.14%.
The diff coverage is 35.8%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #38      +/-   ##
==========================================
+ Coverage   43.47%   47.61%   +4.14%     
==========================================
  Files           8        8              
  Lines         759     1134     +375     
  Branches      144      255     +111     
==========================================
+ Hits          330      540     +210     
- Misses        375      535     +160     
- Partials       54       59       +5
Impacted Files Coverage Δ
xbout/plotting/utils.py 6.93% <11.53%> (+2.01%) ⬆️
xbout/plotting/animate.py 43.93% <24.61%> (-10.94%) ⬇️
xbout/plotting/plotfuncs.py 7.47% <5.71%> (-1.86%) ⬇️
xbout/boutdataarray.py 62.96% <52.94%> (-8.47%) ⬇️
xbout/boutdataset.py 63.7% <81.66%> (+16.83%) ⬆️
xbout/load.py 82.94% <0%> (+5.4%) ⬆️
xbout/geometries.py 79.82% <0%> (+7.2%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update befe417...dba0831. Read the comment docs.

@johnomotani
Copy link
Collaborator Author

Sorry, I made this PR too soon, should have tried out the new method more first. I've rebased on the current version of 1d-animations now, and fixed a few issues with the original code at the same time.

Previously contained 'animate over <>', but the variable being animated
over is shown on the time-slider anyway. Shortening the titles makes
things clearer when multiple plots are animated at once.
Copy link
Collaborator

@TomNicholas TomNicholas left a comment

Choose a reason for hiding this comment

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

This is cool. Have you tried running it with real data? I'm a bit concerned that there might be bugs upstream in animatplot.imshow. Also we should think about how the 2D animations will combine with #34.

xbout/boutdataset.py Outdated Show resolved Hide resolved
xbout/boutdataset.py Show resolved Hide resolved
@@ -7,7 +7,7 @@


def animate_imshow(data, animate_over='t', x='x', y='y', animate=True,
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's likely that we shouldn't actually be using imshow, because it's restricted to square images right? Though I originally wrote this for slab cross-sections, then really this should use pcolormesh so that it can handle any geometry? Integrating it with the multi-region plotting method used in #34 will add a bit of complexity (though I think the blocks abstraction will mean it's not too hard).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Think I agree with all of this, although it does need a bit of thinking... Having poloidal plots with geometry included is (very, very) nice to have as an option (maybe even default), but having cartesian as an option too is also useful - for example poloidal plotting of some arbitrary slice is going to be a bit tricky*; also if there are some very small or distorted cells they may be easier to see in a plot that's just rectangular. The blocks abstraction does very much help with all of this, we can add over time functions that create a block in various ways, and code like this that sticks blocks together would only need to add some logic to say which function to call.

It probably would be sensible to use pcolormesh instead of imshow everywhere though, since it's much more flexible.

--

  • Maybe it would be useful to introduce some 'coordinates' that say which region a point is, e.g. xregion where xregion=0 for x<ixseps1, xregion=1 for ixseps1<=x<ixseps2, xregion=2 for x>=ixseps2 [note ixseps1/ixseps2 include the x-boundary cells, so need to subtract MXG if x does not include the boundary cells]. Hopefully this can be done in such a way that if you do something like n.isel(x=slice(3,7,1) then xregion would get sliced as well. Then we could select regions to join with xregion and a yregion (defined using jyseps*)?

@johnomotani
Copy link
Collaborator Author

This is cool. Have you tried running it with real data? I'm a bit concerned that there might be bugs upstream in animatplot.imshow. Also we should think about how the 2D animations will combine with #34.

I have been using it, and it works. Animations sometimes seem to freeze in a jupyter notebook, but I've not worked out how to reproduce yet. It might also be an issue with my setup, I'm currently having problems with matplotlib in ipython, where pyplot.show() is not blocking. Need to spend more time observing when I have problems before trying seriously to debug...

@johnomotani johnomotani changed the title Method to make multiple animations in one figure WIP: Method to make multiple animations in one figure Aug 10, 2019
@johnomotani johnomotani changed the base branch from 1d-animations to master October 25, 2019 17:16
@johnomotani johnomotani dismissed TomNicholas’s stale review December 5, 2019 18:42

Has been updated to use animatplot.Pcolormesh instead of animatplot.Imshow. Also to be compatible with multi-region plotting from #34 and #50.

'norm' option allows any matplotlib.colors.Normalize instance to be
passed, for general control of the color-scale.

For convenience, also adds a 'logscale' option which can be set to True
to create a standard log-scale if vmin and vmax are both positive or
both negative, and a symmetric log-scale with a linear threshold of
min(abs(vmin),abs(vmax))*1.e-5 if vmin and vmax have opposite signs. If
a float is passed for 'logscale' and vmin and vmax have opposite signs
then the value of 'logscale' is used instead of the default 1.e-5 to set
the linear threshold.
xbout/plotting/animate.py Outdated Show resolved Hide resolved
xbout/plotting/animate.py Outdated Show resolved Hide resolved
xbout/plotting/animate.py Outdated Show resolved Hide resolved
Had been mixed up in an automatic merge.
@rdoyle45
Copy link
Collaborator

rdoyle45 commented Dec 8, 2019

Looks good to me. Did a few animations, no issues, worked well.

Copy link
Collaborator

@TomNicholas TomNicholas left a comment

Choose a reason for hiding this comment

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

This is a big PR! I've made some comments throughout. I think this is complicated enough that it will take multiple PRs to iron out any bugs and so on

@@ -41,26 +41,29 @@ def _decompose_regions(da):
j11, j12, j21, j22, ix1, ix2, nin, _, ny, y_boundary_guards = _get_seps(da)
regions = []

x, y = da.dims[-2:]
Copy link
Collaborator

Choose a reason for hiding this comment

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

#68 is merged now so we can replace this with

x = da.attrs['bout_xdim']
y = da.attrs['bout_ydim']

ystart = 0 # Y index to start the next section
if j11 >= 0:
# plot lower inner leg
region1 = da[:, ystart:(j11 + 1)]
region1 = da.isel(**{y: slice(ystart, (j11 + 1))})
Copy link
Collaborator

Choose a reason for hiding this comment

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

The keyword argument unpacking can be replaced

region1 = da.isel(y=slice(ystart, (j11 + 1)))

print("{} data passed has {} dimensions - making poloidal plot with "
"animate_poloidal()".format(variable, str(n_dims)))
if x is not None:
kwargs['x'] = x
Copy link
Collaborator

@TomNicholas TomNicholas Dec 10, 2019

Choose a reason for hiding this comment

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

Should this now default to ds.attrs['bout_xdim']?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think so. Leaving the default as None passes off setting defaults to the called methods, so lets animate_poloidal use (R,Z) and animate_pcolormesh use da.dims.

Comment on lines 230 to 235
try:
if len(poloidal_plot) != len(variables):
raise ValueError('if poloidal_plot is a sequence, it must have the same '
'number of elements as "variables"')
except TypeError:
poloidal_plot = [poloidal_plot] * len(variables)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This will behave weirdly if you get a TypeError that wasn't the one you were expecting. Perhaps instead we should do

if isinstance(poloidal_plot, bool):
    poloidal_plot = [poloidal_plot] * len(variables)
elif len(poloidal_plot) != len(variables)
    raise ValueError('if poloidal_plot is a sequence, it must have the same '
                     'number of elements as "variables"')

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is used 4 times, so could even refactor into a function:

def _expand_arg_list(sequence, arg_name, num_vars):
    if isinstance(sequence, bool):
        sequence = [sequence] * num_vars
    elif len(sequence) != num_vars
        raise ValueError(f'if {arg_name} is a sequence, it must have the same '
                         'number of elements as "variables"')
    return sequence

poloidal_plot = _expand_arg_list(poloidal_plot, 'poloidal_plot', len(variables))
vmin = _expand_arg_list(vmin, 'vmin', len(variables))
vmax = _expand_arg_list(vmax, 'vmax', len(variables))
logscale = _expand_arg_list(logscale, 'logscale', len(variables))

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just as somewhere to note this: to test for a sequence, as suggested on https://stackoverflow.com/questions/2937114/python-check-if-an-object-is-a-sequence, probably best pattern is to use isinstance(variable, collections.Sequence).

logscale = [logscale] * len(variables)

blocks = []
for this in zip(variables, axes.flatten(), poloidal_plot, vmin, vmax, logscale):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's not use this as a variable name 🙏

(subplot?)

Comment on lines 64 to 66
gridlines : bool or int, optional
If True, draw grid lines on the plot. If an int is passed, it is used as the
stride when plotting grid lines (to reduce the number on the plot)
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would have been good to separate these into a different PR but no matter

if gridlines is not None:
if gridlines is True:
gridlines = (1, 1)
if not isinstance(gridlines, collections.Sequence):
Copy link
Collaborator

Choose a reason for hiding this comment

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

collections.sequence seems an odd choice of container class? We just need to store either one or two ints right? Why not just a short list? Or even a dictionary?

gridlines= {'x': 1, 'y':1}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, this was written quickly. Restricting to int, slice or dict of int or slice is better.

Z_regions = _decompose_regions(da['Z'])

for R, Z in zip(R_regions, Z_regions):
plt.plot(R[::gridlines[0], :].T, Z[::gridlines[0], :].T, color='k', lw=0.1)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Using a dictionary would eliminate the "magic numbers" 0 and 1 here

assert isinstance(animations.blocks[1], Pcolormesh)
assert isinstance(animations.blocks[2], Line)

def test_animate_list_fps(self, create_test_file):
Copy link
Collaborator

Choose a reason for hiding this comment

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

This isn't testing that the fps was actually set. Should do

assert animations.fps == fps

Copy link
Collaborator

Choose a reason for hiding this comment

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

(also to be really nitpicky the animatplot logic is that you have created one animation (singular) which consists of multiple blocks (plural))

@johnomotani
Copy link
Collaborator Author

@TomNicholas

This is a big PR!

It is, sorry! Do you want me to try and split it up?

@TomNicholas
Copy link
Collaborator

It is, sorry! Do you want me to try and split it up?

That might be a good idea - there is at least one clear division: the new arguments to plot2d_wrapper can be their own PR (so gridlines etc. can be discussed elsewhere).

@johnomotani
Copy link
Collaborator Author

Made some updates to address @TomNicholas's review. Will try to split PR now.

@johnomotani
Copy link
Collaborator Author

Replaced by #71, #72 and #73.

@johnomotani johnomotani deleted the animate_list branch December 10, 2019 23:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants