From 71faff34e543df663dd00738679c2574c58f7076 Mon Sep 17 00:00:00 2001 From: loeiten Date: Fri, 24 May 2019 08:51:28 +0200 Subject: [PATCH 01/98] first commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c9b6e7a --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# boututils From 2209dda198b5e21bacd1c721411ea5f75afb46b4 Mon Sep 17 00:00:00 2001 From: loeiten Date: Fri, 24 May 2019 09:30:02 +0200 Subject: [PATCH 02/98] Added files --- .gitignore | 129 +++++ LICENSE | 165 ++++++ boututils/View3D.py | 390 ++++++++++++++ boututils/__init__.py | 39 ++ boututils/analyse_equil_2.py | 272 ++++++++++ boututils/anim.py | 115 ++++ boututils/ask.py | 53 ++ boututils/boutarray.py | 73 +++ boututils/boutgrid.py | 139 +++++ boututils/boutwarnings.py | 19 + boututils/calculus.py | 252 +++++++++ boututils/check_scaling.py | 91 ++++ boututils/closest_line.py | 14 + boututils/contour.py | 82 +++ boututils/crosslines.py | 197 +++++++ boututils/datafile.py | 929 +++++++++++++++++++++++++++++++++ boututils/efit_analyzer.py | 420 +++++++++++++++ boututils/fft_deriv.py | 46 ++ boututils/fft_integrate.py | 64 +++ boututils/file_import.py | 27 + boututils/geqdsk.py | 223 ++++++++ boututils/idl_tabulate.py | 15 + boututils/int_func.py | 53 ++ boututils/linear_regression.py | 27 + boututils/local_min_max.py | 69 +++ boututils/mode_structure.py | 417 +++++++++++++++ boututils/moment_xyzt.py | 80 +++ boututils/options.py | 165 ++++++ boututils/plotdata.py | 90 ++++ boututils/plotpolslice.py | 143 +++++ boututils/radial_grid.py | 68 +++ boututils/read_geqdsk.py | 92 ++++ boututils/run_wrapper.py | 279 ++++++++++ boututils/showdata.py | 702 +++++++++++++++++++++++++ boututils/spectrogram.py | 163 ++++++ boututils/surface_average.py | 103 ++++ boututils/volume_integral.py | 107 ++++ boututils/watch.py | 84 +++ requirements.txt | 8 + 39 files changed, 6404 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 boututils/View3D.py create mode 100644 boututils/__init__.py create mode 100644 boututils/analyse_equil_2.py create mode 100755 boututils/anim.py create mode 100644 boututils/ask.py create mode 100644 boututils/boutarray.py create mode 100755 boututils/boutgrid.py create mode 100644 boututils/boutwarnings.py create mode 100644 boututils/calculus.py create mode 100644 boututils/check_scaling.py create mode 100644 boututils/closest_line.py create mode 100644 boututils/contour.py create mode 100644 boututils/crosslines.py create mode 100644 boututils/datafile.py create mode 100644 boututils/efit_analyzer.py create mode 100644 boututils/fft_deriv.py create mode 100644 boututils/fft_integrate.py create mode 100644 boututils/file_import.py create mode 100755 boututils/geqdsk.py create mode 100644 boututils/idl_tabulate.py create mode 100644 boututils/int_func.py create mode 100644 boututils/linear_regression.py create mode 100644 boututils/local_min_max.py create mode 100644 boututils/mode_structure.py create mode 100644 boututils/moment_xyzt.py create mode 100644 boututils/options.py create mode 100644 boututils/plotdata.py create mode 100644 boututils/plotpolslice.py create mode 100644 boututils/radial_grid.py create mode 100644 boututils/read_geqdsk.py create mode 100644 boututils/run_wrapper.py create mode 100644 boututils/showdata.py create mode 100644 boututils/spectrogram.py create mode 100644 boututils/surface_average.py create mode 100644 boututils/volume_integral.py create mode 100644 boututils/watch.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee0ca72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Other +.DS_Store +.idea/ +*.sw[po] + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cca7fc2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/boututils/View3D.py b/boututils/View3D.py new file mode 100644 index 0000000..3c50950 --- /dev/null +++ b/boututils/View3D.py @@ -0,0 +1,390 @@ +""" +View a 3D rendering of the magnetic field lines and the streamlines of the rational surfaces. +The quality of the later can be used as an indicator of the quality of the grid. The magnetic field +is computed from efit_analyzed.py. The script can be used as a template to show additional properties of the field + +based on enthought's example by Gael Varoquaux +http://docs.enthought.com/mayavi/mayavi/auto/example_magnetic_field.html#example-magnetic-field + +""" +from __future__ import absolute_import +from __future__ import division +from builtins import range +from past.utils import old_div + + +from boutdata.collect import collect +import numpy as np + +import sys + +if sys.version_info[0]>=3: + message = "View3D uses the VTK library through mayavi, which"+\ + " is currently only available in python 2" + raise ImportError(message) +else: + from mayavi import mlab + +from .read_geqdsk import read_geqdsk +from boututils.View2D import View2D +from scipy import interpolate +from .boutgrid import * + + +def View3D(g,path=None, gb=None): + ############################################################################## + # Resolution + + n=51 + + #compute Bxy + [Br,Bz,x,y,q]=View2D(g,option=1) + + + rd=g.r.max()+.5 + zd=g.z.max()+.5 + ############################################################################## + # The grid of points on which we want to evaluate the field + X, Y, Z = np.mgrid[-rd:rd:n*1j, -rd:rd:n*1j, -zd:zd:n*1j] + ## Avoid rounding issues : + #f = 1e4 # this gives the precision we are interested by : + #X = np.round(X * f) / f + #Y = np.round(Y * f) / f + #Z = np.round(Z * f) / f + + r = np.c_[X.ravel(), Y.ravel(), Z.ravel()] + + ############################################################################## + # Calculate field + # First initialize a container matrix for the field vector : + B = np.empty_like(r) + + + #Compute Toroidal field + # fpol is given between simagx (psi on the axis) and sibdry ( + # psi on limiter or separatrix). So the toroidal field (fpol/R) and the q profile are within these boundaries + # For each r,z we have psi thus we get fpol if (r,z) is within the boundary (limiter or separatrix) and fpol=fpol(outer_boundary) for outside + + #The range of psi is g.psi.max(), g.psi.min() but we have f(psi) up to the limit. Thus we use a new extended variable padded up to max psi + # set points between psi_limit and psi_max + + add_psi=np.linspace(g.sibdry,g.psi.max(),10) + + # define the x (psi) array + xf=np.arange(np.float(g.qpsi.size))*(g.sibdry-g.simagx)/np.float(g.qpsi.size-1) + g.simagx + + # pad the extra values excluding the 1st value + + xf=np.concatenate((xf, add_psi[1::]), axis=0) + + # pad fpol with corresponding points + + fp=np.lib.pad(g.fpol, (0,9), 'edge') + + # create interpolating function + + f = interpolate.interp1d(xf, fp) + + #calculate Toroidal field + + Btrz = old_div(f(g.psi), g.r) + + + rmin=g.r[:,0].min() + rmax=g.r[:,0].max() + zmin=g.z[0,:].min() + zmax=g.z[0,:].max() + + + B1p,B2p,B3p,B1t,B2t,B3t = magnetic_field(g,X,Y,Z,rmin,rmax,zmin,zmax, Br,Bz,Btrz) + + bpnorm = np.sqrt(B1p**2 + B2p**2 + B3p**2) + btnorm = np.sqrt(B1t**2 + B2t**2 + B3t**2) + + BBx=B1p+B1t + BBy=B2p+B2t + BBz=B3p+B3t + btotal = np.sqrt(BBx**2 + BBy**2 + BBz**2) + + Psi = psi_field(g,X,Y,Z,rmin,rmax,zmin,zmax) + + ############################################################################## + # Visualization + + # We threshold the data ourselves, as the threshold filter produce a + # data structure inefficient with IsoSurface + #bmax = bnorm.max() + # + #B1[B > bmax] = 0 + #B2[B > bmax] = 0 + #B3[B > bmax] = 0 + #bnorm[bnorm > bmax] = bmax + + mlab.figure(1, size=(1080,1080))#, bgcolor=(1, 1, 1), fgcolor=(0.5, 0.5, 0.5)) + + mlab.clf() + + fieldp = mlab.pipeline.vector_field(X, Y, Z, B1p, B2p, B3p, + scalars=bpnorm, name='Bp field') + + fieldt = mlab.pipeline.vector_field(X, Y, Z, B1t, B2t, B3t, + scalars=btnorm, name='Bt field') + + field = mlab.pipeline.vector_field(X, Y, Z, BBx, BBy, BBz, + scalars=btotal, name='B field') + + + + field2 = mlab.pipeline.scalar_field(X, Y, Z, Psi, name='Psi field') + + #vectors = mlab.pipeline.vectors(field, + # scale_factor=1,#(X[1, 0, 0] - X[0, 0, 0]), + # ) + + #vcp1 = mlab.pipeline.vector_cut_plane(fieldp, + # scale_factor=1, + # colormap='jet', + # plane_orientation='y_axes') + ## + #vcp2 = mlab.pipeline.vector_cut_plane(fieldt, + # scale_factor=1, + # colormap='jet', + # plane_orientation='x_axes') + + + # Mask random points, to have a lighter visualization. + #vectors.glyph.mask_input_points = True + #vectors.glyph.mask_points.on_ratio = 6 + + #vcp = mlab.pipeline.vector_cut_plane(field1) + #vcp.glyph.glyph.scale_factor=5*(X[1, 0, 0] - X[0, 0, 0]) + # For prettier picture: + #vcp1.implicit_plane.widget.enabled = False + #vcp2.implicit_plane.widget.enabled = False + + iso = mlab.pipeline.iso_surface(field2, + contours=[Psi.min()+.01], + opacity=0.4, + colormap='bone') + + for i in range(q.size): + iso.contour.contours[i+1:i+2]=[q[i]] + + iso.compute_normals = True + # + + #mlab.pipeline.image_plane_widget(field2, + # plane_orientation='x_axes', + # #slice_index=10, + # extent=[-rd, rd, -rd, rd, -zd,zd] + # ) + #mlab.pipeline.image_plane_widget(field2, + # plane_orientation='y_axes', + # # slice_index=10, + # extent=[-rd, rd, -rd,rd, -zd,zd] + # ) + + + + #scp = mlab.pipeline.scalar_cut_plane(field2, + # colormap='jet', + # plane_orientation='x_axes') + # For prettier picture and with 2D streamlines: + #scp.implicit_plane.widget.enabled = False + #scp.enable_contours = True + #scp.contour.number_of_contours = 20 + + # + + # Magnetic Axis + + s=mlab.pipeline.streamline(field) + s.streamline_type = 'line' + s.seed.widget = s.seed.widget_list[3] + s.seed.widget.position=[g.rmagx,0.,g.zmagx] + s.seed.widget.enabled = False + + + # q=i surfaces + + for i in range(np.shape(x)[0]): + + s=mlab.pipeline.streamline(field) + s.streamline_type = 'line' + ##s.seed.widget = s.seed.widget_list[0] + ##s.seed.widget.center = 0.0, 0.0, 0.0 + ##s.seed.widget.radius = 1.725 + ##s.seed.widget.phi_resolution = 16 + ##s.seed.widget.handle_direction =[ 1., 0., 0.] + ##s.seed.widget.enabled = False + ##s.seed.widget.enabled = True + ##s.seed.widget.enabled = False + # + if x[i].size>1 : + s.seed.widget = s.seed.widget_list[3] + s.seed.widget.position=[x[i][0],0.,y[i][0]] + s.seed.widget.enabled = False + + + # A trick to make transparency look better: cull the front face + iso.actor.property.frontface_culling = True + + #mlab.view(39, 74, 0.59, [.008, .0007, -.005]) + out=mlab.outline(extent=[-rd, rd, -rd, rd, -zd, zd], line_width=.5 ) + out.outline_mode = 'cornered' + out.outline_filter.corner_factor = 0.0897222 + + + w = mlab.gcf() + w.scene.camera.position = [13.296429046581462, 13.296429046581462, 12.979811259697154] + w.scene.camera.focal_point = [0.0, 0.0, -0.31661778688430786] + w.scene.camera.view_angle = 30.0 + w.scene.camera.view_up = [0.0, 0.0, 1.0] + w.scene.camera.clipping_range = [13.220595435695394, 35.020427055647517] + w.scene.camera.compute_view_plane_normal() + w.scene.render() + w.scene.show_axes = True + + mlab.show() + + if(path is not None): + #BOUT data + #path='../Aiba/' + # + #gb = file_import(path+'aiba.bout.grd.nc') + #gb = file_import("../cbm18_8_y064_x516_090309.nc") + #gb = file_import("cbm18_dens8.grid_nx68ny64.nc") + #gb = file_import("/home/ben/run4/reduced_y064_x256.nc") + + data = collect('P', path=path) + data = data[50,:,:,:] + #data0=collect("P0", path=path) + #data=data+data0[:,:,None] + + s = np.shape(data) + nz = s[2] + + + sgrid = create_grid(gb, data, 1) + + # OVERPLOT the GRID + #mlab.pipeline.add_dataset(sgrid) + #gr=mlab.pipeline.grid_plane(sgrid) + #gr.grid_plane.axis='x' + + + ## pressure scalar cut plane from bout + scpb = mlab.pipeline.scalar_cut_plane(sgrid, + colormap='jet', + plane_orientation='x_axes') + + scpb.implicit_plane.widget.enabled = False + scpb.enable_contours = True + scpb.contour.filled_contours=True + # + scpb.contour.number_of_contours = 20 + # + # + #loc=sgrid.points + #p=sgrid.point_data.scalars + + # compute pressure from scatter points interpolation + #pint=interpolate.griddata(loc, p, (X, Y, Z), method='linear') + #dpint=np.ma.masked_array(pint,np.isnan(pint)).filled(0.) + # + #p2 = mlab.pipeline.scalar_field(X, Y, Z, dpint, name='P field') + # + #scp2 = mlab.pipeline.scalar_cut_plane(p2, + # colormap='jet', + # plane_orientation='y_axes') + # + #scp2.implicit_plane.widget.enabled = False + #scp2.enable_contours = True + #scp2.contour.filled_contours=True + #scp2.contour.number_of_contours = 20 + #scp2.contour.minimum_contour=.001 + + + + # CHECK grid orientation + #fieldr = mlab.pipeline.vector_field(X, Y, Z, -BBx, BBy, BBz, + # scalars=btotal, name='B field') + # + #sg=mlab.pipeline.streamline(fieldr) + #sg.streamline_type = 'tube' + #sg.seed.widget = sg.seed.widget_list[3] + #sg.seed.widget.position=loc[0] + #sg.seed.widget.enabled = False + + + + #OUTPUT grid + + #ww = tvtk.XMLStructuredGridWriter(input=sgrid, file_name='sgrid.vts') + #ww.write() + + return + +def magnetic_field(g,X,Y,Z,rmin,rmax,zmin,zmax,Br,Bz,Btrz): + + rho = np.sqrt(X**2 + Y**2) + phi=np.arctan2(Y,X) + + br=np.zeros(np.shape(X)) + bz=np.zeros(np.shape(X)) + bt=np.zeros(np.shape(X)) + + nx,ny,nz=np.shape(X) + + mask = (rho >= rmin) & (rho <= rmax) & (Z >= zmin) & (Z <= zmax) + k=np.argwhere(mask==True) + + fr=interpolate.interp2d(g.r[:,0], g.z[0,:], Br.T) + fz=interpolate.interp2d(g.r[:,0], g.z[0,:], Bz.T) + ft=interpolate.interp2d(g.r[:,0], g.z[0,:], Btrz.T) + + for i in range(len(k)): + br[k[i,0],k[i,1],k[i,2]]=fr(rho[k[i,0],k[i,1],k[i,2]],Z[k[i,0],k[i,1],k[i,2]]) + bz[k[i,0],k[i,1],k[i,2]]=fz(rho[k[i,0],k[i,1],k[i,2]],Z[k[i,0],k[i,1],k[i,2]]) + bt[k[i,0],k[i,1],k[i,2]]=ft(rho[k[i,0],k[i,1],k[i,2]],Z[k[i,0],k[i,1],k[i,2]]) + + # Toroidal component + B1t=-bt*np.sin(phi) + B2t=bt*np.cos(phi) + B3t=0*bz + + # Poloidal component + B1p=br*np.cos(phi) + B2p=br*np.sin(phi) + B3p=bz + + + # Rotate the field back in the lab's frame + return B1p,B2p,B3p,B1t,B2t,B3t + + +def psi_field(g,X,Y,Z,rmin,rmax,zmin,zmax): + + rho = np.sqrt(X**2 + Y**2) + + psi=np.zeros(np.shape(X)) + + nx,ny,nz=np.shape(X) + + mask = (rho >= rmin) & (rho <= rmax) & (Z >= zmin) & (Z <= zmax) + k=np.argwhere(mask==True) + + f=interpolate.interp2d(g.r[:,0], g.z[0,:], g.psi.T) + + for i in range(len(k)): + psi[k[i,0],k[i,1],k[i,2]]=f(rho[k[i,0],k[i,1],k[i,2]],Z[k[i,0],k[i,1],k[i,2]]) + + # Rotate the field back in the lab's frame + return psi + + +if __name__ == '__main__': + path='../../tokamak_grids/pyGridGen/' + g=read_geqdsk(path+"g118898.03400") + View3D(g) + mlab.show() diff --git a/boututils/__init__.py b/boututils/__init__.py new file mode 100644 index 0000000..f815d3e --- /dev/null +++ b/boututils/__init__.py @@ -0,0 +1,39 @@ +""" Generic routines, useful for all data """ + +import sys + +try: + from builtins import str +except ImportError: + raise ImportError("Please install the future module to use Python 2") + +# Modules to be imported independent of version +for_all_versions = [\ + 'calculus',\ + 'closest_line',\ + 'datafile',\ + # 'efit_analyzer',\ # bunch pkg required + 'fft_deriv',\ + 'fft_integrate',\ + 'file_import',\ + 'int_func',\ + 'linear_regression',\ + 'mode_structure',\ + # 'moment_xyzt',\ # bunch pkg requried + 'run_wrapper',\ + 'shell',\ + 'showdata',\ + # 'surface_average',\ + # 'volume_integral',\ #bunch pkg required + ] + +# Check the current python version +if sys.version_info[0]>=3: + do_import = for_all_versions + __all__ = do_import +else: + do_import = for_all_versions + do_import.append('anim') + do_import.append('plotpolslice') + do_import.append('View3D') + __all__ = do_import diff --git a/boututils/analyse_equil_2.py b/boututils/analyse_equil_2.py new file mode 100644 index 0000000..93b98fc --- /dev/null +++ b/boututils/analyse_equil_2.py @@ -0,0 +1,272 @@ +"""Equilibrium analysis routine + +Takes a RZ psi grid, and finds x-points and o-points +""" + +from __future__ import print_function +from __future__ import division + +from builtins import zip +from builtins import str +from builtins import range +from past.utils import old_div + +import numpy +from bunch import Bunch +from . import local_min_max +from scipy.interpolate import RectBivariateSpline +from matplotlib.pyplot import contour, gradient, annotate, plot, draw +from crosslines import find_inter + + +def analyse_equil(F, R, Z): + """Takes an RZ psi grid, and finds x-points and o-points + + Parameters + ---------- + F : array_like + 2-D array of psi values + R : array_like + 1-D array of major radii, its length should be the same as the + first dimension of F + Z : array_like + 1-D array of heights, its length should be the same as the + second dimension of F + + Returns + ------- + bunch + A structure of critical points containing: + + n_opoint, n_xpoint - Number of O- and X-points + primary_opt - Index of plasma centre O-point + inner_sep - X-point index of inner separatrix + opt_ri, opt_zi - R and Z indices for each O-point + opt_f - Psi value at each O-point + xpt_ri, xpt_zi - R and Z indices for each X-point + xpt_f - Psi value of each X-point + + """ + s = numpy.shape(F) + nx = s[0] + ny = s[1] + + #;;;;;;;;;;;;;;; Find critical points ;;;;;;;;;;;;; + # + # Need to find starting locations for O-points (minima/maxima) + # and X-points (saddle points) + # + Rr=numpy.tile(R,nx).reshape(nx,ny).T + Zz=numpy.tile(Z,ny).reshape(nx,ny) + + contour1=contour(Rr,Zz,gradient(F)[0], levels=[0.0], colors='r') + contour2=contour(Rr,Zz,gradient(F)[1], levels=[0.0], colors='r') + + draw() + + +### 1st method - line crossings --------------------------- + res=find_inter( contour1, contour2) + + #rex1=numpy.interp(res[0], R, numpy.arange(R.size)).astype(int) + #zex1=numpy.interp(res[1], Z, numpy.arange(Z.size)).astype(int) + + rex1=res[0] + zex1=res[1] + + w=numpy.where((rex1 > R[2]) & (rex1 < R[nx-3]) & (zex1 > Z[2]) & (zex1 < Z[nx-3])) + nextrema = numpy.size(w) + rex1=rex1[w].flatten() + zex1=zex1[w].flatten() + + +### 2nd method - local maxima_minima ----------------------- + res1=local_min_max.detect_local_minima(F) + res2=local_min_max.detect_local_maxima(F) + res=numpy.append(res1,res2,1) + + rex2=res[0,:].flatten() + zex2=res[1,:].flatten() + + + w=numpy.where((rex2 > 2) & (rex2 < nx-3) & (zex2 >2) & (zex2 < nx-3)) + nextrema = numpy.size(w) + rex2=rex2[w].flatten() + zex2=zex2[w].flatten() + + + n_opoint=nextrema + n_xpoint=numpy.size(rex1)-n_opoint + + # Needed for interp below + + Rx=numpy.arange(numpy.size(R)) + Zx=numpy.arange(numpy.size(Z)) + + + + print("Number of O-points: "+numpy.str(n_opoint)) + print("Number of X-points: "+numpy.str(n_xpoint)) + + # Deduce the O & X points + + x=R[rex2] + y=Z[zex2] + + dr=old_div((R[numpy.size(R)-1]-R[0]),numpy.size(R)) + dz=old_div((Z[numpy.size(Z)-1]-Z[0]),numpy.size(Z)) + + + repeated=set() + for i in range(numpy.size(rex1)): + for j in range(numpy.size(x)): + if numpy.abs(rex1[i]-x[j]) < 2*dr and numpy.abs(zex1[i]-y[j]) < 2*dz : repeated.add(i) + + # o-points + + o_ri=numpy.take(rex1,numpy.array(list(repeated))) + opt_ri=numpy.interp(o_ri,R,Rx) + o_zi=numpy.take(zex1,numpy.array(list(repeated))) + opt_zi=numpy.interp(o_zi,Z,Zx) + opt_f=numpy.zeros(numpy.size(opt_ri)) + func = RectBivariateSpline(Rx, Zx, F) + for i in range(numpy.size(opt_ri)): opt_f[i]=func(opt_ri[i], opt_zi[i]) + + n_opoint=numpy.size(opt_ri) + + # x-points + + x_ri=numpy.delete(rex1, numpy.array(list(repeated))) + xpt_ri=numpy.interp(x_ri,R,Rx) + x_zi=numpy.delete(zex1, numpy.array(list(repeated))) + xpt_zi=numpy.interp(x_zi,Z,Zx) + xpt_f=numpy.zeros(numpy.size(xpt_ri)) + func = RectBivariateSpline(Rx, Zx, F) + for i in range(numpy.size(xpt_ri)): xpt_f[i]=func(xpt_ri[i], xpt_zi[i]) + + n_xpoint=numpy.size(xpt_ri) + + # plot o-points + + plot(o_ri,o_zi,'o', markersize=10) + + labels = ['{0}'.format(i) for i in range(o_ri.size)] + for label, xp, yp in zip(labels, o_ri, o_zi): + annotate(label, xy = (xp, yp), xytext = (10, 10), textcoords = 'offset points',size='large', color='b') + + draw() + + # plot x-points + + plot(x_ri,x_zi,'x', markersize=10) + + labels = ['{0}'.format(i) for i in range(x_ri.size)] + for label, xp, yp in zip(labels, x_ri, x_zi): + annotate(label, xy = (xp, yp), xytext = (10, 10), textcoords = 'offset points',size='large', color='r') + + draw() + + print("Number of O-points: "+str(n_opoint)) + + if n_opoint == 0 : + print("No O-points! Giving up on this equilibrium") + return Bunch(n_opoint=0, n_xpoint=0, primary_opt=-1) + + + #;;;;;;;;;;;;;; Find plasma centre ;;;;;;;;;;;;;;;;;;; + # Find the O-point closest to the middle of the grid + + mind = (opt_ri[0] - (old_div(numpy.float(nx),2.)))**2 + (opt_zi[0] - (old_div(numpy.float(ny),2.)))**2 + ind = 0 + for i in range (1, n_opoint) : + d = (opt_ri[i] - (old_div(numpy.float(nx),2.)))**2 + (opt_zi[i] - (old_div(numpy.float(ny),2.)))**2 + if d < mind : + ind = i + mind = d + + primary_opt = ind + print("Primary O-point is at "+ numpy.str(numpy.interp(opt_ri[ind],numpy.arange(numpy.size(R)),R)) + ", " + numpy.str(numpy.interp(opt_zi[ind],numpy.arange(numpy.size(Z)),Z))) + print("") + + if n_xpoint > 0 : + + # Find the primary separatrix + + # First remove non-monotonic separatrices + nkeep = 0 + for i in range (n_xpoint) : + # Draw a line between the O-point and X-point + + n = 100 # Number of points + farr = numpy.zeros(n) + dr = old_div((xpt_ri[i] - opt_ri[ind]), numpy.float(n)) + dz = old_div((xpt_zi[i] - opt_zi[ind]), numpy.float(n)) + for j in range (n) : + # interpolate f at this location + func = RectBivariateSpline(Rx, Zx, F) + + farr[j] = func(opt_ri[ind] + dr*numpy.float(j), opt_zi[ind] + dz*numpy.float(j)) + + + # farr should be monotonic, and shouldn't cross any other separatrices + + maxind = numpy.argmax(farr) + minind = numpy.argmin(farr) + if (maxind < minind) : maxind, minind = minind, maxind + + # Allow a little leeway to account for errors + # NOTE: This needs a bit of refining + if (maxind > (n-3)) and (minind < 3) : + # Monotonic, so add this to a list of x-points to keep + if nkeep == 0 : + keep = [i] + else: + keep = numpy.append(keep, i) + + + nkeep = nkeep + 1 + + + if nkeep > 0 : + print("Keeping x-points ", keep) + xpt_ri = xpt_ri[keep] + xpt_zi = xpt_zi[keep] + xpt_f = xpt_f[keep] + else: + "No x-points kept" + + n_xpoint = nkeep + + + # Now find x-point closest to primary O-point + s = numpy.argsort(numpy.abs(opt_f[ind] - xpt_f)) + xpt_ri = xpt_ri[s] + xpt_zi = xpt_zi[s] + xpt_f = xpt_f[s] + inner_sep = 0 + + else: + + # No x-points. Pick mid-point in f + + xpt_f = 0.5*(numpy.max(F) + numpy.min(F)) + + print("WARNING: No X-points. Setting separatrix to F = "+str(xpt_f)) + + xpt_ri = 0 + xpt_zi = 0 + inner_sep = 0 + + + + #;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + # Put results into a structure + + result = Bunch(n_opoint=n_opoint, n_xpoint=n_xpoint, # Number of O- and X-points + primary_opt=primary_opt, # Which O-point is the plasma centre + inner_sep=inner_sep, #Innermost X-point separatrix + opt_ri=opt_ri, opt_zi=opt_zi, opt_f=opt_f, # O-point location (indices) and psi values + xpt_ri=xpt_ri, xpt_zi=xpt_zi, xpt_f=xpt_f) # X-point locations and psi values + + return result + diff --git a/boututils/anim.py b/boututils/anim.py new file mode 100755 index 0000000..d2f7838 --- /dev/null +++ b/boututils/anim.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""Animate graph with mayavi + +""" + +from __future__ import print_function +from builtins import range +from boutdata.collect import collect +import numpy as np +import os +try: + from enthought.mayavi import mlab + from enthought.mayavi.mlab import * +except ImportError: + try: + from mayavi import mlab + from mayavi.mlab import * + except ImportError: + print("No mlab available") + +from tvtk.tools import visual + + +@mlab.show +@mlab.animate(delay=250) +def anim(s, d, *args, **kwargs): + """Animate graph with mayavi + + Parameters + ---------- + s : mayavi axis object + Axis to animate data on + d : array_like + 3-D array to animate + s1 : mayavi axis object, optional + Additional bundled graph (first item in *args) + save : bool, optional + Save png files for creating movie (default: False) + + """ + + if len(args) == 1: + s1 = args[0] + else: + s1=None + + try: + save = kwargs['save'] + except: + save = False + + + nt=d.shape[0] + + print('animating for ',nt,'timesteps') + if save == True : + print('Saving pics in folder Movie') + if not os.path.exists('Movie'): + os.makedirs('Movie') + + + for i in range(nt): + s.mlab_source.scalars = d[i,:,:] + if s1 is not None : s1.mlab_source.scalars = d[i,:,:] + title="t="+np.string0(i) + mlab.title(title,height=1.1, size=0.26) + if save == True : mlab.savefig('Movie/anim%d.png'%i) + yield + +if __name__ == '__main__': + + path='../../../examples/elm-pb/data' + + data = collect("P", path=path) + + nt=data.shape[0] + + ns=data.shape[1] + ne=data.shape[2] + nz=data.shape[3] + + + f = mayavi.mlab.figure(size=(600,600)) + # Tell visual to use this as the viewer. + visual.set_viewer(f) + + #First way + + s1 = contour_surf(data[0,:,:,10]+.1, contours=30, line_width=.5, transparent=True) + s = surf(data[0,:,:,10]+.1, colormap='Spectral')#, warp_scale='.1')#, representation='wireframe') + + + # second way + + #x, y= mgrid[0:ns:1, 0:ne:1] + #s = mesh(x,y,data[0,:,:,10], colormap='Spectral')#, warp_scale='auto')#, representation='wireframe') + s.enable_contours=True + s.contour.filled_contours=True +# + + #x, y, z= mgrid[0:ns:1, 0:ne:1, 0:nz:1] + # + #p=plot3d(x,y,z,data[10,:,:,:], tube_radius=0.025, colormap='Spectral') + #p=points3d(x,y,z,data[10,:,:,:], colormap='Spectral') +# + #s=contour3d(x,y,z,data[10,:,:,:], contours=4, transparent=True) + + #mlab.view(0.,0.) + colorbar() + #axes() + #outline() + + + # Run the animation. + anim(s,data[:,:,:,10]+.1,s1, save=True) diff --git a/boututils/ask.py b/boututils/ask.py new file mode 100644 index 0000000..14aa194 --- /dev/null +++ b/boututils/ask.py @@ -0,0 +1,53 @@ +"""Ask a yes/no question and return the answer. + +""" + +from builtins import input +import sys + + +def query_yes_no(question, default="yes"): + """Ask a yes/no question via input() and return their answer. + + Answers are case-insensitive. + + Probably originally from http://code.activestate.com/recipes/577058/ + via https://stackoverflow.com/a/3041990/2043465 + + Parameters + ---------- + question : str + Question to be presented to the user + default : {"yes", "no", None} + The presumed answer if the user just hits . + It must be "yes" (the default), "no" or None (meaning + an answer is required of the user). + + Returns + ------- + bool + True if the answer was "yes" or "y", False if "no" or "n" + """ + + valid = {"yes":True, "y":True, "ye":True, + "no":False, "n":False, "No":False, "N":False } + + if default is None: + prompt = " [y/n] " + elif default == "yes": + prompt = " [Y/n] " + elif default == "no": + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + while True: + sys.stdout.write(question + prompt) + choice = input().lower() + if default is not None and choice == '': + return valid[default] + elif choice in valid: + return valid[choice] + else: + sys.stdout.write("Please respond with 'yes' or 'no' "\ + "(or 'y' or 'n').\n") diff --git a/boututils/boutarray.py b/boututils/boutarray.py new file mode 100644 index 0000000..ce38bae --- /dev/null +++ b/boututils/boutarray.py @@ -0,0 +1,73 @@ +"""Wrapper for ndarray with extra attributes for BOUT++ fields. + +""" + +import numpy + + +class BoutArray(numpy.ndarray): + """Wrapper for ndarray with extra attributes for BOUT++ fields. + + Parameters + ---------- + input_array : array_like + Data to convert to BoutArray + attributes : dict + Dictionary of extra attributes for BOUT++ fields + + Notably, these attributes should contain + ``bout_type``. Possible values are: + + - scalar + - Field2D + - Field3D + + If the variable is an evolving variable (i.e. has a time + dimension), then it is appended with a "_t" + + """ + + # See https://docs.scipy.org/doc/numpy-1.13.0/user/basics.subclassing.html + # for explanation of the structure of this numpy.ndarray wrapper + + def __new__(cls, input_array, attributes={}): + # Input array is an already formed ndarray instance + # We first cast to be our class type + obj = numpy.asarray(input_array).view(cls) + # add the dict of attributes to the created instance + obj.attributes = attributes + # Finally, we must return the newly created object: + return obj + + def __array_finalize__(self, obj): + # ``self`` is a new object resulting from + # ndarray.__new__(BoutArray, ...), therefore it only has + # attributes that the ndarray.__new__ constructor gave it - + # i.e. those of a standard ndarray. + # + # We could have got to the ndarray.__new__ call in 3 ways: + # From an explicit constructor - e.g. BoutArray(): + # obj is None + # (we're in the middle of the BoutArray.__new__ + # constructor, and self.attributes will be set when we return to + # BoutArray.__new__) + if obj is None: + return + # From view casting - e.g arr.view(BoutArray): + # obj is arr + # (type(obj) can be BoutArray) + # From new-from-template - e.g boutarray[:3] + # type(obj) is BoutArray + # + # Note that it is here, rather than in the __new__ method, that we set + # the default value for 'attributes', because this method sees all + # creation of default objects - with the BoutArray.__new__ constructor, + # but also with arr.view(BoutArray). + self.attributes = getattr(obj, 'attributes', None) + # We do not need to return anything + + def __format__(self, str): + try: + return super().__format__(str) + except TypeError: + return float(self).__format__(str) diff --git a/boututils/boutgrid.py b/boututils/boutgrid.py new file mode 100755 index 0000000..ace6766 --- /dev/null +++ b/boututils/boutgrid.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +from __future__ import print_function +from builtins import range + +import numpy as np +from numpy import cos, sin, pi + +from tvtk.api import tvtk +#from enthought.mayavi.scripts import mayavi2 + +def aligned_points(grid, nz=1, period=1.0, maxshift=0.4): + try: + nx = grid["nx"]#[0] + ny = grid["ny"]#[0] + zshift = grid["zShift"] + Rxy = grid["Rxy"] + Zxy = grid["Zxy"] + except: + print("Missing required data") + return None + + + dz = 2.*pi / (period * (nz-1)) + phi0 = np.linspace(0,2.*pi / period, nz) + + + # Need to insert additional points in Y so mesh looks smooth + #for y in range(1,ny): + # ms = np.max(np.abs(zshift[:,y] - zshift[:,y-1])) + # if( + + # Create array of points, structured + + points = np.zeros([nx*ny*nz, 3]) + + + start = 0 + for y in range(ny): + + + end = start + nx*nz + + phi = zshift[:,y] + phi0[:,None] + r = Rxy[:,y] + (np.zeros([nz]))[:,None] + + xz_points = points[start:end] + + + xz_points[:,0] = (r*cos(phi)).ravel() # X + xz_points[:,1] = (r*sin(phi)).ravel() # Y + xz_points[:,2] = (Zxy[:,y]+(np.zeros([nz]))[:,None]).ravel() # Z + + + start = end + + return points + +def create_grid(grid, data, period=1): + + s = np.shape(data) + + nx = grid["nx"]#[0] + ny = grid["ny"]#[0] + nz = s[2] + + print("data: %d,%d,%d grid: %d,%d\n" % (s[0],s[1],s[2], nx,ny)) + + dims = (nx, nz, ny) + sgrid = tvtk.StructuredGrid(dimensions=dims) + pts = aligned_points(grid, nz, period) + print(np.shape(pts)) + sgrid.points = pts + + scalar = np.zeros([nx*ny*nz]) + start = 0 + for y in range(ny): + end = start + nx*nz + + #scalar[start:end] = (data[:,y,:]).transpose().ravel() + scalar[start:end] = (data[:,y,:]).ravel() + + print(y, " = " , np.max(scalar[start:end])) + start = end + + sgrid.point_data.scalars = np.ravel(scalar.copy()) + sgrid.point_data.scalars.name = "data" + + return sgrid + +#@mayavi2.standalone +def view3d(sgrid): + from mayavi.sources.vtk_data_source import VTKDataSource + from mayavi.modules.api import Outline, GridPlane + from mayavi.api import Engine + from mayavi.core.ui.engine_view import EngineView + e=Engine() + e.start() + s = e.new_scene() + # Do this if you need to see the MayaVi tree view UI. + ev = EngineView(engine=e) + ui = ev.edit_traits() + +# mayavi.new_scene() + src = VTKDataSource(data=sgrid) + e.add_source(src) + e.add_module(Outline()) + g = GridPlane() + g.grid_plane.axis = 'x' + e.add_module(g) + +if __name__ == '__main__': + from boutdata.collect import collect + from boututils.file_import import file_import + + #path = "/media/449db594-b2fe-4171-9e79-2d9b76ac69b6/runs/data_33/" + path="../data" + + g = file_import("../bout.grd.nc") + #g = file_import("../cbm18_8_y064_x516_090309.nc") + #g = file_import("/home/ben/run4/reduced_y064_x256.nc") + + data = collect("P", tind=10, path=path) + data = data[0,:,:,:] + s = np.shape(data) + nz = s[2] + + #bkgd = collect("P0", path=path) + #for z in range(nz): + # data[:,:,z] += bkgd + + # Create a structured grid + sgrid = create_grid(g, data, 1) + + + w = tvtk.XMLStructuredGridWriter(input=sgrid, file_name='sgrid.vts') + w.write() + + # View the structured grid + view3d(sgrid) diff --git a/boututils/boutwarnings.py b/boututils/boutwarnings.py new file mode 100644 index 0000000..cdb03b0 --- /dev/null +++ b/boututils/boutwarnings.py @@ -0,0 +1,19 @@ +""" +Wrappers for warnings functions. + +Allows raising warnings that are always printed by default. +""" + +import warnings + +class AlwaysWarning(UserWarning): + def __init__(self, *args, **kwargs): + super(AlwaysWarning, self).__init__(*args, **kwargs) + +warnings.simplefilter("always", AlwaysWarning) + +def alwayswarn(message): + warnings.warn(message, AlwaysWarning, stacklevel=2) + +def defaultwarn(message): + warnings.warn(message, stacklevel=2) diff --git a/boututils/calculus.py b/boututils/calculus.py new file mode 100644 index 0000000..2742309 --- /dev/null +++ b/boututils/calculus.py @@ -0,0 +1,252 @@ +""" +Derivatives and integrals of periodic and non-periodic functions + + +B.Dudson, University of York, Nov 2009 +""" +from __future__ import print_function +from __future__ import division + +from builtins import range + +try: + from past.utils import old_div +except ImportError: + def old_div(a, b): + return a / b + +from numpy import zeros, pi, array, transpose, sum, where, arange, multiply +from numpy.fft import rfft, irfft + +def deriv(*args, **kwargs): + """Take derivative of 1D array + + result = deriv(y) + result = deriv(x, y) + + keywords + + periodic = False Domain is periodic + """ + + nargs = len(args) + if nargs == 1: + var = args[0] + x = arange(var.size) + elif nargs == 2: + x = args[0] + var = args[1] + else: + raise RuntimeError("deriv must be given 1 or 2 arguments") + + try: + periodic = kwargs['periodic'] + except: + periodic = False + + n = var.size + if periodic: + # Use FFTs to take derivatives + f = rfft(var) + f[0] = 0.0 # Zero constant term + if n % 2 == 0: + # Even n + for i in arange(1,old_div(n,2)): + f[i] *= 2.0j * pi * float(i)/float(n) + f[-1] = 0.0 # Nothing from Nyquist frequency + else: + # Odd n + for i in arange(1,old_div((n-1),2) + 1): + f[i] *= 2.0j * pi * float(i)/float(n) + return irfft(f) + else: + # Non-periodic function + result = zeros(n) # Create empty array + if n > 2: + for i in arange(1, n-1): + # 2nd-order central difference in the middle of the domain + result[i] = old_div((var[i+1] - var[i-1]), (x[i+1] - x[i-1])) + # Use left,right-biased stencils on edges (2nd order) + result[0] = old_div((-1.5*var[0] + 2.*var[1] - 0.5*var[2]), (x[1] - x[0])) + result[n-1] = old_div((1.5*var[n-1] - 2.*var[n-2] + 0.5*var[n-3]), (x[n-1] - x[n-2])) + elif n == 2: + # Just 1st-order difference for both points + result[0] = result[1] = old_div((var[1] - var[0]),(x[1] - x[0])) + elif n == 1: + result[0] = 0.0 + return result + +def deriv2D(data,axis=-1,dx=1.0,noise_suppression=True): + """ Takes 1D or 2D Derivative of 2D array using convolution + + result = deriv2D(data) + result = deriv2D(data, dx) + + output is 2D (if only one axis specified) + output is 3D if no axis specified [nx,ny,2] with the third dimension being [dfdx, dfdy] + + keywords: + axis = 0/1 If no axis specified 2D derivative will be returned + dx = 1.0 axis spacing, must be 2D if 2D deriv is taken - default is [1.0,1.0] + noise_suppression = True noise suppressing coefficients used to take derivative - default = True + """ + + from scipy.signal import convolve + + s = data.shape + if axis > len(s)-1: + raise RuntimeError("ERROR: axis out of bounds for derivative") + + if noise_suppression: + if s[axis] < 11: + raise RuntimeError("Data too small to use 11th order method") + tmp = array([old_div(-1.0,512.0),old_div(-8.0,512.0),old_div(-27.0,512.0),old_div(-48.0,512.0),old_div(-42.0,512.0),0.0,old_div(42.0,512.0),old_div(48.0,512.0),old_div(27.0,512.0),old_div(8.0,512.0),old_div(1.0,512.0)]) + else: + if s[axis] < 9: + raise RuntimeError("Data too small to use 9th order method") + tmp = array([old_div(1.0,280.0),old_div(-4.0,105.0),old_div(1.0,5.0),old_div(-4.0,5.0),0.0,old_div(4.0,5.0),old_div(-1.0,5.0),old_div(4.0,105.0),old_div(-1.0,280.0)]) + + N = int((tmp.size-1)/2) + if axis==1: + W = transpose(tmp[:,None]) + data_deriv = convolve(data,W,mode='same')/dx*-1.0 + for i in range(s[0]): + data_deriv[i,0:N-1] = old_div(deriv(data[i,0:N-1]),dx) + data_deriv[i,s[1]-N:] = old_div(deriv(data[i,s[1]-N:]),dx) + + elif axis==0: + W = tmp[:,None] + data_deriv = convolve(data,W,mode='same')/dx*-1.0 + for i in range(s[1]): + data_deriv[0:N-1,i] = old_div(deriv(data[0:N-1,i]),dx) + data_deriv[s[0]-N:,i] = old_div(deriv(data[s[0]-N:,i]),dx) + else: + data_deriv = zeros((s[0],s[1],2)) + if (not hasattr(dx, '__len__')) or len(dx)==1: + dx = array([dx,dx]) + + W = tmp[:,None]#transpose(multiply(tmp,ones((s[1],tmp.size)))) + data_deriv[:,:,0] = convolve(data,W,mode='same')/dx[0]*-1.0 + for i in range(s[1]): + data_deriv[0:N-1,i,0] = old_div(deriv(data[0:N-1,i]),dx[0]) + data_deriv[s[0]-N:s[0]+1,i,0] = old_div(deriv(data[s[0]-N:s[0]+1,i]),dx[0]) + + W = transpose(tmp[:,None])#multiply(tmp,ones((s[0],tmp.size))) + data_deriv[:,:,1] = convolve(data,W,mode='same')/dx[1]*-1.0 + for i in range(s[0]): + data_deriv[i,0:N-1,1] = old_div(deriv(data[i,0:N-1]),dx[1]) + data_deriv[i,s[1]-N:s[1]+1,1] = old_div(deriv(data[i,s[1]-N:s[1]+1]),dx[1]) + + return data_deriv + +def integrate(var, periodic=False): + """Integrate a 1D array + + Return array is the same size as the input + """ + if periodic: + # Use FFT + f = rfft(var) + n = var.size + # Zero frequency term + result = f[0].real*arange(n, dtype=float) + f[0] = 0. + if n % 2 == 0: + # Even n + for i in arange(1,old_div(n,2)): + f[i] /= 2.0j * pi * float(i)/float(n) + f[-1] = 0.0 # Nothing from Nyquist frequency + else: + # Odd n + for i in arange(1,old_div((n-1),2) + 1): + f[i] /= 2.0j * pi * float(i)/float(n) + return result + irfft(f) + else: + # Non-periodic function + def int_total(f): + """Integrate over a set of points""" + n = f.size + if n > 7: + # Need to split into several segments + # Use one 5-point, leaving at least 4-points + return int_total(f[0:5]) + int_total(f[4:]) + elif (n == 7) or (n == 6): + # Try to keep 4th-order + # Split into 4+4 or 4+3 + return int_total(f[0:4]) + int_total(f[3:]) + elif n == 5: + # 6th-order Bool's rule + return 4.*(7.*f[0] + 32.*f[1] + 12.*f[2] + 32.*f[3] + 7.*f[4])/90. + elif n == 4: + # 4th-order Simpson's 3/8ths rule + return 3.*(f[0] + 3.*f[1] + 3.*f[2] + f[3])/8. + elif n == 3: + # 4th-order Simpson's rule + return (f[0] + 4.*f[1] + f[2])/3. + elif n == 2: + # 2nd-order Trapezium rule + return 0.5*(f[0] + f[1]) + else: + print("WARNING: Integrating a single point") + return 0.0 + # Integrate using maximum number of grid-points + n = var.size + n2 = int(old_div(n,2)) + result = zeros(n) + for i in arange(n2, n): + result[i] = int_total(var[0:(i+1)]) + for i in arange(1, n2): + result[i] = result[-1] - int_total(var[i:]) + return result + +def simpson_integrate(data,dx,dy,kernel=0.0,weight=1.0): + """ Integrates 2D data to one value using the simpson method and matrix convolution + + result = simpson_integrate(data,dx,dy) + + keywords: + + kernel - can be supplied if the simpson matrix is calculated ahead of time + - if not supplied, is calculated within this function + - if you need to integrate the same shape data over and over, calculated + it ahead of time using: + kernel = simpson_matrix(Nx,Ny,dx,dy) + + weight - can be used to scale data if single number + - can be used to mask data if weight is array (same size as data) + """ + s = data.shape + Nx = s[0] + Ny = s[1] + + if len(kernel)==1: + kernel = simpson_matrix(Nx,Ny,dx,dy) + + return sum(multiply(multiply(weight,kernel),data))/sum(multiply(weight,kernel)) + + +def simpson_matrix(Nx,Ny,dx,dy): + """ + Creates a 2D matrix of coefficients for the simpson_integrate function + + Call ahead of time if you need to perform integration of the same size data with the + same dx and dy + + Otherwise, simpson_integrate will automatically call this + + """ + Wx = arange(Nx) + 2 + Wx[where(arange(Nx) % 2 == 1)] = 4 + Wx[0] = 1 + Wx[Nx-1] = 1 + + Wy = arange(Ny) + 2 + Wy[where(arange(Ny) % 2 == 1)] = 4 + Wy[0] = 1 + Wy[Ny-1] = 1 + + W = Wy[None,:] * Wx[:,None] + + A = dx*dy/9.0 + + return W*A diff --git a/boututils/check_scaling.py b/boututils/check_scaling.py new file mode 100644 index 0000000..be22fc0 --- /dev/null +++ b/boututils/check_scaling.py @@ -0,0 +1,91 @@ +"""Functions for checking the error scaling of MMS or MES results + +""" + +from numpy import array, isclose, log, polyfit + + +def get_order(grid_spacing, errors): + """Get the convergence order of errors over the full range of + grid_spacing, and at small spacings + + Parameters + ---------- + grid_spacing : list of float + The grid spacing or inverse of number of grid points + errors : list of float + The error at each grid spacing + + Returns + ------- + tuple of float + The first value is the error scaling over the full range of + grid spacings; the second value is the scaling over the last + two points + + """ + if len(errors) != len(grid_spacing): + raise ValueError("errors (len: {}) and grid_spacing (len: {}) should be the same length" + .format(len(errors), len(grid_spacing))) + + full_range = polyfit(log(grid_spacing), log(errors), 1) + + small_spacing = log(errors[-2] / errors[-1]) / log(grid_spacing[-2] / grid_spacing[-1]) + + return (full_range[0], small_spacing) + + +def check_order(error_list, expected_order, tolerance=2.e-1, spacing=None): + """Check if the actual_order is sufficiently close to the + expected_order within a given tolerance + + """ + + if len(error_list) < 2: + raise RuntimeError("Expected at least 2 data points to calculate error") + + success=True + for i in range(len(error_list)-1): + if spacing is None: + actual_order = log(errors[i] / errors[i+1]) / log(2) + else: + actual_order = log(errors[i] / errors[i+1]) / log(grid_spacing[i] / grid_spacing[i+1]) + + if not isclose(actual_order, expected_order, atol=tolerance, rtol=0): + success=False + return success + + +def error_rate_table(errors, grid_sizes, label): + """Create a nicely formatted table of the error convergence rate over + the grid_sizes + + The error rate is calculated between adjacent points + + Parameters + ---------- + errors : list of float + The errors at each grid size + grid_sizes : list of int + The number of grid points + label : string + What the error is measuring + + Returns + ------- + string + + """ + if len(errors) != len(grid_sizes): + raise ValueError("errors (len: {}) and grid_sizes (len: {}) should be the same length" + .format(len(errors), len(grid_sizes))) + + dx = 1. / array(grid_sizes) + message = "{}:\nGrid points | Error | Rate\n".format(label) + for i, grid_size in enumerate(grid_sizes): + message += "{:<11} | {:f} | ".format(grid_size, errors[i]) + if i > 0: + message += "{:f} \n".format(log(errors[i] / errors[i-1]) / log(dx[i] / dx[i-1])) + else: + message += "--\n" + return message diff --git a/boututils/closest_line.py b/boututils/closest_line.py new file mode 100644 index 0000000..42dbbe0 --- /dev/null +++ b/boututils/closest_line.py @@ -0,0 +1,14 @@ +from builtins import range +import numpy +# Find the closest contour line to a given point +def closest_line(n, x, y, ri, zi, mind=None): + + mind = numpy.min( (x[0] - ri)**2 + (y[0] - zi)**2 ) + ind = 0 + + for i in range (1, n) : + d = numpy.min( (x[i] - ri)**2 + (y[i] - zi)**2 ) + if d < mind : + mind = d + ind = i + return ind diff --git a/boututils/contour.py b/boututils/contour.py new file mode 100644 index 0000000..0d648ca --- /dev/null +++ b/boututils/contour.py @@ -0,0 +1,82 @@ +""" +Contour calculation routines + +http://members.bellatlantic.net/~vze2vrva/thesis.html + +""" +from __future__ import print_function +from __future__ import division +from past.utils import old_div + +import numpy as np + + +def contour(f, level): + """Return a list of contours matching the given level""" + + if len(f.shape) != 2: + print("Contour only works on 2D data") + return None + nx,ny = f.shape + + # Go through each cell edge and mark which ones contain + # a level crossing. Approximating function as + # f = axy + bx + cy + d + # Hence linear interpolation along edges. + + edgecross = {} # Dictionary: (cell number, edge number) to crossing location + + for i in np.arange(nx-1): + for j in np.arange(ny-1): + # Lower-left corner of cell is (i,j) + if (np.max(f[i:(i+2),j:(j+2)]) < level) or (np.min(f[i:(i+2),j:(j+2)]) > level): + # not in the correct range - skip + continue + + # Check each edge + ncross = 0 + def location(a, b): + if (a > level) ^ (a > level): + # One of the corners is > level, and the other is <= level + ncross += 1 + # Find location + return old_div((level - a), (b - a)) + else: + return None + + loc = [ + location(f[i,j], f[i+1,j]), + location(f[i+1,j], f[i+1,j+1]), + location(f[i+1,j+1], f[i,j+1]), + location(f[i,j+1], f[i,j])] + + if ncross != 0: # Only put into dictionary if has a crossing + cellnr = (ny-1)*i + j # The cell number + edgecross[cellnr] = [loc,ncross] # Tack ncross onto the end + + # Process crossings into contour lines + + while True: + # Start from an arbitrary location and follow until + # it goes out of the domain or closes + try: + startcell, cross = edgecross.popitem() + except KeyError: + # No keys left so finished + break + + def follow(): + return + + # Follow + + return + +def find_opoints(var2d): + """Find O-points in psi i.e. local minima/maxima""" + return + +def find_xpoints(var2d): + """Find X-points in psi i.e. inflection points""" + return + diff --git a/boututils/crosslines.py b/boututils/crosslines.py new file mode 100644 index 0000000..0a4a864 --- /dev/null +++ b/boututils/crosslines.py @@ -0,0 +1,197 @@ +from __future__ import print_function +from __future__ import division +from past.utils import old_div +import numpy as np +from numpy.lib.stride_tricks import as_strided +import itertools + +def unique(a, atol=1e-08): + """Find unique rows in 2d array + + Parameters + ---------- + a : 2d ndarray, float + array to find unique rows in + atol : float, optional + tolerance to check uniqueness + + Returns + ------- + out : 2d ndarray, float + array of unique values + + Notes + ----- + Adapted to include tolerance from code at http://stackoverflow.com/questions/8560440/removing-duplicate-columns-and-rows-from-a-numpy-2d-array#answer-8564438 + + """ + + if np.issubdtype(a.dtype, float): + order = np.lexsort(a.T) + a = a[order] + diff = np.diff(a, axis=0) + np.abs(diff,out = diff) + ui = np.ones(len(a), 'bool') + ui[1:] = (diff > atol).any(axis=1) + return a[ui] + else: + order = np.lexsort(a.T) + a = a[order] + diff = np.diff(a, axis=0) + ui = np.ones(len(a), 'bool') + ui[1:] = (diff != 0).any(axis=1) + return a[ui] + +def linelineintersect(a, b, atol=1e-08): + """Find all intersection points of two lines defined by series of x,y pairs + + Intersection points are unordered + Colinear lines that overlap intersect at any end points that fall within the overlap + + Parameters + ---------- + a, b : ndarray + 2 column ndarray of x,y values defining a two dimensional line. 1st + column is x values, 2nd column is x values. + + Notes + ----- + An example of 3 segment line is: a = numpy.array([[0,0],[5.0,3.0],[4.0,1]]) + Function faster when there are no overlapping line segments + + + add some lines for preventing zero-division + """ + + def x_y_from_line(line): + """1st and 2nd column of array""" + return (line[:, 0],line[:, 1]) + def meshgrid_as_strided(x, y, mask=None): + """numpy.meshgrid without copying data (using as_strided)""" + if mask is None: + return (as_strided(x, strides=(0, x.strides[0]), shape=(y.size, x.size)), + as_strided(y, strides=(y.strides[0],0), shape=(y.size, x.size))) + else: + return (np.ma.array(as_strided(x, strides=(0, x.strides[0]), shape=(y.size, x.size)), mask=mask), + np.ma.array(as_strided(y, strides=(y.strides[0],0), shape=(y.size, x.size)), mask=mask)) + + #In the following the indices i, j represents the pairing of the ith segment of b and the jth segment of a + #e.g. if ignore[i,j]==True then the ith segment of b and the jth segment of a cannot intersect + ignore = np.zeros([b.shape[0]-1, a.shape[0]-1], dtype=bool) + + x11, x21 = meshgrid_as_strided(a[:-1, 0], b[:-1, 0], mask=ignore) + x12, x22 = meshgrid_as_strided(a[1: , 0], b[1: , 0], mask=ignore) + y11, y21 = meshgrid_as_strided(a[:-1, 1], b[:-1, 1], mask=ignore) + y12, y22 = meshgrid_as_strided(a[1: , 1], b[1: , 1], mask=ignore) + + #ignore segements with non-overlappiong bounding boxes + ignore[np.ma.maximum(x11, x12) < np.ma.minimum(x21, x22)] = True + ignore[np.ma.minimum(x11, x12) > np.ma.maximum(x21, x22)] = True + ignore[np.ma.maximum(y11, y12) < np.ma.minimum(y21, y22)] = True + ignore[np.ma.minimum(y11, y12) > np.ma.maximum(y21, y22)] = True + + #find intersection of segments, ignoring impossible line segment pairs when new info becomes available + denom_ = np.empty(ignore.shape, dtype=float) + denom = np.ma.array(denom_, mask=ignore) + denom_[:, :] = ((y22 - y21) * (x12 - x11)) - ((x22 - x21) * (y12 - y11)) + #denom_tmp = ((y22 - y21) * (x12 - x11)) - ((x22 - x21) * (y12 - y11)) # H.SETO + denom_[:, :] = np.where(denom_ == 0.0, 1.e-100,denom_) + + ua_ = np.empty(ignore.shape, dtype=float) + ua = np.ma.array(ua_, mask=ignore) + ua_[:, :] = (((x22 - x21) * (y11 - y21)) - ((y22 - y21) * (x11 - x21))) + ua_[:, :] /= denom + + ignore[ua < 0] = True + ignore[ua > 1] = True + + ub_ = np.empty(ignore.shape, dtype=float) + ub = np.ma.array(ub_, mask=ignore) + ub_[:, :] = (((x12 - x11) * (y11 - y21)) - ((y12 - y11) * (x11 - x21))) + ub_[:, :] /= denom + + + ignore[ub < 0] = True + ignore[ub > 1] = True + + nans_ = np.zeros(ignore.shape, dtype = bool) + nans = np.ma.array(nans_, mask = ignore) + nans_[:,:] = np.isnan(ua) + + if not np.ma.any(nans): + + #remove duplicate cases where intersection happens on an endpoint +# ignore[np.ma.where((ua[:, :-1] == 1) & (ua[:, 1:] == 0))] = True +# ignore[np.ma.where((ub[:-1, :] == 1) & (ub[1:, :] == 0))] = True + ignore[np.ma.where((ua[:, :-1] < 1.0 + atol) & (ua[:, :-1] > 1.0 - atol) & (ua[:, 1:] < atol) & (ua[:, 1:] > -atol))] = True + ignore[np.ma.where((ub[:-1, :] < 1 + atol) & (ub[:-1, :] > 1 - atol) & (ub[1:, :] < atol) & (ub[1:, :] > -atol))] = True + + xi = x11 + ua * (x12 - x11) + yi = y11 + ua * (y12 - y11) + return np.ma.compressed(xi), np.ma.compressed(yi) + else: + n_nans = np.ma.sum(nans) + n_standard = np.ma.count(x11) - n_nans + #I initially tried using a set to get unique points but had problems with floating point equality + + #create n by 2 array to hold all possible intersection points, check later for uniqueness + points = np.empty([n_standard + 2 * n_nans, 2], dtype = float) #each colinear segment pair has two intersections, hence the extra n_colinear points + + #add standard intersection points + xi = x11 + ua * (x12 - x11) + yi = y11 + ua * (y12 - y11) + points[:n_standard,0] = np.ma.compressed(xi[~nans]) + points[:n_standard,1] = np.ma.compressed(yi[~nans]) + ignore[~nans]=True + + + #now add the appropriate intersection points for the colinear overlapping segments + #colinear overlapping segments intersect on the two internal points of the four points describing a straight line + #ua and ub have already serverd their purpose. Reuse them here to mean: + #ua is relative position of 1st b segment point along segment a + #ub is relative position of 2nd b segment point along segment a + use_x = x12 != x11 #avoid vertical lines diviiding by zero + use_y = ~use_x + + ua[use_x] = old_div((x21[use_x]-x11[use_x]), (x12[use_x]-x11[use_x])) + ua[use_y] = old_div((y21[use_y]-y11[use_y]), (y12[use_y]-y11[use_y])) + + ub[use_x] = old_div((x22[use_x]-x11[use_x]), (x12[use_x]-x11[use_x])) + ub[use_y] = old_div((y22[use_y]-y11[use_y]), (y12[use_y]-y11[use_y])) + + np.ma.clip(ua, a_min=0,a_max = 1, out = ua) + np.ma.clip(ub, a_min=0,a_max = 1, out = ub) + + xi = x11 + ua * (x12 - x11) + yi = y11 + ua * (y12 - y11) + points[n_standard:n_standard + n_nans,0] = np.ma.compressed(xi) + points[n_standard:n_standard + n_nans,1] = np.ma.compressed(yi) + + xi = x11 + ub * (x12 - x11) + yi = y11 + ub * (y12 - y11) + points[n_standard+n_nans:,0] = np.ma.compressed(xi) + points[n_standard+n_nans:,1] = np.ma.compressed(yi) + + points_unique = unique(points) + return points_unique[:,0], points_unique[:,1] + + +def find_inter( contour1, contour2): + xi = np.array([]) + yi = np.array([]) + + i=0 + ncombos = (sum([len(x.get_paths()) for x in contour1.collections]) * + sum([len(x.get_paths()) for x in contour2.collections])) + for linecol1, linecol2 in itertools.product(contour1.collections, contour2.collections): + for path1, path2 in itertools.product(linecol1.get_paths(),linecol2.get_paths()): + i += 1 + print('line combo %5d of %5d' % (i, ncombos)) + xinter, yinter = linelineintersect(path1.vertices, path2.vertices) + + xi = np.append(xi, xinter) + yi = np.append(yi, yinter) + + #plt.scatter(xi, yi, s=20) + #plt.show() + return xi, yi diff --git a/boututils/datafile.py b/boututils/datafile.py new file mode 100644 index 0000000..a535e32 --- /dev/null +++ b/boututils/datafile.py @@ -0,0 +1,929 @@ +"""File I/O class + +A wrapper around various NetCDF libraries and h5py, used by BOUT++ +routines. Creates a consistent interface across machines + +Supported libraries: + +- ``h5py`` (for HDF5 files) +- ``netCDF4`` (preferred NetCDF library) + +NOTE +---- +NetCDF and HDF5 include unlimited dimensions, but this library is just +for very simple I/O operations. Educated guesses are made for the +dimensions. + +TODO +---- +- Don't raise ``ImportError`` if no NetCDF libraries found, use HDF5 + instead? +- Cleaner handling of different NetCDF libraries +- Support for h5netcdf? + +""" + +from __future__ import print_function +from builtins import map, zip, str, object + +import numpy as np +import time +import getpass +from boututils.boutwarnings import alwayswarn +from boututils.boutarray import BoutArray + +try: + from netCDF4 import Dataset + has_netCDF = True +except ImportError: + raise ImportError( + "DataFile: No supported NetCDF modules available -- requires netCDF4") + +try: + import h5py + has_h5py = True +except ImportError: + has_h5py = False + + +class DataFile(object): + """File I/O class + + A wrapper around various NetCDF libraries and h5py, used by BOUT++ + routines. Creates a consistent interface across machines + + Parameters + ---------- + filename : str, optional + Name of file to open. If no filename supplied, you will need + to call :py:obj:`~DataFile.open` and supply `filename` there + write : bool, optional + If True, open the file in read-write mode (existing files will + be appended to). Default is read-only mode + create : bool, optional + If True, open the file in write mode (existing files will be + truncated). Default is read-only mode + format : str, optional + Name of a filetype to use (e.g. ``NETCDF3_CLASSIC``, + ``NETCDF3_64BIT``, ``NETCDF4``, ``HDF5``) + + TODO + ---- + - `filename` should not be optional! + - Take a ``mode`` argument to be more in line with other file types + - `format` should be checked to be a sensible value + - Make sure ``__init__`` methods are first + - Make `impl` and `handle` private + + """ + impl = None + + def __init__(self, filename=None, write=False, create=False, format='NETCDF3_64BIT', **kwargs): + """ + + NetCDF formats are described here: http://unidata.github.io/netcdf4-python/ + - NETCDF3_CLASSIC Limited to 2.1Gb files + - NETCDF3_64BIT_OFFSET or NETCDF3_64BIT is an extension to allow larger file sizes + - NETCDF3_64BIT_DATA adds 64-bit integer data types and 64-bit dimension sizes + - NETCDF4 and NETCDF4_CLASSIC use HDF5 as the disk format + """ + if filename is not None: + if filename.split('.')[-1] in ('hdf5', 'hdf', 'h5'): + self.impl = DataFile_HDF5( + filename=filename, write=write, create=create, format=format) + else: + self.impl = DataFile_netCDF( + filename=filename, write=write, create=create, format=format, **kwargs) + elif format == 'HDF5': + self.impl = DataFile_HDF5( + filename=filename, write=write, create=create, + format=format) + else: + self.impl = DataFile_netCDF( + filename=filename, write=write, create=create, format=format, **kwargs) + + def open(self, filename, write=False, create=False, + format='NETCDF3_CLASSIC'): + """Open the file + + Parameters + ---------- + filename : str, optional + Name of file to open + write : bool, optional + If True, open the file in read-write mode (existing files will + be appended to). Default is read-only mode + create : bool, optional + If True, open the file in write mode (existing files will be + truncated). Default is read-only mode + format : str, optional + Name of a filetype to use (e.g. ``NETCDF3_CLASSIC``, + ``NETCDF4``, ``HDF5``) + + TODO + ---- + - Return the result of calling open to be more like stdlib's + open + - `keys` should be more pythonic (return generator) + + """ + self.impl.open(filename, write=write, create=create, + format=format) + + def close(self): + """Close a file and flush data to disk + + """ + self.impl.close() + + def __del__(self): + if self.impl is not None: + self.impl.__del__() + + def __enter__(self): + self.impl.__enter__() + return self + + def __exit__(self, type, value, traceback): + self.impl.__exit__(type, value, traceback) + + def read(self, name, ranges=None, asBoutArray=True): + """Read a variable from the file + + Parameters + ---------- + name : str + Name of the variable to read + ranges : list of slice objects, optional + Slices of variable to read, can also be converted from lists or + tuples of (start, stop, stride). The number of elements in `ranges` + should be equal to the number of dimensions of the variable you + wish to read. See :py:obj:`~DataFile.size` for how to get the + dimensions + asBoutArray : bool, optional + If True, return the variable as a + :py:obj:`~boututils.boutarray.BoutArray` (the default) + + Returns + ------- + ndarray or :py:obj:`~boututils.boutarray.BoutArray` + The variable from the file + (:py:obj:`~boututils.boutarray.BoutArray` if `asBoutArray` + is True) + + """ + if ranges is not None: + for x in ranges: + if isinstance(x, (list, tuple)): + x = slice(*x) + return self.impl.read(name, ranges=ranges, asBoutArray=asBoutArray) + + def list(self): + """List all variables in the file + + Returns + ------- + list of str + A list containing all the names of the variables + + """ + return self.impl.list() + + def keys(self): + """A synonym for :py:obj:`~DataFile.list` + + TODO + ---- + - Make a generator to be more like python3 dict keys + + """ + return self.list() + + def dimensions(self, varname): + """Return the names of all the dimensions of a variable + + Parameters + ---------- + varname : str + The name of the variable + + Returns + ------- + tuple of str + The names of the variable's dimensions + + """ + return self.impl.dimensions(varname) + + def ndims(self, varname): + """Return the number of dimensions for a variable + + Parameters + ---------- + varname : str + The name of the variable + + Returns + ------- + int + The number of dimensions + + """ + return self.impl.ndims(varname) + + def sync(self): + """Write pending changes to disk. + + """ + self.impl.sync() + + def size(self, varname): + """Return the size of each dimension of a variable + + Parameters + ---------- + varname : str + The name of the variable + + Returns + ------- + tuple of int + The size of each dimension + + """ + return self.impl.size(varname) + + def bout_type(self, varname): + """Return the name of the BOUT++ type of a variable + + Possible values are: + + - scalar + - Field2D + - Field3D + + If the variable is an evolving variable (i.e. has a time + dimension), then it is appended with a "_t" + + Parameters + ---------- + varname : str + The name of the variable + + Returns + ------- + str + The name of the BOUT++ type + + """ + return self.attributes(varname)["bout_type"] + + def write(self, name, data, info=False): + """Write a variable to file + + If the variable is not a :py:obj:`~boututils.boutarray.BoutArray` with + the ``bout_type`` attribute, a guess will be made for the + dimensions + + Parameters + ---------- + name : str + Name of the variable to use in the file + data : :py:obj:`~boututils.boutarray.BoutArray` or ndarray + An array containing the variable data + info : bool, optional + If True, print information about what is being written to + file + + Returns + ------- + None + + """ + return self.impl.write(name, data, info) + + def __getitem__(self, name): + return self.impl.__getitem__(name) + + def __setitem__(self, key, value): + self.impl.__setitem__(key, value) + + def attributes(self, varname): + """Return a dictionary of attributes + + Parameters + ---------- + varname : str + The name of the variable + + Returns + ------- + dict + The attribute names and their values + + """ + return self.impl.attributes(varname) + + +class DataFile_netCDF(DataFile): + handle = None + + def open(self, filename, write=False, create=False, + format='NETCDF3_CLASSIC'): + if (not write) and (not create): + self.handle = Dataset(filename, "r") + elif create: + self.handle = Dataset(filename, "w", format=format) + else: + self.handle = Dataset(filename, "a") + # Record if writing + self.writeable = write or create + + def close(self): + if self.handle is not None: + self.handle.close() + self.handle = None + + def __init__(self, filename=None, write=False, create=False, + format='NETCDF3_CLASSIC', **kwargs): + self._kwargs = kwargs + if not has_netCDF: + message = "DataFile: No supported NetCDF python-modules available" + raise ImportError(message) + if filename is not None: + self.open(filename, write=write, create=create, format=format) + self._attributes_cache = {} + + def __del__(self): + self.close() + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + + def read(self, name, ranges=None, asBoutArray=True): + """Read a variable from the file.""" + if self.handle is None: + return None + + try: + var = self.handle.variables[name] + n = name + except KeyError: + # Not found. Try to find using case-insensitive search + var = None + for n in list(self.handle.variables.keys()): + if n.lower() == name.lower(): + print( + "WARNING: Reading '" + n + "' instead of '" + name + "'") + var = self.handle.variables[n] + if var is None: + return None + + if asBoutArray: + attributes = self.attributes(n) + + ndims = len(var.dimensions) + if ndims == 0: + data = var.getValue() + if asBoutArray: + data = BoutArray(data, attributes=attributes) + return data # [0] + else: + if ranges: + if len(ranges) == 2 * ndims: + # Reform list of pairs of ints into slices + ranges = [slice(a, b) for a, b in + zip(ranges[::2], ranges[1::2])] + elif len(ranges) != ndims: + raise ValueError("Incorrect number of elements in ranges argument " + "(got {}, expected {} or {})" + .format(len(ranges), ndims, 2 * ndims)) + + data = var[ranges[:ndims]] + if asBoutArray: + data = BoutArray(data, attributes=attributes) + return data + else: + data = var[:] + if asBoutArray: + data = BoutArray(data, attributes=attributes) + return data + + def __getitem__(self, name): + var = self.read(name) + if var is None: + raise KeyError("No variable found: " + name) + return var + + def __setitem__(self, key, value): + self.write(key, value) + + def list(self): + if self.handle is None: + return [] + return list(self.handle.variables.keys()) + + def keys(self): + return self.list() + + def dimensions(self, varname): + if self.handle is None: + return None + try: + var = self.handle.variables[varname] + except KeyError: + raise ValueError("No such variable") + return var.dimensions + + def ndims(self, varname): + if self.handle is None: + raise ValueError("File not open") + try: + var = self.handle.variables[varname] + except KeyError: + raise ValueError("No such variable") + return len(var.dimensions) + + def sync(self): + self.handle.sync() + + def size(self, varname): + if self.handle is None: + return [] + try: + var = self.handle.variables[varname] + except KeyError: + return [] + + def dimlen(d): + dim = self.handle.dimensions[d] + if dim is not None: + t = type(dim).__name__ + if t == 'int': + return dim + return len(dim) + return 0 + return [dimlen(d) for d in var.dimensions] + + def _bout_type_from_dimensions(self, varname): + dims = self.dimensions(varname) + dims_dict = { + ('t', 'x', 'y', 'z'): "Field3D_t", + ('t', 'x', 'y'): "Field2D_t", + ('t',): "scalar_t", + ('x', 'y', 'z'): "Field3D", + ('x', 'y'): "Field2D", + (): "scalar", + } + + return dims_dict.get(dims, None) + + def write(self, name, data, info=False): + + if not self.writeable: + raise Exception("File not writeable. Open with write=True keyword") + + s = np.shape(data) + + # Get the variable type + t = type(data).__name__ + + if t == 'NoneType': + print("DataFile: None passed as data to write. Ignoring") + return + + if t == 'ndarray' or t == 'BoutArray': + # Numpy type or BoutArray wrapper for Numpy type. Get the data type + t = data.dtype.str + + if t == 'list': + # List -> convert to numpy array + data = np.array(data) + t = data.dtype.str + + if (t == 'int') or (t == ' +# +# * Modified to allow calls with only one argument +# + +def int_func( xin, fin=None, simple=None): + if fin is None : + f = copy.deepcopy(xin) + x = numpy.arange(numpy.size(f)).astype(float) + else: + f = copy.deepcopy(fin) + x = copy.deepcopy(xin) + + n = numpy.size(f) + + g = numpy.zeros(n) + + if simple is not None : + # Just use trapezium rule + + g[0] = 0.0 + for i in range (1, n) : + g[i] = g[i-1] + 0.5*(x[i] - x[i-1])*(f[i] + f[i-1]) + + else: + + n2 = numpy.int(old_div(n,2)) + + g[0] = 0.0 + for i in range (n2, n) : + g[i] = simps( f[0:i+1], x[0:i+1]) + + + + for i in range (1, n2) : + g[i] = g[n-1] - simps( f[i::], x[i::]) + + return g + + diff --git a/boututils/linear_regression.py b/boututils/linear_regression.py new file mode 100644 index 0000000..c2f3f2c --- /dev/null +++ b/boututils/linear_regression.py @@ -0,0 +1,27 @@ +from __future__ import division +# +# Perform a linear regression fit +# + +from numpy import mean + +def linear_regression(x, y): + """ Simple linear regression of two variables + + y = a + bx + + a, b = linear_regression(x, y) + + """ + + if x.size != y.size: + raise ValueError("x and y inputs must be the same size") + + mx = mean(x) + my = mean(y) + + b = (mean(x*y) - mx*my) / (mean(x**2) - mx**2) + a = my - b*mx + + return a, b + diff --git a/boututils/local_min_max.py b/boututils/local_min_max.py new file mode 100644 index 0000000..8484da0 --- /dev/null +++ b/boututils/local_min_max.py @@ -0,0 +1,69 @@ +import numpy as np +import scipy.ndimage.filters as filters +import scipy.ndimage.morphology as morphology + +def detect_local_minima(arr): + # http://stackoverflow.com/questions/3684484/peak-detection-in-a-2d-array/3689710#3689710 + """ + Takes an array and detects the troughs using the local maximum filter. + Returns a boolean mask of the troughs (i.e. 1 when + the pixel's value is the neighborhood maximum, 0 otherwise) + """ + # define an connected neighborhood + # http://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#generate_binary_structure + neighborhood = morphology.generate_binary_structure(len(arr.shape),2) + # apply the local minimum filter; all locations of minimum value + # in their neighborhood are set to 1 + # http://www.scipy.org/doc/api_docs/SciPy.ndimage.filters.html#minimum_filter + local_min = (filters.minimum_filter(arr, footprint=neighborhood)==arr) + # local_min is a mask that contains the peaks we are + # looking for, but also the background. + # In order to isolate the peaks we must remove the background from the mask. + # + # we create the mask of the background + background = (arr==0) + # + # a little technicality: we must erode the background in order to + # successfully subtract it from local_min, otherwise a line will + # appear along the background border (artifact of the local minimum filter) + # http://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#binary_erosion + eroded_background = morphology.binary_erosion( + background, structure=neighborhood, border_value=1) + # + # we obtain the final mask, containing only peaks, + # by removing the background from the local_min mask + detected_minima = local_min - eroded_background + return np.where(detected_minima) + +def detect_local_maxima(arr): + # http://stackoverflow.com/questions/3684484/peak-detection-in-a-2d-array/3689710#3689710 + """ + Takes an array and detects the peaks using the local maximum filter. + Returns a boolean mask of the troughs (i.e. 1 when + the pixel's value is the neighborhood maximum, 0 otherwise) + """ + # define an connected neighborhood + # http://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#generate_binary_structure + neighborhood = morphology.generate_binary_structure(len(arr.shape),2) + # apply the local maximum filter; all locations of maximum value + # in their neighborhood are set to 1 + # http://www.scipy.org/doc/api_docs/SciPy.ndimage.filters.html#maximum_filter + local_max = (filters.maximum_filter(arr, footprint=neighborhood)==arr) + # local_max is a mask that contains the peaks we are + # looking for, but also the background. + # In order to isolate the peaks we must remove the background from the mask. + # + # we create the mask of the background + background = (arr==0) + # + # a little technicality: we must erode the background in order to + # successfully subtract it from local_max, otherwise a line will + # appear along the background border (artifact of the local maximum filter) + # http://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#binary_erosion + eroded_background = morphology.binary_erosion( + background, structure=neighborhood, border_value=1) + # + # we obtain the final mask, containing only peaks, + # by removing the background from the local_min mask + detected_maxima = local_max - eroded_background + return np.where(detected_maxima) \ No newline at end of file diff --git a/boututils/mode_structure.py b/boututils/mode_structure.py new file mode 100644 index 0000000..1196c82 --- /dev/null +++ b/boututils/mode_structure.py @@ -0,0 +1,417 @@ +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division +from builtins import range +from past.utils import old_div +import numpy as numpy +import sys +from pylab import plot,xlabel,ylim,savefig,gca, xlim, show, clf, draw, title +from boututils.fft_integrate import fft_integrate +from .ask import query_yes_no + +#; Calculates mode structure from BOUT++ output +#; for comparison to ELITE +#; +#; April 2009 - Added ERGOS flag. This is intended +#; for producing plots similar to the ERGOS +#; vacuum RMP code + +# interpolates a 1D periodic function +def zinterp( v, zind): + + v = numpy.ravel(v) + + nz = numpy.size(v) + z0 = numpy.round(zind) + + p = zind - float(z0) # between -0.5 and 0.5 + + if p < 0.0 : + z0 = z0 - 1 + p = p + 1.0 + + + z0 = ((z0 % (nz-1)) + (nz-1)) % (nz-1) + + # for now 3-point interpolation + + zp = (z0 + 1) % (nz - 1) + zm = (z0 - 1 + (nz-1)) % (nz - 1) + + + result = 0.5*p*(p-1.0)*v[zm.astype(int)] \ + + (1.0 - p*p)*v[z0.astype(int)] \ + + 0.5*p*(p+1.0)*v[zp.astype(int)] + + return result + + +def mode_structure( var_in, grid_in, period=1, + zangle=0.0, n=None, addq=None, output=None, + xq=None, xpsi=None, slow=None, subset=None, + filter=None, famp=None, quiet=None, + ergos=None, ftitle=None, + xrange=None, yrange=None, rational=None, pmodes=None, + _extra=None): + + + #ON_ERROR, 2 + # + # period = 1 ; default = full torus + + if n is None : + if filter is not None : + n = filter*period + else: n = period + + + # if (grid_in.JYSEPS1_1 GE 0) OR (grid_in.JYSEPS1_2 NE grid_in.JYSEPS2_1) OR (grid_in.JYSEPS2_2 NE grid_in.ny-1) THEN BEGIN + # PRINT, "Mesh contains branch-cuts. Keeping only core" + # + # grid = core_mesh(grid_in) + # var = core_mesh(var_in, grid_in) + #ENDIF ELSE BEGIN + grid = grid_in + vr = var_in + #ENDELSE + + + #IF KEYWORD_SET(filter) THEN BEGIN + # var = zfilter(var, filter) + #ENDIF + + nx = grid.get('nx') + ny = grid.get('ny') + + s = numpy.shape(vr) + if numpy.size(s) != 3 : + print("Error: Variable must be 3 dimensional") + return + + if (s[0] != nx) or (s[1] != ny) : + print("Error: Size of variable doesn't match grid") + + return + + nz = s[2] + + dz = 2.0*numpy.pi / numpy.float(period*(nz-1)) + + # GET THE TOROIDAL SHIFT + tn = list(grid.keys()) + tn = numpy.char.upper(tn) + count = numpy.where(tn == "QINTY") + if numpy.size(count) > 0 : + print("Using qinty as toroidal shift angle") + zShift = grid.get('qinty') + else: + count = numpy.where(tn == "ZSHIFT") + if numpy.size(count) > 0 : + print("Using zShift as toroidal shift angle") + zShift = grid.get('zShift') + else: + print("ERROR: Can't find qinty or zShift variable") + return + + zshift=grid.get('zShift') + + rxy=grid.get('Rxy') + zxy=grid.get('Zxy') + Btxy=grid.get('Btxy') + Bpxy=grid.get('Bpxy') + shiftangle=grid.get('ShiftAngle') + psixy=grid.get('psixy') + psi_axis=grid.get('psi_axis') + psi_bndry=grid.get('psi_bndry') + + np = 4*ny + + nf = old_div((np - 2), 2) + famp = numpy.zeros((nx, nf)) + + for x in range (nx): + #;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + # transform data into fixed poloidal angle + + # get number of poloidal points + nskip = numpy.zeros(ny-1) + for y in range (ny-1): + yp = y + 1 + nskip[y] = old_div(numpy.abs(zshift[x,yp] - zshift[x,y]), dz) - 1 + + + nskip =numpy.int_(numpy.round(nskip)) + nskip=numpy.where(nskip > 0, nskip, 0) + + + ny2 = numpy.int_(ny + numpy.sum(nskip)) # number of poloidal points + + # IF NOT KEYWORD_SET(quiet) THEN PRINT, x, ny2 + + f = numpy.zeros(ny2) # array for values + R = numpy.zeros(ny2) # Rxy + Z = numpy.zeros(ny2) # Zxy + BtBp = numpy.zeros(ny2) # Bt / Bp + + # interpolate values onto points + + ypos = 0 + for y in range(ny-1): + # original points + zind = old_div((zangle - zshift[x,y]),dz) + + + if numpy.size(zind) != 1 : sys.exit() + f[ypos] = zinterp(vr[x,y,:], zind) + R[ypos] = rxy[x,y] + Z[ypos] = zxy[x,y] + BtBp[ypos] = old_div(Btxy[x,y], Bpxy[x,y]) + + ypos = ypos + 1 + + # add the extra points + + zi0 = old_div((zangle - zshift[x,y]),dz) + zip1 = old_div((zangle - zshift[x,y+1]),dz) + + dzi = old_div((zip1 - zi0), (nskip[y] + 1)) + + for i in range (nskip[y]): + zi = zi0 + numpy.float(i+1)*dzi # zindex + w = old_div(numpy.float(i+1),numpy.float(nskip[y]+1)) # weighting + + f[ypos+i] = w*zinterp(vr[x,y+1,:], zi) + (1.0-w)*zinterp(vr[x,y,:], zi) + + R[ypos+i] = w*rxy[x,y+1] + (1.0-w)*rxy[x,y] + Z[ypos+i] = w*zxy[x,y+1] + (1.0-w)*zxy[x,y] + BtBp[ypos+i] = old_div((w*Btxy[x,y+1] + (1.0-w)*Btxy[x,y]), (w*Bpxy[x,y+1] + (1.0-w)*Bpxy[x,y])) + + ypos = ypos + nskip[y] + + # final point + + zind = old_div((zangle - zShift[x,ny-1]),dz) + + f[ypos] = zinterp(vr[x,ny-1,:], zind) + R[ypos] = rxy[x,ny-1] + Z[ypos] = zxy[x,ny-1] + BtBp[ypos] = old_div(Btxy[x,ny-1], Bpxy[x,ny-1]) + + + #STOP + + #;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + #; calculate poloidal angle + + + drxy = numpy.gradient(R) + dzxy = numpy.gradient(Z) + dl = numpy.sqrt(drxy*drxy + dzxy*dzxy) + + nu = dl * BtBp / R # field-line pitch + theta = old_div(numpy.real(fft_integrate(nu)), shiftangle[x]) + + if numpy.max(theta) > 1.0 : + # mis-match between q and nu (integration error?) + if quiet is None : print("Mismatch ", x, numpy.max(theta)) + theta = old_div(theta, (numpy.max(theta) + numpy.abs(theta[1] - theta[0]))) + + + theta = 2.0*numpy.pi * theta + + #;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + #; take Fourier transform in theta angle + + tarr = 2.0*numpy.pi*numpy.arange(np) / numpy.float(np) # regular array in theta + + farr = numpy.interp(tarr, theta, f) + + #STOP + + ff = old_div(numpy.fft.fft(farr),numpy.size(farr)) + + for i in range (nf): + famp[x, i] = 2.0*numpy.abs(ff[i+1]) + + + + + # sort modes by maximum size + + fmax = numpy.zeros(nf) + for i in range(nf): + fmax[i] = numpy.max(famp[:,i]) + + + inds = numpy.argsort(fmax)[::-1] + + + if pmodes is None : pmodes = 10 + + qprof = old_div(numpy.abs(shiftangle), (2.0*numpy.pi)) + + xarr = numpy.arange(nx) + xtitle="Radial index" + if xq is not None : + # show as a function of q*n + xarr = qprof*numpy.float(n) + + xtitle="q * n" + elif xpsi is not None : + # show as a function of psi. Should be normalised psi + xarr = psixy[:,0] + + # Check if the grid includes psi axis and boundary + count1 = numpy.where(tn == "PSI_AXIS") + count2 = numpy.where(tn == "PSI_BNDRY") + + if (numpy.size(count1) > 0) and (numpy.size(count2) > 0) : + xarr = old_div((xarr - psi_axis), (psi_bndry - psi_axis)) + + else: + # Use hard-wired values + print("WARNING: Using hard-wired psi normalisation") + # for circular case + #xarr = (xarr + 0.1937) / (0.25044 + 0.1937) + # for ellipse case + #xarr = xarr / 0.74156 + + # cbm18_dens8 + xarr = old_div((xarr + 0.854856), (0.854856 + 0.0760856)) + + + xtitle="Psi normalised" + + + + if slow is not None : + # plot modes slowly for examination + #safe_colors, /first +# ax = fig.add_subplot(111) + # go through and plot each mode + for i in range(nf): + if numpy.max(famp[:,i]) > 0.05*numpy.max(famp): + print("Mode m = ", i+1, " of ", nf) + plot(xarr, famp[:,i], 'k') + ylim(0,numpy.max(famp)) + xlim(xrange) + xlabel(xtitle) + show(block=False) + + q = old_div(numpy.float(i+1), numpy.float(n)) + + pos = numpy.interp(q, qprof, xarr) + + plot( [pos, pos],[0, 2.*numpy.max(fmax)], 'k--') + draw() + + ans=query_yes_no('next mode') + if ans: + clf() + + + + elif ergos is not None : + # ERGOS - style output + + if output is not None and slow is None : + savefig('output.png') + + +# +# contour2, famp, xarr, indgen(nf)+1, $ +# xlabel=xtitle, xrange=xrange, yrange=yrange, _extra=_extra +# +# ; overplot the q profile +# +# oplot, xarr, qprof * n, color=1, thick=2 +# +# IF KEYWORD_SET(rational) THEN BEGIN +# maxm = FIX(MAX(qprof)) * n +# +# qreson = (FINDGEN(maxm)+1) / FLOAT(n) +# +# ; get x location for each of these resonances +# qloc = INTERPOL(xarr, qprof, qreson) +# +# oplot, qloc, findgen(maxm)+1., psym=4, color=1 +# ENDIF +# +# IF KEYWORD_SET(output) THEN BEGIN +# ; output data to save file +# SAVE, xarr, qprof, famp, file=output+".idl" +# +# DEVICE, /close +# SET_PLOT, 'X' +# ENDIF + + else: + if output is not None and slow is None : + savefig('output.png') + # savefig('output.ps') + + # + # + if subset is not None : + + # get number of modes larger than 5% of the maximum + count = numpy.size(numpy.where(fmax > 0.10*numpy.max(fmax))) + + minind = numpy.min(inds[0:count]) + maxind = numpy.max(inds[0:count]) + + print("Mode number range: ", minind, maxind) + + plot( xarr, famp[:,0], 'k', visible=False) + ylim(0,numpy.max(famp)) + xlabel(xtitle) + xlim(xrange) + title(ftitle) + + gca().set_color_cycle(['red', 'red', 'black', 'black']) + + for i in range(minind, maxind+1, subset): + plot( xarr, famp[:,i]) + + q = old_div(numpy.float(i+1), numpy.float(n)) + pos = numpy.interp(q, qprof, xarr) + + plot( [pos, pos], [0, 2.*numpy.max(fmax)], '--') + + + # + else: + # default - just plot everything + gca().set_color_cycle(['black', 'red']) + + plot(xarr, famp[:,0]) + ylim(0,numpy.max(famp)) #, color=1, + xlabel(xtitle) #, chars=1.5, xrange=xrange,title=title, _extra=_extra + xlim(xrange) + for i in range (nf): + plot( xarr, famp[:,i]) + + + # + # IF KEYWORD_SET(addq) THEN BEGIN + # + # FOR i=0, pmodes-1 DO BEGIN + # PRINT, "m = "+STRTRIM(STRING(inds[i]+1), 2)+" amp = "+STRTRIM(STRING(fmax[inds[i]]),2) + # q = FLOAT(inds[i]+1) / FLOAT(n) + # + # pos = INTERPOL(xarr, qprof, q) + # + # oplot, [pos, pos], [0, 2.*MAX(fmax)], lines=2, color=1 + # ENDFOR + # ENDIF + # + # ENDELSE + # IF KEYWORD_SET(output) THEN BEGIN + # ; output data to save file + # SAVE, xarr, qprof, famp, file=output+".idl" + # + # DEVICE, /close + # SET_PLOT, 'X' + # ENDIF + # ENDELSE + # diff --git a/boututils/moment_xyzt.py b/boututils/moment_xyzt.py new file mode 100644 index 0000000..8d7dc4c --- /dev/null +++ b/boututils/moment_xyzt.py @@ -0,0 +1,80 @@ +from __future__ import print_function +from __future__ import division +from builtins import range +from past.utils import old_div +import numpy as np +from bunch import Bunch + + +def RMSvalue( vec1d): +#; +#; -get rms of a 1D signal +#;------------------------ + + nel=np.size(vec1d) + valav=old_div(np.sum(vec1d),nel) + valrms=np.sqrt(old_div(np.sum((vec1d-valav)**2),nel)) + acvec=vec1d-valav + + + return Bunch(valrms=valrms, valav=valav, acvec=acvec) + + + + +def moment_xyzt( sig_xyzt, *args):#rms=None, dc=None, ac=None): +#; +#; Calculate moments of a 4d signal of (x,y,z,t), i.e, +#; -RMS, i.e., a function of (x,y,t) +#; -DC (average in z), i.e., a function of (x,y,t) +#; -AC (DC subtracted out), i.e., a function of (x,y,z,t) +#;------------------------------------------------------------------- + + try: # return to caller + + d = np.shape(sig_xyzt) + if np.size(d) != 4 : + print("Error: Variable must be 4D (x,y,z,t)") + return + + + siz=np.shape(sig_xyzt) + rms=np.zeros((siz[0],siz[1],siz[2])) + dc=np.zeros((siz[0],siz[1],siz[2])) + if 'AC' in args : ac=np.zeros((siz[0],siz[1],siz[2],siz[3])) + + + data = sig_xyzt + if np.modf(np.log2(siz[3]))[0] != 0.0 : + print("WARNING: Expecting a power of 2 in Z direction") + + if np.modf(np.log2(siz[3]-1))[0] and (siz[3] > 1) : + print(" -> Truncating last point to get power of 2") + data = data[:,:,0:(siz[3]-2),:] + siz[3] = siz[3] - 1 + + + for ix in range (siz[1]): + for iy in range (siz[2]): + for it in range (siz[0]): + val=RMSvalue(sig_xyzt[it,ix,iy,:]) + + rms[it,ix,iy]=val.valrms + dc[it,ix,iy]=val.valav + if 'AC' in args : ac[it,ix,iy,:]=[val.acvec,val.acvec[0]] + + res=Bunch() + + if 'RMS' in args: + res.rms = rms + if 'DC' in args: + res.dc = dc + if 'AC' in args: + res.ac = ac + + if 'RMS' not in args and 'DC' not in args and 'AC' not in args : + print('Wrong argument') + return res + except: + print('moment_xyz failed') + return diff --git a/boututils/options.py b/boututils/options.py new file mode 100644 index 0000000..2de3ebc --- /dev/null +++ b/boututils/options.py @@ -0,0 +1,165 @@ +"""Module to allow BOUT.inp files to be read into python and +manipulated with ease. + + +Nick Walkden, June 2015 +nick.walkden@ccfe.ac.uk + +""" + +from copy import copy +import os + + +class BOUTOptions(object): + """Class to store and interact with options from BOUT++ + + Parameters + ---------- + inp_path : str, optional + Path to BOUT++ options file + + Examples + -------- + + Instantiate with + + >>> myOpts = BOUTOptions() + >>> myOpts.read_inp('path/to/input/file') + + or + + >>> myOpts = BOUTOptions('path/to/input/file') + + To get a list of sections use + + >>> section_list = myOpts.list_sections + >>> # Also print to screen: + >>> section_list = myOpts.list_sections(verbose=True) + + Each section of the input is stored as a dictionary attribute so + that, if you want all the settings in the section [ddx]: + + >> ddx_opt_dict = myOpts.ddx + + and access individual settings by + + >>> ddx_setting = myOpts.ddx['first'] + + Any settings in BOUT.inp without a section are stored in + + >>> root_dict = myOpts.root + + TODO + ---- + - Merge this and BoutOptionsFile or replace with better class + + """ + + def __init__(self, inp_path=None): + + self._sections = ['root'] + + for section in self._sections: + super(BOUTOptions,self).__setattr__(section,{}) + + if inp_path is not None: + self.read_inp(inp_path) + + def read_inp(self, inp_path=''): + """Read a BOUT++ input file + + Parameters + ---------- + inp_path : str, optional + Path to the input file (default: current directory) + + """ + + try: + inpfile = open(os.path.join(inp_path, 'BOUT.inp'),'r') + except: + raise TypeError("ERROR: Could not read file "+\ + os.path.join(inp_path, "BOUT.inp")) + + current_section = 'root' + inplines = inpfile.read().splitlines() + # Close the file after use + inpfile.close() + for line in inplines: + #remove white space + line = line.replace(" ","") + + + if len(line) > 0 and line[0] is not '#': + #Only read lines that are not comments or blank + if '[' in line: + #Section header + section = line.split('[')[1].split(']')[0] + current_section = copy(section) + if current_section not in self._sections: + self.add_section(current_section) + + elif '=' in line: + #option setting + attribute = line.split('=')[0] + value = line.split('=')[1].split('#')[0] + value = value.replace("\n","") + value = value.replace("\t","") + value = value.replace("\r","") + value = value.replace("\"","") + self.__dict__[copy(current_section)][copy(attribute)] = copy(value) + else: + pass + + def add_section(self, section): + """Add a section to the options + + Parameters + ---------- + section : str + The name of a new section + + TODO + ---- + - Guard against wrong type + """ + self._sections.append(section) + super(BOUTOptions,self).__setattr__(section,{}) + + def remove_section(self, section): + """Remove a section from the options + + Parameters + ---------- + section : str + The name of a section to remove + + TODO + ---- + - Fix undefined variable + """ + if section in self._sections: + self._sections.pop(self._sections.index(sections)) + super(BOUTOptions,self).__delattr__(section) + else: + print("WARNING: Section "+section+" not found.\n") + + def list_sections(self, verbose=False): + """Return all the sections in the options + + Parameters + ---------- + verbose : bool, optional + If True, print sections to screen + + TODO + ---- + - Better pretty-print + """ + if verbose: + print("Sections Contained: \n") + for section in self._sections: + print("\t"+section+"\n") + + return self._sections diff --git a/boututils/plotdata.py b/boututils/plotdata.py new file mode 100644 index 0000000..72627f4 --- /dev/null +++ b/boututils/plotdata.py @@ -0,0 +1,90 @@ +from __future__ import print_function +# Plot a data set + +import numpy as np +import matplotlib +import matplotlib.cm as cm +import matplotlib.mlab as mlab +import matplotlib.pyplot as plt + +matplotlib.rcParams['xtick.direction'] = 'out' +matplotlib.rcParams['ytick.direction'] = 'out' + +def plotdata(data, x=None, y=None, + title=None, xtitle=None, ytitle=None, + output=None, range=None, + fill=True, mono=False, colorbar=True, + xerr=None, yerr=None): + """Plot 1D or 2D data, with a variety of options.""" + + size = data.shape + ndims = len(size) + + if ndims == 1: + if (xerr is not None) or (yerr is not None): + # Points with error bars + if x is None: + x = np.arange(size) + errorbar(x, data, xerr, yerr) + # Line plot + if x is None: + plt.plot(data) + else: + plt.plot(x, data) + + elif ndims == 2: + # A contour plot + + if x is None: + x = np.arange(size[1]) + if y is None: + y = np.arange(size[0]) + + if fill: + #plt.contourf(data, colors=colors) + cmap=None + if mono: cmap = cm.gray + plt.imshow(data, interpolation='bilinear', cmap=cmap) + else: + colors = None + if mono: colors = 'k' + + plt.contour(x, y, data, colors=colors) + + # Add a color bar + if colorbar: + CB = plt.colorbar(shrink=0.8, extend='both') + + else: + print("Sorry, can't handle %d-D variables" % ndims) + return + + if title is not None: + plt.title(title) + if xtitle is not None: + plt.xlabel(xtitle) + if ytitle is not None: + plt.ylabel(ytitle) + + if output is not None: + # Write to a file + plt.savefig(output) + else: + # Plot to screen + plt.show() + +def test(): + """Test the plotdata routine.""" + # Generate and plot test data + + delta = 0.025 + x = np.arange(-3.0, 3.0, delta) + y = np.arange(-2.0, 2.0, delta) + X, Y = np.meshgrid(x, y) + Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0) + Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1) + # difference of Gaussians + Z = 10.0 * (Z2 - Z1) + + plotdata(Z, title="test data", fill=False, mono=False) + plotdata(Z, title="Fill in mono", fill=True, mono=True) diff --git a/boututils/plotpolslice.py b/boututils/plotpolslice.py new file mode 100644 index 0000000..924272f --- /dev/null +++ b/boututils/plotpolslice.py @@ -0,0 +1,143 @@ +from __future__ import print_function +from __future__ import division +from builtins import str +from builtins import range +from past.utils import old_div +import numpy as np +from boututils.file_import import file_import +import sys + +if sys.version_info[0]>=3: + message = "polplotslice uses the VTK library through mayavi, which"+\ + " is currently only available in python 2" + raise ImportError(message) +else: + from mayavi import mlab + from tvtk.tools import visual + + + +def zinterp( v, zind): + #v = REFORM(v) + nz = np.size(v) + z0 = np.round(zind) + + p = zind - float(z0) # between -0.5 and 0.5 + + if p < 0.0 : + z0 = z0 - 1 + p = p + 1.0 + + + z0 = ((z0 % (nz-1)) + (nz-1)) % (nz-1) + + # for now 3-point interpolation + + zp = (z0 + 1) % (nz - 1) + zm = (z0 - 1 + (nz-1)) % (nz - 1) + + result = 0.5*p*(p-1.0)*v[zm] \ + + (1.0 - p*p)*v[z0] \ + + 0.5*p*(p+1.0)*v[zp] + + return result + + +def plotpolslice(var3d,gridfile,period=1,zangle=0.0, rz=1, fig=0): + """ data2d = plotpolslice(data3d, 'gridfile' , period=1, zangle=0.0, rz:return (r,z) grid also=1, fig: to do the graph, set to 1 ) """ + + g=file_import(gridfile) + + nx=var3d.shape[0] + ny=var3d.shape[1] + nz=var3d.shape[2] + + + zShift=g.get('zShift') + rxy=g.get('Rxy') + zxy=g.get('Zxy') + + dz = 2.0*np.pi / float(period*nz) + + ny2=ny + nskip=np.zeros(ny-1) + for i in range(ny-1): + ip=(i+1)%ny + nskip[i]=0 + for x in range(nx): + ns=old_div(np.max(np.abs(zShift[x,ip]-zShift[x,i])),dz)-1 + if ns > nskip[i] : nskip[i] = ns + + nskip = np.int_(np.round(nskip)) + ny2 = np.int_(ny2 + np.sum(nskip)) + + print("Number of poloidal points in output:" + str(ny2)) + + var2d = np.zeros((nx, ny2)) + r = np.zeros((nx, ny2)) + z = np.zeros((nx, ny2)) + + ypos = 0 + for y in range (ny-1) : + # put in the original points + for x in range (nx): + zind = old_div((zangle - zShift[x,y]),dz) + var2d[x,ypos] = zinterp(var3d[x,y,:], zind) + # IF KEYWORD_SET(profile) THEN var2d[x,ypos] = var2d[x,ypos] + profile[x,y] + r[x,ypos] = rxy[x,y] + z[x,ypos] = zxy[x,y] + + ypos = ypos + 1 + + print((y, ypos)) + + # and the extra points + + for x in range (nx): + zi0 = old_div((zangle - zShift[x,y]),dz) + zip1 = old_div((zangle - zShift[x,y+1]),dz) + + dzi = old_div((zip1 - zi0), (nskip[y] + 1)) + + for i in range (nskip[y]): + zi = zi0 + float(i+1)*dzi # zindex + w = old_div(float(i+1),float(nskip[y]+1)) # weighting + + var2d[x,ypos+i] = w*zinterp(var3d[x,y+1,:], zi) + (1.0-w)*zinterp(var3d[x,y,:], zi) + # IF KEYWORD_SET(profile) THEN var2d[x,ypos+i] = var2d[x,ypos+i] + w*profile[x,y+1] + (1.0-w)*profile[x,y] + r[x,ypos+i] = w*rxy[x,y+1] + (1.0-w)*rxy[x,y] + z[x,ypos+i] = w*zxy[x,y+1] + (1.0-w)*zxy[x,y] + + + + ypos = ypos + nskip[y] + + + # FINAL POINT + + for x in range(nx): + zind = old_div((zangle - zShift[x,ny-1]),dz) + var2d[x,ypos] = zinterp(var3d[x,ny-1,:], zind) + # IF KEYWORD_SET(profile) THEN var2d[x,ypos] = var2d[x,ypos] + profile[x,ny-1] + r[x,ypos] = rxy[x,ny-1] + z[x,ypos] = zxy[x,ny-1] + + + if(fig==1): + + f = mlab.figure(size=(600,600)) + # Tell visual to use this as the viewer. + visual.set_viewer(f) + + + s = mlab.mesh(r,z,var2d, colormap='PuOr')#, wrap_scale='true')#, representation='wireframe') + s.enable_contours=True + s.contour.filled_contours=True + mlab.view(0,0) + + else: + # return according to opt + if rz==1 : + return r,z,var2d + else: + return var2d diff --git a/boututils/radial_grid.py b/boututils/radial_grid.py new file mode 100644 index 0000000..e0cf9c4 --- /dev/null +++ b/boututils/radial_grid.py @@ -0,0 +1,68 @@ +from __future__ import division +from past.utils import old_div +import numpy +#;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +# +# radial grid +# +# n - number of grid points +# pin, pout - range of psi +# seps - locations of separatrices +# sep_factor - separatrix peaking +# in_dp=in_dp - Fix the dx on the lower side +# out_dp=out_dp - Fix the dx on the upper side + +def radial_grid( n, pin, pout, include_in, include_out, seps, sep_factor, + in_dp=None, out_dp=None): + + if n == 1 : + return [0.5*(pin+pout)] + + + x = numpy.arange(0.,n) + m = numpy.float(n-1) + if include_in is None : + x = x + 0.5 + m = m + 0.5 + + + if include_out is None: + m = m + 0.5 + + x = old_div(x, m) + + + if in_dp is None and out_dp is None : + # Neither inner or outer gradients set. Just return equal spacing + return pin + (pout - pin)*x + + + norm = (x[1] - x[0])*(pout - pin) + + if in_dp is not None and out_dp is not None : + # Fit to dist = a*i^3 + b*i^2 + c*i + c = old_div(in_dp,norm) + b = 3.*(1. - c) - old_div(out_dp,norm) + c + a = 1. - c - b + elif in_dp is not None : + # Only inner set + c = old_div(in_dp,norm) + a = 0.5*(c-1.) + b = 1. - c - a + + #a = 0 + #c = in_dp/norm + #b = 1. - c + else: + # Only outer set. Used in PF region + # Fit to (1-b)*x^a + bx for fixed b + df = old_div(out_dp, norm) + b = 0.25 < df # Make sure a > 0 + a = old_div((df - b), (1. - b)) + vals = pin + (pout - pin)*( (1.-b)*x^a + b*x ) + return vals + + + vals = pin + (pout - pin)*(c*x + b*x^2 + a*x^3) + #STOP + return vals diff --git a/boututils/read_geqdsk.py b/boututils/read_geqdsk.py new file mode 100644 index 0000000..755b3f1 --- /dev/null +++ b/boututils/read_geqdsk.py @@ -0,0 +1,92 @@ +from __future__ import print_function +from builtins import range +import numpy +from bunch import Bunch +from geqdsk import Geqdsk + +def read_geqdsk (file): + + data=Geqdsk() + + data.openFile(file) + + nxefit =data.get('nw') + nyefit =data.get('nh') + xdim =data.get('rdim') + zdim =data.get('zdim') + rcentr =data.get('rcentr') + rgrid1 =data.get('rleft') + zmid =data.get('zmid') + + rmagx =data.get('rmaxis') + zmagx =data.get('zmaxis') + simagx =data.get('simag') + sibdry =data.get('sibry') + bcentr =data.get('bcentr') + + cpasma =data.get('current') + #simagx =data.get('simag') + #xdum =data.get() + #rmagx =data.get('rmaxis') + #xdum =data.get() + + #zmagx =data.get('zmaxis') + #xdum =data.get() + #sibdry =data.get('sibry') + #xdum =data.get() + #xdum =data.get() + +# Read arrays + + fpol=data.get('fpol') + pres=data.get('pres') + + f=data.get('psirz') + qpsi=data.get('qpsi') + + nbdry=data.get('nbbbs') + nlim=data.get('limitr') + + if(nlim != 0) : + xlim=data.get('rlim') + ylim=data.get('zlim') + else: + xlim=[0] + ylim=[0] + + rbdry=data.get('rbbbs') + zbdry=data.get('zbbbs') + + + # Reconstruct the (R,Z) mesh + r=numpy.zeros((nxefit, nyefit), numpy.float64) + z=numpy.zeros((nxefit, nyefit), numpy.float64) + + + for i in range(0,nxefit): + for j in range(0,nyefit): + r[i,j] = rgrid1 + xdim*i/(nxefit-1) + z[i,j] = (zmid-0.5*zdim) + zdim*j/(nyefit-1) + + + + f=f.T + + print('nxefit = ', nxefit, ' nyefit= ', nyefit) + + return Bunch(nx=nxefit, ny=nyefit, # Number of horizontal and vertical points + r=r, z=z, # Location of the grid-points + xdim=xdim, zdim=zdim, # Size of the domain in meters + rcentr=rcentr, bcentr=bcentr, # Reference vacuum toroidal field (m, T) + rgrid1=rgrid1, # R of left side of domain + zmid=zmid, # Z at the middle of the domain + rmagx=rmagx, zmagx=zmagx, # Location of magnetic axis + simagx=simagx, # Poloidal flux at the axis (Weber / rad) + sibdry=sibdry, # Poloidal flux at plasma boundary (Weber / rad) + cpasma=cpasma, # + psi=f, # Poloidal flux in Weber/rad on grid points + fpol=fpol, # Poloidal current function on uniform flux grid + pres=pres, # Plasma pressure in nt/m^2 on uniform flux grid + qpsi=qpsi, # q values on uniform flux grid + nbdry=nbdry, rbdry=rbdry, zbdry=zbdry, # Plasma boundary + nlim=nlim, xlim=xlim, ylim=ylim) # Wall boundary \ No newline at end of file diff --git a/boututils/run_wrapper.py b/boututils/run_wrapper.py new file mode 100644 index 0000000..5e05052 --- /dev/null +++ b/boututils/run_wrapper.py @@ -0,0 +1,279 @@ +"""Collection of functions which can be used to make a BOUT++ run""" + +from builtins import str +import os +import re +import subprocess + +try: + # Python 2.4 onwards + from subprocess import call, Popen, STDOUT, PIPE + lib = "call" +except ImportError: + # FIXME: drop support for python < 2.4! + # Use os.system (depreciated) + from os import popen4, system + lib = "system" + + +def getmpirun(default="mpirun -np"): + """Return environment variable named MPIRUN, if it exists else return + a default mpirun command + + Parameters + ---------- + default : str, optional + An mpirun command to return if ``MPIRUN`` is not set in the environment + + """ + MPIRUN = os.getenv("MPIRUN") + + if MPIRUN is None or MPIRUN == "": + MPIRUN = default + print("getmpirun: using the default " + str(default)) + + return MPIRUN + + +def shell(command, pipe=False): + """Run a shell command + + Parameters + ---------- + command : list of str + The command to run, split into (shell) words + pipe : bool, optional + Grab the output as text, else just run the command in the + background + + Returns + ------- + tuple : (int, str) + The return code, and either command output if pipe=True else None + """ + output = None + status = 0 + if lib == "system": + if pipe: + handle = popen4(command) + output = handle[1].read() + else: + status = system(command) + else: + if pipe: + child = Popen(command, stderr=STDOUT, stdout=PIPE, shell=True) + # This returns a b'string' which is casted to string in + # python 2. However, as we want to use f.write() in our + # runtest, we cast this to utf-8 here + output = child.stdout.read().decode("utf-8", "ignore") + # Wait for the process to finish. Note that child.wait() + # would have deadlocked the system as stdout is PIPEd, we + # therefore use communicate, which in the end also waits for + # the process to finish + child.communicate() + status = child.returncode + else: + status = call(command, shell=True) + + return status, output + + +def determineNumberOfCPUs(): + """Number of virtual or physical CPUs on this system + + i.e. user/real as output by time(1) when called with an optimally + scaling userspace-only program + + Taken from a post on stackoverflow: + http://stackoverflow.com/questions/1006289/how-to-find-out-the-number-of-cpus-in-python + + Returns + ------- + int + The number of CPUs + """ + + # Python 2.6+ + try: + import multiprocessing + return multiprocessing.cpu_count() + except (ImportError,NotImplementedError): + pass + + # POSIX + try: + res = int(os.sysconf('SC_NPROCESSORS_ONLN')) + + if res > 0: + return res + except (AttributeError,ValueError): + pass + + # Windows + try: + res = int(os.environ['NUMBER_OF_PROCESSORS']) + + if res > 0: + return res + except (KeyError, ValueError): + pass + + # jython + try: + from java.lang import Runtime + runtime = Runtime.getRuntime() + res = runtime.availableProcessors() + if res > 0: + return res + except ImportError: + pass + + # BSD + try: + sysctl = subprocess.Popen(['sysctl', '-n', 'hw.ncpu'], + stdout=subprocess.PIPE) + scStdout = sysctl.communicate()[0] + res = int(scStdout) + + if res > 0: + return res + except (OSError, ValueError): + pass + + # Linux + try: + res = open('/proc/cpuinfo').read().count('processor\t:') + + if res > 0: + return res + except IOError: + pass + + # Solaris + try: + pseudoDevices = os.listdir('/devices/pseudo/') + expr = re.compile('^cpuid@[0-9]+$') + + res = 0 + for pd in pseudoDevices: + if expr.match(pd) is not None: + res += 1 + + if res > 0: + return res + except OSError: + pass + + # Other UNIXes (heuristic) + try: + try: + dmesg = open('/var/run/dmesg.boot').read() + except IOError: + dmesgProcess = subprocess.Popen(['dmesg'], stdout=subprocess.PIPE) + dmesg = dmesgProcess.communicate()[0] + + res = 0 + while '\ncpu' + str(res) + ':' in dmesg: + res += 1 + + if res > 0: + return res + except OSError: + pass + + raise Exception('Can not determine number of CPUs on this system') + + +def launch(command, runcmd=None, nproc=None, mthread=None, + output=None, pipe=False, verbose=False): + """Launch parallel MPI jobs + + >>> status = launch(command, nproc, output=None) + + Parameters + ---------- + command : str + The command to run + runcmd : str, optional + Command for running parallel job; defaults to what getmpirun() returns" + nproc : int, optional + Number of processors (default: all available processors) + mthread : int, optional + Number of omp threads (default: the value of the + ``OMP_NUM_THREADS`` environment variable + output : str, optional + Name of file to save output to + pipe : bool, optional + If True, return the output of the command + verbose : bool, optional + Print the full command to be run before running it + + Returns + ------- + tuple : (int, str) + The return code, and either command output if pipe=True else None + + """ + + if runcmd is None: + runcmd = getmpirun() + + if nproc is None: + # Determine number of CPUs on this machine + nproc = determineNumberOfCPUs() + + cmd = runcmd + " " + str(nproc) + " " + command + + if output is not None: + cmd = cmd + " > "+output + + if mthread is not None: + cmd = "OMP_NUM_THREADS={j} ".format(j=mthread)+cmd + + if verbose == True: + print(cmd) + + return shell(cmd, pipe=pipe) + + +def shell_safe(command, *args, **kwargs): + """'Safe' version of shell. + + Raises a `RuntimeError` exception if the command is not + successful + + Parameters + ---------- + command : str + The command to run + *args, **kwargs + Optional arguments passed to `shell` + + """ + s, out = shell(command,*args,**kwargs) + if s: + raise RuntimeError("Run failed with %d.\nCommand was:\n%s\n\n" + "Output was\n\n%s"% + (s,command,out)) + return s, out + + +def launch_safe(command, *args, **kwargs): + """'Safe' version of launch. + + Raises an RuntimeError exception if the command is not successful + + Parameters + ---------- + command : str + The command to run + *args, **kwargs + Optional arguments passed to `shell` + + """ + s, out = launch(command,*args,**kwargs) + if s: + raise RuntimeError("Run failed with %d.\nCommand was:\n%s\n\n" + "Output was\n\n%s"% + (s,command,out)) + return s, out diff --git a/boututils/showdata.py b/boututils/showdata.py new file mode 100644 index 0000000..9959402 --- /dev/null +++ b/boututils/showdata.py @@ -0,0 +1,702 @@ +""" +Visualisation and animation routines + +Written by Luke Easy +le590@york.ac.uk +Last Updated 19/3/2015 +Additional functionality by George Breyiannis 26/12/2014 + +""" +from __future__ import print_function +from __future__ import division +from builtins import str, chr, range + +from matplotlib import pyplot as plt +from matplotlib import animation +from numpy import linspace, meshgrid, array, min, max, abs, floor, pi, isclose +from boutdata.collect import collect +from boututils.boutwarnings import alwayswarn + +#################################################################### +# Specify manually ffmpeg path +#plt.rcParams['animation.ffmpeg_path'] = '/usr/bin/ffmpeg' + +FFwriter = animation.FFMpegWriter() +#################################################################### + + +################### +#http://stackoverflow.com/questions/16732379/stop-start-pause-in-python-matplotlib-animation +# +j=-2 +pause = False +################### + + +def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_array=None, + movie=0, fps=28, dpi=200, intv=1, Ncolors=25, x=[], y=[], + global_colors=False, symmetric_colors=False, hold_aspect=False, + cmap=None, clear_between_frames=None, return_animation=False, window_title=""): + """A Function to animate time dependent data from BOUT++ + + To animate multiple variables on different axes: + + >>> showdata([var1, var2, var3]) + + To animate more than one line on a single axes: + + >>> showdata([[var1, var2, var3]]) + + The default graph types are: + 2D (time + 1 spatial dimension) arrays = animated line plot + 3D (time + 2 spatial dimensions) arrays = animated contour plot. + + To use surface or polar plots: + + >>> showdata(var, surf=1) + >>> showdata(var, polar=1) + + Can plot different graph types on different axes. Default graph types will + be used depending on the dimensions of the input arrays. To specify + polar/surface plots on different axes: + + >>> showdata([var1, var2], surf=[1, 0], polar=[0, 1]) + + Movies require FFmpeg (for .mp4) and/or ImageMagick (for .gif) to be + installed. The 'movie' option can be set to 1 (which will produce an mp4 + called 'animation.mp4'), to a name with no extension (which will produce an + mp4 called '.mp4') + + The `tslice` variable is used to control the time value that is printed on + each frame of the animation. If the input data matches the time values + found within BOUT++'s dmp data files, then these time values will be used. + Otherwise, an integer counter is used. + + The `cmap` variable (if specified) will set the colormap used in the plot + cmap must be a matplotlib colormap instance, or the name of a registered + matplotlib colormap + + During animation click once to stop in the current frame. Click again to + continue. + + Parameters + ---------- + vars : array_like or list of array_like + Variable or list of variables to plot + titles : str or list of str, optional + Title or list of titles for each axis + legendlabels : str or list of str, optional + Legend or list of legends for each variable + surf : list of int + Which axes to plot as a surface plot + polar : list of int + Which axes to plot as a polar plot + tslice : list of int + Use these time values from a dump file (see above) + t_array : array + Pass in t_array using this argument to use the simulation time in plot + titles. Otherwise, just use the t-index. + movie : int + If 1, save the animation to file + fps : int + Number of frames per second to use when saving animation + dpi : int + Dots per inch to use when saving animation + intv : int + ??? + Ncolors : int + Number of levels in contour plots + x, y : array_like, list of array_like + X, Y coordinates + global_colors : bool + If "vars" is a list the colorlevels are determined from the + maximum of the maxima and and the minimum of the minima in all + fields in vars + symmetric_colors : bool + Colour levels are symmetric + hold_aspect : bool + Use equal aspect ratio in plots + cmap : colormap + A matplotlib colormap instance to use + clear_between_frames : bool, optional + - Default (None) - all plots except line plots will clear between frames + - True - all plots will clear between frames + - False - no plots will clear between frames + return_animation : bool + Return the matplotlib animation instance + window_title : str + Give a title for the animation window + + TODO + ---- + - Replace empty lists in signature with None + - Use bools in sensible places + - Put massive list of arguments in kwargs + - Speed up animations ???? + - Look at theta in polar plots - periodic?!? + - Log axes, colorbars + - Figureplot + + """ + plt.ioff() + + # Check to see whether vars is a list or not. + if isinstance(vars, list): + Nvar = len(vars) + else: + vars = [vars] + Nvar = len(vars) + + if Nvar < 1: + raise ValueError("No data supplied") + + # Check to see whether each variable is a list - used for line plots only + Nlines = [] + for i in range(0, Nvar): + if isinstance(vars[i], list): + Nlines.append(len(vars[i])) + else: + Nlines.append(1) + vars[i] = [vars[i]] + + # Sort out titles + if len(titles) == 0: + for i in range(0,Nvar): + titles.append(('Var' + str(i+1))) + elif len(titles) != Nvar: + raise ValueError('The length of the titles input list must match the length of the vars list.') + + # Sort out legend labels + if len(legendlabels) == 0: + for i in range(0,Nvar): + legendlabels.append([]) + for j in range(0,Nlines[i]): + legendlabels[i].append(chr(97+j)) + elif (isinstance(legendlabels[0], list) != 1): + if Nvar != 1: + check = 0 + for i in range(0,Nvar): + if len(legendlabels) != Nlines[i]: + check = check+1 + if check == 0: + alwayswarn("The legendlabels list does not contain a sublist for each variable, but its length matches the number of lines on each plot. Will apply labels to each plot") + legendlabelsdummy = [] + for i in range(0, Nvar): + legendlabelsdummy.append([]) + for j in range(0,Nlines[i]): + legendlabelsdummy[i].append(legendlabels[j]) + legendlabels = legendlabelsdummy + else: + alwayswarn("The legendlabels list does not contain a sublist for each variable, and it's length does not match the number of lines on each plot. Will default apply labels to each plot") + legendlabels = [] + for i in range(0,Nvar): + legendlabels.append([]) + for j in range(0,Nlines[i]): + legendlabels[i].append(chr(97+j)) + else: + if (Nlines[0] == len(legendlabels)): + legendlabels = [legendlabels] + elif len(legendlabels) != Nvar: + alwayswarn("The length of the legendlabels list does not match the length of the vars list, will continue with default values") + legendlabels = [] + for i in range(0,Nvar): + legendlabels.append([]) + for j in range(0,Nlines[i]): + legendlabels[i].append(chr(97+j)) + else: + for i in range(0,Nvar): + if isinstance(legendlabels[i], list): + if len(legendlabels[i]) != Nlines[i]: + alwayswarn('The length of the legendlabel (sub)list for each plot does not match the number of datasets for each plot. Will continue with default values') + legendlabels[i] = [] + for j in range(0,Nlines[i]): + legendlabels[i].append(chr(97+j)) + else: + legendlabels[i] = [legendlabels[i]] + if len(legendlabels[i]) != Nlines[i]: + alwayswarn('The length of the legendlabel (sub)list for each plot does not match the number of datasets for each plot. Will continue with default values') + legendlabels[i] = [] + for j in range(0,Nlines[i]): + legendlabels[i].append(chr(97+j)) + + + # Sort out surf list + if isinstance(surf, list): + if (len(surf) == Nvar): + for i in range(0, Nvar): + if surf[i] >= 1: + surf[i] = 1 + else: + surf[i] = 0 + elif (len(surf) == 1): + if surf[0] >= 1: + surf[0] = 1 + else: + surf[0] = 0 + if (Nvar > 1): + for i in range(1,Nvar): + surf.append(surf[0]) + elif (len(surf) == 0): + for i in range(0,Nvar): + surf.append(0) + else: + alwayswarn('Length of surf list does not match number of variables. Will default to no polar plots') + for i in range(0,Nvar): + surf.append(0) + + else: + surf = [surf] + if surf[0] >= 1: + surf[0] = 1 + else: + surf[0] = 0 + if (Nvar > 1): + for i in range(1,Nvar): + surf.append(surf[0]) + + # Sort out polar list + if isinstance(polar, list): + if (len(polar) == Nvar): + for i in range(0, Nvar): + if polar[i] >= 1: + polar[i] = 1 + else: + polar[i] = 0 + elif (len(polar) == 1): + if polar[0] >= 1: + polar[0] = 1 + else: + polar[0] = 0 + if (Nvar > 1): + for i in range(1,Nvar): + polar.append(polar[0]) + elif (len(polar) == 0): + for i in range(0,Nvar): + polar.append(0) + else: + alwayswarn('Length of polar list does not match number of variables. Will default to no polar plots') + for i in range(0,Nvar): + polar.append(0) + else: + polar = [polar] + if polar[0] >= 1: + polar[0] = 1 + else: + polar[0] = 0 + if (Nvar > 1): + for i in range(1,Nvar): + polar.append(polar[0]) + + # Determine shapes of arrays + dims = [] + Ndims = [] + lineplot = [] + contour = [] + for i in range(0,Nvar): + dims.append([]) + Ndims.append([]) + for j in range(0, Nlines[i]): + dims[i].append(array((vars[i][j].shape))) + Ndims[i].append(dims[i][j].shape[0]) + # Perform check to make sure that data is either 2D or 3D + if (Ndims[i][j] < 2): + raise ValueError('data must be either 2 or 3 dimensional. Exiting') + + if (Ndims[i][j] > 3): + raise ValueError('data must be either 2 or 3 dimensional. Exiting') + + if ((Ndims[i][j] == 2) & (polar[i] != 0)): + alwayswarn('Data must be 3 dimensional (time, r, theta) for polar plots. Will plot lineplot instead') + + if ((Ndims[i][j] == 2) & (surf[i] != 0)): + alwayswarn('Data must be 3 dimensional (time, x, y) for surface plots. Will plot lineplot instead') + + if ((Ndims[i][j] == 3) & (Nlines[i] != 1)): + raise ValueError('cannot have multiple sets of 3D (time + 2 spatial dimensions) on each subplot') + + + if ((Ndims[i][j] != Ndims[i][0])): + raise ValueError('Error, Number of dimensions must be the same for all variables on each plot.') + + if (Ndims[i][0] == 2): # Set polar and surf list entries to 0 + polar[i] = 0 + surf[i] = 0 + lineplot.append(1) + contour.append(0) + else: + if ((polar[i] == 1) & (surf[i] == 1)): + alwayswarn('Cannot do polar and surface plots at the same time. Default to contour plot') + contour.append(1) + lineplot.append(0) + polar[i] = 0 + surf[i] = 0 + elif (polar[i] == 1) | (surf[i] == 1): + contour.append(0) + lineplot.append(0) + else: + contour.append(1) + lineplot.append(0) + + # Obtain size of data arrays + Nt = [] + Nx = [] + Ny = [] + for i in range(0, Nvar): + Nt.append([]) + Nx.append([]) + Ny.append([]) + for j in range(0, Nlines[i]): + Nt[i].append(vars[i][j].shape[0]) + Nx[i].append(vars[i][j].shape[1]) + if (Nt[i][j] != Nt[0][0]): + raise ValueError('time dimensions must be the same for all variables.') + + #if (Nx[i][j] != Nx[i][0]): + # raise ValueError('Dimensions must be the same for all variables on each plot.') + + if (Ndims[i][j] == 3): + Ny[i].append(vars[i][j].shape[2]) + #if (Ny[i][j] != Ny[i][0]): + # raise ValueError('Dimensions must be the same for all variables.') + + # Obtain number of frames + Nframes = int(Nt[0][0]/intv) + + # Generate grids for plotting + # Try to use provided grids where possible + # If x and/or y are not lists, apply to all variables + if not isinstance(x, (list,tuple)): + x = [x]*Nvar # Make list of x with length Nvar + if not isinstance(y, (list,tuple)): + y = [y]*Nvar # Make list of x with length Nvar + xnew = [] + ynew = [] + for i in range(0,Nvar): + xnew.append([]) + try: + xnew[i].append(x[i]) + if not (x[i].shape==(Nx[i][0],) or x[i].shape==(Nx[i][0],Ny[i][0]) or x[i].shape==(Nt[i][0],Nx[i][0],Ny[i],[0])): + raise ValueError("For variable number "+str(i)+", "+titles[i]+", the shape of x is not compatible with the shape of the variable. Shape of x should be (Nx), (Nx,Ny) or (Nt,Nx,Ny).") + except: + for j in range(0, Nlines[i]): + xnew[i].append(linspace(0,Nx[i][j]-1, Nx[i][j])) + + #x.append(linspace(0,Nx[i][0]-1, Nx[i][0])) + + if (Ndims[i][0] == 3): + try: + ynew.append(y[i]) + if not (y[i].shape==(Ny[i][0],) or y[i].shape==(Nx[i][0],Ny[i][0]) or y[i].shape==(Nt[i][0],Nx[i][0],Ny[i],[0])): + raise ValueError("For variable number "+str(i)+", "+titles[i]+", the shape of y is not compatible with the shape of the variable. Shape of y should be (Ny), (Nx,Ny) or (Nt,Nx,Ny).") + except: + ynew.append(linspace(0, Ny[i][0]-1, Ny[i][0])) + else: + ynew.append(0) + x = xnew + y = ynew + # Determine range of data. Used to ensure constant colour map and + # to set y scale of line plot. + fmax = [] + fmin = [] + xmax = [] + dummymax = [] + dummymin = [] + clevels = [] + + for i in range(0,Nvar): + + dummymax.append([]) + dummymin.append([]) + for j in range(0,Nlines[i]): + dummymax[i].append(max(vars[i][j])) + dummymin[i].append(min(vars[i][j])) + + fmax.append(max(dummymax[i])) + fmin.append(min(dummymin[i])) + + if(symmetric_colors): + absmax =max(abs(array(fmax[i], fmin[i]))) + fmax[i] = absmax + fmin[i] = -absmax + + for j in range(0,Nlines[i]): + dummymax[i][j] = max(x[i][j]) + xmax.append(max(dummymax[i])) + + + if not (global_colors): + if isclose(fmin[i], fmax[i]): + # add/subtract very small constant in case fmin=fmax=0 + thiscontourmin = fmin[i] - 3.e-15*abs(fmin[i]) - 1.e-36 + thiscontourmax = fmax[i] + 3.e-15*abs(fmax[i]) + 1.e-36 + alwayswarn("Contour levels too close, adding padding to colorbar range") + clevels.append(linspace(thiscontourmin, thiscontourmax, Ncolors)) + else: + clevels.append(linspace(fmin[i], fmax[i], Ncolors)) + + if(global_colors): + fmaxglobal = max(fmax) + fminglobal = min(fmin) + if isclose(fminglobal, fmaxglobal): + fminglobal = fminglobal - 3.e-15*abs(fminglobal) - 1.e-36 + fmaxglobal = fmaxglobal + 3.e-15*abs(fmaxglobal) + 1.e-36 + for i in range(0,Nvar): + clevels.append(linspace(fminglobal, fmaxglobal, Ncolors)) + + # Create figures for animation plotting + if (Nvar < 2): + row = 1 + col = 1 + h = 6.0 + w = 8.0 + elif (Nvar <3): + row = 1 + col = 2 + h = 6.0 + w = 12.0 + elif (Nvar < 5): + row = 2 + col = 2 + h = 8.0 + w = 12.0 + + elif (Nvar < 7): + row = 2 + col = 3 + h = 8.0 + w = 14.0 + + elif (Nvar < 10) : + row = 3 + col = 3 + h = 12.0 + w = 14.0 + else: + raise ValueError('too many variables...') + + + fig = plt.figure(window_title, figsize=(w,h)) + title = fig.suptitle(r' ', fontsize=14 ) + + # Initiate all list variables required for plotting here + ax = [] + lines = [] + plots = [] + cbars = [] + xstride = [] + ystride = [] + r = [] + theta = [] + + + # Initiate figure frame + for i in range(0,Nvar): + lines.append([]) + if (lineplot[i] == 1): + ax.append(fig.add_subplot(row,col,i+1)) + ax[i].set_xlim((0,xmax[i])) + ax[i].set_ylim((fmin[i], fmax[i])) + for j in range(0,Nlines[i]): + lines[i].append(ax[i].plot([],[],lw=2, label = legendlabels[i][j])[0]) + #Need the [0] to 'unpack' the line object from tuple. Alternatively: + #lines[i], = lines[i] + ax[i].set_xlabel(r'x') + ax[i].set_ylabel(titles[i]) + if (Nlines[i] != 1): + legendneeded = 1 + for k in range(0,i): + if (Nlines[i] == Nlines[k]): + legendneeded = 0 + if (legendneeded == 1): + plt.axes(ax[i]) + plt.legend(loc = 0) + # Pad out unused list variables with zeros + plots.append(0) + cbars.append(0) + xstride.append(0) + ystride.append(0) + r.append(0) + theta.append(0) + + elif (contour[i] == 1): + ax.append(fig.add_subplot(row,col,i+1)) + #ax[i].set_xlim((0,Nx[i][0]-1)) + #ax[i].set_ylim((0,Ny[i][0]-1)) + ax[i].set_xlim(min(x[i]),max(x[i])) + ax[i].set_ylim(min(y[i]),max(y[i])) + ax[i].set_xlabel(r'x') + ax[i].set_ylabel(r'y') + ax[i].set_title(titles[i]) + if hold_aspect: + ax[i].set_aspect('equal') + thisx = x[i][0] + if len(thisx.shape) == 3: + thisx = thisx[0] + thisy = y[i] + if len(thisy.shape) == 3: + thisy = thisy[0] + plots.append(ax[i].contourf(thisx.T,thisy.T,vars[i][0][0,:,:].T, Ncolors, cmap=cmap, lw=0, levels=clevels[i] )) + plt.axes(ax[i]) + cbars.append(fig.colorbar(plots[i], format='%1.1e')) + # Pad out unused list variables with zeros + lines[i].append(0) + xstride.append(0) + ystride.append(0) + r.append(0) + theta.append(0) + + elif (surf[i] == 1): + if (len(x[i][0].shape)==1 and len(y[i].shape)==1): + # plot_wireframe() requires 2d arrays for x and y coordinates + x[i][0],y[i] = meshgrid(x[i][0],y[i]) + thisx = x[i][0] + if len(thisx.shape) == 3: + thisx = thisx[0] + thisy = y[i] + if len(thisy.shape) == 3: + thisy = thisy[0] + if (Nx[i][0]<= 20): + xstride.append(1) + else: + xstride.append(int(floor(Nx[i][0]/20))) + if (Ny[i][0]<=20): + ystride.append(1) + else: + ystride.append(int(floor(Ny[i][0]/20))) + ax.append(fig.add_subplot(row,col,i+1, projection='3d')) + plots.append(ax[i].plot_wireframe(thisx, thisy, vars[i][0][0,:,:].T, rstride=ystride[i], cstride=xstride[i])) + title = fig.suptitle(r'', fontsize=14 ) + ax[i].set_xlabel(r'x') + ax[i].set_ylabel(r'y') + ax[i].set_zlabel(titles[i]) + # Pad out unused list variables with zeros + lines[i].append(0) + cbars.append(0) + r.append(0) + theta.append(0) + + elif (polar[i] == 1): + r.append(linspace(1,Nx[i][0], Nx[i][0])) + theta.append(linspace(0,2*pi, Ny[i][0])) + r[i],theta[i] = meshgrid(r[i], theta[i]) + ax.append(fig.add_subplot(row,col,i+1, projection='polar')) + plots.append(ax[i].contourf(theta[i], r[i], vars[i][0][0,:,:].T, cmap=cmap, levels=clevels[i])) + plt.axes(ax[i]) + cbars.append(fig.colorbar(plots[i], format='%1.1e')) + ax[i].set_rmax(Nx[i][0]-1) + ax[i].set_title(titles[i]) + # Pad out unused list variables with zeros + lines[i].append(0) + xstride.append(0) + ystride.append(0) + + + + def onClick(event): + global pause + pause ^= True + + + def control(): + global j, pause + if j == Nframes-1 : j = -1 + if not pause: + j=j+1 + + return j + + + # Animation function + def animate(i): + j=control() + + index = j*intv + + for j in range(0,Nvar): + #Default to clearing axis between frames on all plots except line plots + if (clear_between_frames is None and lineplot[j] != 1 ) or clear_between_frames is True: + ax[j].cla() #Clear axis between frames so that masked arrays can be plotted + if (lineplot[j] == 1): + for k in range(0,Nlines[j]): + lines[j][k].set_data(x[j][k], vars[j][k][index,:]) + elif (contour[j] == 1): + thisx = x[j][0] + if len(thisx.shape) == 3: + thisx = thisx[index] + thisy = y[j] + if len(thisy.shape) == 3: + thisy = thisy[index] + plots[j] = ax[j].contourf(x[j][0].T,y[j].T,vars[j][0][index,:,:].T, Ncolors, cmap=cmap, lw=0, levels=clevels[j]) + ax[j].set_xlabel(r'x') + ax[j].set_ylabel(r'y') + ax[j].set_title(titles[j]) + elif (surf[j] == 1): + thisx = x[j][0] + if len(thisx.shape) == 3: + thisx = thisx[index] + thisy = y[j][0] + if len(thisy.shape) == 3: + thisy = thisy[index] + ax[j] = fig.add_subplot(row,col,j+1, projection='3d') + plots[j] = ax[j].plot_wireframe(thisx, thisy, vars[j][0][index,:,:].T, rstride=ystride[j], cstride=xstride[j]) + ax[j].set_zlim(fmin[j],fmax[j]) + ax[j].set_xlabel(r'x') + ax[j].set_ylabel(r'y') + ax[j].set_title(titles[j]) + elif (polar[j] == 1): + plots[j] = ax[j].contourf(theta[j], r[j], vars[j][0][index,:,:].T,cmap=cmap, levels=clevels[j]) + ax[j].set_rmax(Nx[j][0]-1) + ax[j].set_title(titles[j]) + + if t_array is not None: + title.set_text('t = %1.2e' % t_array[index]) + else: + title.set_text('t = %i' % index) + return plots + + def init(): + global j, pause + j=-2 + pause = False + return animate(0) + + + + + + + # Call Animation function + + fig.canvas.mpl_connect('button_press_event', onClick) + anim = animation.FuncAnimation(fig, animate, init_func=init, frames=Nframes) + + #If movie is not passed as a string assign the default filename + if (movie==1): + movie='animation.mp4' + + # Save movie with given or default name + if ((isinstance(movie,str)==1)): + movietype = movie.split('.')[-1] + if movietype == 'mp4': + try: + anim.save(movie,writer = FFwriter, fps=fps, dpi=dpi, extra_args=['-vcodec', 'libx264']) + except Exception: + #Try specifying writer by string if ffmpeg not found + try: + anim.save(movie,writer = 'ffmpeg', fps=fps, dpi=dpi, extra_args=['-vcodec', 'libx264']) + except Exception: + print('Save failed: Check ffmpeg path') + raise + elif movietype == 'gif': + anim.save(movie+'.gif',writer = 'imagemagick', fps=fps, dpi=dpi) + else: + raise ValueError("Unrecognized file type for movie. Supported types are .mp4 and .gif") + + # Show animation if not saved or returned, otherwise close the plot + if (movie==0 and return_animation == 0): + plt.show() + else: + plt.close() + # Return animation object + if(return_animation == 1): + return(anim) diff --git a/boututils/spectrogram.py b/boututils/spectrogram.py new file mode 100644 index 0000000..d1c2a36 --- /dev/null +++ b/boututils/spectrogram.py @@ -0,0 +1,163 @@ +"""Creates spectrograms using the Gabor transform to maintain time and +frequency resolution + +written by: Jarrod Leddy +updated: 23/06/2016 + +""" +from __future__ import print_function +from __future__ import division +from builtins import range + +from numpy import arange, zeros, exp, power, transpose, sin, cos, linspace, min, max +from scipy import fftpack, pi + + +def spectrogram(data, dx, sigma, clip=1.0, optimise_clipping=True, nskip=1.0): + """Creates spectrograms using the Gabor transform to maintain time + and frequency resolution + + .. note:: Very early and very late times will have some issues due + to the method - truncate them after taking the spectrogram + if they are below your required standards + + .. note:: If you are seeing issues at the top or bottom of the + frequency range, you need a longer time series + + written by: Jarrod Leddy + updated: 23/06/2016 + + Parameters + ---------- + data : array_like + The time series you want spectrogrammed + dt : float + Time resolution + sigma : float + Used in the Gabor transform, will balance time and frequency + resolution suggested value is 1.0, but may need to be adjusted + manually until result is as desired: + + - If bands are too tall raise sigma + - If bands are too wide, lower sigma + clip : float, optional + Makes the spectrogram run faster, but decreases frequency + resolution. clip is by what factor the time spectrum should be + clipped by --> N_new = N / clip + optimise_clip : bool + If true (default) will change the data length to be 2^N + (rounded down from your inputed clip value) to make FFT's fast + nskip : float + Scales final time axis, skipping points over which to centre + the gaussian window for the FFTs + + Returns + ------- + tuple : (array_like, array_like, array_like) + A tuple containing the spectrogram, frequency and time + + """ + n = data.size + nnew = int(n/nskip) + xx = arange(n)*dx + xxnew = arange(nnew)*dx*nskip + sigma = sigma * dx + + n_clipped = int(n/clip) + + # check to see if n_clipped is near a 2^n factor for speed + if(optimise_clipping): + nn = n_clipped + two_count = 1 + while(1): + nn = nn/2.0 + if(nn <= 2.0): + n_clipped = 2**two_count + print('clipping window length from ',n,' to ',n_clipped,' points') + break + else: + two_count += 1 + else: + print('using full window length: ',n_clipped,' points') + + halfclip = int(n_clipped/2) + spectra = zeros((nnew,halfclip)) + + omega = fftpack.fftfreq(n_clipped, dx) + omega = omega[0:halfclip] + + for i in range(nnew): + beg = i*nskip-halfclip + end = i*nskip+halfclip-1 + + if beg < 0: + end = end-beg + beg = 0 + elif end >= n: + end = n-1 + beg = end - n_clipped + 1 + + gaussian = 1.0 / (sigma * 2.0 * pi) * exp(-0.5 * power(( xx[beg:end] - xx[i*nskip] ),2.0) / (2.0 * sigma) ) + fftt = abs(fftpack.fft(data[beg:end] * gaussian)) + fftt = fftt[:halfclip] + spectra[i,:] = fftt + + return (transpose(spectra), omega, xxnew) + + +def test_spectrogram(n, d, s): + """Function used to test the performance of spectrogram with various + values of sigma + + Parameters + ---------- + n : int + Number of points + d : float + Grid spacing + s : float + Initial sigma + + """ + + import matplotlib.pyplot as plt + + nskip = 10 + xx = arange(n)/d + test_data = sin(2.0*pi*512.0*xx * ( 1.0 + 0.005*cos(xx*50.0))) + 0.5*exp(xx)*cos(2.0*pi*100.0*power(xx,2)) + test_sigma = s + dx = 1.0/d + + s1 = test_sigma*0.1 + s2 = test_sigma + s3 = test_sigma*10.0 + + (spec2,omega2,xx) = spectrogram(test_data, dx, s2, clip=5.0, nskip=nskip) + (spec3,omega3,xx) = spectrogram(test_data, dx, s3, clip=5.0, nskip=nskip) + (spec1,omega1,xx) = spectrogram(test_data, dx, s1, clip=5.0, nskip=nskip) + + levels = linspace(min(spec1),max(spec1),100) + plt.subplot(311) + plt.contourf(xx,omega1,spec1,levels=levels) + plt.ylabel("frequency") + plt.xlabel(r"$t$") + plt.title(r"Spectrogram of $sin(t + cos(t) )$ with $\sigma=$%3.1f"%s1) + + levels = linspace(min(spec2),max(spec2),100) + plt.subplot(312) + plt.contourf(xx,omega2,spec2,levels=levels) + plt.ylabel("frequency") + plt.xlabel(r"$t$") + plt.title(r"Spectrogram of $sin(t + cos(t) )$ with $\sigma=$%3.1f"%s2) + + levels = linspace(min(spec3),max(spec3),100) + plt.subplot(313) + plt.contourf(xx,omega3,spec3,levels=levels) + plt.ylabel("frequency") + plt.xlabel(r"$t$") + plt.title(r"Spectrogram of $sin(t + cos(t) )$ with $\sigma=$%3.1f"%s3) + plt.tight_layout() + plt.show() + +if __name__ == "__main__": + test_spectrogram(2048, 2048.0, 0.01) # array size, divisions per unit, sigma of gaussian diff --git a/boututils/surface_average.py b/boututils/surface_average.py new file mode 100644 index 0000000..d58e9a3 --- /dev/null +++ b/boututils/surface_average.py @@ -0,0 +1,103 @@ +"""Average over a surface + +""" + +from __future__ import print_function +from __future__ import absolute_import +from __future__ import division +from builtins import range +from past.utils import old_div +import numpy as np +from boututils.calculus import deriv +from boututils.int_func import int_func +from .idl_tabulate import idl_tabulate +from bunch import bunchify + + +def surface_average(var, g, area=None): + """Average a variable over a surface + + Parameters + ---------- + var : array_like + 3-D or 4D variable to integrate (either [x,y,z] or [t,x,y,z]) + g : dict + A dictionary of various grid quantities + area : bool + Average by flux-surface area = (B/Bp)*dl * R*dz + + Returns + ------- + float + Surface average of variable + + """ + + s = np.ndim(var) + + + + if s == 4 : + nx = np.shape(var)[1] + ny = np.shape(var)[2] + nt = np.shape(var)[0] + + result = np.zeros((nx,nt)) + for t in range (nt): + + result[:,t] = surface_average(var[t,:,:,:], g, area=area) + + return result + elif s != 3 : + print("ERROR: surface_average var must be 3 or 4D") + return 0 + + + # 3D [x,y,z] + nx = np.shape(var)[0] + ny = np.shape(var)[1] +# nz = np.shape(var)[2] + +# Use bunch to create grid structure + grid=bunchify(g) + + + # Calculate poloidal angle from grid + theta = np.zeros((nx,ny)) + + #status = gen_surface(mesh=grid) ; Start generator + xi = -1 + yi = np.arange(0,ny,dtype=int) + last = 0 + while True: + #yi = gen_surface(last=last, xi=xi, period=periodic) + xi = xi + 1 + if xi == nx-1 : + last = 1 + + dtheta = 2.*np.pi / np.float(ny) + r = grid.Rxy[xi,yi] + z = grid.Zxy[xi,yi] + n = np.size(r) + + dl = old_div(np.sqrt( deriv(r)**2 + deriv(z)**2 ), dtheta) + if area: + dA = (old_div(grid.Bxy[xi,yi],grid.Bpxy[xi,yi]))*r*dl + A = int_func(np.arange(n),dA) + theta[xi,yi] = 2.*np.pi*A/A[n-1] + else: + nu = dl * (grid.Btxy[xi,yi]) / ((grid.Bpxy[xi,yi]) * r ) + theta[xi,yi] = int_func(np.arange(n)*dtheta,nu) + theta[xi,yi] = 2.*np.pi*theta[xi,yi] / theta[xi,yi[n-1]] + + if last==1 : break + + vy = np.zeros(ny) + result = np.zeros(nx) + for x in range(nx) : + for y in range(ny) : + vy[y] = np.mean(var[x,y,:]) + + result[x] = old_div(idl_tabulate(theta[x,:], vy), (2.*np.pi)) + + return result diff --git a/boututils/volume_integral.py b/boututils/volume_integral.py new file mode 100644 index 0000000..9fdb083 --- /dev/null +++ b/boututils/volume_integral.py @@ -0,0 +1,107 @@ +"""Integrate over a volume + +""" + +from __future__ import print_function +from __future__ import division +from builtins import range +from past.utils import old_div +import numpy as np +from boututils.calculus import deriv +from bunch import bunchify + + +def volume_integral(var, g, xr=False): + """Integrate a variable over a volume + + Parameters + ---------- + var : array_like + Variable to integrate + g : dict + A dictionary of various grid quantities + xr : (int, int), optional + Range of x indices (default: all of x) + + Returns + ------- + float + Volumne integral of variable + + """ + + s = np.ndim(var) + + grid=bunchify(g) + + + if s == 4 : + # 4D [t,x,y,z] - integrate for each t + nx = np.shape(var)[1] + ny = np.shape(var)[2] + nt = np.shape(var)[0] + + result = np.zeros(nt) + for t in range(nt) : + result[t] = volume_integral(var[t,:,:,:],g,xr=xr) + return result + + elif s == 3 : + # 3D [x,y,z] - average in Z + nx = np.shape(var)[0] + ny = np.shape(var)[1] + # nz = np.shape(var)[2] + + zi = np.zeros((nx, ny)) + for x in range(nx): + for y in range(ny): + zi[x,y] = np.mean(var[x,y,:]) + + return volume_integral(zi, g, xr=xr) + + + elif s != 2 : + print("ERROR: volume_integral var must be 2, 3 or 4D") + + + # 2D [x,y] + nx = np.shape(var)[0] + ny = np.shape(var)[1] + + if xr == False : xr=[0,nx-1] + + result = 0.0 + + #status = gen_surface(mesh=grid) ; Start generator + xi = -1 + yi = np.arange(0,ny,dtype=int) + last = 0 + # iy = np.zeros(nx) + while True: + #yi = gen_surface(last=last, xi=xi, period=periodic) + xi = xi + 1 + if xi == nx-1 : last = 1 + + if (xi >= np.min(xr)) & (xi <= np.max(xr)) : + dtheta = 2.*np.pi / np.float(ny) + r = grid.Rxy[xi,yi] + z = grid.Zxy[xi,yi] + n = np.size(r) + dl = old_div(np.sqrt( deriv(r)**2 + deriv(z)**2 ), dtheta) + + # Area of flux-surface + dA = (grid.Bxy[xi,yi]/grid.Bpxy[xi,yi]*dl) * (r*2.*np.pi) + # Volume + if xi == nx-1 : + dpsi = (grid.psixy[xi,yi] - grid.psixy[xi-1,yi]) + else: + dpsi = (grid.psixy[xi+1,yi] - grid.psixy[xi,yi]) + + dV = dA * dpsi / (r*(grid.Bpxy[xi,yi])) # May need factor of 2pi + dV = np.abs(dV) + + result = result + np.sum(var[xi,yi] * dV) + + if last==1 : break + + return result diff --git a/boututils/watch.py b/boututils/watch.py new file mode 100644 index 0000000..e7d038c --- /dev/null +++ b/boututils/watch.py @@ -0,0 +1,84 @@ +""" +Routines for watching files for changes + +""" +from __future__ import print_function +from builtins import zip + +import time +import os + + +def watch(files, timeout=None, poll=2): + """Watch a given file or collection of files until one changes. Uses + polling. + + Parameters + ---------- + files : str or list of str + Name of one or more files to watch + timeout : int, optional + Timeout in seconds (default is no timeout) + poll : int, optional + Polling interval in seconds (default: 2) + + Returns + ------- + str + The name of the first changed file, + or None if timed out before any changes + + Examples + -------- + + To watch one file, timing out after 60 seconds: + + >>> watch('file1', timeout=60) + + To watch 2 files, never timing out: + + >>> watch(['file1', 'file2']) + + Author: Ben Dudson + + """ + + # Get modification time of file(s) + try: + if hasattr(files, '__iter__'): + # Iterable + lastmod = [ os.stat(f).st_mtime for f in files ] + iterable = True + else: + # Not iterable -> just one file + lastmod = os.stat(files).st_mtime + iterable = False + except: + print("Can't test modified time. Wrong file name?") + raise + + start_time = time.time() + running = True + while running: + sleepfor = poll + if timeout: + # Check if timeout will be reached before next poll + if time.time() - start_time + sleepfor > timeout: + # Adjust time so that finish at timeout + sleepfor = timeout - (time.time() - start_time) + running = False # Stop after next test + + time.sleep(sleepfor) + + if iterable: + for last_t, f in zip(lastmod, files): + # Get the new modification time + t = os.stat(f).st_mtime + if t > last_t + 1.0: # +1 to reduce risk of false alarms + # File has been modified + return f + else: + t = os.stat(files).st_mtime + if t > lastmod + 1.0: + return files + return None diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a6805a4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +numpy==1.16.3 +matplotlib==3.0.3 +h5py==2.9.0 +scipy==1.2.1 +future==0.17.1 +bunch==1.0.1 +mayavi==4.6.2 +netCDF4==1.5.1.2 From 68c7b6a668d89859ae55640aa586625761038bdd Mon Sep 17 00:00:00 2001 From: loeiten Date: Fri, 24 May 2019 10:20:55 +0200 Subject: [PATCH 03/98] pip 0.1.0 commit --- README.md | 8 ++++++ boututils/__init__.py | 3 +++ setup.py | 62 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 setup.py diff --git a/README.md b/README.md index c9b6e7a..6aebb4e 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ # boututils + +[![Python](https://img.shields.io/badge/python->=3.6-blue.svg)](https://www.python.org/) +[![pypi package](https://badge.fury.io/py/boututils.svg)](https://pypi.org/project/boututils/) +[![PEP8](https://img.shields.io/badge/code%20style-PEP8-brightgreen.svg)](https://www.python.org/dev/peps/pep-0008/) +[![License](https://img.shields.io/badge/license-LGPL--3.0-blue.svg)](https://github.com/CELMA-project/bout_install/blob/master/LICENSE) + +pip-package of what was previously found in +`BOUT-dev/tools/pylib/boututils` \ No newline at end of file diff --git a/boututils/__init__.py b/boututils/__init__.py index f815d3e..3f3b367 100644 --- a/boututils/__init__.py +++ b/boututils/__init__.py @@ -37,3 +37,6 @@ do_import.append('plotpolslice') do_import.append('View3D') __all__ = do_import + +__version__ = '0.1.0' +__name__ = 'boututils' diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3bdd19b --- /dev/null +++ b/setup.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re +import setuptools +from pathlib import Path + +name = 'boututils' +root_path = Path(__file__).parent +init_path = root_path.joinpath(name, '__init__.py') +readme_path = root_path.joinpath('README.md') + +# https://packaging.python.org/guides/single-sourcing-package-version/ +with init_path.open('r') as f: + version_file = f.read() + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + version_file, re.M) + if version_match: + version = version_match.group(1) + else: + raise RuntimeError('Unable to find version string.') + +with readme_path.open('r') as f: + long_description = f.read() + +setuptools.setup( + name=name, + version=version, + author='Ben Dudson et al.', + description='Python package to install BOUT++ and its dependencies', + long_description=long_description, + long_description_content_type='text/markdown', + url='http://boutproject.github.io', + project_urls={ + "Bug Tracker": "https://github.com/boutproject/boututils/issues/", + "Documentation": "http://bout-dev.readthedocs.io/en/latest/", + "Source Code": "https://github.com/boutproject/boututils/", + }, + packages=setuptools.find_packages(), + keywords=['bout++', + 'bout', + 'plasma', + 'physics', + 'data-extraction', + 'data-analysis', + 'data-visualization'], + install_requires=['numpy', + 'matplotlib', + 'scipy', + 'h5py', + 'boututils', + 'future', + 'bunch', + 'mayavi', + 'netCDF'], + classifiers=[ + 'Programming Language :: Python :: 3', + ('License :: OSI Approved :: ' + 'GNU Lesser General Public License v3 or later (LGPLv3+)'), + 'Operating System :: OS Independent', + ], +) From 07c75174c8f837aab2d7d2b79c113490cb020910 Mon Sep 17 00:00:00 2001 From: loeiten Date: Fri, 24 May 2019 10:50:13 +0200 Subject: [PATCH 04/98] Fixed netcdf error --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3bdd19b..f354e19 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ 'future', 'bunch', 'mayavi', - 'netCDF'], + 'netCDF4'], classifiers=[ 'Programming Language :: Python :: 3', ('License :: OSI Approved :: ' From d7a9262a8c87482437bff4057c4ad571293a2e66 Mon Sep 17 00:00:00 2001 From: loeiten Date: Fri, 24 May 2019 11:30:19 +0200 Subject: [PATCH 05/98] Addressing the mayavi issue --- README.md | 21 ++++++++++++++++++++- boututils/__init__.py | 2 +- requirements.txt | 1 + setup.py | 1 + 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6aebb4e..36e6e4f 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,23 @@ [![License](https://img.shields.io/badge/license-LGPL--3.0-blue.svg)](https://github.com/CELMA-project/bout_install/blob/master/LICENSE) pip-package of what was previously found in -`BOUT-dev/tools/pylib/boututils` \ No newline at end of file +`BOUT-dev/tools/pylib/boututils` + +# Dependencies + +`boututils` depends on +[`netcfd4`](https://github.com/Unidata/netcdf4-python) which requires +[`HDF5`](http://www.h5py.org) and +[`netcdf-4`](https://github.com/Unidata/netcdf-c/releases) are +installed, and that the `nc-config` utility is in your `PATH`. This +can be install with + +``` +sudo apt-get install libhdf5-serial-dev netcdf-bin libnetcdf-dev +``` + +in ubuntu + +# Install + +`pip install boututils` \ No newline at end of file diff --git a/boututils/__init__.py b/boututils/__init__.py index 3f3b367..7d851de 100644 --- a/boututils/__init__.py +++ b/boututils/__init__.py @@ -38,5 +38,5 @@ do_import.append('View3D') __all__ = do_import -__version__ = '0.1.0' +__version__ = '0.1.2' __name__ = 'boututils' diff --git a/requirements.txt b/requirements.txt index a6805a4..292be85 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,6 @@ h5py==2.9.0 scipy==1.2.1 future==0.17.1 bunch==1.0.1 +PyQt5==5.12.2 mayavi==4.6.2 netCDF4==1.5.1.2 diff --git a/setup.py b/setup.py index f354e19..bd9939f 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,7 @@ 'boututils', 'future', 'bunch', + 'PyQt5', 'mayavi', 'netCDF4'], classifiers=[ From 46915012d20eb7231a2bd59249b6a37f35da57b2 Mon Sep 17 00:00:00 2001 From: loeiten Date: Fri, 24 May 2019 13:56:57 +0200 Subject: [PATCH 06/98] Added .travis.yml --- .travis.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ee159cd --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ +dist: xenial +language: python +python: + - "3.7" + +env: + - QT_QPA_PLATFORM=offscreen + - LANG=en_US.UTF-8 + - LANGUAGE=en_US:en + - LC_ALL=en_US.UTF-8 + +# Using addons rather than apt-get install +addons: + apt: + packages: + - libhdf5-serial-dev + - netcdf-bin + - libnetcdf-dev + - libsm6 + - libxext6 + - libxrender-dev + - libxt6 + - libgl1-mesa-glx + - libfontconfig + - libxkbcommon-x11-0 + - locales + +before_script: + - locale-gen en_US.UTF-8 + +install: + - pip install boututils \ No newline at end of file From 090c3b44c525b6523db01abae4b8c8d84ff7b701 Mon Sep 17 00:00:00 2001 From: loeiten Date: Fri, 24 May 2019 14:20:50 +0200 Subject: [PATCH 07/98] Fixes to travis and README --- .travis.yml | 8 +------- README.md | 4 +++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index ee159cd..ed6ed4b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,11 +5,9 @@ python: env: - QT_QPA_PLATFORM=offscreen - - LANG=en_US.UTF-8 - - LANGUAGE=en_US:en - - LC_ALL=en_US.UTF-8 # Using addons rather than apt-get install +# NOTE: LANG=en_US.UTF-8 etc. is predefined in travis addons: apt: packages: @@ -23,10 +21,6 @@ addons: - libgl1-mesa-glx - libfontconfig - libxkbcommon-x11-0 - - locales - -before_script: - - locale-gen en_US.UTF-8 install: - pip install boututils \ No newline at end of file diff --git a/README.md b/README.md index 36e6e4f..c5e3832 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # boututils +[![Build Status](https://travis-ci.org/boutproject/boututils.svg?branch=master)](https://travis-ci.org/boutproject/boututils.svg) +[![codecov](https://codecov.io/gh/boutproject/boututils/branch/master/graph/badge.svg)](https://codecov.io/gh/boutproject/boututils) [![Python](https://img.shields.io/badge/python->=3.6-blue.svg)](https://www.python.org/) [![pypi package](https://badge.fury.io/py/boututils.svg)](https://pypi.org/project/boututils/) [![PEP8](https://img.shields.io/badge/code%20style-PEP8-brightgreen.svg)](https://www.python.org/dev/peps/pep-0008/) -[![License](https://img.shields.io/badge/license-LGPL--3.0-blue.svg)](https://github.com/CELMA-project/bout_install/blob/master/LICENSE) +[![License](https://img.shields.io/badge/license-LGPL--3.0-blue.svg)](https://github.com/boutproject/boututils/blob/master/LICENSE) pip-package of what was previously found in `BOUT-dev/tools/pylib/boututils` From 29c9484b353603e17c33e556c00ee5cfc784c3ca Mon Sep 17 00:00:00 2001 From: loeiten Date: Fri, 24 May 2019 14:35:46 +0200 Subject: [PATCH 08/98] Added import test --- .travis.yml | 6 +++++- test/__init__.py | 0 test/test_import.py | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 test/__init__.py create mode 100644 test/test_import.py diff --git a/.travis.yml b/.travis.yml index ed6ed4b..dd5297b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,4 +23,8 @@ addons: - libxkbcommon-x11-0 install: - - pip install boututils \ No newline at end of file + - pip install boututils + - pip install codecov pytest-cov + +script: + - pytest --cov=./ \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_import.py b/test/test_import.py new file mode 100644 index 0000000..ced6a37 --- /dev/null +++ b/test/test_import.py @@ -0,0 +1,12 @@ +import unittest +from boututils.datafile import DataFile + + +class TestImport(unittest.TestCase): + def test_import(self): + d = DataFile() + self.assertIsInstance(d, DataFile) + + +if __name__ == '__main__': + unittest.main() From e1b0229a0281581ac243f8f09f82c80ba8969eb5 Mon Sep 17 00:00:00 2001 From: loeiten Date: Fri, 24 May 2019 16:32:43 +0200 Subject: [PATCH 09/98] Added secret --- .travis.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index dd5297b..ac90b4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,14 @@ language: python python: - "3.7" +# NOTE: LANG=en_US.UTF-8 etc. is predefined in travis env: - - QT_QPA_PLATFORM=offscreen + matrix: + - QT_QPA_PLATFORM=offscreen + global: + secure: "FnR3amw3i/68RLIhEFnAxR9/+xhVWey+24NwI5NSKMpNGs8hoT6Fg/erjfNXw/RyJReD7ikXBMp9rSImjmWxZ3fXK9j545CAYOVlgpj7b9oIfle5eYfukuPQWT/xTkEaGFxHEuygzH+J1rCm1ZFyJ03IFvN/Fw00bsJG8Vg5C8RFqLIrwwjjx8hhHDMKY7hV/+rG7Ufq8irRJFNXrfOZ1dw/H0XRivbhtpQ1K8jSoPb8EGg2WVm8XI3rT1zv6ydGTNVvRoDbvKzCHEDgAwpucY6dk56EdsnweJabjzlqDK2L184OzJHvd7iwAyxjCIgYEiMrCfiCYBNz+73ief7w6rO2rrGuLMUF04VzlxnEXK+W2ylJzpBmwLmfVLJkFOEkwehbxe7OneI6nGR/AA35X/VbuR2HGdYtcZ5aF10Lje341UEeogoPg0hGSBIc+9a48eDyJiPq1pIZeBv6y3IfW72nrDH04tpbGZeS0DFC0fkAC/TzNtqk2k+X9fsEuoHVx5WiL3YHlCCHfLdYhV52gHsbK6QR0ceouG6JxVPKDebc7Jt1BZggdv4nKiVewXbhyNhycZPkQE+WB2dgVE6gXLaMwwtAJus5f+gEv/vwEGRkLT+Kep8LutubZKDUsd8ZZbWaGol5qDGLm3Jv5+3g+RoCY34q6obKxh+jOvsy2EY=" # Using addons rather than apt-get install -# NOTE: LANG=en_US.UTF-8 etc. is predefined in travis addons: apt: packages: @@ -27,4 +30,7 @@ install: - pip install codecov pytest-cov script: - - pytest --cov=./ \ No newline at end of file + - pytest --cov=./ + +after_success: + - codecov \ No newline at end of file From 972846083d553242539673325206fce7bf15535b Mon Sep 17 00:00:00 2001 From: loeiten Date: Sun, 18 Aug 2019 18:50:32 +0200 Subject: [PATCH 10/98] Updated README with links to PRs and issues NOTE: The files are in synch with https://github.com/boutproject/BOUT-dev/commit/50e3b46b2bea4a8c207aeb7bfe9b42b89baa7969 --- README.md | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c5e3832..1f2ce01 100644 --- a/README.md +++ b/README.md @@ -7,24 +7,33 @@ [![PEP8](https://img.shields.io/badge/code%20style-PEP8-brightgreen.svg)](https://www.python.org/dev/peps/pep-0008/) [![License](https://img.shields.io/badge/license-LGPL--3.0-blue.svg)](https://github.com/boutproject/boututils/blob/master/LICENSE) -pip-package of what was previously found in +pip-package of what was previously found in `BOUT-dev/tools/pylib/boututils` +Note that `BOUT-dev/tools/pylib/boututils` will likely be replaced by this repo +in `BOUT++ v4.3.0`. +See [this issue](https://github.com/boutproject/BOUT-dev/issues/1347), +[this pull request](https://github.com/boutproject/BOUT-dev/pull/1766) and +[this pull request](https://github.com/boutproject/BOUT-dev/pull/1740) for details. + +> **NOTE**: This package will likely be superseded by + [`xBOUT`](https://github.com/boutproject/xBOUT) in the near future # Dependencies -`boututils` depends on -[`netcfd4`](https://github.com/Unidata/netcdf4-python) which requires -[`HDF5`](http://www.h5py.org) and -[`netcdf-4`](https://github.com/Unidata/netcdf-c/releases) are -installed, and that the `nc-config` utility is in your `PATH`. This -can be install with +`boututils` depends on +[`netcfd4`](https://github.com/Unidata/netcdf4-python) which requires +[`HDF5`](http://www.h5py.org) and +[`netcdf-4`](https://github.com/Unidata/netcdf-c/releases) are +installed, and that the `nc-config` utility is in your `PATH`. This +can be install with ``` sudo apt-get install libhdf5-serial-dev netcdf-bin libnetcdf-dev ``` - + in ubuntu # Install -`pip install boututils` \ No newline at end of file +`pip install boututils` + From 9dc144840dabec1fa738b2c65600344253abfe48 Mon Sep 17 00:00:00 2001 From: johnomotani Date: Fri, 25 Oct 2019 19:11:38 +0100 Subject: [PATCH 11/98] Make mayavi an optional dependency `mayavi` is not required for all the `boututils` to work, so make it an 'extra' requirement. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index bd9939f..db9e003 100644 --- a/setup.py +++ b/setup.py @@ -51,9 +51,9 @@ 'boututils', 'future', 'bunch', - 'PyQt5', - 'mayavi', 'netCDF4'], + extras_require={ + 'mayavi': ['mayavi', 'PyQt5']}, classifiers=[ 'Programming Language :: Python :: 3', ('License :: OSI Approved :: ' From fc723b7bc5a956590322051889f4603ccb7a8607 Mon Sep 17 00:00:00 2001 From: johnomotani Date: Fri, 25 Oct 2019 19:15:06 +0100 Subject: [PATCH 12/98] Make mayavi optional in requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 292be85..ec9bd5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,6 @@ h5py==2.9.0 scipy==1.2.1 future==0.17.1 bunch==1.0.1 -PyQt5==5.12.2 -mayavi==4.6.2 +PyQt5==5.12.2 [mayavi] +mayavi==4.6.2 [mayavi] netCDF4==1.5.1.2 From 6cf46fc7b4cf6f7d5ac5c7dafd34333c4f80023c Mon Sep 17 00:00:00 2001 From: johnomotani Date: Sat, 26 Oct 2019 17:33:02 +0100 Subject: [PATCH 13/98] Bump version number 0.1.2 -> 0.1.3 --- boututils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boututils/__init__.py b/boututils/__init__.py index 7d851de..a3f8128 100644 --- a/boututils/__init__.py +++ b/boututils/__init__.py @@ -38,5 +38,5 @@ do_import.append('View3D') __all__ = do_import -__version__ = '0.1.2' +__version__ = '0.1.3' __name__ = 'boututils' From 3c9217aec4e167d63eddccd520ceb746102a29d3 Mon Sep 17 00:00:00 2001 From: loeiten Date: Sat, 11 Apr 2020 21:12:43 +0200 Subject: [PATCH 14/98] Typofixes --- README.md | 2 +- boututils/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1f2ce01..6407bba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # boututils -[![Build Status](https://travis-ci.org/boutproject/boututils.svg?branch=master)](https://travis-ci.org/boutproject/boututils.svg) +[![Build Status](https://travis-ci.org/boutproject/boututils.svg?branch=master)](https://travis-ci.org/boutproject/boututils) [![codecov](https://codecov.io/gh/boutproject/boututils/branch/master/graph/badge.svg)](https://codecov.io/gh/boutproject/boututils) [![Python](https://img.shields.io/badge/python->=3.6-blue.svg)](https://www.python.org/) [![pypi package](https://badge.fury.io/py/boututils.svg)](https://pypi.org/project/boututils/) diff --git a/boututils/__init__.py b/boututils/__init__.py index a3f8128..2b3a54d 100644 --- a/boututils/__init__.py +++ b/boututils/__init__.py @@ -38,5 +38,5 @@ do_import.append('View3D') __all__ = do_import -__version__ = '0.1.3' +__version__ = '0.1.4' __name__ = 'boututils' diff --git a/setup.py b/setup.py index db9e003..8218192 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ name=name, version=version, author='Ben Dudson et al.', - description='Python package to install BOUT++ and its dependencies', + description='Python package containing BOUT++ utils', long_description=long_description, long_description_content_type='text/markdown', url='http://boutproject.github.io', From c2746422d1e1bad9eb80cfffb03ffbe99d6593a1 Mon Sep 17 00:00:00 2001 From: loeiten Date: Tue, 14 Apr 2020 09:23:41 +0200 Subject: [PATCH 15/98] Bumped versions in requirements.txt --- requirements.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index ec9bd5d..1dbb35b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ -numpy==1.16.3 -matplotlib==3.0.3 -h5py==2.9.0 -scipy==1.2.1 -future==0.17.1 +numpy==1.18.2 +matplotlib==3.2.1 +h5py==2.10.0 +scipy==1.4.1 +future==0.18.2 bunch==1.0.1 PyQt5==5.12.2 [mayavi] mayavi==4.6.2 [mayavi] -netCDF4==1.5.1.2 +netCDF4==1.5.3 From bc9317044c706303e6f0a2c8242115faf5a37cf9 Mon Sep 17 00:00:00 2001 From: loeiten Date: Tue, 14 Apr 2020 09:27:02 +0200 Subject: [PATCH 16/98] Added cftime --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 1dbb35b..8b97d6e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +cftime==1.1.1.2 numpy==1.18.2 matplotlib==3.2.1 h5py==2.10.0 From 5f0c2e554cd2178ef333aacd365fb80893e508aa Mon Sep 17 00:00:00 2001 From: loeiten Date: Tue, 14 Apr 2020 09:59:36 +0200 Subject: [PATCH 17/98] Added --ignore-installed --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ac90b4c..d46d345 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ addons: - libxkbcommon-x11-0 install: - - pip install boututils + - pip install --ignore-installed boututils - pip install codecov pytest-cov script: From 49ed81af029c7746615cdb4eaee2defb61bf20b2 Mon Sep 17 00:00:00 2001 From: loeiten Date: Tue, 14 Apr 2020 10:07:35 +0200 Subject: [PATCH 18/98] Uninstall previous numpy version --- .travis.yml | 3 ++- requirements.txt | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d46d345..120dcea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,8 @@ addons: - libxkbcommon-x11-0 install: - - pip install --ignore-installed boututils + - pip uninstall -y numpy + - pip install boututils - pip install codecov pytest-cov script: diff --git a/requirements.txt b/requirements.txt index 8b97d6e..1dbb35b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -cftime==1.1.1.2 numpy==1.18.2 matplotlib==3.2.1 h5py==2.10.0 From 3672ead3c1e179f3cef3d3c7da2221c3adc2fde8 Mon Sep 17 00:00:00 2001 From: loeiten Date: Mon, 4 May 2020 09:59:06 +0200 Subject: [PATCH 19/98] Updated website info --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8218192..8f04497 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ description='Python package containing BOUT++ utils', long_description=long_description, long_description_content_type='text/markdown', - url='http://boutproject.github.io', + url='https://github.com/boutproject/boututils', project_urls={ "Bug Tracker": "https://github.com/boutproject/boututils/issues/", "Documentation": "http://bout-dev.readthedocs.io/en/latest/", From c2a3226cdcaa01034ff171ea391bec357efe6135 Mon Sep 17 00:00:00 2001 From: loeiten Date: Tue, 5 May 2020 07:58:06 +0200 Subject: [PATCH 20/98] http -> https --- LICENSE | 2 +- README.md | 2 +- boututils/View3D.py | 2 +- boututils/ask.py | 2 +- boututils/contour.py | 24 ++++++------- boututils/crosslines.py | 70 +++++++++++++++++++------------------- boututils/datafile.py | 2 +- boututils/geqdsk.py | 2 +- boututils/local_min_max.py | 50 +++++++++++++-------------- boututils/run_wrapper.py | 4 +-- boututils/showdata.py | 10 +++--- setup.py | 2 +- 12 files changed, 86 insertions(+), 86 deletions(-) diff --git a/LICENSE b/LICENSE index cca7fc2..491226c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. diff --git a/README.md b/README.md index 6407bba..6402c5f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ See [this issue](https://github.com/boutproject/BOUT-dev/issues/1347), `boututils` depends on [`netcfd4`](https://github.com/Unidata/netcdf4-python) which requires -[`HDF5`](http://www.h5py.org) and +[`HDF5`](https://www.h5py.org) and [`netcdf-4`](https://github.com/Unidata/netcdf-c/releases) are installed, and that the `nc-config` utility is in your `PATH`. This can be install with diff --git a/boututils/View3D.py b/boututils/View3D.py index 3c50950..f3a771d 100644 --- a/boututils/View3D.py +++ b/boututils/View3D.py @@ -4,7 +4,7 @@ is computed from efit_analyzed.py. The script can be used as a template to show additional properties of the field based on enthought's example by Gael Varoquaux -http://docs.enthought.com/mayavi/mayavi/auto/example_magnetic_field.html#example-magnetic-field +https://docs.enthought.com/mayavi/mayavi/auto/example_magnetic_field.html#example-magnetic-field """ from __future__ import absolute_import diff --git a/boututils/ask.py b/boututils/ask.py index 14aa194..31cbef0 100644 --- a/boututils/ask.py +++ b/boututils/ask.py @@ -11,7 +11,7 @@ def query_yes_no(question, default="yes"): Answers are case-insensitive. - Probably originally from http://code.activestate.com/recipes/577058/ + Probably originally from https://code.activestate.com/recipes/577058/ via https://stackoverflow.com/a/3041990/2043465 Parameters diff --git a/boututils/contour.py b/boututils/contour.py index 0d648ca..9141ff9 100644 --- a/boututils/contour.py +++ b/boututils/contour.py @@ -1,7 +1,7 @@ """ Contour calculation routines -http://members.bellatlantic.net/~vze2vrva/thesis.html +https://members.bellatlantic.net/~vze2vrva/thesis.html """ from __future__ import print_function @@ -18,21 +18,21 @@ def contour(f, level): print("Contour only works on 2D data") return None nx,ny = f.shape - + # Go through each cell edge and mark which ones contain # a level crossing. Approximating function as - # f = axy + bx + cy + d + # f = axy + bx + cy + d # Hence linear interpolation along edges. - + edgecross = {} # Dictionary: (cell number, edge number) to crossing location - + for i in np.arange(nx-1): for j in np.arange(ny-1): # Lower-left corner of cell is (i,j) if (np.max(f[i:(i+2),j:(j+2)]) < level) or (np.min(f[i:(i+2),j:(j+2)]) > level): # not in the correct range - skip continue - + # Check each edge ncross = 0 def location(a, b): @@ -43,19 +43,19 @@ def location(a, b): return old_div((level - a), (b - a)) else: return None - + loc = [ location(f[i,j], f[i+1,j]), location(f[i+1,j], f[i+1,j+1]), location(f[i+1,j+1], f[i,j+1]), location(f[i,j+1], f[i,j])] - + if ncross != 0: # Only put into dictionary if has a crossing cellnr = (ny-1)*i + j # The cell number edgecross[cellnr] = [loc,ncross] # Tack ncross onto the end # Process crossings into contour lines - + while True: # Start from an arbitrary location and follow until # it goes out of the domain or closes @@ -64,12 +64,12 @@ def location(a, b): except KeyError: # No keys left so finished break - + def follow(): return - # Follow - + # Follow + return def find_opoints(var2d): diff --git a/boututils/crosslines.py b/boututils/crosslines.py index 0a4a864..732a9f0 100644 --- a/boututils/crosslines.py +++ b/boututils/crosslines.py @@ -3,7 +3,7 @@ from past.utils import old_div import numpy as np from numpy.lib.stride_tricks import as_strided -import itertools +import itertools def unique(a, atol=1e-08): """Find unique rows in 2d array @@ -11,7 +11,7 @@ def unique(a, atol=1e-08): Parameters ---------- a : 2d ndarray, float - array to find unique rows in + array to find unique rows in atol : float, optional tolerance to check uniqueness @@ -22,37 +22,37 @@ def unique(a, atol=1e-08): Notes ----- - Adapted to include tolerance from code at http://stackoverflow.com/questions/8560440/removing-duplicate-columns-and-rows-from-a-numpy-2d-array#answer-8564438 + Adapted to include tolerance from code at https://stackoverflow.com/questions/8560440/removing-duplicate-columns-and-rows-from-a-numpy-2d-array#answer-8564438 """ - if np.issubdtype(a.dtype, float): + if np.issubdtype(a.dtype, float): order = np.lexsort(a.T) a = a[order] diff = np.diff(a, axis=0) np.abs(diff,out = diff) ui = np.ones(len(a), 'bool') - ui[1:] = (diff > atol).any(axis=1) + ui[1:] = (diff > atol).any(axis=1) return a[ui] else: order = np.lexsort(a.T) a = a[order] - diff = np.diff(a, axis=0) + diff = np.diff(a, axis=0) ui = np.ones(len(a), 'bool') - ui[1:] = (diff != 0).any(axis=1) + ui[1:] = (diff != 0).any(axis=1) return a[ui] def linelineintersect(a, b, atol=1e-08): """Find all intersection points of two lines defined by series of x,y pairs Intersection points are unordered - Colinear lines that overlap intersect at any end points that fall within the overlap + Colinear lines that overlap intersect at any end points that fall within the overlap Parameters ---------- a, b : ndarray - 2 column ndarray of x,y values defining a two dimensional line. 1st - column is x values, 2nd column is x values. + 2 column ndarray of x,y values defining a two dimensional line. 1st + column is x values, 2nd column is x values. Notes ----- @@ -60,29 +60,29 @@ def linelineintersect(a, b, atol=1e-08): Function faster when there are no overlapping line segments - add some lines for preventing zero-division - """ + add some lines for preventing zero-division + """ def x_y_from_line(line): """1st and 2nd column of array""" return (line[:, 0],line[:, 1]) def meshgrid_as_strided(x, y, mask=None): - """numpy.meshgrid without copying data (using as_strided)""" - if mask is None: + """numpy.meshgrid without copying data (using as_strided)""" + if mask is None: return (as_strided(x, strides=(0, x.strides[0]), shape=(y.size, x.size)), - as_strided(y, strides=(y.strides[0],0), shape=(y.size, x.size))) - else: + as_strided(y, strides=(y.strides[0],0), shape=(y.size, x.size))) + else: return (np.ma.array(as_strided(x, strides=(0, x.strides[0]), shape=(y.size, x.size)), mask=mask), np.ma.array(as_strided(y, strides=(y.strides[0],0), shape=(y.size, x.size)), mask=mask)) #In the following the indices i, j represents the pairing of the ith segment of b and the jth segment of a #e.g. if ignore[i,j]==True then the ith segment of b and the jth segment of a cannot intersect - ignore = np.zeros([b.shape[0]-1, a.shape[0]-1], dtype=bool) + ignore = np.zeros([b.shape[0]-1, a.shape[0]-1], dtype=bool) x11, x21 = meshgrid_as_strided(a[:-1, 0], b[:-1, 0], mask=ignore) x12, x22 = meshgrid_as_strided(a[1: , 0], b[1: , 0], mask=ignore) y11, y21 = meshgrid_as_strided(a[:-1, 1], b[:-1, 1], mask=ignore) - y12, y22 = meshgrid_as_strided(a[1: , 1], b[1: , 1], mask=ignore) + y12, y22 = meshgrid_as_strided(a[1: , 1], b[1: , 1], mask=ignore) #ignore segements with non-overlappiong bounding boxes ignore[np.ma.maximum(x11, x12) < np.ma.minimum(x21, x22)] = True @@ -90,14 +90,14 @@ def meshgrid_as_strided(x, y, mask=None): ignore[np.ma.maximum(y11, y12) < np.ma.minimum(y21, y22)] = True ignore[np.ma.minimum(y11, y12) > np.ma.maximum(y21, y22)] = True - #find intersection of segments, ignoring impossible line segment pairs when new info becomes available - denom_ = np.empty(ignore.shape, dtype=float) + #find intersection of segments, ignoring impossible line segment pairs when new info becomes available + denom_ = np.empty(ignore.shape, dtype=float) denom = np.ma.array(denom_, mask=ignore) - denom_[:, :] = ((y22 - y21) * (x12 - x11)) - ((x22 - x21) * (y12 - y11)) + denom_[:, :] = ((y22 - y21) * (x12 - x11)) - ((x22 - x21) * (y12 - y11)) #denom_tmp = ((y22 - y21) * (x12 - x11)) - ((x22 - x21) * (y12 - y11)) # H.SETO denom_[:, :] = np.where(denom_ == 0.0, 1.e-100,denom_) - ua_ = np.empty(ignore.shape, dtype=float) + ua_ = np.empty(ignore.shape, dtype=float) ua = np.ma.array(ua_, mask=ignore) ua_[:, :] = (((x22 - x21) * (y11 - y21)) - ((y22 - y21) * (x11 - x21))) ua_[:, :] /= denom @@ -105,7 +105,7 @@ def meshgrid_as_strided(x, y, mask=None): ignore[ua < 0] = True ignore[ua > 1] = True - ub_ = np.empty(ignore.shape, dtype=float) + ub_ = np.empty(ignore.shape, dtype=float) ub = np.ma.array(ub_, mask=ignore) ub_[:, :] = (((x12 - x11) * (y11 - y21)) - ((y12 - y11) * (x11 - x21))) ub_[:, :] /= denom @@ -115,33 +115,33 @@ def meshgrid_as_strided(x, y, mask=None): ignore[ub > 1] = True nans_ = np.zeros(ignore.shape, dtype = bool) - nans = np.ma.array(nans_, mask = ignore) - nans_[:,:] = np.isnan(ua) + nans = np.ma.array(nans_, mask = ignore) + nans_[:,:] = np.isnan(ua) if not np.ma.any(nans): #remove duplicate cases where intersection happens on an endpoint # ignore[np.ma.where((ua[:, :-1] == 1) & (ua[:, 1:] == 0))] = True -# ignore[np.ma.where((ub[:-1, :] == 1) & (ub[1:, :] == 0))] = True +# ignore[np.ma.where((ub[:-1, :] == 1) & (ub[1:, :] == 0))] = True ignore[np.ma.where((ua[:, :-1] < 1.0 + atol) & (ua[:, :-1] > 1.0 - atol) & (ua[:, 1:] < atol) & (ua[:, 1:] > -atol))] = True - ignore[np.ma.where((ub[:-1, :] < 1 + atol) & (ub[:-1, :] > 1 - atol) & (ub[1:, :] < atol) & (ub[1:, :] > -atol))] = True + ignore[np.ma.where((ub[:-1, :] < 1 + atol) & (ub[:-1, :] > 1 - atol) & (ub[1:, :] < atol) & (ub[1:, :] > -atol))] = True xi = x11 + ua * (x12 - x11) yi = y11 + ua * (y12 - y11) return np.ma.compressed(xi), np.ma.compressed(yi) else: - n_nans = np.ma.sum(nans) + n_nans = np.ma.sum(nans) n_standard = np.ma.count(x11) - n_nans #I initially tried using a set to get unique points but had problems with floating point equality #create n by 2 array to hold all possible intersection points, check later for uniqueness - points = np.empty([n_standard + 2 * n_nans, 2], dtype = float) #each colinear segment pair has two intersections, hence the extra n_colinear points + points = np.empty([n_standard + 2 * n_nans, 2], dtype = float) #each colinear segment pair has two intersections, hence the extra n_colinear points #add standard intersection points xi = x11 + ua * (x12 - x11) - yi = y11 + ua * (y12 - y11) + yi = y11 + ua * (y12 - y11) points[:n_standard,0] = np.ma.compressed(xi[~nans]) - points[:n_standard,1] = np.ma.compressed(yi[~nans]) + points[:n_standard,1] = np.ma.compressed(yi[~nans]) ignore[~nans]=True @@ -180,18 +180,18 @@ def find_inter( contour1, contour2): xi = np.array([]) yi = np.array([]) - i=0 - ncombos = (sum([len(x.get_paths()) for x in contour1.collections]) * + i=0 + ncombos = (sum([len(x.get_paths()) for x in contour1.collections]) * sum([len(x.get_paths()) for x in contour2.collections])) for linecol1, linecol2 in itertools.product(contour1.collections, contour2.collections): for path1, path2 in itertools.product(linecol1.get_paths(),linecol2.get_paths()): i += 1 - print('line combo %5d of %5d' % (i, ncombos)) + print('line combo %5d of %5d' % (i, ncombos)) xinter, yinter = linelineintersect(path1.vertices, path2.vertices) xi = np.append(xi, xinter) yi = np.append(yi, yinter) - #plt.scatter(xi, yi, s=20) + #plt.scatter(xi, yi, s=20) #plt.show() return xi, yi diff --git a/boututils/datafile.py b/boututils/datafile.py index a535e32..3bef6b7 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -81,7 +81,7 @@ class DataFile(object): def __init__(self, filename=None, write=False, create=False, format='NETCDF3_64BIT', **kwargs): """ - NetCDF formats are described here: http://unidata.github.io/netcdf4-python/ + NetCDF formats are described here: https://unidata.github.io/netcdf4-python/ - NETCDF3_CLASSIC Limited to 2.1Gb files - NETCDF3_64BIT_OFFSET or NETCDF3_64BIT is an extension to allow larger file sizes - NETCDF3_64BIT_DATA adds 64-bit integer data types and 64-bit dimension sizes diff --git a/boututils/geqdsk.py b/boututils/geqdsk.py index 6fc4d07..4335b03 100755 --- a/boututils/geqdsk.py +++ b/boututils/geqdsk.py @@ -16,7 +16,7 @@ See LICENSE file for conditions of use. The official document describing g-eqdsk files: -http://fusion.gat.com/conferences/snowmass/working/mfe/physics/p3/equilibria/g_eqdsk_s.pdf +https://fusion.gat.com/conferences/snowmass/working/mfe/physics/p3/equilibria/g_eqdsk_s.pdf """ class Geqdsk(object): diff --git a/boututils/local_min_max.py b/boututils/local_min_max.py index 8484da0..456d213 100644 --- a/boututils/local_min_max.py +++ b/boututils/local_min_max.py @@ -3,67 +3,67 @@ import scipy.ndimage.morphology as morphology def detect_local_minima(arr): - # http://stackoverflow.com/questions/3684484/peak-detection-in-a-2d-array/3689710#3689710 + # https://stackoverflow.com/questions/3684484/peak-detection-in-a-2d-array/3689710#3689710 """ Takes an array and detects the troughs using the local maximum filter. Returns a boolean mask of the troughs (i.e. 1 when the pixel's value is the neighborhood maximum, 0 otherwise) """ # define an connected neighborhood - # http://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#generate_binary_structure + # https://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#generate_binary_structure neighborhood = morphology.generate_binary_structure(len(arr.shape),2) - # apply the local minimum filter; all locations of minimum value + # apply the local minimum filter; all locations of minimum value # in their neighborhood are set to 1 - # http://www.scipy.org/doc/api_docs/SciPy.ndimage.filters.html#minimum_filter + # https://www.scipy.org/doc/api_docs/SciPy.ndimage.filters.html#minimum_filter local_min = (filters.minimum_filter(arr, footprint=neighborhood)==arr) - # local_min is a mask that contains the peaks we are + # local_min is a mask that contains the peaks we are # looking for, but also the background. # In order to isolate the peaks we must remove the background from the mask. - # + # # we create the mask of the background background = (arr==0) - # - # a little technicality: we must erode the background in order to - # successfully subtract it from local_min, otherwise a line will + # + # a little technicality: we must erode the background in order to + # successfully subtract it from local_min, otherwise a line will # appear along the background border (artifact of the local minimum filter) - # http://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#binary_erosion + # https://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#binary_erosion eroded_background = morphology.binary_erosion( background, structure=neighborhood, border_value=1) - # - # we obtain the final mask, containing only peaks, + # + # we obtain the final mask, containing only peaks, # by removing the background from the local_min mask detected_minima = local_min - eroded_background return np.where(detected_minima) - + def detect_local_maxima(arr): - # http://stackoverflow.com/questions/3684484/peak-detection-in-a-2d-array/3689710#3689710 + # https://stackoverflow.com/questions/3684484/peak-detection-in-a-2d-array/3689710#3689710 """ Takes an array and detects the peaks using the local maximum filter. Returns a boolean mask of the troughs (i.e. 1 when the pixel's value is the neighborhood maximum, 0 otherwise) """ # define an connected neighborhood - # http://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#generate_binary_structure + # https://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#generate_binary_structure neighborhood = morphology.generate_binary_structure(len(arr.shape),2) - # apply the local maximum filter; all locations of maximum value + # apply the local maximum filter; all locations of maximum value # in their neighborhood are set to 1 - # http://www.scipy.org/doc/api_docs/SciPy.ndimage.filters.html#maximum_filter + # https://www.scipy.org/doc/api_docs/SciPy.ndimage.filters.html#maximum_filter local_max = (filters.maximum_filter(arr, footprint=neighborhood)==arr) - # local_max is a mask that contains the peaks we are + # local_max is a mask that contains the peaks we are # looking for, but also the background. # In order to isolate the peaks we must remove the background from the mask. - # + # # we create the mask of the background background = (arr==0) - # - # a little technicality: we must erode the background in order to - # successfully subtract it from local_max, otherwise a line will + # + # a little technicality: we must erode the background in order to + # successfully subtract it from local_max, otherwise a line will # appear along the background border (artifact of the local maximum filter) - # http://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#binary_erosion + # https://www.scipy.org/doc/api_docs/SciPy.ndimage.morphology.html#binary_erosion eroded_background = morphology.binary_erosion( background, structure=neighborhood, border_value=1) - # - # we obtain the final mask, containing only peaks, + # + # we obtain the final mask, containing only peaks, # by removing the background from the local_min mask detected_maxima = local_max - eroded_background return np.where(detected_maxima) \ No newline at end of file diff --git a/boututils/run_wrapper.py b/boututils/run_wrapper.py index 5e05052..c25e343 100644 --- a/boututils/run_wrapper.py +++ b/boututils/run_wrapper.py @@ -85,7 +85,7 @@ def determineNumberOfCPUs(): scaling userspace-only program Taken from a post on stackoverflow: - http://stackoverflow.com/questions/1006289/how-to-find-out-the-number-of-cpus-in-python + https://stackoverflow.com/questions/1006289/how-to-find-out-the-number-of-cpus-in-python Returns ------- @@ -229,7 +229,7 @@ def launch(command, runcmd=None, nproc=None, mthread=None, if mthread is not None: cmd = "OMP_NUM_THREADS={j} ".format(j=mthread)+cmd - + if verbose == True: print(cmd) diff --git a/boututils/showdata.py b/boututils/showdata.py index 9959402..fefed60 100644 --- a/boututils/showdata.py +++ b/boututils/showdata.py @@ -26,7 +26,7 @@ ################### -#http://stackoverflow.com/questions/16732379/stop-start-pause-in-python-matplotlib-animation +#https://stackoverflow.com/questions/16732379/stop-start-pause-in-python-matplotlib-animation # j=-2 pause = False @@ -363,7 +363,7 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar Nframes = int(Nt[0][0]/intv) # Generate grids for plotting - # Try to use provided grids where possible + # Try to use provided grids where possible # If x and/or y are not lists, apply to all variables if not isinstance(x, (list,tuple)): x = [x]*Nvar # Make list of x with length Nvar @@ -377,18 +377,18 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar xnew[i].append(x[i]) if not (x[i].shape==(Nx[i][0],) or x[i].shape==(Nx[i][0],Ny[i][0]) or x[i].shape==(Nt[i][0],Nx[i][0],Ny[i],[0])): raise ValueError("For variable number "+str(i)+", "+titles[i]+", the shape of x is not compatible with the shape of the variable. Shape of x should be (Nx), (Nx,Ny) or (Nt,Nx,Ny).") - except: + except: for j in range(0, Nlines[i]): xnew[i].append(linspace(0,Nx[i][j]-1, Nx[i][j])) #x.append(linspace(0,Nx[i][0]-1, Nx[i][0])) - + if (Ndims[i][0] == 3): try: ynew.append(y[i]) if not (y[i].shape==(Ny[i][0],) or y[i].shape==(Nx[i][0],Ny[i][0]) or y[i].shape==(Nt[i][0],Nx[i][0],Ny[i],[0])): raise ValueError("For variable number "+str(i)+", "+titles[i]+", the shape of y is not compatible with the shape of the variable. Shape of y should be (Ny), (Nx,Ny) or (Nt,Nx,Ny).") - except: + except: ynew.append(linspace(0, Ny[i][0]-1, Ny[i][0])) else: ynew.append(0) diff --git a/setup.py b/setup.py index 8f04497..260d573 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ url='https://github.com/boutproject/boututils', project_urls={ "Bug Tracker": "https://github.com/boutproject/boututils/issues/", - "Documentation": "http://bout-dev.readthedocs.io/en/latest/", + "Documentation": "https://bout-dev.readthedocs.io/en/latest/", "Source Code": "https://github.com/boutproject/boututils/", }, packages=setuptools.find_packages(), From ed445a3e80a2483e490bfcfab61cbe01a6f810bb Mon Sep 17 00:00:00 2001 From: loeiten Date: Tue, 5 May 2020 08:03:19 +0200 Subject: [PATCH 21/98] Updated contour link --- boututils/contour.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/boututils/contour.py b/boututils/contour.py index 9141ff9..1d3fc97 100644 --- a/boututils/contour.py +++ b/boututils/contour.py @@ -1,9 +1,7 @@ """ Contour calculation routines -https://members.bellatlantic.net/~vze2vrva/thesis.html - -""" +https://web.archive.org/web/20140901225541/https://members.bellatlantic.net/~vze2vrva/thesis.html""" from __future__ import print_function from __future__ import division from past.utils import old_div From 7ed3a7b46960ff2adf76a40966f4cc9d761083cc Mon Sep 17 00:00:00 2001 From: John Omotani Date: Sun, 5 Jul 2020 12:33:45 +0100 Subject: [PATCH 22/98] Support FieldPerp in DataFile --- boututils/datafile.py | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/boututils/datafile.py b/boututils/datafile.py index 3bef6b7..c42db73 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -473,14 +473,32 @@ def _bout_type_from_dimensions(self, varname): dims_dict = { ('t', 'x', 'y', 'z'): "Field3D_t", ('t', 'x', 'y'): "Field2D_t", + ('t', 'x', 'z'): "FieldPerp_t", ('t',): "scalar_t", ('x', 'y', 'z'): "Field3D", ('x', 'y'): "Field2D", + ('x', 'z'): "FieldPerp", + ('x'): "ArrayX", (): "scalar", } return dims_dict.get(dims, None) + def _bout_dimensions_from_type(self, bout_type): + dims_dict = { + "Field3D_t": ('t', 'x', 'y', 'z'), + "Field2D_t": ('t', 'x', 'y'), + "FieldPerp_t": ('t', 'x', 'z'), + "scalar_t": ('t',), + "Field3D": ('x', 'y', 'z'), + "Field2D": ('x', 'y'), + "FieldPerp": ('x', 'z'), + "ArrayX": ('x'), + "scalar": (), + } + + return dims_dict.get(bout_type, None) + def write(self, name, data, info=False): if not self.writeable: @@ -523,11 +541,15 @@ def write(self, name, data, info=False): # Not found, so add. # Get dimensions - defdims = [(), - ('t',), - ('x', 'y'), - ('x', 'y', 'z'), - ('t', 'x', 'y', 'z')] + try: + defdims = self._bout_dimensions_from_type(data.attributes['bout_type']) + except AttributeError: + defdims_list = [(), + ('t',), + ('x', 'y'), + ('x', 'y', 'z'), + ('t', 'x', 'y', 'z')] + defdims = defdims_list[len(s)] def find_dim(dim): # Find a dimension with given name and size @@ -585,7 +607,7 @@ def find_dim(dim): return name # List of (size, 'name') tuples - dlist = list(zip(s, defdims[len(s)])) + dlist = list(zip(s, defdims)) # Get new list of variables, and turn into a tuple dims = tuple(map(find_dim, dlist)) @@ -760,10 +782,13 @@ def dimensions(self, varname): bout_type = self.bout_type(varname) dims_dict = { "Field3D_t": ('t', 'x', 'y', 'z'), + "FieldPerp_t": ('t', 'x', 'z'), "Field2D_t": ('t', 'x', 'y'), "scalar_t": ('t',), "Field3D": ('x', 'y', 'z'), + "FieldPerp": ('x', 'z'), "Field2D": ('x', 'y'), + "ArrayX": ('x'), "scalar": (), } try: @@ -866,7 +891,7 @@ def write(self, name, data, info=False): print("Creating variable '" + name + "' with bout_type '" + bout_type + "'") - if bout_type in ["Field3D_t", "Field2D_t", "scalar_t"]: + if bout_type in ["Field3D_t", "Field2D_t", "FieldPerp_t", "scalar_t"]: # time evolving fields shape = list(data.shape) # set time dimension to None to make unlimited @@ -896,7 +921,7 @@ def write(self, name, data, info=False): try: for attrname in data.attributes: attrval = data.attributes[attrname] - if type(attrval == str): + if type(attrval) == str: attrval = attrval.encode(encoding='utf-8') self.handle[name].attrs.create(attrname, attrval) except AttributeError: From dcf0b57d5fd19ccc6d0c80ba8944877e372e64c9 Mon Sep 17 00:00:00 2001 From: johnomotani Date: Mon, 6 Jul 2020 10:42:21 +0100 Subject: [PATCH 23/98] Use comma in single-element tuples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michael Løiten --- boututils/datafile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boututils/datafile.py b/boututils/datafile.py index c42db73..e8d385b 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -478,7 +478,7 @@ def _bout_type_from_dimensions(self, varname): ('x', 'y', 'z'): "Field3D", ('x', 'y'): "Field2D", ('x', 'z'): "FieldPerp", - ('x'): "ArrayX", + ('x',): "ArrayX", (): "scalar", } @@ -493,7 +493,7 @@ def _bout_dimensions_from_type(self, bout_type): "Field3D": ('x', 'y', 'z'), "Field2D": ('x', 'y'), "FieldPerp": ('x', 'z'), - "ArrayX": ('x'), + "ArrayX": ('x',), "scalar": (), } @@ -788,7 +788,7 @@ def dimensions(self, varname): "Field3D": ('x', 'y', 'z'), "FieldPerp": ('x', 'z'), "Field2D": ('x', 'y'), - "ArrayX": ('x'), + "ArrayX": ('x',), "scalar": (), } try: From 620a42e0e8ebb2b617edab1e5433073f610fa047 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 19 Sep 2020 21:39:09 +0200 Subject: [PATCH 24/98] Merge changes from BOUT-dev --- boututils/analyse_equil_2.py | 12 ++-- boututils/bunch.py | 6 ++ boututils/check_scaling.py | 11 ++-- boututils/datafile.py | 7 ++- boututils/efit_analyzer.py | 2 +- boututils/geqdsk.py | 2 +- boututils/moment_xyzt.py | 16 ++--- boututils/read_geqdsk.py | 6 +- boututils/run_wrapper.py | 117 +++++++++++++++++++++++++---------- boututils/surface_average.py | 32 ++++------ boututils/volume_integral.py | 21 +++---- 11 files changed, 133 insertions(+), 99 deletions(-) create mode 100644 boututils/bunch.py diff --git a/boututils/analyse_equil_2.py b/boututils/analyse_equil_2.py index 93b98fc..d315c6a 100644 --- a/boututils/analyse_equil_2.py +++ b/boututils/analyse_equil_2.py @@ -12,7 +12,6 @@ from past.utils import old_div import numpy -from bunch import Bunch from . import local_min_max from scipy.interpolate import RectBivariateSpline from matplotlib.pyplot import contour, gradient, annotate, plot, draw @@ -35,8 +34,8 @@ def analyse_equil(F, R, Z): Returns ------- - bunch - A structure of critical points containing: + object + An object of critical points containing: n_opoint, n_xpoint - Number of O- and X-points primary_opt - Index of plasma centre O-point @@ -169,8 +168,7 @@ def analyse_equil(F, R, Z): print("Number of O-points: "+str(n_opoint)) if n_opoint == 0 : - print("No O-points! Giving up on this equilibrium") - return Bunch(n_opoint=0, n_xpoint=0, primary_opt=-1) + raise RuntimeError("No O-points! Giving up on this equilibrium") #;;;;;;;;;;;;;; Find plasma centre ;;;;;;;;;;;;;;;;;;; @@ -259,8 +257,8 @@ def analyse_equil(F, R, Z): - #;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - # Put results into a structure + #;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + # Put results into a structure result = Bunch(n_opoint=n_opoint, n_xpoint=n_xpoint, # Number of O- and X-points primary_opt=primary_opt, # Which O-point is the plasma centre diff --git a/boututils/bunch.py b/boututils/bunch.py new file mode 100644 index 0000000..2bc1ca0 --- /dev/null +++ b/boututils/bunch.py @@ -0,0 +1,6 @@ +# what we need from bunch + +class Bunch: + def __init__(self, **dict): + for k in dict: + setattr(self, k, dict[k]) diff --git a/boututils/check_scaling.py b/boututils/check_scaling.py index be22fc0..af59b0b 100644 --- a/boututils/check_scaling.py +++ b/boututils/check_scaling.py @@ -44,15 +44,14 @@ def check_order(error_list, expected_order, tolerance=2.e-1, spacing=None): if len(error_list) < 2: raise RuntimeError("Expected at least 2 data points to calculate error") - success=True + success = True + for i in range(len(error_list)-1): - if spacing is None: - actual_order = log(errors[i] / errors[i+1]) / log(2) - else: - actual_order = log(errors[i] / errors[i+1]) / log(grid_spacing[i] / grid_spacing[i+1]) + grid_spacing = 2 if spacing is None else spacing[i] / spacing[i+1] + actual_order = log(error_list[i] / error_list[i+1]) / log(grid_spacing) if not isclose(actual_order, expected_order, atol=tolerance, rtol=0): - success=False + success = False return success diff --git a/boututils/datafile.py b/boututils/datafile.py index e8d385b..00bb987 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -729,11 +729,12 @@ def read(self, name, ranges=None, asBoutArray=True): if var is None: return None - if asBoutArray: - attributes = self.attributes(n) + attributes = self.attributes(n) if asBoutArray else {} + + time_dependent = attributes.get("bout_type", "none").endswith("_t") ndims = len(var.shape) - if ndims == 1 and var.shape[0] == 1: + if ndims == 1 and var.shape[0] == 1 and not time_dependent: data = var if asBoutArray: data = BoutArray(data, attributes=attributes) diff --git a/boututils/efit_analyzer.py b/boututils/efit_analyzer.py index 0b15f42..6577b78 100644 --- a/boututils/efit_analyzer.py +++ b/boututils/efit_analyzer.py @@ -4,7 +4,7 @@ from builtins import range from past.utils import old_div import numpy as np -from bunch import Bunch +from boututils.bunch import Bunch from .radial_grid import radial_grid from .analyse_equil_2 import analyse_equil from pylab import (cm, clabel, contour, draw, legend, plot, setp, show, diff --git a/boututils/geqdsk.py b/boututils/geqdsk.py index 4335b03..d63caf1 100755 --- a/boututils/geqdsk.py +++ b/boututils/geqdsk.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from __future__ import print_function from __future__ import division diff --git a/boututils/moment_xyzt.py b/boututils/moment_xyzt.py index 8d7dc4c..675d1f1 100644 --- a/boututils/moment_xyzt.py +++ b/boututils/moment_xyzt.py @@ -3,8 +3,7 @@ from builtins import range from past.utils import old_div import numpy as np -from bunch import Bunch - +from boututils.bunch import Bunch def RMSvalue( vec1d): #; @@ -16,9 +15,9 @@ def RMSvalue( vec1d): valrms=np.sqrt(old_div(np.sum((vec1d-valav)**2),nel)) acvec=vec1d-valav - - return Bunch(valrms=valrms, valav=valav, acvec=acvec) - + return Bunch(valrms=valrms, + valav=valav, + acvec=acvec) @@ -30,8 +29,6 @@ def moment_xyzt( sig_xyzt, *args):#rms=None, dc=None, ac=None): #; -AC (DC subtracted out), i.e., a function of (x,y,z,t) #;------------------------------------------------------------------- - try: # return to caller - d = np.shape(sig_xyzt) if np.size(d) != 4 : print("Error: Variable must be 4D (x,y,z,t)") @@ -73,8 +70,5 @@ def moment_xyzt( sig_xyzt, *args):#rms=None, dc=None, ac=None): res.ac = ac if 'RMS' not in args and 'DC' not in args and 'AC' not in args : - print('Wrong argument') + raise RuntimeError('Wrong argument') return res - except: - print('moment_xyz failed') - return diff --git a/boututils/read_geqdsk.py b/boututils/read_geqdsk.py index 755b3f1..f665f59 100644 --- a/boututils/read_geqdsk.py +++ b/boututils/read_geqdsk.py @@ -1,8 +1,8 @@ from __future__ import print_function from builtins import range import numpy -from bunch import Bunch from geqdsk import Geqdsk +from boututils.bunch import Bunch def read_geqdsk (file): @@ -67,8 +67,6 @@ def read_geqdsk (file): for j in range(0,nyefit): r[i,j] = rgrid1 + xdim*i/(nxefit-1) z[i,j] = (zmid-0.5*zdim) + zdim*j/(nyefit-1) - - f=f.T @@ -89,4 +87,4 @@ def read_geqdsk (file): pres=pres, # Plasma pressure in nt/m^2 on uniform flux grid qpsi=qpsi, # q values on uniform flux grid nbdry=nbdry, rbdry=rbdry, zbdry=zbdry, # Plasma boundary - nlim=nlim, xlim=xlim, ylim=ylim) # Wall boundary \ No newline at end of file + nlim=nlim, xlim=xlim, ylim=ylim) # Wall boundary diff --git a/boututils/run_wrapper.py b/boututils/run_wrapper.py index c25e343..9a91d2c 100644 --- a/boututils/run_wrapper.py +++ b/boututils/run_wrapper.py @@ -2,21 +2,20 @@ from builtins import str import os +import pathlib import re import subprocess +from subprocess import call, Popen, STDOUT, PIPE -try: - # Python 2.4 onwards - from subprocess import call, Popen, STDOUT, PIPE - lib = "call" -except ImportError: - # FIXME: drop support for python < 2.4! - # Use os.system (depreciated) - from os import popen4, system - lib = "system" +if os.name == "nt": + # Default on Windows + DEFAULT_MPIRUN = "mpiexec.exe -n" +else: + DEFAULT_MPIRUN = "mpirun -np" -def getmpirun(default="mpirun -np"): + +def getmpirun(default=DEFAULT_MPIRUN): """Return environment variable named MPIRUN, if it exists else return a default mpirun command @@ -53,27 +52,20 @@ def shell(command, pipe=False): """ output = None status = 0 - if lib == "system": - if pipe: - handle = popen4(command) - output = handle[1].read() - else: - status = system(command) + if pipe: + child = Popen(command, stderr=STDOUT, stdout=PIPE, shell=True) + # This returns a b'string' which is casted to string in + # python 2. However, as we want to use f.write() in our + # runtest, we cast this to utf-8 here + output = child.stdout.read().decode("utf-8", "ignore") + # Wait for the process to finish. Note that child.wait() + # would have deadlocked the system as stdout is PIPEd, we + # therefore use communicate, which in the end also waits for + # the process to finish + child.communicate() + status = child.returncode else: - if pipe: - child = Popen(command, stderr=STDOUT, stdout=PIPE, shell=True) - # This returns a b'string' which is casted to string in - # python 2. However, as we want to use f.write() in our - # runtest, we cast this to utf-8 here - output = child.stdout.read().decode("utf-8", "ignore") - # Wait for the process to finish. Note that child.wait() - # would have deadlocked the system as stdout is PIPEd, we - # therefore use communicate, which in the end also waits for - # the process to finish - child.communicate() - status = child.returncode - else: - status = call(command, shell=True) + status = call(command, shell=True) return status, output @@ -93,6 +85,18 @@ def determineNumberOfCPUs(): The number of CPUs """ + # cpuset + # cpuset may restrict the number of *available* processors + try: + m = re.search(r'(?m)^Cpus_allowed:\s*(.*)$', + open('/proc/self/status').read()) + if m: + res = bin(int(m.group(1).replace(',', ''), 16)).count('1') + if res > 0: + return res + except IOError: + pass + # Python 2.6+ try: import multiprocessing @@ -228,8 +232,12 @@ def launch(command, runcmd=None, nproc=None, mthread=None, cmd = cmd + " > "+output if mthread is not None: - cmd = "OMP_NUM_THREADS={j} ".format(j=mthread)+cmd - + if os.name == "nt": + # We're on windows, so we have to do it a little different + cmd = 'cmd /C "set OMP_NUM_THREADS={} && {}"'.format(mthread, cmd) + else: + cmd = "OMP_NUM_THREADS={} {}".format(mthread, cmd) + if verbose == True: print(cmd) @@ -277,3 +285,48 @@ def launch_safe(command, *args, **kwargs): "Output was\n\n%s"% (s,command,out)) return s, out + + +def build_and_log(test): + """Run make and redirect the output to a log file. Prints input + + On Windows, does nothing because executable should have already + been built + + """ + + if os.name == "nt": + return + + print("Making {}".format(test)) + + if os.path.exists("makefile") or os.path.exists("Makefile"): + return shell_safe("make > make.log") + + ctest_filename = "CTestTestfile.cmake" + if not os.path.exists(ctest_filename): + raise RuntimeError("Could not build: no makefile and no CMake files detected") + + # We're using CMake, but we need to know the target name. If + # bout_add_integrated_test was used (which it should have been!), + # then the test name is the same as the target name + with open(ctest_filename, "r") as f: + contents = f.read() + match = re.search("add_test.(.*) ", contents) + if match is None: + raise RuntimeError("Using CMake, but could not determine test name") + test_name = match.group(1) + + # Now we need to find the build directory. It'll be the first + # parent containing CMakeCache.txt + here = pathlib.Path(".").absolute() + for parent in here.parents: + if (parent / "CMakeCache.txt").exists(): + return shell_safe( + "cmake --build {} --target {} > make.log".format(parent, test_name) + ) + + # We've just looked up the entire directory structure and not + # found the build directory, this could happen if CMakeCache was + # deleted, in which case we can't build anyway + raise RuntimeError("Using CMake, but could not find build directory") diff --git a/boututils/surface_average.py b/boututils/surface_average.py index d58e9a3..340d252 100644 --- a/boututils/surface_average.py +++ b/boututils/surface_average.py @@ -11,17 +11,16 @@ from boututils.calculus import deriv from boututils.int_func import int_func from .idl_tabulate import idl_tabulate -from bunch import bunchify -def surface_average(var, g, area=None): +def surface_average(var, grid, area=None): """Average a variable over a surface Parameters ---------- var : array_like 3-D or 4D variable to integrate (either [x,y,z] or [t,x,y,z]) - g : dict + grid : dict A dictionary of various grid quantities area : bool Average by flux-surface area = (B/Bp)*dl * R*dz @@ -35,8 +34,6 @@ def surface_average(var, g, area=None): s = np.ndim(var) - - if s == 4 : nx = np.shape(var)[1] ny = np.shape(var)[2] @@ -44,28 +41,21 @@ def surface_average(var, g, area=None): result = np.zeros((nx,nt)) for t in range (nt): - - result[:,t] = surface_average(var[t,:,:,:], g, area=area) + result[:,t] = surface_average(var[t,:,:,:], grid, area=area) return result elif s != 3 : - print("ERROR: surface_average var must be 3 or 4D") - return 0 + raise RuntimeError("ERROR: surface_average var must be 3D or 4D") - - # 3D [x,y,z] + # 3D [x,y,z] nx = np.shape(var)[0] ny = np.shape(var)[1] -# nz = np.shape(var)[2] - -# Use bunch to create grid structure - grid=bunchify(g) - # Calculate poloidal angle from grid + # Calculate poloidal angle from grid theta = np.zeros((nx,ny)) - #status = gen_surface(mesh=grid) ; Start generator + #status = gen_surface(mesh=grid) ; Start generator xi = -1 yi = np.arange(0,ny,dtype=int) last = 0 @@ -76,17 +66,17 @@ def surface_average(var, g, area=None): last = 1 dtheta = 2.*np.pi / np.float(ny) - r = grid.Rxy[xi,yi] - z = grid.Zxy[xi,yi] + r = grid['Rxy'][xi,yi] + z = grid['Zxy'][xi,yi] n = np.size(r) dl = old_div(np.sqrt( deriv(r)**2 + deriv(z)**2 ), dtheta) if area: - dA = (old_div(grid.Bxy[xi,yi],grid.Bpxy[xi,yi]))*r*dl + dA = (old_div(grid['Bxy'][xi,yi],grid['Bpxy'][xi,yi]))*r*dl A = int_func(np.arange(n),dA) theta[xi,yi] = 2.*np.pi*A/A[n-1] else: - nu = dl * (grid.Btxy[xi,yi]) / ((grid.Bpxy[xi,yi]) * r ) + nu = dl * (grid['Btxy'][xi,yi]) / ((grid['Bpxy'][xi,yi]) * r ) theta[xi,yi] = int_func(np.arange(n)*dtheta,nu) theta[xi,yi] = 2.*np.pi*theta[xi,yi] / theta[xi,yi[n-1]] diff --git a/boututils/volume_integral.py b/boututils/volume_integral.py index 9fdb083..7e5d4da 100644 --- a/boututils/volume_integral.py +++ b/boututils/volume_integral.py @@ -8,17 +8,15 @@ from past.utils import old_div import numpy as np from boututils.calculus import deriv -from bunch import bunchify - -def volume_integral(var, g, xr=False): +def volume_integral(var, grid, xr=False): """Integrate a variable over a volume Parameters ---------- var : array_like Variable to integrate - g : dict + grid : dict A dictionary of various grid quantities xr : (int, int), optional Range of x indices (default: all of x) @@ -32,9 +30,6 @@ def volume_integral(var, g, xr=False): s = np.ndim(var) - grid=bunchify(g) - - if s == 4 : # 4D [t,x,y,z] - integrate for each t nx = np.shape(var)[1] @@ -84,20 +79,20 @@ def volume_integral(var, g, xr=False): if (xi >= np.min(xr)) & (xi <= np.max(xr)) : dtheta = 2.*np.pi / np.float(ny) - r = grid.Rxy[xi,yi] - z = grid.Zxy[xi,yi] + r = grid['Rxy'][xi,yi] + z = grid['Zxy'][xi,yi] n = np.size(r) dl = old_div(np.sqrt( deriv(r)**2 + deriv(z)**2 ), dtheta) # Area of flux-surface - dA = (grid.Bxy[xi,yi]/grid.Bpxy[xi,yi]*dl) * (r*2.*np.pi) + dA = (grid['Bxy'][xi,yi]/grid['Bpxy'][xi,yi]*dl) * (r*2.*np.pi) # Volume if xi == nx-1 : - dpsi = (grid.psixy[xi,yi] - grid.psixy[xi-1,yi]) + dpsi = (grid['psixy'][xi,yi] - grid['psixy'][xi-1,yi]) else: - dpsi = (grid.psixy[xi+1,yi] - grid.psixy[xi,yi]) + dpsi = (grid['psixy'][xi+1,yi] - grid['psixy'][xi,yi]) - dV = dA * dpsi / (r*(grid.Bpxy[xi,yi])) # May need factor of 2pi + dV = dA * dpsi / (r*(grid['Bpxy'][xi,yi])) # May need factor of 2pi dV = np.abs(dV) result = result + np.sum(var[xi,yi] * dV) From f599a57d38e60f342cd7c4c932a83139054dbf10 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 20 Sep 2020 08:51:57 +0200 Subject: [PATCH 25/98] remove bunch --- requirements.txt | 1 - setup.py | 1 - 2 files changed, 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1dbb35b..7f6d297 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ matplotlib==3.2.1 h5py==2.10.0 scipy==1.4.1 future==0.18.2 -bunch==1.0.1 PyQt5==5.12.2 [mayavi] mayavi==4.6.2 [mayavi] netCDF4==1.5.3 diff --git a/setup.py b/setup.py index 260d573..5de0663 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,6 @@ 'h5py', 'boututils', 'future', - 'bunch', 'netCDF4'], extras_require={ 'mayavi': ['mayavi', 'PyQt5']}, From 986d5180c6c97e9b906fbef6706923a175aeaa44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Mon, 21 Sep 2020 07:50:04 +0200 Subject: [PATCH 26/98] Moved test directory This closes #9 --- {test => boututils/tests}/__init__.py | 0 {test => boututils/tests}/test_import.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {test => boututils/tests}/__init__.py (100%) rename {test => boututils/tests}/test_import.py (100%) diff --git a/test/__init__.py b/boututils/tests/__init__.py similarity index 100% rename from test/__init__.py rename to boututils/tests/__init__.py diff --git a/test/test_import.py b/boututils/tests/test_import.py similarity index 100% rename from test/test_import.py rename to boututils/tests/test_import.py From cc1f090c2b5afd1001b2ae77ca22cb4fe2e92f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Mon, 21 Dec 2020 21:52:12 +0100 Subject: [PATCH 27/98] Added automatic release --- .github/workflows/python_publish.yml | 27 +++++++++++++++++++++++++++ setup.py | 19 ++++++------------- 2 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/python_publish.yml diff --git a/.github/workflows/python_publish.yml b/.github/workflows/python_publish.yml new file mode 100644 index 0000000..ee6730c --- /dev/null +++ b/.github/workflows/python_publish.yml @@ -0,0 +1,27 @@ +name: Upload Python Package + +on: + # Only run when a release is created + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/setup.py b/setup.py index 5de0663..4e8246d 100644 --- a/setup.py +++ b/setup.py @@ -10,22 +10,11 @@ init_path = root_path.joinpath(name, '__init__.py') readme_path = root_path.joinpath('README.md') -# https://packaging.python.org/guides/single-sourcing-package-version/ -with init_path.open('r') as f: - version_file = f.read() - version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - version_file, re.M) - if version_match: - version = version_match.group(1) - else: - raise RuntimeError('Unable to find version string.') - with readme_path.open('r') as f: long_description = f.read() setuptools.setup( name=name, - version=version, author='Ben Dudson et al.', description='Python package containing BOUT++ utils', long_description=long_description, @@ -44,13 +33,17 @@ 'data-extraction', 'data-analysis', 'data-visualization'], + use_scm_version=True, + setup_requires=['setuptools>=42', + 'setuptools_scm[toml]>=3.4', + 'setuptools_scm_git_archive'], install_requires=['numpy', 'matplotlib', 'scipy', 'h5py', - 'boututils', 'future', - 'netCDF4'], + 'netCDF4' + "importlib-metadata ; python_version<'3.8'"], extras_require={ 'mayavi': ['mayavi', 'PyQt5']}, classifiers=[ From a65d9a7339188b13facaee990d013f2759c169be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Mon, 21 Dec 2020 22:37:38 +0100 Subject: [PATCH 28/98] Updates from review #1 --- pyproject.toml | 2 ++ requirements.txt | 1 + 2 files changed, 3 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ec95545 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.setuptools_scm] +write_to = "boututils/_version.py" diff --git a/requirements.txt b/requirements.txt index 7f6d297..9e85c88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ future==0.18.2 PyQt5==5.12.2 [mayavi] mayavi==4.6.2 [mayavi] netCDF4==1.5.3 +setuptools_scm==5.0.1 From b9045e40cb6505bdc4620f559279d9798ffd7569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Tue, 22 Dec 2020 06:58:16 +0100 Subject: [PATCH 29/98] Fixes from reveiw 2 --- boututils/__init__.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/boututils/__init__.py b/boututils/__init__.py index 2b3a54d..54eb75b 100644 --- a/boututils/__init__.py +++ b/boututils/__init__.py @@ -38,5 +38,23 @@ do_import.append('View3D') __all__ = do_import -__version__ = '0.1.4' __name__ = 'boututils' + +try: + from importlib.metadata import version, PackageNotFoundError +except ModuleNotFoundError: + from importlib_metadata import version, PackageNotFoundError +try: + __version__ = version(__name__) +except PackageNotFoundError: + try: + from setuptools_scm import get_version + except ModuleNotFoundError as e: + error_info = ( + "'setuptools_scm' is required to get the version number when running " + "boututils from the git repo. Please install 'setuptools_scm'." + ) + print(error_info) + raise ModuleNotFoundError(str(e) + ". " + error_info) + else: + __version__ = get_version(root="..", relative_to=__file__) From 2610609308cf1198eb351a0c1c7b9afeabe7cac2 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Sun, 3 Jan 2021 12:30:07 +0000 Subject: [PATCH 30/98] Support passing test arguments with CMake --- boututils/run_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boututils/run_wrapper.py b/boututils/run_wrapper.py index 9a91d2c..8c0aa65 100644 --- a/boututils/run_wrapper.py +++ b/boututils/run_wrapper.py @@ -315,7 +315,7 @@ def build_and_log(test): match = re.search("add_test.(.*) ", contents) if match is None: raise RuntimeError("Using CMake, but could not determine test name") - test_name = match.group(1) + test_name = match.group(1).split()[0] # Now we need to find the build directory. It'll be the first # parent containing CMakeCache.txt From 5b083227141fdf337a8c9019b7afdd2a28ee5a8e Mon Sep 17 00:00:00 2001 From: John Omotani Date: Sun, 3 Jan 2021 12:31:41 +0000 Subject: [PATCH 31/98] Fix typo in showdata gif save * Previously, files would have been named '.gif.gif' --- boututils/showdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boututils/showdata.py b/boututils/showdata.py index fefed60..4c0eb97 100644 --- a/boututils/showdata.py +++ b/boututils/showdata.py @@ -688,7 +688,7 @@ def init(): print('Save failed: Check ffmpeg path') raise elif movietype == 'gif': - anim.save(movie+'.gif',writer = 'imagemagick', fps=fps, dpi=dpi) + anim.save(movie,writer = 'imagemagick', fps=fps, dpi=dpi) else: raise ValueError("Unrecognized file type for movie. Supported types are .mp4 and .gif") From 380da7c9c3c78d403c153be90b6e1a2cd7ee3e20 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Sun, 3 Jan 2021 14:47:27 +0000 Subject: [PATCH 32/98] Support string-like variables in DataFile BOUT++ may now write 'string' variables. In NetCDF files this have a 'char' dimension, where '' is the length of the dimension. In HDF5 files they should have an attribute bout_type="string" or bout_type="string_t" (for time-dependent arrays of strings). --- boututils/datafile.py | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/boututils/datafile.py b/boututils/datafile.py index 00bb987..f2fb5ba 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -470,6 +470,13 @@ def dimlen(d): def _bout_type_from_dimensions(self, varname): dims = self.dimensions(varname) + + if any("char" in d for d in dims): + if 't' in dims: + return "string_t" + else: + return "string" + dims_dict = { ('t', 'x', 'y', 'z'): "Field3D_t", ('t', 'x', 'y'): "Field2D_t", @@ -484,7 +491,24 @@ def _bout_type_from_dimensions(self, varname): return dims_dict.get(dims, None) - def _bout_dimensions_from_type(self, bout_type): + def _bout_dimensions_from_var(self, data): + try: + bout_type = data.attributes["bout_type"] + except AttributeError: + defdims_list = [(), + ('t',), + ('x', 'y'), + ('x', 'y', 'z'), + ('t', 'x', 'y', 'z')] + return defdims_list[len(np.shape(data))] + + if bout_type == "string_t": + nt, string_length = data.shape + return ('t', "char" + str(string_length),) + elif bout_type == "string": + string_length = len(data) + return ("char" + str(string_length),) + dims_dict = { "Field3D_t": ('t', 'x', 'y', 'z'), "Field2D_t": ('t', 'x', 'y'), @@ -541,15 +565,7 @@ def write(self, name, data, info=False): # Not found, so add. # Get dimensions - try: - defdims = self._bout_dimensions_from_type(data.attributes['bout_type']) - except AttributeError: - defdims_list = [(), - ('t',), - ('x', 'y'), - ('x', 'y', 'z'), - ('t', 'x', 'y', 'z')] - defdims = defdims_list[len(s)] + defdims = self._bout_dimensions_from_var(data) def find_dim(dim): # Find a dimension with given name and size @@ -786,11 +802,13 @@ def dimensions(self, varname): "FieldPerp_t": ('t', 'x', 'z'), "Field2D_t": ('t', 'x', 'y'), "scalar_t": ('t',), + "string_t": ('t', 'char'), "Field3D": ('x', 'y', 'z'), "FieldPerp": ('x', 'z'), "Field2D": ('x', 'y'), "ArrayX": ('x',), "scalar": (), + "string": ('char',), } try: return dims_dict[bout_type] @@ -892,7 +910,7 @@ def write(self, name, data, info=False): print("Creating variable '" + name + "' with bout_type '" + bout_type + "'") - if bout_type in ["Field3D_t", "Field2D_t", "FieldPerp_t", "scalar_t"]: + if bout_type[-2:] == "_t": # time evolving fields shape = list(data.shape) # set time dimension to None to make unlimited From ab394cbaee1de80b50560e9eb23d2a0cbeefa41a Mon Sep 17 00:00:00 2001 From: John Omotani Date: Mon, 4 Jan 2021 13:43:02 +0000 Subject: [PATCH 33/98] When getting version with setuptools_scm, resolve __file__ path If symlinks to the boututils directory are used, they can confuse the version-number finding function. Fix this by using pathlib.Path.resolve() to resolve all symlinks. --- boututils/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/boututils/__init__.py b/boututils/__init__.py index 54eb75b..12319c7 100644 --- a/boututils/__init__.py +++ b/boututils/__init__.py @@ -57,4 +57,6 @@ print(error_info) raise ModuleNotFoundError(str(e) + ". " + error_info) else: - __version__ = get_version(root="..", relative_to=__file__) + from pathlib import Path + path = Path(__file__).resolve() + __version__ = get_version(root="..", relative_to=path) From 7ccff96028bac628a20eb497fdcc38676414f3c0 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Mon, 4 Jan 2021 14:10:57 +0000 Subject: [PATCH 34/98] Add missing comma --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e8246d..78ef91b 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ 'scipy', 'h5py', 'future', - 'netCDF4' + 'netCDF4', "importlib-metadata ; python_version<'3.8'"], extras_require={ 'mayavi': ['mayavi', 'PyQt5']}, From 93d95993dfb0a948820b5c2e9eb6487aa5e964d5 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Mon, 4 Jan 2021 14:21:01 +0000 Subject: [PATCH 35/98] Don't pip-install boututils on Travis Want to use repo version, not PyPi version. --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 120dcea..ed51570 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,11 +27,10 @@ addons: install: - pip uninstall -y numpy - - pip install boututils - pip install codecov pytest-cov script: - pytest --cov=./ after_success: - - codecov \ No newline at end of file + - codecov From e799596e2411e044bb86647891e8e4aadbfb3658 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Mon, 4 Jan 2021 14:34:38 +0000 Subject: [PATCH 36/98] Install setuptools_scm for Travis test --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ed51570..3092128 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ addons: install: - pip uninstall -y numpy - - pip install codecov pytest-cov + - pip install codecov pytest-cov setuptools_scm script: - pytest --cov=./ From 9a1e904ccf9841db411660c2f20cd783edc5cb3c Mon Sep 17 00:00:00 2001 From: John Omotani Date: Mon, 4 Jan 2021 14:45:00 +0000 Subject: [PATCH 37/98] Install requirements in Travis job --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3092128..5b0f823 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,8 +26,7 @@ addons: - libxkbcommon-x11-0 install: - - pip uninstall -y numpy - - pip install codecov pytest-cov setuptools_scm + - pip install codecov pytest-cov numpy matplotlib scipy h5py future netCDF4 setuptools_scm script: - pytest --cov=./ From 3d7ac265be020171d755aa874e7cc7e33a26ebf1 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Mon, 4 Jan 2021 14:57:39 +0000 Subject: [PATCH 38/98] Simplify requirments installation for CI --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5b0f823..a362bee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,8 @@ addons: - libxkbcommon-x11-0 install: - - pip install codecov pytest-cov numpy matplotlib scipy h5py future netCDF4 setuptools_scm + - pip install setuptools_scm + - pip install . script: - pytest --cov=./ From 9cc3cd4b141afff0eea61d915925286063cba222 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Mon, 4 Jan 2021 15:10:02 +0000 Subject: [PATCH 39/98] Install pytest-cov on Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a362bee..a567dfc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ addons: - libxkbcommon-x11-0 install: - - pip install setuptools_scm + - pip install setuptools_scm pytest-cov - pip install . script: From b7ba9ac4d094fa8419d45a77c65d601ba2d89602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Tue, 5 Jan 2021 10:50:17 +0100 Subject: [PATCH 40/98] Converted travis to actions --- .github/workflows/test.yml | 58 ++++++++++++++++++++++++++++++++++++++ .travis.yml | 36 ----------------------- 2 files changed, 58 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..4157c40 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,58 @@ +name: Test + +on: + # Run each time we push and pull requests + push: + pull_request: + # Cron job + # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows#scheduled-events-schedule + schedule: + # https://crontab.guru/#0_0_1_*_* + - cron: "0 0 1 * *" + +jobs: + # As we are running on different environments, we are splitting the jobs + # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobs + local: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + python-version: [3.7, 3.8, 3.9] + os: [ubuntu-latest] + + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Setup prerequisites + run: | + sudo apt update + sudo apt install libhdf5-serial-dev + sudo apt install netcdf-bin + sudo apt install libnetcdf-dev + sudo apt install libsm6 + sudo apt install libxext6 + sudo apt install libxrender-dev + sudo apt install libxt6 + sudo apt install libgl1-mesa-glx + sudo apt install libfontconfig + sudo apt install libxkbcommon-x11-0 + + - name: Install dependencies + run: | + pip install setuptools_scm pytest-cov + pip install . + + - name: Test local run + run: | + pytest --cov=./ + + - name: Upload to codecov + run: | + codecov diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a567dfc..0000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -dist: xenial -language: python -python: - - "3.7" - -# NOTE: LANG=en_US.UTF-8 etc. is predefined in travis -env: - matrix: - - QT_QPA_PLATFORM=offscreen - global: - secure: "FnR3amw3i/68RLIhEFnAxR9/+xhVWey+24NwI5NSKMpNGs8hoT6Fg/erjfNXw/RyJReD7ikXBMp9rSImjmWxZ3fXK9j545CAYOVlgpj7b9oIfle5eYfukuPQWT/xTkEaGFxHEuygzH+J1rCm1ZFyJ03IFvN/Fw00bsJG8Vg5C8RFqLIrwwjjx8hhHDMKY7hV/+rG7Ufq8irRJFNXrfOZ1dw/H0XRivbhtpQ1K8jSoPb8EGg2WVm8XI3rT1zv6ydGTNVvRoDbvKzCHEDgAwpucY6dk56EdsnweJabjzlqDK2L184OzJHvd7iwAyxjCIgYEiMrCfiCYBNz+73ief7w6rO2rrGuLMUF04VzlxnEXK+W2ylJzpBmwLmfVLJkFOEkwehbxe7OneI6nGR/AA35X/VbuR2HGdYtcZ5aF10Lje341UEeogoPg0hGSBIc+9a48eDyJiPq1pIZeBv6y3IfW72nrDH04tpbGZeS0DFC0fkAC/TzNtqk2k+X9fsEuoHVx5WiL3YHlCCHfLdYhV52gHsbK6QR0ceouG6JxVPKDebc7Jt1BZggdv4nKiVewXbhyNhycZPkQE+WB2dgVE6gXLaMwwtAJus5f+gEv/vwEGRkLT+Kep8LutubZKDUsd8ZZbWaGol5qDGLm3Jv5+3g+RoCY34q6obKxh+jOvsy2EY=" - -# Using addons rather than apt-get install -addons: - apt: - packages: - - libhdf5-serial-dev - - netcdf-bin - - libnetcdf-dev - - libsm6 - - libxext6 - - libxrender-dev - - libxt6 - - libgl1-mesa-glx - - libfontconfig - - libxkbcommon-x11-0 - -install: - - pip install setuptools_scm pytest-cov - - pip install . - -script: - - pytest --cov=./ - -after_success: - - codecov From 2bb5cc6405a3d18861325df819a3b8a8c4d57dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Tue, 5 Jan 2021 10:54:00 +0100 Subject: [PATCH 41/98] Removed 3.9 as it seems unavailable on ubuntu atm --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4157c40..5557ae2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: [3.7, 3.8, 3.9] + python-version: [3.7, 3.8] os: [ubuntu-latest] steps: From 553f069be56343d3b08fb3fac6dbc8d16008ee4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Tue, 5 Jan 2021 10:59:46 +0100 Subject: [PATCH 42/98] Installing missing codecov package --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5557ae2..b02eea2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,4 +55,5 @@ jobs: - name: Upload to codecov run: | + pip install codecov codecov From eb7d4aac391c39b85b1642f82e0b9977d0f4f7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Tue, 5 Jan 2021 11:34:08 +0100 Subject: [PATCH 43/98] Update .github/workflows/test.yml Co-authored-by: Peter Hill --- .github/workflows/test.yml | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b02eea2..3f390e3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,18 +31,17 @@ jobs: python-version: ${{ matrix.python-version }} - name: Setup prerequisites - run: | - sudo apt update - sudo apt install libhdf5-serial-dev - sudo apt install netcdf-bin - sudo apt install libnetcdf-dev - sudo apt install libsm6 - sudo apt install libxext6 - sudo apt install libxrender-dev - sudo apt install libxt6 - sudo apt install libgl1-mesa-glx - sudo apt install libfontconfig - sudo apt install libxkbcommon-x11-0 + run: sudo apt update && + sudo apt install -y libhdf5-serial-dev + netcdf-bin + libnetcdf-dev + libsm6 + libxext6 + libxrender-dev + libxt6 + libgl1-mesa-glx + libfontconfig + libxkbcommon-x11-0 - name: Install dependencies run: | From 2f16de1dbb450e161397245d04c9c9f5a07c50b7 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Tue, 5 Jan 2021 15:30:30 +0000 Subject: [PATCH 44/98] Fall-back to avoid hard dependency on setuptools_scm --- boututils/__init__.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/boututils/__init__.py b/boututils/__init__.py index 12319c7..4bde708 100644 --- a/boututils/__init__.py +++ b/boututils/__init__.py @@ -45,18 +45,24 @@ except ModuleNotFoundError: from importlib_metadata import version, PackageNotFoundError try: + # This gives the version if the boututils package was installed __version__ = version(__name__) except PackageNotFoundError: + # This branch handles the case when boututils is used from the git repo try: from setuptools_scm import get_version - except ModuleNotFoundError as e: - error_info = ( - "'setuptools_scm' is required to get the version number when running " - "boututils from the git repo. Please install 'setuptools_scm'." - ) - print(error_info) - raise ModuleNotFoundError(str(e) + ". " + error_info) - else: from pathlib import Path path = Path(__file__).resolve() __version__ = get_version(root="..", relative_to=path) + except (ModuleNotFoundError, LookupError) as e: + # ModuleNotFoundError if setuptools_scm is not installed. + # LookupError if git is not installed, or the code is not in a git repo even + # though it has not been installed. + from warnings import warn + warn( + "'setuptools_scm' and git are required to get the version number when " + "running boututils from the git repo. Please install 'setuptools_scm' and " + "check 'git rev-parse HEAD' works. Setting __version__='dev' as a " + "workaround." + ) + __version__ = "dev" From ea63a774757bdf8d851c555a2d4360097f418b58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Tue, 5 Jan 2021 21:26:27 +0100 Subject: [PATCH 45/98] Added coverage file --- .coveragerc | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..f96e43f --- /dev/null +++ b/.coveragerc @@ -0,0 +1,10 @@ +[run] +branch = true +omit = */BOUT-dev/* + +[report] +exclude_lines = + if __name__ == .__main__.: +omit = + */tests/* + */docs/* From 3c4bd150cf947fb988de63f6a355f45498d343b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Fri, 8 Jan 2021 23:35:10 +0100 Subject: [PATCH 46/98] Added linting files (without actually linting) --- .github/workflows/lint.yml | 42 ++++++++++++++++++++++++++++++++++++++ .pre-commit-config.yml | 29 ++++++++++++++++++++++++++ setup.cfg | 11 ++++++++++ 3 files changed, 82 insertions(+) create mode 100644 .github/workflows/lint.yml create mode 100644 .pre-commit-config.yml create mode 100644 setup.cfg diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..900b2eb --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,42 @@ +name: Lint + +on: + # Run each time we push and pull requests + push: + pull_request: + # Cron job + # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows#scheduled-events-schedule + schedule: + # https://crontab.guru/#0_0_1_*_* + - cron: "0 0 1 * *" + +jobs: + linting: + runs-on: ubuntu-latest + + steps: + # Use the v2 tag of: https://github.com/actions/checkout + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Set up Python 3.x + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Set up Python ${{ matrix.python-version }} + # Use the v2 tag of: https://github.com/actions/setup-python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + # https://github.com/PyCQA/pylint/issues/352 + # pylint need the requirements to do the checks as the pre-commit repo is local + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade pre-commit + pip install . + + - name: Lint + run: pre-commit run --all-files \ No newline at end of file diff --git a/.pre-commit-config.yml b/.pre-commit-config.yml new file mode 100644 index 0000000..03ba7f9 --- /dev/null +++ b/.pre-commit-config.yml @@ -0,0 +1,29 @@ +# NOTE: The versions can be updated by calling +# pre-commit autoupdate +repos: + - repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.3.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + + - repo: https://github.com/prettier/pre-commit + rev: 413fd9da8e03249b74acf8543567e5debbad8bfa + hooks: + - id: prettier + args: [--prose-wrap=always, --print-width=88] + + - repo: https://github.com/PyCQA/isort + rev: 5.6.4 + hooks: + - id: isort \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..d85d118 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,11 @@ +[isort] +multi_line_output = 3 +include_trailing_comma = True +force_grid_wrap = 0 +use_parentheses = True +ensure_newline_before_comments = True +line_length = 88 + +[flake8] +max-line-length = 88 +extend-ignore = E203, W503 \ No newline at end of file From 0a61f110f58aa48124fc39a16dc5c16ac477ef8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Sat, 9 Jan 2021 13:29:28 +0100 Subject: [PATCH 47/98] Passed black, whitespace, eof, prettier, isort flake8 needs manual fixing --- .github/workflows/lint.yml | 2 +- .github/workflows/python_publish.yml | 32 +- .gitignore | 1 - ...mmit-config.yml => .pre-commit-config.yaml | 8 +- .travis.yml | 20 +- README.md | 21 +- boututils/View3D.py | 647 +++++++++--------- boututils/__init__.py | 52 +- boututils/analyse_equil_2.py | 340 ++++----- boututils/anim.py | 99 +-- boututils/ask.py | 18 +- boututils/boutarray.py | 2 +- boututils/boutgrid.py | 136 ++-- boututils/boutwarnings.py | 4 + boututils/bunch.py | 1 + boututils/calculus.py | 309 +++++---- boututils/check_scaling.py | 32 +- boututils/closest_line.py | 17 +- boututils/contour.py | 36 +- boututils/crosslines.py | 169 +++-- boututils/datafile.py | 246 ++++--- boututils/efit_analyzer.py | 474 ++++++------- boututils/fft_deriv.py | 62 +- boututils/fft_integrate.py | 62 +- boututils/file_import.py | 6 +- boututils/geqdsk.py | 379 ++++++---- boututils/idl_tabulate.py | 15 +- boututils/int_func.py | 43 +- boututils/linear_regression.py | 21 +- boututils/local_min_max.py | 22 +- boututils/mode_structure.py | 638 ++++++++--------- boututils/moment_xyzt.py | 139 ++-- boututils/options.py | 58 +- boututils/plotdata.py | 66 +- boututils/plotpolslice.py | 166 ++--- boututils/radial_grid.py | 87 ++- boututils/read_geqdsk.py | 169 ++--- boututils/run_wrapper.py | 96 +-- boututils/showdata.py | 553 +++++++++------ boututils/spectrogram.py | 89 +-- boututils/surface_average.py | 62 +- boututils/tests/test_import.py | 3 +- boututils/volume_integral.py | 68 +- boututils/watch.py | 12 +- setup.cfg | 2 +- setup.py | 72 +- 46 files changed, 2992 insertions(+), 2564 deletions(-) rename .pre-commit-config.yml => .pre-commit-config.yaml (85%) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 900b2eb..5ae524b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,4 +39,4 @@ jobs: pip install . - name: Lint - run: pre-commit run --all-files \ No newline at end of file + run: pre-commit run --all-files diff --git a/.github/workflows/python_publish.yml b/.github/workflows/python_publish.yml index ee6730c..4c0c66b 100644 --- a/.github/workflows/python_publish.yml +++ b/.github/workflows/python_publish.yml @@ -9,19 +9,19 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/.gitignore b/.gitignore index ee0ca72..6fd2411 100644 --- a/.gitignore +++ b/.gitignore @@ -126,4 +126,3 @@ dmypy.json .DS_Store .idea/ *.sw[po] - diff --git a/.pre-commit-config.yml b/.pre-commit-config.yaml similarity index 85% rename from .pre-commit-config.yml rename to .pre-commit-config.yaml index 03ba7f9..c816694 100644 --- a/.pre-commit-config.yml +++ b/.pre-commit-config.yaml @@ -12,18 +12,18 @@ repos: - id: flake8 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.3.0 + rev: v3.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - repo: https://github.com/prettier/pre-commit - rev: 413fd9da8e03249b74acf8543567e5debbad8bfa + rev: 57f39166b5a5a504d6808b87ab98d41ebf095b46 hooks: - id: prettier args: [--prose-wrap=always, --print-width=88] - repo: https://github.com/PyCQA/isort - rev: 5.6.4 + rev: 5.7.0 hooks: - - id: isort \ No newline at end of file + - id: isort diff --git a/.travis.yml b/.travis.yml index a567dfc..18f6bb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,16 +14,16 @@ env: addons: apt: packages: - - libhdf5-serial-dev - - netcdf-bin - - libnetcdf-dev - - libsm6 - - libxext6 - - libxrender-dev - - libxt6 - - libgl1-mesa-glx - - libfontconfig - - libxkbcommon-x11-0 + - libhdf5-serial-dev + - netcdf-bin + - libnetcdf-dev + - libsm6 + - libxext6 + - libxrender-dev + - libxt6 + - libgl1-mesa-glx + - libfontconfig + - libxkbcommon-x11-0 install: - pip install setuptools_scm pytest-cov diff --git a/README.md b/README.md index 6402c5f..9c87cc9 100644 --- a/README.md +++ b/README.md @@ -7,25 +7,21 @@ [![PEP8](https://img.shields.io/badge/code%20style-PEP8-brightgreen.svg)](https://www.python.org/dev/peps/pep-0008/) [![License](https://img.shields.io/badge/license-LGPL--3.0-blue.svg)](https://github.com/boutproject/boututils/blob/master/LICENSE) -pip-package of what was previously found in -`BOUT-dev/tools/pylib/boututils` -Note that `BOUT-dev/tools/pylib/boututils` will likely be replaced by this repo -in `BOUT++ v4.3.0`. -See [this issue](https://github.com/boutproject/BOUT-dev/issues/1347), +pip-package of what was previously found in `BOUT-dev/tools/pylib/boututils` Note that +`BOUT-dev/tools/pylib/boututils` will likely be replaced by this repo in +`BOUT++ v4.3.0`. See [this issue](https://github.com/boutproject/BOUT-dev/issues/1347), [this pull request](https://github.com/boutproject/BOUT-dev/pull/1766) and [this pull request](https://github.com/boutproject/BOUT-dev/pull/1740) for details. > **NOTE**: This package will likely be superseded by - [`xBOUT`](https://github.com/boutproject/xBOUT) in the near future +> [`xBOUT`](https://github.com/boutproject/xBOUT) in the near future # Dependencies -`boututils` depends on -[`netcfd4`](https://github.com/Unidata/netcdf4-python) which requires -[`HDF5`](https://www.h5py.org) and -[`netcdf-4`](https://github.com/Unidata/netcdf-c/releases) are -installed, and that the `nc-config` utility is in your `PATH`. This -can be install with +`boututils` depends on [`netcfd4`](https://github.com/Unidata/netcdf4-python) which +requires [`HDF5`](https://www.h5py.org) and +[`netcdf-4`](https://github.com/Unidata/netcdf-c/releases) are installed, and that the +`nc-config` utility is in your `PATH`. This can be install with ``` sudo apt-get install libhdf5-serial-dev netcdf-bin libnetcdf-dev @@ -36,4 +32,3 @@ in ubuntu # Install `pip install boututils` - diff --git a/boututils/View3D.py b/boututils/View3D.py index f3a771d..9024938 100644 --- a/boututils/View3D.py +++ b/boututils/View3D.py @@ -7,384 +7,389 @@ https://docs.enthought.com/mayavi/mayavi/auto/example_magnetic_field.html#example-magnetic-field """ -from __future__ import absolute_import -from __future__ import division -from builtins import range -from past.utils import old_div +from __future__ import absolute_import, division +import sys +from builtins import range -from boutdata.collect import collect import numpy as np +from boutdata.collect import collect +from past.utils import old_div -import sys - -if sys.version_info[0]>=3: - message = "View3D uses the VTK library through mayavi, which"+\ - " is currently only available in python 2" +if sys.version_info[0] >= 3: + message = ( + "View3D uses the VTK library through mayavi, which" + + " is currently only available in python 2" + ) raise ImportError(message) else: from mayavi import mlab -from .read_geqdsk import read_geqdsk -from boututils.View2D import View2D from scipy import interpolate -from .boutgrid import * - - -def View3D(g,path=None, gb=None): - ############################################################################## - # Resolution - - n=51 - - #compute Bxy - [Br,Bz,x,y,q]=View2D(g,option=1) - - - rd=g.r.max()+.5 - zd=g.z.max()+.5 - ############################################################################## - # The grid of points on which we want to evaluate the field - X, Y, Z = np.mgrid[-rd:rd:n*1j, -rd:rd:n*1j, -zd:zd:n*1j] - ## Avoid rounding issues : - #f = 1e4 # this gives the precision we are interested by : - #X = np.round(X * f) / f - #Y = np.round(Y * f) / f - #Z = np.round(Z * f) / f - - r = np.c_[X.ravel(), Y.ravel(), Z.ravel()] - - ############################################################################## - # Calculate field - # First initialize a container matrix for the field vector : - B = np.empty_like(r) - - - #Compute Toroidal field - # fpol is given between simagx (psi on the axis) and sibdry ( - # psi on limiter or separatrix). So the toroidal field (fpol/R) and the q profile are within these boundaries - # For each r,z we have psi thus we get fpol if (r,z) is within the boundary (limiter or separatrix) and fpol=fpol(outer_boundary) for outside - - #The range of psi is g.psi.max(), g.psi.min() but we have f(psi) up to the limit. Thus we use a new extended variable padded up to max psi - # set points between psi_limit and psi_max - - add_psi=np.linspace(g.sibdry,g.psi.max(),10) - - # define the x (psi) array - xf=np.arange(np.float(g.qpsi.size))*(g.sibdry-g.simagx)/np.float(g.qpsi.size-1) + g.simagx - # pad the extra values excluding the 1st value - - xf=np.concatenate((xf, add_psi[1::]), axis=0) - - # pad fpol with corresponding points - - fp=np.lib.pad(g.fpol, (0,9), 'edge') - - # create interpolating function - - f = interpolate.interp1d(xf, fp) - - #calculate Toroidal field - - Btrz = old_div(f(g.psi), g.r) - - - rmin=g.r[:,0].min() - rmax=g.r[:,0].max() - zmin=g.z[0,:].min() - zmax=g.z[0,:].max() - - - B1p,B2p,B3p,B1t,B2t,B3t = magnetic_field(g,X,Y,Z,rmin,rmax,zmin,zmax, Br,Bz,Btrz) - - bpnorm = np.sqrt(B1p**2 + B2p**2 + B3p**2) - btnorm = np.sqrt(B1t**2 + B2t**2 + B3t**2) - - BBx=B1p+B1t - BBy=B2p+B2t - BBz=B3p+B3t - btotal = np.sqrt(BBx**2 + BBy**2 + BBz**2) - - Psi = psi_field(g,X,Y,Z,rmin,rmax,zmin,zmax) - - ############################################################################## - # Visualization - - # We threshold the data ourselves, as the threshold filter produce a - # data structure inefficient with IsoSurface - #bmax = bnorm.max() - # - #B1[B > bmax] = 0 - #B2[B > bmax] = 0 - #B3[B > bmax] = 0 - #bnorm[bnorm > bmax] = bmax - - mlab.figure(1, size=(1080,1080))#, bgcolor=(1, 1, 1), fgcolor=(0.5, 0.5, 0.5)) - - mlab.clf() - - fieldp = mlab.pipeline.vector_field(X, Y, Z, B1p, B2p, B3p, - scalars=bpnorm, name='Bp field') +from boututils.View2D import View2D - fieldt = mlab.pipeline.vector_field(X, Y, Z, B1t, B2t, B3t, - scalars=btnorm, name='Bt field') +from .boutgrid import * +from .read_geqdsk import read_geqdsk - field = mlab.pipeline.vector_field(X, Y, Z, BBx, BBy, BBz, - scalars=btotal, name='B field') +def View3D(g, path=None, gb=None): + ############################################################################## + # Resolution + n = 51 - field2 = mlab.pipeline.scalar_field(X, Y, Z, Psi, name='Psi field') + # compute Bxy + [Br, Bz, x, y, q] = View2D(g, option=1) - #vectors = mlab.pipeline.vectors(field, - # scale_factor=1,#(X[1, 0, 0] - X[0, 0, 0]), - # ) + rd = g.r.max() + 0.5 + zd = g.z.max() + 0.5 + ############################################################################## + # The grid of points on which we want to evaluate the field + X, Y, Z = np.mgrid[-rd : rd : n * 1j, -rd : rd : n * 1j, -zd : zd : n * 1j] + ## Avoid rounding issues : + # f = 1e4 # this gives the precision we are interested by : + # X = np.round(X * f) / f + # Y = np.round(Y * f) / f + # Z = np.round(Z * f) / f - #vcp1 = mlab.pipeline.vector_cut_plane(fieldp, - # scale_factor=1, - # colormap='jet', - # plane_orientation='y_axes') - ## - #vcp2 = mlab.pipeline.vector_cut_plane(fieldt, - # scale_factor=1, - # colormap='jet', - # plane_orientation='x_axes') + r = np.c_[X.ravel(), Y.ravel(), Z.ravel()] + ############################################################################## + # Calculate field + # First initialize a container matrix for the field vector : + B = np.empty_like(r) - # Mask random points, to have a lighter visualization. - #vectors.glyph.mask_input_points = True - #vectors.glyph.mask_points.on_ratio = 6 + # Compute Toroidal field + # fpol is given between simagx (psi on the axis) and sibdry ( + # psi on limiter or separatrix). So the toroidal field (fpol/R) and the q profile are within these boundaries + # For each r,z we have psi thus we get fpol if (r,z) is within the boundary (limiter or separatrix) and fpol=fpol(outer_boundary) for outside - #vcp = mlab.pipeline.vector_cut_plane(field1) - #vcp.glyph.glyph.scale_factor=5*(X[1, 0, 0] - X[0, 0, 0]) - # For prettier picture: - #vcp1.implicit_plane.widget.enabled = False - #vcp2.implicit_plane.widget.enabled = False + # The range of psi is g.psi.max(), g.psi.min() but we have f(psi) up to the limit. Thus we use a new extended variable padded up to max psi + # set points between psi_limit and psi_max - iso = mlab.pipeline.iso_surface(field2, - contours=[Psi.min()+.01], - opacity=0.4, - colormap='bone') + add_psi = np.linspace(g.sibdry, g.psi.max(), 10) - for i in range(q.size): - iso.contour.contours[i+1:i+2]=[q[i]] + # define the x (psi) array + xf = ( + np.arange(np.float(g.qpsi.size)) + * (g.sibdry - g.simagx) + / np.float(g.qpsi.size - 1) + + g.simagx + ) - iso.compute_normals = True - # + # pad the extra values excluding the 1st value - #mlab.pipeline.image_plane_widget(field2, - # plane_orientation='x_axes', - # #slice_index=10, - # extent=[-rd, rd, -rd, rd, -zd,zd] - # ) - #mlab.pipeline.image_plane_widget(field2, - # plane_orientation='y_axes', - # # slice_index=10, - # extent=[-rd, rd, -rd,rd, -zd,zd] - # ) + xf = np.concatenate((xf, add_psi[1::]), axis=0) + # pad fpol with corresponding points + fp = np.lib.pad(g.fpol, (0, 9), "edge") - #scp = mlab.pipeline.scalar_cut_plane(field2, - # colormap='jet', - # plane_orientation='x_axes') - # For prettier picture and with 2D streamlines: - #scp.implicit_plane.widget.enabled = False - #scp.enable_contours = True - #scp.contour.number_of_contours = 20 + # create interpolating function - # - - # Magnetic Axis - - s=mlab.pipeline.streamline(field) - s.streamline_type = 'line' - s.seed.widget = s.seed.widget_list[3] - s.seed.widget.position=[g.rmagx,0.,g.zmagx] - s.seed.widget.enabled = False - - - # q=i surfaces - - for i in range(np.shape(x)[0]): - - s=mlab.pipeline.streamline(field) - s.streamline_type = 'line' - ##s.seed.widget = s.seed.widget_list[0] - ##s.seed.widget.center = 0.0, 0.0, 0.0 - ##s.seed.widget.radius = 1.725 - ##s.seed.widget.phi_resolution = 16 - ##s.seed.widget.handle_direction =[ 1., 0., 0.] - ##s.seed.widget.enabled = False - ##s.seed.widget.enabled = True - ##s.seed.widget.enabled = False - # - if x[i].size>1 : - s.seed.widget = s.seed.widget_list[3] - s.seed.widget.position=[x[i][0],0.,y[i][0]] - s.seed.widget.enabled = False - - - # A trick to make transparency look better: cull the front face - iso.actor.property.frontface_culling = True - - #mlab.view(39, 74, 0.59, [.008, .0007, -.005]) - out=mlab.outline(extent=[-rd, rd, -rd, rd, -zd, zd], line_width=.5 ) - out.outline_mode = 'cornered' - out.outline_filter.corner_factor = 0.0897222 - - - w = mlab.gcf() - w.scene.camera.position = [13.296429046581462, 13.296429046581462, 12.979811259697154] - w.scene.camera.focal_point = [0.0, 0.0, -0.31661778688430786] - w.scene.camera.view_angle = 30.0 - w.scene.camera.view_up = [0.0, 0.0, 1.0] - w.scene.camera.clipping_range = [13.220595435695394, 35.020427055647517] - w.scene.camera.compute_view_plane_normal() - w.scene.render() - w.scene.show_axes = True - - mlab.show() - - if(path is not None): - #BOUT data - #path='../Aiba/' - # - #gb = file_import(path+'aiba.bout.grd.nc') - #gb = file_import("../cbm18_8_y064_x516_090309.nc") - #gb = file_import("cbm18_dens8.grid_nx68ny64.nc") - #gb = file_import("/home/ben/run4/reduced_y064_x256.nc") - - data = collect('P', path=path) - data = data[50,:,:,:] - #data0=collect("P0", path=path) - #data=data+data0[:,:,None] - - s = np.shape(data) - nz = s[2] - - - sgrid = create_grid(gb, data, 1) + f = interpolate.interp1d(xf, fp) - # OVERPLOT the GRID - #mlab.pipeline.add_dataset(sgrid) - #gr=mlab.pipeline.grid_plane(sgrid) - #gr.grid_plane.axis='x' - - - ## pressure scalar cut plane from bout - scpb = mlab.pipeline.scalar_cut_plane(sgrid, - colormap='jet', - plane_orientation='x_axes') - - scpb.implicit_plane.widget.enabled = False - scpb.enable_contours = True - scpb.contour.filled_contours=True - # - scpb.contour.number_of_contours = 20 - # - # - #loc=sgrid.points - #p=sgrid.point_data.scalars - - # compute pressure from scatter points interpolation - #pint=interpolate.griddata(loc, p, (X, Y, Z), method='linear') - #dpint=np.ma.masked_array(pint,np.isnan(pint)).filled(0.) - # - #p2 = mlab.pipeline.scalar_field(X, Y, Z, dpint, name='P field') - # - #scp2 = mlab.pipeline.scalar_cut_plane(p2, - # colormap='jet', - # plane_orientation='y_axes') - # - #scp2.implicit_plane.widget.enabled = False - #scp2.enable_contours = True - #scp2.contour.filled_contours=True - #scp2.contour.number_of_contours = 20 - #scp2.contour.minimum_contour=.001 - - - - # CHECK grid orientation - #fieldr = mlab.pipeline.vector_field(X, Y, Z, -BBx, BBy, BBz, - # scalars=btotal, name='B field') - # - #sg=mlab.pipeline.streamline(fieldr) - #sg.streamline_type = 'tube' - #sg.seed.widget = sg.seed.widget_list[3] - #sg.seed.widget.position=loc[0] - #sg.seed.widget.enabled = False + # calculate Toroidal field + Btrz = old_div(f(g.psi), g.r) + rmin = g.r[:, 0].min() + rmax = g.r[:, 0].max() + zmin = g.z[0, :].min() + zmax = g.z[0, :].max() - #OUTPUT grid + B1p, B2p, B3p, B1t, B2t, B3t = magnetic_field( + g, X, Y, Z, rmin, rmax, zmin, zmax, Br, Bz, Btrz + ) - #ww = tvtk.XMLStructuredGridWriter(input=sgrid, file_name='sgrid.vts') - #ww.write() + bpnorm = np.sqrt(B1p ** 2 + B2p ** 2 + B3p ** 2) + btnorm = np.sqrt(B1t ** 2 + B2t ** 2 + B3t ** 2) - return + BBx = B1p + B1t + BBy = B2p + B2t + BBz = B3p + B3t + btotal = np.sqrt(BBx ** 2 + BBy ** 2 + BBz ** 2) -def magnetic_field(g,X,Y,Z,rmin,rmax,zmin,zmax,Br,Bz,Btrz): + Psi = psi_field(g, X, Y, Z, rmin, rmax, zmin, zmax) - rho = np.sqrt(X**2 + Y**2) - phi=np.arctan2(Y,X) + ############################################################################## + # Visualization - br=np.zeros(np.shape(X)) - bz=np.zeros(np.shape(X)) - bt=np.zeros(np.shape(X)) + # We threshold the data ourselves, as the threshold filter produce a + # data structure inefficient with IsoSurface + # bmax = bnorm.max() + # + # B1[B > bmax] = 0 + # B2[B > bmax] = 0 + # B3[B > bmax] = 0 + # bnorm[bnorm > bmax] = bmax + + mlab.figure(1, size=(1080, 1080)) # , bgcolor=(1, 1, 1), fgcolor=(0.5, 0.5, 0.5)) + + mlab.clf() - nx,ny,nz=np.shape(X) + fieldp = mlab.pipeline.vector_field( + X, Y, Z, B1p, B2p, B3p, scalars=bpnorm, name="Bp field" + ) + + fieldt = mlab.pipeline.vector_field( + X, Y, Z, B1t, B2t, B3t, scalars=btnorm, name="Bt field" + ) + + field = mlab.pipeline.vector_field( + X, Y, Z, BBx, BBy, BBz, scalars=btotal, name="B field" + ) + + field2 = mlab.pipeline.scalar_field(X, Y, Z, Psi, name="Psi field") + + # vectors = mlab.pipeline.vectors(field, + # scale_factor=1,#(X[1, 0, 0] - X[0, 0, 0]), + # ) + + # vcp1 = mlab.pipeline.vector_cut_plane(fieldp, + # scale_factor=1, + # colormap='jet', + # plane_orientation='y_axes') + ## + # vcp2 = mlab.pipeline.vector_cut_plane(fieldt, + # scale_factor=1, + # colormap='jet', + # plane_orientation='x_axes') + + # Mask random points, to have a lighter visualization. + # vectors.glyph.mask_input_points = True + # vectors.glyph.mask_points.on_ratio = 6 + + # vcp = mlab.pipeline.vector_cut_plane(field1) + # vcp.glyph.glyph.scale_factor=5*(X[1, 0, 0] - X[0, 0, 0]) + # For prettier picture: + # vcp1.implicit_plane.widget.enabled = False + # vcp2.implicit_plane.widget.enabled = False + + iso = mlab.pipeline.iso_surface( + field2, contours=[Psi.min() + 0.01], opacity=0.4, colormap="bone" + ) + + for i in range(q.size): + iso.contour.contours[i + 1 : i + 2] = [q[i]] + + iso.compute_normals = True + # + + # mlab.pipeline.image_plane_widget(field2, + # plane_orientation='x_axes', + # #slice_index=10, + # extent=[-rd, rd, -rd, rd, -zd,zd] + # ) + # mlab.pipeline.image_plane_widget(field2, + # plane_orientation='y_axes', + # # slice_index=10, + # extent=[-rd, rd, -rd,rd, -zd,zd] + # ) + + # scp = mlab.pipeline.scalar_cut_plane(field2, + # colormap='jet', + # plane_orientation='x_axes') + # For prettier picture and with 2D streamlines: + # scp.implicit_plane.widget.enabled = False + # scp.enable_contours = True + # scp.contour.number_of_contours = 20 + + # + + # Magnetic Axis + + s = mlab.pipeline.streamline(field) + s.streamline_type = "line" + s.seed.widget = s.seed.widget_list[3] + s.seed.widget.position = [g.rmagx, 0.0, g.zmagx] + s.seed.widget.enabled = False + + # q=i surfaces + + for i in range(np.shape(x)[0]): + + s = mlab.pipeline.streamline(field) + s.streamline_type = "line" + ##s.seed.widget = s.seed.widget_list[0] + ##s.seed.widget.center = 0.0, 0.0, 0.0 + ##s.seed.widget.radius = 1.725 + ##s.seed.widget.phi_resolution = 16 + ##s.seed.widget.handle_direction =[ 1., 0., 0.] + ##s.seed.widget.enabled = False + ##s.seed.widget.enabled = True + ##s.seed.widget.enabled = False + # + if x[i].size > 1: + s.seed.widget = s.seed.widget_list[3] + s.seed.widget.position = [x[i][0], 0.0, y[i][0]] + s.seed.widget.enabled = False + + # A trick to make transparency look better: cull the front face + iso.actor.property.frontface_culling = True + + # mlab.view(39, 74, 0.59, [.008, .0007, -.005]) + out = mlab.outline(extent=[-rd, rd, -rd, rd, -zd, zd], line_width=0.5) + out.outline_mode = "cornered" + out.outline_filter.corner_factor = 0.0897222 + + w = mlab.gcf() + w.scene.camera.position = [ + 13.296429046581462, + 13.296429046581462, + 12.979811259697154, + ] + w.scene.camera.focal_point = [0.0, 0.0, -0.31661778688430786] + w.scene.camera.view_angle = 30.0 + w.scene.camera.view_up = [0.0, 0.0, 1.0] + w.scene.camera.clipping_range = [13.220595435695394, 35.020427055647517] + w.scene.camera.compute_view_plane_normal() + w.scene.render() + w.scene.show_axes = True + + mlab.show() + + if path is not None: + # BOUT data + # path='../Aiba/' + # + # gb = file_import(path+'aiba.bout.grd.nc') + # gb = file_import("../cbm18_8_y064_x516_090309.nc") + # gb = file_import("cbm18_dens8.grid_nx68ny64.nc") + # gb = file_import("/home/ben/run4/reduced_y064_x256.nc") + + data = collect("P", path=path) + data = data[50, :, :, :] + # data0=collect("P0", path=path) + # data=data+data0[:,:,None] + + s = np.shape(data) + nz = s[2] + + sgrid = create_grid(gb, data, 1) + + # OVERPLOT the GRID + # mlab.pipeline.add_dataset(sgrid) + # gr=mlab.pipeline.grid_plane(sgrid) + # gr.grid_plane.axis='x' + + ## pressure scalar cut plane from bout + scpb = mlab.pipeline.scalar_cut_plane( + sgrid, colormap="jet", plane_orientation="x_axes" + ) + + scpb.implicit_plane.widget.enabled = False + scpb.enable_contours = True + scpb.contour.filled_contours = True + # + scpb.contour.number_of_contours = 20 + # + # + # loc=sgrid.points + # p=sgrid.point_data.scalars + + # compute pressure from scatter points interpolation + # pint=interpolate.griddata(loc, p, (X, Y, Z), method='linear') + # dpint=np.ma.masked_array(pint,np.isnan(pint)).filled(0.) + # + # p2 = mlab.pipeline.scalar_field(X, Y, Z, dpint, name='P field') + # + # scp2 = mlab.pipeline.scalar_cut_plane(p2, + # colormap='jet', + # plane_orientation='y_axes') + # + # scp2.implicit_plane.widget.enabled = False + # scp2.enable_contours = True + # scp2.contour.filled_contours=True + # scp2.contour.number_of_contours = 20 + # scp2.contour.minimum_contour=.001 + + # CHECK grid orientation + # fieldr = mlab.pipeline.vector_field(X, Y, Z, -BBx, BBy, BBz, + # scalars=btotal, name='B field') + # + # sg=mlab.pipeline.streamline(fieldr) + # sg.streamline_type = 'tube' + # sg.seed.widget = sg.seed.widget_list[3] + # sg.seed.widget.position=loc[0] + # sg.seed.widget.enabled = False + + # OUTPUT grid + + # ww = tvtk.XMLStructuredGridWriter(input=sgrid, file_name='sgrid.vts') + # ww.write() + + return + + +def magnetic_field(g, X, Y, Z, rmin, rmax, zmin, zmax, Br, Bz, Btrz): + + rho = np.sqrt(X ** 2 + Y ** 2) + phi = np.arctan2(Y, X) + + br = np.zeros(np.shape(X)) + bz = np.zeros(np.shape(X)) + bt = np.zeros(np.shape(X)) + + nx, ny, nz = np.shape(X) mask = (rho >= rmin) & (rho <= rmax) & (Z >= zmin) & (Z <= zmax) - k=np.argwhere(mask==True) + k = np.argwhere(mask == True) - fr=interpolate.interp2d(g.r[:,0], g.z[0,:], Br.T) - fz=interpolate.interp2d(g.r[:,0], g.z[0,:], Bz.T) - ft=interpolate.interp2d(g.r[:,0], g.z[0,:], Btrz.T) + fr = interpolate.interp2d(g.r[:, 0], g.z[0, :], Br.T) + fz = interpolate.interp2d(g.r[:, 0], g.z[0, :], Bz.T) + ft = interpolate.interp2d(g.r[:, 0], g.z[0, :], Btrz.T) for i in range(len(k)): - br[k[i,0],k[i,1],k[i,2]]=fr(rho[k[i,0],k[i,1],k[i,2]],Z[k[i,0],k[i,1],k[i,2]]) - bz[k[i,0],k[i,1],k[i,2]]=fz(rho[k[i,0],k[i,1],k[i,2]],Z[k[i,0],k[i,1],k[i,2]]) - bt[k[i,0],k[i,1],k[i,2]]=ft(rho[k[i,0],k[i,1],k[i,2]],Z[k[i,0],k[i,1],k[i,2]]) - - # Toroidal component - B1t=-bt*np.sin(phi) - B2t=bt*np.cos(phi) - B3t=0*bz - - # Poloidal component - B1p=br*np.cos(phi) - B2p=br*np.sin(phi) - B3p=bz - + br[k[i, 0], k[i, 1], k[i, 2]] = fr( + rho[k[i, 0], k[i, 1], k[i, 2]], Z[k[i, 0], k[i, 1], k[i, 2]] + ) + bz[k[i, 0], k[i, 1], k[i, 2]] = fz( + rho[k[i, 0], k[i, 1], k[i, 2]], Z[k[i, 0], k[i, 1], k[i, 2]] + ) + bt[k[i, 0], k[i, 1], k[i, 2]] = ft( + rho[k[i, 0], k[i, 1], k[i, 2]], Z[k[i, 0], k[i, 1], k[i, 2]] + ) + + # Toroidal component + B1t = -bt * np.sin(phi) + B2t = bt * np.cos(phi) + B3t = 0 * bz + + # Poloidal component + B1p = br * np.cos(phi) + B2p = br * np.sin(phi) + B3p = bz # Rotate the field back in the lab's frame - return B1p,B2p,B3p,B1t,B2t,B3t + return B1p, B2p, B3p, B1t, B2t, B3t -def psi_field(g,X,Y,Z,rmin,rmax,zmin,zmax): +def psi_field(g, X, Y, Z, rmin, rmax, zmin, zmax): - rho = np.sqrt(X**2 + Y**2) + rho = np.sqrt(X ** 2 + Y ** 2) - psi=np.zeros(np.shape(X)) + psi = np.zeros(np.shape(X)) - nx,ny,nz=np.shape(X) + nx, ny, nz = np.shape(X) mask = (rho >= rmin) & (rho <= rmax) & (Z >= zmin) & (Z <= zmax) - k=np.argwhere(mask==True) + k = np.argwhere(mask == True) - f=interpolate.interp2d(g.r[:,0], g.z[0,:], g.psi.T) + f = interpolate.interp2d(g.r[:, 0], g.z[0, :], g.psi.T) for i in range(len(k)): - psi[k[i,0],k[i,1],k[i,2]]=f(rho[k[i,0],k[i,1],k[i,2]],Z[k[i,0],k[i,1],k[i,2]]) + psi[k[i, 0], k[i, 1], k[i, 2]] = f( + rho[k[i, 0], k[i, 1], k[i, 2]], Z[k[i, 0], k[i, 1], k[i, 2]] + ) # Rotate the field back in the lab's frame return psi -if __name__ == '__main__': - path='../../tokamak_grids/pyGridGen/' - g=read_geqdsk(path+"g118898.03400") - View3D(g) - mlab.show() +if __name__ == "__main__": + path = "../../tokamak_grids/pyGridGen/" + g = read_geqdsk(path + "g118898.03400") + View3D(g) + mlab.show() diff --git a/boututils/__init__.py b/boututils/__init__.py index 4bde708..811338b 100644 --- a/boututils/__init__.py +++ b/boututils/__init__.py @@ -8,50 +8,49 @@ raise ImportError("Please install the future module to use Python 2") # Modules to be imported independent of version -for_all_versions = [\ - 'calculus',\ - 'closest_line',\ - 'datafile',\ - # 'efit_analyzer',\ # bunch pkg required - 'fft_deriv',\ - 'fft_integrate',\ - 'file_import',\ - 'int_func',\ - 'linear_regression',\ - 'mode_structure',\ - # 'moment_xyzt',\ # bunch pkg requried - 'run_wrapper',\ - 'shell',\ - 'showdata',\ - # 'surface_average',\ - # 'volume_integral',\ #bunch pkg required - ] +for_all_versions = [ + "calculus", + "closest_line", + "datafile", # 'efit_analyzer',\ # bunch pkg required + "fft_deriv", + "fft_integrate", + "file_import", + "int_func", + "linear_regression", + "mode_structure", # 'moment_xyzt',\ # bunch pkg requried + "run_wrapper", + "shell", + "showdata", # 'surface_average',\ + # 'volume_integral',\ #bunch pkg required +] # Check the current python version -if sys.version_info[0]>=3: +if sys.version_info[0] >= 3: do_import = for_all_versions __all__ = do_import else: do_import = for_all_versions - do_import.append('anim') - do_import.append('plotpolslice') - do_import.append('View3D') + do_import.append("anim") + do_import.append("plotpolslice") + do_import.append("View3D") __all__ = do_import -__name__ = 'boututils' +__name__ = "boututils" try: - from importlib.metadata import version, PackageNotFoundError + from importlib.metadata import PackageNotFoundError, version except ModuleNotFoundError: - from importlib_metadata import version, PackageNotFoundError + from importlib_metadata import PackageNotFoundError, version try: # This gives the version if the boututils package was installed __version__ = version(__name__) except PackageNotFoundError: # This branch handles the case when boututils is used from the git repo try: - from setuptools_scm import get_version from pathlib import Path + + from setuptools_scm import get_version + path = Path(__file__).resolve() __version__ = get_version(root="..", relative_to=path) except (ModuleNotFoundError, LookupError) as e: @@ -59,6 +58,7 @@ # LookupError if git is not installed, or the code is not in a git repo even # though it has not been installed. from warnings import warn + warn( "'setuptools_scm' and git are required to get the version number when " "running boututils from the git repo. Please install 'setuptools_scm' and " diff --git a/boututils/analyse_equil_2.py b/boututils/analyse_equil_2.py index d315c6a..53fc3b3 100644 --- a/boututils/analyse_equil_2.py +++ b/boututils/analyse_equil_2.py @@ -3,19 +3,17 @@ Takes a RZ psi grid, and finds x-points and o-points """ -from __future__ import print_function -from __future__ import division +from __future__ import division, print_function -from builtins import zip -from builtins import str -from builtins import range -from past.utils import old_div +from builtins import range, str, zip import numpy -from . import local_min_max -from scipy.interpolate import RectBivariateSpline -from matplotlib.pyplot import contour, gradient, annotate, plot, draw from crosslines import find_inter +from matplotlib.pyplot import annotate, contour, draw, gradient, plot +from past.utils import old_div +from scipy.interpolate import RectBivariateSpline + +from . import local_min_max def analyse_equil(F, R, Z): @@ -49,222 +47,248 @@ def analyse_equil(F, R, Z): s = numpy.shape(F) nx = s[0] ny = s[1] - - #;;;;;;;;;;;;;;; Find critical points ;;;;;;;;;;;;; - # - # Need to find starting locations for O-points (minima/maxima) - # and X-points (saddle points) - # - Rr=numpy.tile(R,nx).reshape(nx,ny).T - Zz=numpy.tile(Z,ny).reshape(nx,ny) - - contour1=contour(Rr,Zz,gradient(F)[0], levels=[0.0], colors='r') - contour2=contour(Rr,Zz,gradient(F)[1], levels=[0.0], colors='r') + + # ;;;;;;;;;;;;;;; Find critical points ;;;;;;;;;;;;; + # + # Need to find starting locations for O-points (minima/maxima) + # and X-points (saddle points) + # + Rr = numpy.tile(R, nx).reshape(nx, ny).T + Zz = numpy.tile(Z, ny).reshape(nx, ny) + + contour1 = contour(Rr, Zz, gradient(F)[0], levels=[0.0], colors="r") + contour2 = contour(Rr, Zz, gradient(F)[1], levels=[0.0], colors="r") draw() + ### 1st method - line crossings --------------------------- + res = find_inter(contour1, contour2) -### 1st method - line crossings --------------------------- - res=find_inter( contour1, contour2) - - #rex1=numpy.interp(res[0], R, numpy.arange(R.size)).astype(int) - #zex1=numpy.interp(res[1], Z, numpy.arange(Z.size)).astype(int) + # rex1=numpy.interp(res[0], R, numpy.arange(R.size)).astype(int) + # zex1=numpy.interp(res[1], Z, numpy.arange(Z.size)).astype(int) - rex1=res[0] - zex1=res[1] - - w=numpy.where((rex1 > R[2]) & (rex1 < R[nx-3]) & (zex1 > Z[2]) & (zex1 < Z[nx-3])) + rex1 = res[0] + zex1 = res[1] + + w = numpy.where( + (rex1 > R[2]) & (rex1 < R[nx - 3]) & (zex1 > Z[2]) & (zex1 < Z[nx - 3]) + ) nextrema = numpy.size(w) - rex1=rex1[w].flatten() - zex1=zex1[w].flatten() - - -### 2nd method - local maxima_minima ----------------------- - res1=local_min_max.detect_local_minima(F) - res2=local_min_max.detect_local_maxima(F) - res=numpy.append(res1,res2,1) - - rex2=res[0,:].flatten() - zex2=res[1,:].flatten() - - - w=numpy.where((rex2 > 2) & (rex2 < nx-3) & (zex2 >2) & (zex2 < nx-3)) + rex1 = rex1[w].flatten() + zex1 = zex1[w].flatten() + + ### 2nd method - local maxima_minima ----------------------- + res1 = local_min_max.detect_local_minima(F) + res2 = local_min_max.detect_local_maxima(F) + res = numpy.append(res1, res2, 1) + + rex2 = res[0, :].flatten() + zex2 = res[1, :].flatten() + + w = numpy.where((rex2 > 2) & (rex2 < nx - 3) & (zex2 > 2) & (zex2 < nx - 3)) nextrema = numpy.size(w) - rex2=rex2[w].flatten() - zex2=zex2[w].flatten() - - - n_opoint=nextrema - n_xpoint=numpy.size(rex1)-n_opoint - - # Needed for interp below - - Rx=numpy.arange(numpy.size(R)) - Zx=numpy.arange(numpy.size(Z)) - - - - print("Number of O-points: "+numpy.str(n_opoint)) - print("Number of X-points: "+numpy.str(n_xpoint)) - - # Deduce the O & X points - - x=R[rex2] - y=Z[zex2] - - dr=old_div((R[numpy.size(R)-1]-R[0]),numpy.size(R)) - dz=old_div((Z[numpy.size(Z)-1]-Z[0]),numpy.size(Z)) - - - repeated=set() + rex2 = rex2[w].flatten() + zex2 = zex2[w].flatten() + + n_opoint = nextrema + n_xpoint = numpy.size(rex1) - n_opoint + + # Needed for interp below + + Rx = numpy.arange(numpy.size(R)) + Zx = numpy.arange(numpy.size(Z)) + + print("Number of O-points: " + numpy.str(n_opoint)) + print("Number of X-points: " + numpy.str(n_xpoint)) + + # Deduce the O & X points + + x = R[rex2] + y = Z[zex2] + + dr = old_div((R[numpy.size(R) - 1] - R[0]), numpy.size(R)) + dz = old_div((Z[numpy.size(Z) - 1] - Z[0]), numpy.size(Z)) + + repeated = set() for i in range(numpy.size(rex1)): for j in range(numpy.size(x)): - if numpy.abs(rex1[i]-x[j]) < 2*dr and numpy.abs(zex1[i]-y[j]) < 2*dz : repeated.add(i) - - # o-points - - o_ri=numpy.take(rex1,numpy.array(list(repeated))) - opt_ri=numpy.interp(o_ri,R,Rx) - o_zi=numpy.take(zex1,numpy.array(list(repeated))) - opt_zi=numpy.interp(o_zi,Z,Zx) - opt_f=numpy.zeros(numpy.size(opt_ri)) + if ( + numpy.abs(rex1[i] - x[j]) < 2 * dr + and numpy.abs(zex1[i] - y[j]) < 2 * dz + ): + repeated.add(i) + + # o-points + + o_ri = numpy.take(rex1, numpy.array(list(repeated))) + opt_ri = numpy.interp(o_ri, R, Rx) + o_zi = numpy.take(zex1, numpy.array(list(repeated))) + opt_zi = numpy.interp(o_zi, Z, Zx) + opt_f = numpy.zeros(numpy.size(opt_ri)) func = RectBivariateSpline(Rx, Zx, F) - for i in range(numpy.size(opt_ri)): opt_f[i]=func(opt_ri[i], opt_zi[i]) - - n_opoint=numpy.size(opt_ri) - - # x-points - - x_ri=numpy.delete(rex1, numpy.array(list(repeated))) - xpt_ri=numpy.interp(x_ri,R,Rx) - x_zi=numpy.delete(zex1, numpy.array(list(repeated))) - xpt_zi=numpy.interp(x_zi,Z,Zx) - xpt_f=numpy.zeros(numpy.size(xpt_ri)) + for i in range(numpy.size(opt_ri)): + opt_f[i] = func(opt_ri[i], opt_zi[i]) + + n_opoint = numpy.size(opt_ri) + + # x-points + + x_ri = numpy.delete(rex1, numpy.array(list(repeated))) + xpt_ri = numpy.interp(x_ri, R, Rx) + x_zi = numpy.delete(zex1, numpy.array(list(repeated))) + xpt_zi = numpy.interp(x_zi, Z, Zx) + xpt_f = numpy.zeros(numpy.size(xpt_ri)) func = RectBivariateSpline(Rx, Zx, F) - for i in range(numpy.size(xpt_ri)): xpt_f[i]=func(xpt_ri[i], xpt_zi[i]) - - n_xpoint=numpy.size(xpt_ri) - - # plot o-points - - plot(o_ri,o_zi,'o', markersize=10) - - labels = ['{0}'.format(i) for i in range(o_ri.size)] + for i in range(numpy.size(xpt_ri)): + xpt_f[i] = func(xpt_ri[i], xpt_zi[i]) + + n_xpoint = numpy.size(xpt_ri) + + # plot o-points + + plot(o_ri, o_zi, "o", markersize=10) + + labels = ["{0}".format(i) for i in range(o_ri.size)] for label, xp, yp in zip(labels, o_ri, o_zi): - annotate(label, xy = (xp, yp), xytext = (10, 10), textcoords = 'offset points',size='large', color='b') + annotate( + label, + xy=(xp, yp), + xytext=(10, 10), + textcoords="offset points", + size="large", + color="b", + ) draw() - - # plot x-points - - plot(x_ri,x_zi,'x', markersize=10) - - labels = ['{0}'.format(i) for i in range(x_ri.size)] + + # plot x-points + + plot(x_ri, x_zi, "x", markersize=10) + + labels = ["{0}".format(i) for i in range(x_ri.size)] for label, xp, yp in zip(labels, x_ri, x_zi): - annotate(label, xy = (xp, yp), xytext = (10, 10), textcoords = 'offset points',size='large', color='r') + annotate( + label, + xy=(xp, yp), + xytext=(10, 10), + textcoords="offset points", + size="large", + color="r", + ) draw() - print("Number of O-points: "+str(n_opoint)) + print("Number of O-points: " + str(n_opoint)) - if n_opoint == 0 : + if n_opoint == 0: raise RuntimeError("No O-points! Giving up on this equilibrium") + # ;;;;;;;;;;;;;; Find plasma centre ;;;;;;;;;;;;;;;;;;; + # Find the O-point closest to the middle of the grid - #;;;;;;;;;;;;;; Find plasma centre ;;;;;;;;;;;;;;;;;;; - # Find the O-point closest to the middle of the grid - - mind = (opt_ri[0] - (old_div(numpy.float(nx),2.)))**2 + (opt_zi[0] - (old_div(numpy.float(ny),2.)))**2 + mind = (opt_ri[0] - (old_div(numpy.float(nx), 2.0))) ** 2 + ( + opt_zi[0] - (old_div(numpy.float(ny), 2.0)) + ) ** 2 ind = 0 - for i in range (1, n_opoint) : - d = (opt_ri[i] - (old_div(numpy.float(nx),2.)))**2 + (opt_zi[i] - (old_div(numpy.float(ny),2.)))**2 - if d < mind : + for i in range(1, n_opoint): + d = (opt_ri[i] - (old_div(numpy.float(nx), 2.0))) ** 2 + ( + opt_zi[i] - (old_div(numpy.float(ny), 2.0)) + ) ** 2 + if d < mind: ind = i mind = d - + primary_opt = ind - print("Primary O-point is at "+ numpy.str(numpy.interp(opt_ri[ind],numpy.arange(numpy.size(R)),R)) + ", " + numpy.str(numpy.interp(opt_zi[ind],numpy.arange(numpy.size(Z)),Z))) + print( + "Primary O-point is at " + + numpy.str(numpy.interp(opt_ri[ind], numpy.arange(numpy.size(R)), R)) + + ", " + + numpy.str(numpy.interp(opt_zi[ind], numpy.arange(numpy.size(Z)), Z)) + ) print("") - - if n_xpoint > 0 : - # Find the primary separatrix + if n_xpoint > 0: + + # Find the primary separatrix - # First remove non-monotonic separatrices + # First remove non-monotonic separatrices nkeep = 0 - for i in range (n_xpoint) : - # Draw a line between the O-point and X-point + for i in range(n_xpoint): + # Draw a line between the O-point and X-point - n = 100 # Number of points + n = 100 # Number of points farr = numpy.zeros(n) dr = old_div((xpt_ri[i] - opt_ri[ind]), numpy.float(n)) dz = old_div((xpt_zi[i] - opt_zi[ind]), numpy.float(n)) - for j in range (n) : + for j in range(n): # interpolate f at this location func = RectBivariateSpline(Rx, Zx, F) - farr[j] = func(opt_ri[ind] + dr*numpy.float(j), opt_zi[ind] + dz*numpy.float(j)) - + farr[j] = func( + opt_ri[ind] + dr * numpy.float(j), opt_zi[ind] + dz * numpy.float(j) + ) # farr should be monotonic, and shouldn't cross any other separatrices maxind = numpy.argmax(farr) minind = numpy.argmin(farr) - if (maxind < minind) : maxind, minind = minind, maxind - - # Allow a little leeway to account for errors - # NOTE: This needs a bit of refining - if (maxind > (n-3)) and (minind < 3) : - # Monotonic, so add this to a list of x-points to keep - if nkeep == 0 : + if maxind < minind: + maxind, minind = minind, maxind + + # Allow a little leeway to account for errors + # NOTE: This needs a bit of refining + if (maxind > (n - 3)) and (minind < 3): + # Monotonic, so add this to a list of x-points to keep + if nkeep == 0: keep = [i] else: keep = numpy.append(keep, i) - - + nkeep = nkeep + 1 - - if nkeep > 0 : + if nkeep > 0: print("Keeping x-points ", keep) xpt_ri = xpt_ri[keep] xpt_zi = xpt_zi[keep] xpt_f = xpt_f[keep] else: "No x-points kept" - + n_xpoint = nkeep - # Now find x-point closest to primary O-point s = numpy.argsort(numpy.abs(opt_f[ind] - xpt_f)) xpt_ri = xpt_ri[s] xpt_zi = xpt_zi[s] xpt_f = xpt_f[s] inner_sep = 0 - + else: - # No x-points. Pick mid-point in f - - xpt_f = 0.5*(numpy.max(F) + numpy.min(F)) - - print("WARNING: No X-points. Setting separatrix to F = "+str(xpt_f)) + # No x-points. Pick mid-point in f + + xpt_f = 0.5 * (numpy.max(F) + numpy.min(F)) + + print("WARNING: No X-points. Setting separatrix to F = " + str(xpt_f)) xpt_ri = 0 xpt_zi = 0 inner_sep = 0 - - - #;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; # Put results into a structure - - result = Bunch(n_opoint=n_opoint, n_xpoint=n_xpoint, # Number of O- and X-points - primary_opt=primary_opt, # Which O-point is the plasma centre - inner_sep=inner_sep, #Innermost X-point separatrix - opt_ri=opt_ri, opt_zi=opt_zi, opt_f=opt_f, # O-point location (indices) and psi values - xpt_ri=xpt_ri, xpt_zi=xpt_zi, xpt_f=xpt_f) # X-point locations and psi values - - return result + result = Bunch( + n_opoint=n_opoint, + n_xpoint=n_xpoint, # Number of O- and X-points + primary_opt=primary_opt, # Which O-point is the plasma centre + inner_sep=inner_sep, # Innermost X-point separatrix + opt_ri=opt_ri, + opt_zi=opt_zi, + opt_f=opt_f, # O-point location (indices) and psi values + xpt_ri=xpt_ri, + xpt_zi=xpt_zi, + xpt_f=xpt_f, + ) # X-point locations and psi values + + return result diff --git a/boututils/anim.py b/boututils/anim.py index d2f7838..0f34e45 100755 --- a/boututils/anim.py +++ b/boututils/anim.py @@ -4,10 +4,13 @@ """ from __future__ import print_function + +import os from builtins import range -from boutdata.collect import collect + import numpy as np -import os +from boutdata.collect import collect + try: from enthought.mayavi import mlab from enthought.mayavi.mlab import * @@ -42,74 +45,76 @@ def anim(s, d, *args, **kwargs): if len(args) == 1: s1 = args[0] else: - s1=None + s1 = None try: - save = kwargs['save'] + save = kwargs["save"] except: save = False - - - nt=d.shape[0] - - print('animating for ',nt,'timesteps') - if save == True : - print('Saving pics in folder Movie') - if not os.path.exists('Movie'): - os.makedirs('Movie') - - + + nt = d.shape[0] + + print("animating for ", nt, "timesteps") + if save == True: + print("Saving pics in folder Movie") + if not os.path.exists("Movie"): + os.makedirs("Movie") + for i in range(nt): - s.mlab_source.scalars = d[i,:,:] - if s1 is not None : s1.mlab_source.scalars = d[i,:,:] - title="t="+np.string0(i) - mlab.title(title,height=1.1, size=0.26) - if save == True : mlab.savefig('Movie/anim%d.png'%i) + s.mlab_source.scalars = d[i, :, :] + if s1 is not None: + s1.mlab_source.scalars = d[i, :, :] + title = "t=" + np.string0(i) + mlab.title(title, height=1.1, size=0.26) + if save == True: + mlab.savefig("Movie/anim%d.png" % i) yield -if __name__ == '__main__': - path='../../../examples/elm-pb/data' +if __name__ == "__main__": + + path = "../../../examples/elm-pb/data" data = collect("P", path=path) - nt=data.shape[0] + nt = data.shape[0] - ns=data.shape[1] - ne=data.shape[2] - nz=data.shape[3] + ns = data.shape[1] + ne = data.shape[2] + nz = data.shape[3] - - f = mayavi.mlab.figure(size=(600,600)) + f = mayavi.mlab.figure(size=(600, 600)) # Tell visual to use this as the viewer. visual.set_viewer(f) - #First way - - s1 = contour_surf(data[0,:,:,10]+.1, contours=30, line_width=.5, transparent=True) - s = surf(data[0,:,:,10]+.1, colormap='Spectral')#, warp_scale='.1')#, representation='wireframe') + # First way + s1 = contour_surf( + data[0, :, :, 10] + 0.1, contours=30, line_width=0.5, transparent=True + ) + s = surf( + data[0, :, :, 10] + 0.1, colormap="Spectral" + ) # , warp_scale='.1')#, representation='wireframe') # second way - #x, y= mgrid[0:ns:1, 0:ne:1] - #s = mesh(x,y,data[0,:,:,10], colormap='Spectral')#, warp_scale='auto')#, representation='wireframe') - s.enable_contours=True - s.contour.filled_contours=True -# + # x, y= mgrid[0:ns:1, 0:ne:1] + # s = mesh(x,y,data[0,:,:,10], colormap='Spectral')#, warp_scale='auto')#, representation='wireframe') + s.enable_contours = True + s.contour.filled_contours = True + # - #x, y, z= mgrid[0:ns:1, 0:ne:1, 0:nz:1] + # x, y, z= mgrid[0:ns:1, 0:ne:1, 0:nz:1] + # + # p=plot3d(x,y,z,data[10,:,:,:], tube_radius=0.025, colormap='Spectral') + # p=points3d(x,y,z,data[10,:,:,:], colormap='Spectral') # - #p=plot3d(x,y,z,data[10,:,:,:], tube_radius=0.025, colormap='Spectral') - #p=points3d(x,y,z,data[10,:,:,:], colormap='Spectral') -# - #s=contour3d(x,y,z,data[10,:,:,:], contours=4, transparent=True) + # s=contour3d(x,y,z,data[10,:,:,:], contours=4, transparent=True) - #mlab.view(0.,0.) + # mlab.view(0.,0.) colorbar() - #axes() - #outline() - + # axes() + # outline() # Run the animation. - anim(s,data[:,:,:,10]+.1,s1, save=True) + anim(s, data[:, :, :, 10] + 0.1, s1, save=True) diff --git a/boututils/ask.py b/boututils/ask.py index 31cbef0..cae018c 100644 --- a/boututils/ask.py +++ b/boututils/ask.py @@ -2,8 +2,8 @@ """ -from builtins import input import sys +from builtins import input def query_yes_no(question, default="yes"): @@ -29,8 +29,15 @@ def query_yes_no(question, default="yes"): True if the answer was "yes" or "y", False if "no" or "n" """ - valid = {"yes":True, "y":True, "ye":True, - "no":False, "n":False, "No":False, "N":False } + valid = { + "yes": True, + "y": True, + "ye": True, + "no": False, + "n": False, + "No": False, + "N": False, + } if default is None: prompt = " [y/n] " @@ -44,10 +51,9 @@ def query_yes_no(question, default="yes"): while True: sys.stdout.write(question + prompt) choice = input().lower() - if default is not None and choice == '': + if default is not None and choice == "": return valid[default] elif choice in valid: return valid[choice] else: - sys.stdout.write("Please respond with 'yes' or 'no' "\ - "(or 'y' or 'n').\n") + sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") diff --git a/boututils/boutarray.py b/boututils/boutarray.py index ce38bae..9b20df8 100644 --- a/boututils/boutarray.py +++ b/boututils/boutarray.py @@ -63,7 +63,7 @@ def __array_finalize__(self, obj): # the default value for 'attributes', because this method sees all # creation of default objects - with the BoutArray.__new__ constructor, # but also with arr.view(BoutArray). - self.attributes = getattr(obj, 'attributes', None) + self.attributes = getattr(obj, "attributes", None) # We do not need to return anything def __format__(self, str): diff --git a/boututils/boutgrid.py b/boututils/boutgrid.py index ace6766..4767892 100755 --- a/boututils/boutgrid.py +++ b/boututils/boutgrid.py @@ -1,139 +1,139 @@ #!/usr/bin/env python3 from __future__ import print_function + from builtins import range import numpy as np -from numpy import cos, sin, pi - +from numpy import cos, pi, sin from tvtk.api import tvtk -#from enthought.mayavi.scripts import mayavi2 + +# from enthought.mayavi.scripts import mayavi2 + def aligned_points(grid, nz=1, period=1.0, maxshift=0.4): try: - nx = grid["nx"]#[0] - ny = grid["ny"]#[0] + nx = grid["nx"] # [0] + ny = grid["ny"] # [0] zshift = grid["zShift"] Rxy = grid["Rxy"] Zxy = grid["Zxy"] except: print("Missing required data") return None - - - dz = 2.*pi / (period * (nz-1)) - phi0 = np.linspace(0,2.*pi / period, nz) - - + + dz = 2.0 * pi / (period * (nz - 1)) + phi0 = np.linspace(0, 2.0 * pi / period, nz) + # Need to insert additional points in Y so mesh looks smooth - #for y in range(1,ny): + # for y in range(1,ny): # ms = np.max(np.abs(zshift[:,y] - zshift[:,y-1])) # if( # Create array of points, structured - points = np.zeros([nx*ny*nz, 3]) - - + points = np.zeros([nx * ny * nz, 3]) + start = 0 for y in range(ny): - - - end = start + nx*nz - - phi = zshift[:,y] + phi0[:,None] - r = Rxy[:,y] + (np.zeros([nz]))[:,None] - + + end = start + nx * nz + + phi = zshift[:, y] + phi0[:, None] + r = Rxy[:, y] + (np.zeros([nz]))[:, None] + xz_points = points[start:end] - - - xz_points[:,0] = (r*cos(phi)).ravel() # X - xz_points[:,1] = (r*sin(phi)).ravel() # Y - xz_points[:,2] = (Zxy[:,y]+(np.zeros([nz]))[:,None]).ravel() # Z - - + + xz_points[:, 0] = (r * cos(phi)).ravel() # X + xz_points[:, 1] = (r * sin(phi)).ravel() # Y + xz_points[:, 2] = (Zxy[:, y] + (np.zeros([nz]))[:, None]).ravel() # Z + start = end - + return points + def create_grid(grid, data, period=1): - + s = np.shape(data) - - nx = grid["nx"]#[0] - ny = grid["ny"]#[0] + + nx = grid["nx"] # [0] + ny = grid["ny"] # [0] nz = s[2] - - print("data: %d,%d,%d grid: %d,%d\n" % (s[0],s[1],s[2], nx,ny)) - + + print("data: %d,%d,%d grid: %d,%d\n" % (s[0], s[1], s[2], nx, ny)) + dims = (nx, nz, ny) sgrid = tvtk.StructuredGrid(dimensions=dims) pts = aligned_points(grid, nz, period) print(np.shape(pts)) sgrid.points = pts - - scalar = np.zeros([nx*ny*nz]) + + scalar = np.zeros([nx * ny * nz]) start = 0 for y in range(ny): - end = start + nx*nz - - #scalar[start:end] = (data[:,y,:]).transpose().ravel() - scalar[start:end] = (data[:,y,:]).ravel() + end = start + nx * nz + + # scalar[start:end] = (data[:,y,:]).transpose().ravel() + scalar[start:end] = (data[:, y, :]).ravel() - print(y, " = " , np.max(scalar[start:end])) + print(y, " = ", np.max(scalar[start:end])) start = end - + sgrid.point_data.scalars = np.ravel(scalar.copy()) sgrid.point_data.scalars.name = "data" - + return sgrid -#@mayavi2.standalone + +# @mayavi2.standalone def view3d(sgrid): - from mayavi.sources.vtk_data_source import VTKDataSource - from mayavi.modules.api import Outline, GridPlane from mayavi.api import Engine from mayavi.core.ui.engine_view import EngineView - e=Engine() + from mayavi.modules.api import GridPlane, Outline + from mayavi.sources.vtk_data_source import VTKDataSource + + e = Engine() e.start() s = e.new_scene() - # Do this if you need to see the MayaVi tree view UI. + # Do this if you need to see the MayaVi tree view UI. ev = EngineView(engine=e) ui = ev.edit_traits() -# mayavi.new_scene() + # mayavi.new_scene() src = VTKDataSource(data=sgrid) e.add_source(src) e.add_module(Outline()) g = GridPlane() - g.grid_plane.axis = 'x' + g.grid_plane.axis = "x" e.add_module(g) -if __name__ == '__main__': + +if __name__ == "__main__": from boutdata.collect import collect + from boututils.file_import import file_import - - #path = "/media/449db594-b2fe-4171-9e79-2d9b76ac69b6/runs/data_33/" - path="../data" + + # path = "/media/449db594-b2fe-4171-9e79-2d9b76ac69b6/runs/data_33/" + path = "../data" g = file_import("../bout.grd.nc") - #g = file_import("../cbm18_8_y064_x516_090309.nc") - #g = file_import("/home/ben/run4/reduced_y064_x256.nc") - + # g = file_import("../cbm18_8_y064_x516_090309.nc") + # g = file_import("/home/ben/run4/reduced_y064_x256.nc") + data = collect("P", tind=10, path=path) - data = data[0,:,:,:] + data = data[0, :, :, :] s = np.shape(data) nz = s[2] - - #bkgd = collect("P0", path=path) - #for z in range(nz): - # data[:,:,z] += bkgd + + # bkgd = collect("P0", path=path) + # for z in range(nz): + # data[:,:,z] += bkgd # Create a structured grid sgrid = create_grid(g, data, 1) - - w = tvtk.XMLStructuredGridWriter(input=sgrid, file_name='sgrid.vts') + w = tvtk.XMLStructuredGridWriter(input=sgrid, file_name="sgrid.vts") w.write() - + # View the structured grid view3d(sgrid) diff --git a/boututils/boutwarnings.py b/boututils/boutwarnings.py index cdb03b0..692890f 100644 --- a/boututils/boutwarnings.py +++ b/boututils/boutwarnings.py @@ -6,14 +6,18 @@ import warnings + class AlwaysWarning(UserWarning): def __init__(self, *args, **kwargs): super(AlwaysWarning, self).__init__(*args, **kwargs) + warnings.simplefilter("always", AlwaysWarning) + def alwayswarn(message): warnings.warn(message, AlwaysWarning, stacklevel=2) + def defaultwarn(message): warnings.warn(message, stacklevel=2) diff --git a/boututils/bunch.py b/boututils/bunch.py index 2bc1ca0..825658a 100644 --- a/boututils/bunch.py +++ b/boututils/bunch.py @@ -1,5 +1,6 @@ # what we need from bunch + class Bunch: def __init__(self, **dict): for k in dict: diff --git a/boututils/calculus.py b/boututils/calculus.py index 2742309..6b6521f 100644 --- a/boututils/calculus.py +++ b/boututils/calculus.py @@ -4,19 +4,21 @@ B.Dudson, University of York, Nov 2009 """ -from __future__ import print_function -from __future__ import division +from __future__ import division, print_function from builtins import range try: from past.utils import old_div except ImportError: + def old_div(a, b): return a / b -from numpy import zeros, pi, array, transpose, sum, where, arange, multiply -from numpy.fft import rfft, irfft + +from numpy import arange, array, multiply, pi, sum, transpose, where, zeros +from numpy.fft import irfft, rfft + def deriv(*args, **kwargs): """Take derivative of 1D array @@ -40,7 +42,7 @@ def deriv(*args, **kwargs): raise RuntimeError("deriv must be given 1 or 2 arguments") try: - periodic = kwargs['periodic'] + periodic = kwargs["periodic"] except: periodic = False @@ -48,96 +50,133 @@ def deriv(*args, **kwargs): if periodic: # Use FFTs to take derivatives f = rfft(var) - f[0] = 0.0 # Zero constant term + f[0] = 0.0 # Zero constant term if n % 2 == 0: # Even n - for i in arange(1,old_div(n,2)): - f[i] *= 2.0j * pi * float(i)/float(n) - f[-1] = 0.0 # Nothing from Nyquist frequency + for i in arange(1, old_div(n, 2)): + f[i] *= 2.0j * pi * float(i) / float(n) + f[-1] = 0.0 # Nothing from Nyquist frequency else: # Odd n - for i in arange(1,old_div((n-1),2) + 1): - f[i] *= 2.0j * pi * float(i)/float(n) + for i in arange(1, old_div((n - 1), 2) + 1): + f[i] *= 2.0j * pi * float(i) / float(n) return irfft(f) else: # Non-periodic function - result = zeros(n) # Create empty array + result = zeros(n) # Create empty array if n > 2: - for i in arange(1, n-1): + for i in arange(1, n - 1): # 2nd-order central difference in the middle of the domain - result[i] = old_div((var[i+1] - var[i-1]), (x[i+1] - x[i-1])) + result[i] = old_div((var[i + 1] - var[i - 1]), (x[i + 1] - x[i - 1])) # Use left,right-biased stencils on edges (2nd order) - result[0] = old_div((-1.5*var[0] + 2.*var[1] - 0.5*var[2]), (x[1] - x[0])) - result[n-1] = old_div((1.5*var[n-1] - 2.*var[n-2] + 0.5*var[n-3]), (x[n-1] - x[n-2])) + result[0] = old_div( + (-1.5 * var[0] + 2.0 * var[1] - 0.5 * var[2]), (x[1] - x[0]) + ) + result[n - 1] = old_div( + (1.5 * var[n - 1] - 2.0 * var[n - 2] + 0.5 * var[n - 3]), + (x[n - 1] - x[n - 2]), + ) elif n == 2: # Just 1st-order difference for both points - result[0] = result[1] = old_div((var[1] - var[0]),(x[1] - x[0])) + result[0] = result[1] = old_div((var[1] - var[0]), (x[1] - x[0])) elif n == 1: result[0] = 0.0 return result -def deriv2D(data,axis=-1,dx=1.0,noise_suppression=True): - """ Takes 1D or 2D Derivative of 2D array using convolution - - result = deriv2D(data) - result = deriv2D(data, dx) - - output is 2D (if only one axis specified) - output is 3D if no axis specified [nx,ny,2] with the third dimension being [dfdx, dfdy] - - keywords: - axis = 0/1 If no axis specified 2D derivative will be returned - dx = 1.0 axis spacing, must be 2D if 2D deriv is taken - default is [1.0,1.0] - noise_suppression = True noise suppressing coefficients used to take derivative - default = True - """ - - from scipy.signal import convolve - - s = data.shape - if axis > len(s)-1: - raise RuntimeError("ERROR: axis out of bounds for derivative") - - if noise_suppression: - if s[axis] < 11: - raise RuntimeError("Data too small to use 11th order method") - tmp = array([old_div(-1.0,512.0),old_div(-8.0,512.0),old_div(-27.0,512.0),old_div(-48.0,512.0),old_div(-42.0,512.0),0.0,old_div(42.0,512.0),old_div(48.0,512.0),old_div(27.0,512.0),old_div(8.0,512.0),old_div(1.0,512.0)]) - else: - if s[axis] < 9: - raise RuntimeError("Data too small to use 9th order method") - tmp = array([old_div(1.0,280.0),old_div(-4.0,105.0),old_div(1.0,5.0),old_div(-4.0,5.0),0.0,old_div(4.0,5.0),old_div(-1.0,5.0),old_div(4.0,105.0),old_div(-1.0,280.0)]) - - N = int((tmp.size-1)/2) - if axis==1: - W = transpose(tmp[:,None]) - data_deriv = convolve(data,W,mode='same')/dx*-1.0 - for i in range(s[0]): - data_deriv[i,0:N-1] = old_div(deriv(data[i,0:N-1]),dx) - data_deriv[i,s[1]-N:] = old_div(deriv(data[i,s[1]-N:]),dx) - - elif axis==0: - W = tmp[:,None] - data_deriv = convolve(data,W,mode='same')/dx*-1.0 - for i in range(s[1]): - data_deriv[0:N-1,i] = old_div(deriv(data[0:N-1,i]),dx) - data_deriv[s[0]-N:,i] = old_div(deriv(data[s[0]-N:,i]),dx) - else: - data_deriv = zeros((s[0],s[1],2)) - if (not hasattr(dx, '__len__')) or len(dx)==1: - dx = array([dx,dx]) - - W = tmp[:,None]#transpose(multiply(tmp,ones((s[1],tmp.size)))) - data_deriv[:,:,0] = convolve(data,W,mode='same')/dx[0]*-1.0 - for i in range(s[1]): - data_deriv[0:N-1,i,0] = old_div(deriv(data[0:N-1,i]),dx[0]) - data_deriv[s[0]-N:s[0]+1,i,0] = old_div(deriv(data[s[0]-N:s[0]+1,i]),dx[0]) - - W = transpose(tmp[:,None])#multiply(tmp,ones((s[0],tmp.size))) - data_deriv[:,:,1] = convolve(data,W,mode='same')/dx[1]*-1.0 - for i in range(s[0]): - data_deriv[i,0:N-1,1] = old_div(deriv(data[i,0:N-1]),dx[1]) - data_deriv[i,s[1]-N:s[1]+1,1] = old_div(deriv(data[i,s[1]-N:s[1]+1]),dx[1]) - - return data_deriv + +def deriv2D(data, axis=-1, dx=1.0, noise_suppression=True): + """Takes 1D or 2D Derivative of 2D array using convolution + + result = deriv2D(data) + result = deriv2D(data, dx) + + output is 2D (if only one axis specified) + output is 3D if no axis specified [nx,ny,2] with the third dimension being [dfdx, dfdy] + + keywords: + axis = 0/1 If no axis specified 2D derivative will be returned + dx = 1.0 axis spacing, must be 2D if 2D deriv is taken - default is [1.0,1.0] + noise_suppression = True noise suppressing coefficients used to take derivative - default = True + """ + + from scipy.signal import convolve + + s = data.shape + if axis > len(s) - 1: + raise RuntimeError("ERROR: axis out of bounds for derivative") + + if noise_suppression: + if s[axis] < 11: + raise RuntimeError("Data too small to use 11th order method") + tmp = array( + [ + old_div(-1.0, 512.0), + old_div(-8.0, 512.0), + old_div(-27.0, 512.0), + old_div(-48.0, 512.0), + old_div(-42.0, 512.0), + 0.0, + old_div(42.0, 512.0), + old_div(48.0, 512.0), + old_div(27.0, 512.0), + old_div(8.0, 512.0), + old_div(1.0, 512.0), + ] + ) + else: + if s[axis] < 9: + raise RuntimeError("Data too small to use 9th order method") + tmp = array( + [ + old_div(1.0, 280.0), + old_div(-4.0, 105.0), + old_div(1.0, 5.0), + old_div(-4.0, 5.0), + 0.0, + old_div(4.0, 5.0), + old_div(-1.0, 5.0), + old_div(4.0, 105.0), + old_div(-1.0, 280.0), + ] + ) + + N = int((tmp.size - 1) / 2) + if axis == 1: + W = transpose(tmp[:, None]) + data_deriv = convolve(data, W, mode="same") / dx * -1.0 + for i in range(s[0]): + data_deriv[i, 0 : N - 1] = old_div(deriv(data[i, 0 : N - 1]), dx) + data_deriv[i, s[1] - N :] = old_div(deriv(data[i, s[1] - N :]), dx) + + elif axis == 0: + W = tmp[:, None] + data_deriv = convolve(data, W, mode="same") / dx * -1.0 + for i in range(s[1]): + data_deriv[0 : N - 1, i] = old_div(deriv(data[0 : N - 1, i]), dx) + data_deriv[s[0] - N :, i] = old_div(deriv(data[s[0] - N :, i]), dx) + else: + data_deriv = zeros((s[0], s[1], 2)) + if (not hasattr(dx, "__len__")) or len(dx) == 1: + dx = array([dx, dx]) + + W = tmp[:, None] # transpose(multiply(tmp,ones((s[1],tmp.size)))) + data_deriv[:, :, 0] = convolve(data, W, mode="same") / dx[0] * -1.0 + for i in range(s[1]): + data_deriv[0 : N - 1, i, 0] = old_div(deriv(data[0 : N - 1, i]), dx[0]) + data_deriv[s[0] - N : s[0] + 1, i, 0] = old_div( + deriv(data[s[0] - N : s[0] + 1, i]), dx[0] + ) + + W = transpose(tmp[:, None]) # multiply(tmp,ones((s[0],tmp.size))) + data_deriv[:, :, 1] = convolve(data, W, mode="same") / dx[1] * -1.0 + for i in range(s[0]): + data_deriv[i, 0 : N - 1, 1] = old_div(deriv(data[i, 0 : N - 1]), dx[1]) + data_deriv[i, s[1] - N : s[1] + 1, 1] = old_div( + deriv(data[i, s[1] - N : s[1] + 1]), dx[1] + ) + + return data_deriv + def integrate(var, periodic=False): """Integrate a 1D array @@ -149,17 +188,17 @@ def integrate(var, periodic=False): f = rfft(var) n = var.size # Zero frequency term - result = f[0].real*arange(n, dtype=float) - f[0] = 0. + result = f[0].real * arange(n, dtype=float) + f[0] = 0.0 if n % 2 == 0: # Even n - for i in arange(1,old_div(n,2)): - f[i] /= 2.0j * pi * float(i)/float(n) - f[-1] = 0.0 # Nothing from Nyquist frequency + for i in arange(1, old_div(n, 2)): + f[i] /= 2.0j * pi * float(i) / float(n) + f[-1] = 0.0 # Nothing from Nyquist frequency else: # Odd n - for i in arange(1,old_div((n-1),2) + 1): - f[i] /= 2.0j * pi * float(i)/float(n) + for i in arange(1, old_div((n - 1), 2) + 1): + f[i] /= 2.0j * pi * float(i) / float(n) return result + irfft(f) else: # Non-periodic function @@ -176,77 +215,89 @@ def int_total(f): return int_total(f[0:4]) + int_total(f[3:]) elif n == 5: # 6th-order Bool's rule - return 4.*(7.*f[0] + 32.*f[1] + 12.*f[2] + 32.*f[3] + 7.*f[4])/90. + return ( + 4.0 + * ( + 7.0 * f[0] + + 32.0 * f[1] + + 12.0 * f[2] + + 32.0 * f[3] + + 7.0 * f[4] + ) + / 90.0 + ) elif n == 4: # 4th-order Simpson's 3/8ths rule - return 3.*(f[0] + 3.*f[1] + 3.*f[2] + f[3])/8. + return 3.0 * (f[0] + 3.0 * f[1] + 3.0 * f[2] + f[3]) / 8.0 elif n == 3: # 4th-order Simpson's rule - return (f[0] + 4.*f[1] + f[2])/3. + return (f[0] + 4.0 * f[1] + f[2]) / 3.0 elif n == 2: # 2nd-order Trapezium rule - return 0.5*(f[0] + f[1]) + return 0.5 * (f[0] + f[1]) else: print("WARNING: Integrating a single point") return 0.0 + # Integrate using maximum number of grid-points n = var.size - n2 = int(old_div(n,2)) + n2 = int(old_div(n, 2)) result = zeros(n) for i in arange(n2, n): - result[i] = int_total(var[0:(i+1)]) + result[i] = int_total(var[0 : (i + 1)]) for i in arange(1, n2): result[i] = result[-1] - int_total(var[i:]) return result -def simpson_integrate(data,dx,dy,kernel=0.0,weight=1.0): - """ Integrates 2D data to one value using the simpson method and matrix convolution - result = simpson_integrate(data,dx,dy) +def simpson_integrate(data, dx, dy, kernel=0.0, weight=1.0): + """Integrates 2D data to one value using the simpson method and matrix convolution - keywords: + result = simpson_integrate(data,dx,dy) - kernel - can be supplied if the simpson matrix is calculated ahead of time - - if not supplied, is calculated within this function - - if you need to integrate the same shape data over and over, calculated - it ahead of time using: - kernel = simpson_matrix(Nx,Ny,dx,dy) + keywords: - weight - can be used to scale data if single number - - can be used to mask data if weight is array (same size as data) - """ - s = data.shape - Nx = s[0] - Ny = s[1] + kernel - can be supplied if the simpson matrix is calculated ahead of time + - if not supplied, is calculated within this function + - if you need to integrate the same shape data over and over, calculated + it ahead of time using: + kernel = simpson_matrix(Nx,Ny,dx,dy) - if len(kernel)==1: - kernel = simpson_matrix(Nx,Ny,dx,dy) + weight - can be used to scale data if single number + - can be used to mask data if weight is array (same size as data) + """ + s = data.shape + Nx = s[0] + Ny = s[1] + + if len(kernel) == 1: + kernel = simpson_matrix(Nx, Ny, dx, dy) - return sum(multiply(multiply(weight,kernel),data))/sum(multiply(weight,kernel)) + return sum(multiply(multiply(weight, kernel), data)) / sum(multiply(weight, kernel)) -def simpson_matrix(Nx,Ny,dx,dy): - """ - Creates a 2D matrix of coefficients for the simpson_integrate function +def simpson_matrix(Nx, Ny, dx, dy): + """ + Creates a 2D matrix of coefficients for the simpson_integrate function - Call ahead of time if you need to perform integration of the same size data with the - same dx and dy - - Otherwise, simpson_integrate will automatically call this + Call ahead of time if you need to perform integration of the same size data with the + same dx and dy - """ - Wx = arange(Nx) + 2 - Wx[where(arange(Nx) % 2 == 1)] = 4 - Wx[0] = 1 - Wx[Nx-1] = 1 + Otherwise, simpson_integrate will automatically call this + + """ + Wx = arange(Nx) + 2 + Wx[where(arange(Nx) % 2 == 1)] = 4 + Wx[0] = 1 + Wx[Nx - 1] = 1 - Wy = arange(Ny) + 2 - Wy[where(arange(Ny) % 2 == 1)] = 4 - Wy[0] = 1 - Wy[Ny-1] = 1 + Wy = arange(Ny) + 2 + Wy[where(arange(Ny) % 2 == 1)] = 4 + Wy[0] = 1 + Wy[Ny - 1] = 1 - W = Wy[None,:] * Wx[:,None] + W = Wy[None, :] * Wx[:, None] - A = dx*dy/9.0 + A = dx * dy / 9.0 - return W*A + return W * A diff --git a/boututils/check_scaling.py b/boututils/check_scaling.py index af59b0b..3a7922c 100644 --- a/boututils/check_scaling.py +++ b/boututils/check_scaling.py @@ -25,17 +25,22 @@ def get_order(grid_spacing, errors): """ if len(errors) != len(grid_spacing): - raise ValueError("errors (len: {}) and grid_spacing (len: {}) should be the same length" - .format(len(errors), len(grid_spacing))) + raise ValueError( + "errors (len: {}) and grid_spacing (len: {}) should be the same length".format( + len(errors), len(grid_spacing) + ) + ) full_range = polyfit(log(grid_spacing), log(errors), 1) - small_spacing = log(errors[-2] / errors[-1]) / log(grid_spacing[-2] / grid_spacing[-1]) + small_spacing = log(errors[-2] / errors[-1]) / log( + grid_spacing[-2] / grid_spacing[-1] + ) return (full_range[0], small_spacing) -def check_order(error_list, expected_order, tolerance=2.e-1, spacing=None): +def check_order(error_list, expected_order, tolerance=2.0e-1, spacing=None): """Check if the actual_order is sufficiently close to the expected_order within a given tolerance @@ -46,9 +51,9 @@ def check_order(error_list, expected_order, tolerance=2.e-1, spacing=None): success = True - for i in range(len(error_list)-1): - grid_spacing = 2 if spacing is None else spacing[i] / spacing[i+1] - actual_order = log(error_list[i] / error_list[i+1]) / log(grid_spacing) + for i in range(len(error_list) - 1): + grid_spacing = 2 if spacing is None else spacing[i] / spacing[i + 1] + actual_order = log(error_list[i] / error_list[i + 1]) / log(grid_spacing) if not isclose(actual_order, expected_order, atol=tolerance, rtol=0): success = False @@ -76,15 +81,20 @@ def error_rate_table(errors, grid_sizes, label): """ if len(errors) != len(grid_sizes): - raise ValueError("errors (len: {}) and grid_sizes (len: {}) should be the same length" - .format(len(errors), len(grid_sizes))) + raise ValueError( + "errors (len: {}) and grid_sizes (len: {}) should be the same length".format( + len(errors), len(grid_sizes) + ) + ) - dx = 1. / array(grid_sizes) + dx = 1.0 / array(grid_sizes) message = "{}:\nGrid points | Error | Rate\n".format(label) for i, grid_size in enumerate(grid_sizes): message += "{:<11} | {:f} | ".format(grid_size, errors[i]) if i > 0: - message += "{:f} \n".format(log(errors[i] / errors[i-1]) / log(dx[i] / dx[i-1])) + message += "{:f} \n".format( + log(errors[i] / errors[i - 1]) / log(dx[i] / dx[i - 1]) + ) else: message += "--\n" return message diff --git a/boututils/closest_line.py b/boututils/closest_line.py index 42dbbe0..5d6f136 100644 --- a/boututils/closest_line.py +++ b/boututils/closest_line.py @@ -1,14 +1,17 @@ from builtins import range + import numpy + + # Find the closest contour line to a given point def closest_line(n, x, y, ri, zi, mind=None): - - mind = numpy.min( (x[0] - ri)**2 + (y[0] - zi)**2 ) + + mind = numpy.min((x[0] - ri) ** 2 + (y[0] - zi) ** 2) ind = 0 - - for i in range (1, n) : - d = numpy.min( (x[i] - ri)**2 + (y[i] - zi)**2 ) - if d < mind : + + for i in range(1, n): + d = numpy.min((x[i] - ri) ** 2 + (y[i] - zi) ** 2) + if d < mind: mind = d ind = i - return ind + return ind diff --git a/boututils/contour.py b/boututils/contour.py index 1d3fc97..f37ea9c 100644 --- a/boututils/contour.py +++ b/boututils/contour.py @@ -2,11 +2,10 @@ Contour calculation routines https://web.archive.org/web/20140901225541/https://members.bellatlantic.net/~vze2vrva/thesis.html""" -from __future__ import print_function -from __future__ import division -from past.utils import old_div +from __future__ import division, print_function import numpy as np +from past.utils import old_div def contour(f, level): @@ -15,24 +14,27 @@ def contour(f, level): if len(f.shape) != 2: print("Contour only works on 2D data") return None - nx,ny = f.shape + nx, ny = f.shape # Go through each cell edge and mark which ones contain # a level crossing. Approximating function as # f = axy + bx + cy + d # Hence linear interpolation along edges. - edgecross = {} # Dictionary: (cell number, edge number) to crossing location + edgecross = {} # Dictionary: (cell number, edge number) to crossing location - for i in np.arange(nx-1): - for j in np.arange(ny-1): + for i in np.arange(nx - 1): + for j in np.arange(ny - 1): # Lower-left corner of cell is (i,j) - if (np.max(f[i:(i+2),j:(j+2)]) < level) or (np.min(f[i:(i+2),j:(j+2)]) > level): + if (np.max(f[i : (i + 2), j : (j + 2)]) < level) or ( + np.min(f[i : (i + 2), j : (j + 2)]) > level + ): # not in the correct range - skip continue # Check each edge ncross = 0 + def location(a, b): if (a > level) ^ (a > level): # One of the corners is > level, and the other is <= level @@ -43,14 +45,15 @@ def location(a, b): return None loc = [ - location(f[i,j], f[i+1,j]), - location(f[i+1,j], f[i+1,j+1]), - location(f[i+1,j+1], f[i,j+1]), - location(f[i,j+1], f[i,j])] + location(f[i, j], f[i + 1, j]), + location(f[i + 1, j], f[i + 1, j + 1]), + location(f[i + 1, j + 1], f[i, j + 1]), + location(f[i, j + 1], f[i, j]), + ] - if ncross != 0: # Only put into dictionary if has a crossing - cellnr = (ny-1)*i + j # The cell number - edgecross[cellnr] = [loc,ncross] # Tack ncross onto the end + if ncross != 0: # Only put into dictionary if has a crossing + cellnr = (ny - 1) * i + j # The cell number + edgecross[cellnr] = [loc, ncross] # Tack ncross onto the end # Process crossings into contour lines @@ -70,11 +73,12 @@ def follow(): return + def find_opoints(var2d): """Find O-points in psi i.e. local minima/maxima""" return + def find_xpoints(var2d): """Find X-points in psi i.e. inflection points""" return - diff --git a/boututils/crosslines.py b/boututils/crosslines.py index 732a9f0..58af72e 100644 --- a/boututils/crosslines.py +++ b/boututils/crosslines.py @@ -1,9 +1,11 @@ -from __future__ import print_function -from __future__ import division -from past.utils import old_div +from __future__ import division, print_function + +import itertools + import numpy as np from numpy.lib.stride_tricks import as_strided -import itertools +from past.utils import old_div + def unique(a, atol=1e-08): """Find unique rows in 2d array @@ -30,18 +32,19 @@ def unique(a, atol=1e-08): order = np.lexsort(a.T) a = a[order] diff = np.diff(a, axis=0) - np.abs(diff,out = diff) - ui = np.ones(len(a), 'bool') + np.abs(diff, out=diff) + ui = np.ones(len(a), "bool") ui[1:] = (diff > atol).any(axis=1) return a[ui] else: order = np.lexsort(a.T) a = a[order] diff = np.diff(a, axis=0) - ui = np.ones(len(a), 'bool') + ui = np.ones(len(a), "bool") ui[1:] = (diff != 0).any(axis=1) return a[ui] + def linelineintersect(a, b, atol=1e-08): """Find all intersection points of two lines defined by series of x,y pairs @@ -65,41 +68,52 @@ def linelineintersect(a, b, atol=1e-08): def x_y_from_line(line): """1st and 2nd column of array""" - return (line[:, 0],line[:, 1]) + return (line[:, 0], line[:, 1]) + def meshgrid_as_strided(x, y, mask=None): """numpy.meshgrid without copying data (using as_strided)""" if mask is None: - return (as_strided(x, strides=(0, x.strides[0]), shape=(y.size, x.size)), - as_strided(y, strides=(y.strides[0],0), shape=(y.size, x.size))) + return ( + as_strided(x, strides=(0, x.strides[0]), shape=(y.size, x.size)), + as_strided(y, strides=(y.strides[0], 0), shape=(y.size, x.size)), + ) else: - return (np.ma.array(as_strided(x, strides=(0, x.strides[0]), shape=(y.size, x.size)), mask=mask), - np.ma.array(as_strided(y, strides=(y.strides[0],0), shape=(y.size, x.size)), mask=mask)) - - #In the following the indices i, j represents the pairing of the ith segment of b and the jth segment of a - #e.g. if ignore[i,j]==True then the ith segment of b and the jth segment of a cannot intersect - ignore = np.zeros([b.shape[0]-1, a.shape[0]-1], dtype=bool) + return ( + np.ma.array( + as_strided(x, strides=(0, x.strides[0]), shape=(y.size, x.size)), + mask=mask, + ), + np.ma.array( + as_strided(y, strides=(y.strides[0], 0), shape=(y.size, x.size)), + mask=mask, + ), + ) + + # In the following the indices i, j represents the pairing of the ith segment of b and the jth segment of a + # e.g. if ignore[i,j]==True then the ith segment of b and the jth segment of a cannot intersect + ignore = np.zeros([b.shape[0] - 1, a.shape[0] - 1], dtype=bool) x11, x21 = meshgrid_as_strided(a[:-1, 0], b[:-1, 0], mask=ignore) - x12, x22 = meshgrid_as_strided(a[1: , 0], b[1: , 0], mask=ignore) + x12, x22 = meshgrid_as_strided(a[1:, 0], b[1:, 0], mask=ignore) y11, y21 = meshgrid_as_strided(a[:-1, 1], b[:-1, 1], mask=ignore) - y12, y22 = meshgrid_as_strided(a[1: , 1], b[1: , 1], mask=ignore) + y12, y22 = meshgrid_as_strided(a[1:, 1], b[1:, 1], mask=ignore) - #ignore segements with non-overlappiong bounding boxes + # ignore segements with non-overlappiong bounding boxes ignore[np.ma.maximum(x11, x12) < np.ma.minimum(x21, x22)] = True ignore[np.ma.minimum(x11, x12) > np.ma.maximum(x21, x22)] = True ignore[np.ma.maximum(y11, y12) < np.ma.minimum(y21, y22)] = True ignore[np.ma.minimum(y11, y12) > np.ma.maximum(y21, y22)] = True - #find intersection of segments, ignoring impossible line segment pairs when new info becomes available + # find intersection of segments, ignoring impossible line segment pairs when new info becomes available denom_ = np.empty(ignore.shape, dtype=float) denom = np.ma.array(denom_, mask=ignore) denom_[:, :] = ((y22 - y21) * (x12 - x11)) - ((x22 - x21) * (y12 - y11)) - #denom_tmp = ((y22 - y21) * (x12 - x11)) - ((x22 - x21) * (y12 - y11)) # H.SETO - denom_[:, :] = np.where(denom_ == 0.0, 1.e-100,denom_) + # denom_tmp = ((y22 - y21) * (x12 - x11)) - ((x22 - x21) * (y12 - y11)) # H.SETO + denom_[:, :] = np.where(denom_ == 0.0, 1.0e-100, denom_) ua_ = np.empty(ignore.shape, dtype=float) ua = np.ma.array(ua_, mask=ignore) - ua_[:, :] = (((x22 - x21) * (y11 - y21)) - ((y22 - y21) * (x11 - x21))) + ua_[:, :] = ((x22 - x21) * (y11 - y21)) - ((y22 - y21) * (x11 - x21)) ua_[:, :] /= denom ignore[ua < 0] = True @@ -107,24 +121,37 @@ def meshgrid_as_strided(x, y, mask=None): ub_ = np.empty(ignore.shape, dtype=float) ub = np.ma.array(ub_, mask=ignore) - ub_[:, :] = (((x12 - x11) * (y11 - y21)) - ((y12 - y11) * (x11 - x21))) + ub_[:, :] = ((x12 - x11) * (y11 - y21)) - ((y12 - y11) * (x11 - x21)) ub_[:, :] /= denom - ignore[ub < 0] = True ignore[ub > 1] = True - nans_ = np.zeros(ignore.shape, dtype = bool) - nans = np.ma.array(nans_, mask = ignore) - nans_[:,:] = np.isnan(ua) + nans_ = np.zeros(ignore.shape, dtype=bool) + nans = np.ma.array(nans_, mask=ignore) + nans_[:, :] = np.isnan(ua) if not np.ma.any(nans): - #remove duplicate cases where intersection happens on an endpoint -# ignore[np.ma.where((ua[:, :-1] == 1) & (ua[:, 1:] == 0))] = True -# ignore[np.ma.where((ub[:-1, :] == 1) & (ub[1:, :] == 0))] = True - ignore[np.ma.where((ua[:, :-1] < 1.0 + atol) & (ua[:, :-1] > 1.0 - atol) & (ua[:, 1:] < atol) & (ua[:, 1:] > -atol))] = True - ignore[np.ma.where((ub[:-1, :] < 1 + atol) & (ub[:-1, :] > 1 - atol) & (ub[1:, :] < atol) & (ub[1:, :] > -atol))] = True + # remove duplicate cases where intersection happens on an endpoint + # ignore[np.ma.where((ua[:, :-1] == 1) & (ua[:, 1:] == 0))] = True + # ignore[np.ma.where((ub[:-1, :] == 1) & (ub[1:, :] == 0))] = True + ignore[ + np.ma.where( + (ua[:, :-1] < 1.0 + atol) + & (ua[:, :-1] > 1.0 - atol) + & (ua[:, 1:] < atol) + & (ua[:, 1:] > -atol) + ) + ] = True + ignore[ + np.ma.where( + (ub[:-1, :] < 1 + atol) + & (ub[:-1, :] > 1 - atol) + & (ub[1:, :] < atol) + & (ub[1:, :] > -atol) + ) + ] = True xi = x11 + ua * (x12 - x11) yi = y11 + ua * (y12 - y11) @@ -132,66 +159,72 @@ def meshgrid_as_strided(x, y, mask=None): else: n_nans = np.ma.sum(nans) n_standard = np.ma.count(x11) - n_nans - #I initially tried using a set to get unique points but had problems with floating point equality + # I initially tried using a set to get unique points but had problems with floating point equality - #create n by 2 array to hold all possible intersection points, check later for uniqueness - points = np.empty([n_standard + 2 * n_nans, 2], dtype = float) #each colinear segment pair has two intersections, hence the extra n_colinear points + # create n by 2 array to hold all possible intersection points, check later for uniqueness + points = np.empty( + [n_standard + 2 * n_nans, 2], dtype=float + ) # each colinear segment pair has two intersections, hence the extra n_colinear points - #add standard intersection points + # add standard intersection points xi = x11 + ua * (x12 - x11) yi = y11 + ua * (y12 - y11) - points[:n_standard,0] = np.ma.compressed(xi[~nans]) - points[:n_standard,1] = np.ma.compressed(yi[~nans]) - ignore[~nans]=True - - - #now add the appropriate intersection points for the colinear overlapping segments - #colinear overlapping segments intersect on the two internal points of the four points describing a straight line - #ua and ub have already serverd their purpose. Reuse them here to mean: - #ua is relative position of 1st b segment point along segment a - #ub is relative position of 2nd b segment point along segment a - use_x = x12 != x11 #avoid vertical lines diviiding by zero + points[:n_standard, 0] = np.ma.compressed(xi[~nans]) + points[:n_standard, 1] = np.ma.compressed(yi[~nans]) + ignore[~nans] = True + + # now add the appropriate intersection points for the colinear overlapping segments + # colinear overlapping segments intersect on the two internal points of the four points describing a straight line + # ua and ub have already serverd their purpose. Reuse them here to mean: + # ua is relative position of 1st b segment point along segment a + # ub is relative position of 2nd b segment point along segment a + use_x = x12 != x11 # avoid vertical lines diviiding by zero use_y = ~use_x - ua[use_x] = old_div((x21[use_x]-x11[use_x]), (x12[use_x]-x11[use_x])) - ua[use_y] = old_div((y21[use_y]-y11[use_y]), (y12[use_y]-y11[use_y])) + ua[use_x] = old_div((x21[use_x] - x11[use_x]), (x12[use_x] - x11[use_x])) + ua[use_y] = old_div((y21[use_y] - y11[use_y]), (y12[use_y] - y11[use_y])) - ub[use_x] = old_div((x22[use_x]-x11[use_x]), (x12[use_x]-x11[use_x])) - ub[use_y] = old_div((y22[use_y]-y11[use_y]), (y12[use_y]-y11[use_y])) + ub[use_x] = old_div((x22[use_x] - x11[use_x]), (x12[use_x] - x11[use_x])) + ub[use_y] = old_div((y22[use_y] - y11[use_y]), (y12[use_y] - y11[use_y])) - np.ma.clip(ua, a_min=0,a_max = 1, out = ua) - np.ma.clip(ub, a_min=0,a_max = 1, out = ub) + np.ma.clip(ua, a_min=0, a_max=1, out=ua) + np.ma.clip(ub, a_min=0, a_max=1, out=ub) xi = x11 + ua * (x12 - x11) yi = y11 + ua * (y12 - y11) - points[n_standard:n_standard + n_nans,0] = np.ma.compressed(xi) - points[n_standard:n_standard + n_nans,1] = np.ma.compressed(yi) + points[n_standard : n_standard + n_nans, 0] = np.ma.compressed(xi) + points[n_standard : n_standard + n_nans, 1] = np.ma.compressed(yi) xi = x11 + ub * (x12 - x11) yi = y11 + ub * (y12 - y11) - points[n_standard+n_nans:,0] = np.ma.compressed(xi) - points[n_standard+n_nans:,1] = np.ma.compressed(yi) + points[n_standard + n_nans :, 0] = np.ma.compressed(xi) + points[n_standard + n_nans :, 1] = np.ma.compressed(yi) points_unique = unique(points) - return points_unique[:,0], points_unique[:,1] + return points_unique[:, 0], points_unique[:, 1] -def find_inter( contour1, contour2): +def find_inter(contour1, contour2): xi = np.array([]) yi = np.array([]) - i=0 - ncombos = (sum([len(x.get_paths()) for x in contour1.collections]) * - sum([len(x.get_paths()) for x in contour2.collections])) - for linecol1, linecol2 in itertools.product(contour1.collections, contour2.collections): - for path1, path2 in itertools.product(linecol1.get_paths(),linecol2.get_paths()): + i = 0 + ncombos = sum([len(x.get_paths()) for x in contour1.collections]) * sum( + [len(x.get_paths()) for x in contour2.collections] + ) + for linecol1, linecol2 in itertools.product( + contour1.collections, contour2.collections + ): + for path1, path2 in itertools.product( + linecol1.get_paths(), linecol2.get_paths() + ): i += 1 - print('line combo %5d of %5d' % (i, ncombos)) + print("line combo %5d of %5d" % (i, ncombos)) xinter, yinter = linelineintersect(path1.vertices, path2.vertices) xi = np.append(xi, xinter) yi = np.append(yi, yinter) - #plt.scatter(xi, yi, s=20) - #plt.show() + # plt.scatter(xi, yi, s=20) + # plt.show() return xi, yi diff --git a/boututils/datafile.py b/boututils/datafile.py index f2fb5ba..b3234ea 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -24,23 +24,28 @@ """ from __future__ import print_function -from builtins import map, zip, str, object -import numpy as np -import time import getpass -from boututils.boutwarnings import alwayswarn +import time +from builtins import map, object, str, zip + +import numpy as np + from boututils.boutarray import BoutArray +from boututils.boutwarnings import alwayswarn try: from netCDF4 import Dataset + has_netCDF = True except ImportError: raise ImportError( - "DataFile: No supported NetCDF modules available -- requires netCDF4") + "DataFile: No supported NetCDF modules available -- requires netCDF4" + ) try: import h5py + has_h5py = True except ImportError: has_h5py = False @@ -76,9 +81,12 @@ class DataFile(object): - Make `impl` and `handle` private """ + impl = None - def __init__(self, filename=None, write=False, create=False, format='NETCDF3_64BIT', **kwargs): + def __init__( + self, filename=None, write=False, create=False, format="NETCDF3_64BIT", **kwargs + ): """ NetCDF formats are described here: https://unidata.github.io/netcdf4-python/ @@ -88,22 +96,28 @@ def __init__(self, filename=None, write=False, create=False, format='NETCDF3_64B - NETCDF4 and NETCDF4_CLASSIC use HDF5 as the disk format """ if filename is not None: - if filename.split('.')[-1] in ('hdf5', 'hdf', 'h5'): + if filename.split(".")[-1] in ("hdf5", "hdf", "h5"): self.impl = DataFile_HDF5( - filename=filename, write=write, create=create, format=format) + filename=filename, write=write, create=create, format=format + ) else: self.impl = DataFile_netCDF( - filename=filename, write=write, create=create, format=format, **kwargs) - elif format == 'HDF5': + filename=filename, + write=write, + create=create, + format=format, + **kwargs + ) + elif format == "HDF5": self.impl = DataFile_HDF5( - filename=filename, write=write, create=create, - format=format) + filename=filename, write=write, create=create, format=format + ) else: self.impl = DataFile_netCDF( - filename=filename, write=write, create=create, format=format, **kwargs) + filename=filename, write=write, create=create, format=format, **kwargs + ) - def open(self, filename, write=False, create=False, - format='NETCDF3_CLASSIC'): + def open(self, filename, write=False, create=False, format="NETCDF3_CLASSIC"): """Open the file Parameters @@ -127,13 +141,10 @@ def open(self, filename, write=False, create=False, - `keys` should be more pythonic (return generator) """ - self.impl.open(filename, write=write, create=create, - format=format) + self.impl.open(filename, write=write, create=create, format=format) def close(self): - """Close a file and flush data to disk - - """ + """Close a file and flush data to disk""" self.impl.close() def __del__(self): @@ -232,9 +243,7 @@ def ndims(self, varname): return self.impl.ndims(varname) def sync(self): - """Write pending changes to disk. - - """ + """Write pending changes to disk.""" self.impl.sync() def size(self, varname): @@ -328,8 +337,7 @@ def attributes(self, varname): class DataFile_netCDF(DataFile): handle = None - def open(self, filename, write=False, create=False, - format='NETCDF3_CLASSIC'): + def open(self, filename, write=False, create=False, format="NETCDF3_CLASSIC"): if (not write) and (not create): self.handle = Dataset(filename, "r") elif create: @@ -344,8 +352,14 @@ def close(self): self.handle.close() self.handle = None - def __init__(self, filename=None, write=False, create=False, - format='NETCDF3_CLASSIC', **kwargs): + def __init__( + self, + filename=None, + write=False, + create=False, + format="NETCDF3_CLASSIC", + **kwargs + ): self._kwargs = kwargs if not has_netCDF: message = "DataFile: No supported NetCDF python-modules available" @@ -376,8 +390,7 @@ def read(self, name, ranges=None, asBoutArray=True): var = None for n in list(self.handle.variables.keys()): if n.lower() == name.lower(): - print( - "WARNING: Reading '" + n + "' instead of '" + name + "'") + print("WARNING: Reading '" + n + "' instead of '" + name + "'") var = self.handle.variables[n] if var is None: return None @@ -395,12 +408,14 @@ def read(self, name, ranges=None, asBoutArray=True): if ranges: if len(ranges) == 2 * ndims: # Reform list of pairs of ints into slices - ranges = [slice(a, b) for a, b in - zip(ranges[::2], ranges[1::2])] + ranges = [slice(a, b) for a, b in zip(ranges[::2], ranges[1::2])] elif len(ranges) != ndims: - raise ValueError("Incorrect number of elements in ranges argument " - "(got {}, expected {} or {})" - .format(len(ranges), ndims, 2 * ndims)) + raise ValueError( + "Incorrect number of elements in ranges argument " + "(got {}, expected {} or {})".format( + len(ranges), ndims, 2 * ndims + ) + ) data = var[ranges[:ndims]] if asBoutArray: @@ -462,30 +477,31 @@ def dimlen(d): dim = self.handle.dimensions[d] if dim is not None: t = type(dim).__name__ - if t == 'int': + if t == "int": return dim return len(dim) return 0 + return [dimlen(d) for d in var.dimensions] def _bout_type_from_dimensions(self, varname): dims = self.dimensions(varname) if any("char" in d for d in dims): - if 't' in dims: + if "t" in dims: return "string_t" else: return "string" dims_dict = { - ('t', 'x', 'y', 'z'): "Field3D_t", - ('t', 'x', 'y'): "Field2D_t", - ('t', 'x', 'z'): "FieldPerp_t", - ('t',): "scalar_t", - ('x', 'y', 'z'): "Field3D", - ('x', 'y'): "Field2D", - ('x', 'z'): "FieldPerp", - ('x',): "ArrayX", + ("t", "x", "y", "z"): "Field3D_t", + ("t", "x", "y"): "Field2D_t", + ("t", "x", "z"): "FieldPerp_t", + ("t",): "scalar_t", + ("x", "y", "z"): "Field3D", + ("x", "y"): "Field2D", + ("x", "z"): "FieldPerp", + ("x",): "ArrayX", (): "scalar", } @@ -495,29 +511,34 @@ def _bout_dimensions_from_var(self, data): try: bout_type = data.attributes["bout_type"] except AttributeError: - defdims_list = [(), - ('t',), - ('x', 'y'), - ('x', 'y', 'z'), - ('t', 'x', 'y', 'z')] + defdims_list = [ + (), + ("t",), + ("x", "y"), + ("x", "y", "z"), + ("t", "x", "y", "z"), + ] return defdims_list[len(np.shape(data))] if bout_type == "string_t": nt, string_length = data.shape - return ('t', "char" + str(string_length),) + return ( + "t", + "char" + str(string_length), + ) elif bout_type == "string": string_length = len(data) return ("char" + str(string_length),) dims_dict = { - "Field3D_t": ('t', 'x', 'y', 'z'), - "Field2D_t": ('t', 'x', 'y'), - "FieldPerp_t": ('t', 'x', 'z'), - "scalar_t": ('t',), - "Field3D": ('x', 'y', 'z'), - "Field2D": ('x', 'y'), - "FieldPerp": ('x', 'z'), - "ArrayX": ('x',), + "Field3D_t": ("t", "x", "y", "z"), + "Field2D_t": ("t", "x", "y"), + "FieldPerp_t": ("t", "x", "z"), + "scalar_t": ("t",), + "Field3D": ("x", "y", "z"), + "Field2D": ("x", "y"), + "FieldPerp": ("x", "z"), + "ArrayX": ("x",), "scalar": (), } @@ -533,20 +554,20 @@ def write(self, name, data, info=False): # Get the variable type t = type(data).__name__ - if t == 'NoneType': + if t == "NoneType": print("DataFile: None passed as data to write. Ignoring") return - if t == 'ndarray' or t == 'BoutArray': + if t == "ndarray" or t == "BoutArray": # Numpy type or BoutArray wrapper for Numpy type. Get the data type t = data.dtype.str - if t == 'list': + if t == "list": # List -> convert to numpy array data = np.array(data) t = data.dtype.str - if (t == 'int') or (t == ' 0 : + if numpy.size(count) > 0: print("Using qinty as toroidal shift angle") - zShift = grid.get('qinty') + zShift = grid.get("qinty") else: count = numpy.where(tn == "ZSHIFT") - if numpy.size(count) > 0 : - print("Using zShift as toroidal shift angle") - zShift = grid.get('zShift') + if numpy.size(count) > 0: + print("Using zShift as toroidal shift angle") + zShift = grid.get("zShift") else: - print("ERROR: Can't find qinty or zShift variable") - return - - zshift=grid.get('zShift') - - rxy=grid.get('Rxy') - zxy=grid.get('Zxy') - Btxy=grid.get('Btxy') - Bpxy=grid.get('Bpxy') - shiftangle=grid.get('ShiftAngle') - psixy=grid.get('psixy') - psi_axis=grid.get('psi_axis') - psi_bndry=grid.get('psi_bndry') - - np = 4*ny + print("ERROR: Can't find qinty or zShift variable") + return + + zshift = grid.get("zShift") + + rxy = grid.get("Rxy") + zxy = grid.get("Zxy") + Btxy = grid.get("Btxy") + Bpxy = grid.get("Bpxy") + shiftangle = grid.get("ShiftAngle") + psixy = grid.get("psixy") + psi_axis = grid.get("psi_axis") + psi_bndry = grid.get("psi_bndry") + + np = 4 * ny nf = old_div((np - 2), 2) famp = numpy.zeros((nx, nf)) - for x in range (nx): - #;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - # transform data into fixed poloidal angle - - # get number of poloidal points - nskip = numpy.zeros(ny-1) - for y in range (ny-1): + for x in range(nx): + # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + # transform data into fixed poloidal angle + + # get number of poloidal points + nskip = numpy.zeros(ny - 1) + for y in range(ny - 1): yp = y + 1 - nskip[y] = old_div(numpy.abs(zshift[x,yp] - zshift[x,y]), dz) - 1 - - - nskip =numpy.int_(numpy.round(nskip)) - nskip=numpy.where(nskip > 0, nskip, 0) - - - ny2 = numpy.int_(ny + numpy.sum(nskip)) # number of poloidal points - - # IF NOT KEYWORD_SET(quiet) THEN PRINT, x, ny2 - - f = numpy.zeros(ny2) # array for values - R = numpy.zeros(ny2) # Rxy - Z = numpy.zeros(ny2) # Zxy - BtBp = numpy.zeros(ny2) # Bt / Bp - - # interpolate values onto points - + nskip[y] = old_div(numpy.abs(zshift[x, yp] - zshift[x, y]), dz) - 1 + + nskip = numpy.int_(numpy.round(nskip)) + nskip = numpy.where(nskip > 0, nskip, 0) + + ny2 = numpy.int_(ny + numpy.sum(nskip)) # number of poloidal points + + # IF NOT KEYWORD_SET(quiet) THEN PRINT, x, ny2 + + f = numpy.zeros(ny2) # array for values + R = numpy.zeros(ny2) # Rxy + Z = numpy.zeros(ny2) # Zxy + BtBp = numpy.zeros(ny2) # Bt / Bp + + # interpolate values onto points + ypos = 0 - for y in range(ny-1): - # original points - zind = old_div((zangle - zshift[x,y]),dz) - - - if numpy.size(zind) != 1 : sys.exit() - f[ypos] = zinterp(vr[x,y,:], zind) - R[ypos] = rxy[x,y] - Z[ypos] = zxy[x,y] - BtBp[ypos] = old_div(Btxy[x,y], Bpxy[x,y]) + for y in range(ny - 1): + # original points + zind = old_div((zangle - zshift[x, y]), dz) + + if numpy.size(zind) != 1: + sys.exit() + f[ypos] = zinterp(vr[x, y, :], zind) + R[ypos] = rxy[x, y] + Z[ypos] = zxy[x, y] + BtBp[ypos] = old_div(Btxy[x, y], Bpxy[x, y]) ypos = ypos + 1 - # add the extra points - - zi0 = old_div((zangle - zshift[x,y]),dz) - zip1 = old_div((zangle - zshift[x,y+1]),dz) + # add the extra points + + zi0 = old_div((zangle - zshift[x, y]), dz) + zip1 = old_div((zangle - zshift[x, y + 1]), dz) dzi = old_div((zip1 - zi0), (nskip[y] + 1)) - for i in range (nskip[y]): - zi = zi0 + numpy.float(i+1)*dzi # zindex - w = old_div(numpy.float(i+1),numpy.float(nskip[y]+1)) # weighting - - f[ypos+i] = w*zinterp(vr[x,y+1,:], zi) + (1.0-w)*zinterp(vr[x,y,:], zi) - - R[ypos+i] = w*rxy[x,y+1] + (1.0-w)*rxy[x,y] - Z[ypos+i] = w*zxy[x,y+1] + (1.0-w)*zxy[x,y] - BtBp[ypos+i] = old_div((w*Btxy[x,y+1] + (1.0-w)*Btxy[x,y]), (w*Bpxy[x,y+1] + (1.0-w)*Bpxy[x,y])) - + for i in range(nskip[y]): + zi = zi0 + numpy.float(i + 1) * dzi # zindex + w = old_div(numpy.float(i + 1), numpy.float(nskip[y] + 1)) # weighting + + f[ypos + i] = w * zinterp(vr[x, y + 1, :], zi) + (1.0 - w) * zinterp( + vr[x, y, :], zi + ) + + R[ypos + i] = w * rxy[x, y + 1] + (1.0 - w) * rxy[x, y] + Z[ypos + i] = w * zxy[x, y + 1] + (1.0 - w) * zxy[x, y] + BtBp[ypos + i] = old_div( + (w * Btxy[x, y + 1] + (1.0 - w) * Btxy[x, y]), + (w * Bpxy[x, y + 1] + (1.0 - w) * Bpxy[x, y]), + ) + ypos = ypos + nskip[y] - + # final point - zind = old_div((zangle - zShift[x,ny-1]),dz) + zind = old_div((zangle - zShift[x, ny - 1]), dz) - f[ypos] = zinterp(vr[x,ny-1,:], zind) - R[ypos] = rxy[x,ny-1] - Z[ypos] = zxy[x,ny-1] - BtBp[ypos] = old_div(Btxy[x,ny-1], Bpxy[x,ny-1]) - + f[ypos] = zinterp(vr[x, ny - 1, :], zind) + R[ypos] = rxy[x, ny - 1] + Z[ypos] = zxy[x, ny - 1] + BtBp[ypos] = old_div(Btxy[x, ny - 1], Bpxy[x, ny - 1]) - #STOP + # STOP - #;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - #; calculate poloidal angle + # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + # ; calculate poloidal angle - drxy = numpy.gradient(R) dzxy = numpy.gradient(Z) - dl = numpy.sqrt(drxy*drxy + dzxy*dzxy) - - nu = dl * BtBp / R # field-line pitch + dl = numpy.sqrt(drxy * drxy + dzxy * dzxy) + + nu = dl * BtBp / R # field-line pitch theta = old_div(numpy.real(fft_integrate(nu)), shiftangle[x]) - - if numpy.max(theta) > 1.0 : - # mis-match between q and nu (integration error?) - if quiet is None : print("Mismatch ", x, numpy.max(theta)) + + if numpy.max(theta) > 1.0: + # mis-match between q and nu (integration error?) + if quiet is None: + print("Mismatch ", x, numpy.max(theta)) theta = old_div(theta, (numpy.max(theta) + numpy.abs(theta[1] - theta[0]))) - - - theta = 2.0*numpy.pi * theta - #;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - #; take Fourier transform in theta angle + theta = 2.0 * numpy.pi * theta + + # ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + # ; take Fourier transform in theta angle - tarr = 2.0*numpy.pi*numpy.arange(np) / numpy.float(np) # regular array in theta + tarr = ( + 2.0 * numpy.pi * numpy.arange(np) / numpy.float(np) + ) # regular array in theta farr = numpy.interp(tarr, theta, f) - #STOP + # STOP - ff = old_div(numpy.fft.fft(farr),numpy.size(farr)) + ff = old_div(numpy.fft.fft(farr), numpy.size(farr)) - for i in range (nf): - famp[x, i] = 2.0*numpy.abs(ff[i+1]) - - - - - # sort modes by maximum size + for i in range(nf): + famp[x, i] = 2.0 * numpy.abs(ff[i + 1]) + + # sort modes by maximum size fmax = numpy.zeros(nf) for i in range(nf): - fmax[i] = numpy.max(famp[:,i]) - + fmax[i] = numpy.max(famp[:, i]) inds = numpy.argsort(fmax)[::-1] - - if pmodes is None : pmodes = 10 + if pmodes is None: + pmodes = 10 - qprof = old_div(numpy.abs(shiftangle), (2.0*numpy.pi)) + qprof = old_div(numpy.abs(shiftangle), (2.0 * numpy.pi)) xarr = numpy.arange(nx) - xtitle="Radial index" - if xq is not None : + xtitle = "Radial index" + if xq is not None: # show as a function of q*n - xarr = qprof*numpy.float(n) + xarr = qprof * numpy.float(n) - xtitle="q * n" - elif xpsi is not None : + xtitle = "q * n" + elif xpsi is not None: # show as a function of psi. Should be normalised psi - xarr = psixy[:,0] + xarr = psixy[:, 0] # Check if the grid includes psi axis and boundary count1 = numpy.where(tn == "PSI_AXIS") count2 = numpy.where(tn == "PSI_BNDRY") - - if (numpy.size(count1) > 0) and (numpy.size(count2) > 0) : + + if (numpy.size(count1) > 0) and (numpy.size(count2) > 0): xarr = old_div((xarr - psi_axis), (psi_bndry - psi_axis)) - + else: # Use hard-wired values print("WARNING: Using hard-wired psi normalisation") # for circular case - #xarr = (xarr + 0.1937) / (0.25044 + 0.1937) + # xarr = (xarr + 0.1937) / (0.25044 + 0.1937) # for ellipse case - #xarr = xarr / 0.74156 - + # xarr = xarr / 0.74156 + # cbm18_dens8 xarr = old_div((xarr + 0.854856), (0.854856 + 0.0760856)) - - - xtitle="Psi normalised" - - - if slow is not None : + xtitle = "Psi normalised" + + if slow is not None: # plot modes slowly for examination - #safe_colors, /first -# ax = fig.add_subplot(111) + # safe_colors, /first + # ax = fig.add_subplot(111) # go through and plot each mode for i in range(nf): - if numpy.max(famp[:,i]) > 0.05*numpy.max(famp): - print("Mode m = ", i+1, " of ", nf) - plot(xarr, famp[:,i], 'k') - ylim(0,numpy.max(famp)) + if numpy.max(famp[:, i]) > 0.05 * numpy.max(famp): + print("Mode m = ", i + 1, " of ", nf) + plot(xarr, famp[:, i], "k") + ylim(0, numpy.max(famp)) xlim(xrange) xlabel(xtitle) show(block=False) - - q = old_div(numpy.float(i+1), numpy.float(n)) - + + q = old_div(numpy.float(i + 1), numpy.float(n)) + pos = numpy.interp(q, qprof, xarr) - - plot( [pos, pos],[0, 2.*numpy.max(fmax)], 'k--') + + plot([pos, pos], [0, 2.0 * numpy.max(fmax)], "k--") draw() - - ans=query_yes_no('next mode') + + ans = query_yes_no("next mode") if ans: clf() - - - - elif ergos is not None : + + elif ergos is not None: # ERGOS - style output - - if output is not None and slow is None : - savefig('output.png') - - -# -# contour2, famp, xarr, indgen(nf)+1, $ -# xlabel=xtitle, xrange=xrange, yrange=yrange, _extra=_extra -# -# ; overplot the q profile -# -# oplot, xarr, qprof * n, color=1, thick=2 -# -# IF KEYWORD_SET(rational) THEN BEGIN -# maxm = FIX(MAX(qprof)) * n -# -# qreson = (FINDGEN(maxm)+1) / FLOAT(n) -# -# ; get x location for each of these resonances -# qloc = INTERPOL(xarr, qprof, qreson) -# -# oplot, qloc, findgen(maxm)+1., psym=4, color=1 -# ENDIF -# -# IF KEYWORD_SET(output) THEN BEGIN -# ; output data to save file -# SAVE, xarr, qprof, famp, file=output+".idl" -# -# DEVICE, /close -# SET_PLOT, 'X' -# ENDIF + + if output is not None and slow is None: + savefig("output.png") + + # + # contour2, famp, xarr, indgen(nf)+1, $ + # xlabel=xtitle, xrange=xrange, yrange=yrange, _extra=_extra + # + # ; overplot the q profile + # + # oplot, xarr, qprof * n, color=1, thick=2 + # + # IF KEYWORD_SET(rational) THEN BEGIN + # maxm = FIX(MAX(qprof)) * n + # + # qreson = (FINDGEN(maxm)+1) / FLOAT(n) + # + # ; get x location for each of these resonances + # qloc = INTERPOL(xarr, qprof, qreson) + # + # oplot, qloc, findgen(maxm)+1., psym=4, color=1 + # ENDIF + # + # IF KEYWORD_SET(output) THEN BEGIN + # ; output data to save file + # SAVE, xarr, qprof, famp, file=output+".idl" + # + # DEVICE, /close + # SET_PLOT, 'X' + # ENDIF else: - if output is not None and slow is None : - savefig('output.png') - # savefig('output.ps') - - # - # - if subset is not None : - + if output is not None and slow is None: + savefig("output.png") + # savefig('output.ps') + + # + # + if subset is not None: + # get number of modes larger than 5% of the maximum - count = numpy.size(numpy.where(fmax > 0.10*numpy.max(fmax))) - + count = numpy.size(numpy.where(fmax > 0.10 * numpy.max(fmax))) + minind = numpy.min(inds[0:count]) maxind = numpy.max(inds[0:count]) - + print("Mode number range: ", minind, maxind) - - plot( xarr, famp[:,0], 'k', visible=False) - ylim(0,numpy.max(famp)) + + plot(xarr, famp[:, 0], "k", visible=False) + ylim(0, numpy.max(famp)) xlabel(xtitle) xlim(xrange) title(ftitle) - - gca().set_color_cycle(['red', 'red', 'black', 'black']) - - for i in range(minind, maxind+1, subset): - plot( xarr, famp[:,i]) - - q = old_div(numpy.float(i+1), numpy.float(n)) + + gca().set_color_cycle(["red", "red", "black", "black"]) + + for i in range(minind, maxind + 1, subset): + plot(xarr, famp[:, i]) + + q = old_div(numpy.float(i + 1), numpy.float(n)) pos = numpy.interp(q, qprof, xarr) - - plot( [pos, pos], [0, 2.*numpy.max(fmax)], '--') - - # + plot([pos, pos], [0, 2.0 * numpy.max(fmax)], "--") + + # else: # default - just plot everything - gca().set_color_cycle(['black', 'red']) - - plot(xarr, famp[:,0]) - ylim(0,numpy.max(famp)) #, color=1, - xlabel(xtitle) #, chars=1.5, xrange=xrange,title=title, _extra=_extra - xlim(xrange) - for i in range (nf): - plot( xarr, famp[:,i]) - - - # - # IF KEYWORD_SET(addq) THEN BEGIN - # - # FOR i=0, pmodes-1 DO BEGIN - # PRINT, "m = "+STRTRIM(STRING(inds[i]+1), 2)+" amp = "+STRTRIM(STRING(fmax[inds[i]]),2) - # q = FLOAT(inds[i]+1) / FLOAT(n) - # - # pos = INTERPOL(xarr, qprof, q) - # - # oplot, [pos, pos], [0, 2.*MAX(fmax)], lines=2, color=1 - # ENDFOR - # ENDIF - # - # ENDELSE - # IF KEYWORD_SET(output) THEN BEGIN - # ; output data to save file - # SAVE, xarr, qprof, famp, file=output+".idl" - # - # DEVICE, /close - # SET_PLOT, 'X' - # ENDIF - # ENDELSE - # + gca().set_color_cycle(["black", "red"]) + + plot(xarr, famp[:, 0]) + ylim(0, numpy.max(famp)) # , color=1, + xlabel(xtitle) # , chars=1.5, xrange=xrange,title=title, _extra=_extra + xlim(xrange) + for i in range(nf): + plot(xarr, famp[:, i]) + + +# +# IF KEYWORD_SET(addq) THEN BEGIN +# +# FOR i=0, pmodes-1 DO BEGIN +# PRINT, "m = "+STRTRIM(STRING(inds[i]+1), 2)+" amp = "+STRTRIM(STRING(fmax[inds[i]]),2) +# q = FLOAT(inds[i]+1) / FLOAT(n) +# +# pos = INTERPOL(xarr, qprof, q) +# +# oplot, [pos, pos], [0, 2.*MAX(fmax)], lines=2, color=1 +# ENDFOR +# ENDIF +# +# ENDELSE +# IF KEYWORD_SET(output) THEN BEGIN +# ; output data to save file +# SAVE, xarr, qprof, famp, file=output+".idl" +# +# DEVICE, /close +# SET_PLOT, 'X' +# ENDIF +# ENDELSE +# diff --git a/boututils/moment_xyzt.py b/boututils/moment_xyzt.py index 675d1f1..ef906d0 100644 --- a/boututils/moment_xyzt.py +++ b/boututils/moment_xyzt.py @@ -1,74 +1,73 @@ -from __future__ import print_function -from __future__ import division +from __future__ import division, print_function + from builtins import range -from past.utils import old_div + import numpy as np +from past.utils import old_div + from boututils.bunch import Bunch -def RMSvalue( vec1d): -#; -#; -get rms of a 1D signal -#;------------------------ - - nel=np.size(vec1d) - valav=old_div(np.sum(vec1d),nel) - valrms=np.sqrt(old_div(np.sum((vec1d-valav)**2),nel)) - acvec=vec1d-valav - - return Bunch(valrms=valrms, - valav=valav, - acvec=acvec) - - - -def moment_xyzt( sig_xyzt, *args):#rms=None, dc=None, ac=None): -#; -#; Calculate moments of a 4d signal of (x,y,z,t), i.e, -#; -RMS, i.e., a function of (x,y,t) -#; -DC (average in z), i.e., a function of (x,y,t) -#; -AC (DC subtracted out), i.e., a function of (x,y,z,t) -#;------------------------------------------------------------------- - - d = np.shape(sig_xyzt) - if np.size(d) != 4 : - print("Error: Variable must be 4D (x,y,z,t)") - return - - - siz=np.shape(sig_xyzt) - rms=np.zeros((siz[0],siz[1],siz[2])) - dc=np.zeros((siz[0],siz[1],siz[2])) - if 'AC' in args : ac=np.zeros((siz[0],siz[1],siz[2],siz[3])) - - - data = sig_xyzt - if np.modf(np.log2(siz[3]))[0] != 0.0 : - print("WARNING: Expecting a power of 2 in Z direction") - - if np.modf(np.log2(siz[3]-1))[0] and (siz[3] > 1) : - print(" -> Truncating last point to get power of 2") - data = data[:,:,0:(siz[3]-2),:] - siz[3] = siz[3] - 1 - - - for ix in range (siz[1]): - for iy in range (siz[2]): - for it in range (siz[0]): - val=RMSvalue(sig_xyzt[it,ix,iy,:]) - - rms[it,ix,iy]=val.valrms - dc[it,ix,iy]=val.valav - if 'AC' in args : ac[it,ix,iy,:]=[val.acvec,val.acvec[0]] - - res=Bunch() - - if 'RMS' in args: - res.rms = rms - if 'DC' in args: - res.dc = dc - if 'AC' in args: - res.ac = ac - - if 'RMS' not in args and 'DC' not in args and 'AC' not in args : - raise RuntimeError('Wrong argument') - return res + +def RMSvalue(vec1d): + # ; + # ; -get rms of a 1D signal + # ;------------------------ + + nel = np.size(vec1d) + valav = old_div(np.sum(vec1d), nel) + valrms = np.sqrt(old_div(np.sum((vec1d - valav) ** 2), nel)) + acvec = vec1d - valav + + return Bunch(valrms=valrms, valav=valav, acvec=acvec) + + +def moment_xyzt(sig_xyzt, *args): # rms=None, dc=None, ac=None): + # ; + # ; Calculate moments of a 4d signal of (x,y,z,t), i.e, + # ; -RMS, i.e., a function of (x,y,t) + # ; -DC (average in z), i.e., a function of (x,y,t) + # ; -AC (DC subtracted out), i.e., a function of (x,y,z,t) + # ;------------------------------------------------------------------- + + d = np.shape(sig_xyzt) + if np.size(d) != 4: + print("Error: Variable must be 4D (x,y,z,t)") + return + + siz = np.shape(sig_xyzt) + rms = np.zeros((siz[0], siz[1], siz[2])) + dc = np.zeros((siz[0], siz[1], siz[2])) + if "AC" in args: + ac = np.zeros((siz[0], siz[1], siz[2], siz[3])) + + data = sig_xyzt + if np.modf(np.log2(siz[3]))[0] != 0.0: + print("WARNING: Expecting a power of 2 in Z direction") + + if np.modf(np.log2(siz[3] - 1))[0] and (siz[3] > 1): + print(" -> Truncating last point to get power of 2") + data = data[:, :, 0 : (siz[3] - 2), :] + siz[3] = siz[3] - 1 + + for ix in range(siz[1]): + for iy in range(siz[2]): + for it in range(siz[0]): + val = RMSvalue(sig_xyzt[it, ix, iy, :]) + + rms[it, ix, iy] = val.valrms + dc[it, ix, iy] = val.valav + if "AC" in args: + ac[it, ix, iy, :] = [val.acvec, val.acvec[0]] + + res = Bunch() + + if "RMS" in args: + res.rms = rms + if "DC" in args: + res.dc = dc + if "AC" in args: + res.ac = ac + + if "RMS" not in args and "DC" not in args and "AC" not in args: + raise RuntimeError("Wrong argument") + return res diff --git a/boututils/options.py b/boututils/options.py index 2de3ebc..de471af 100644 --- a/boututils/options.py +++ b/boututils/options.py @@ -7,8 +7,8 @@ """ -from copy import copy import os +from copy import copy class BOUTOptions(object): @@ -58,15 +58,15 @@ class BOUTOptions(object): def __init__(self, inp_path=None): - self._sections = ['root'] + self._sections = ["root"] for section in self._sections: - super(BOUTOptions,self).__setattr__(section,{}) + super(BOUTOptions, self).__setattr__(section, {}) if inp_path is not None: self.read_inp(inp_path) - def read_inp(self, inp_path=''): + def read_inp(self, inp_path=""): """Read a BOUT++ input file Parameters @@ -77,37 +77,37 @@ def read_inp(self, inp_path=''): """ try: - inpfile = open(os.path.join(inp_path, 'BOUT.inp'),'r') + inpfile = open(os.path.join(inp_path, "BOUT.inp"), "r") except: - raise TypeError("ERROR: Could not read file "+\ - os.path.join(inp_path, "BOUT.inp")) + raise TypeError( + "ERROR: Could not read file " + os.path.join(inp_path, "BOUT.inp") + ) - current_section = 'root' + current_section = "root" inplines = inpfile.read().splitlines() # Close the file after use inpfile.close() for line in inplines: - #remove white space - line = line.replace(" ","") - - - if len(line) > 0 and line[0] is not '#': - #Only read lines that are not comments or blank - if '[' in line: - #Section header - section = line.split('[')[1].split(']')[0] + # remove white space + line = line.replace(" ", "") + + if len(line) > 0 and line[0] is not "#": + # Only read lines that are not comments or blank + if "[" in line: + # Section header + section = line.split("[")[1].split("]")[0] current_section = copy(section) if current_section not in self._sections: self.add_section(current_section) - elif '=' in line: - #option setting - attribute = line.split('=')[0] - value = line.split('=')[1].split('#')[0] - value = value.replace("\n","") - value = value.replace("\t","") - value = value.replace("\r","") - value = value.replace("\"","") + elif "=" in line: + # option setting + attribute = line.split("=")[0] + value = line.split("=")[1].split("#")[0] + value = value.replace("\n", "") + value = value.replace("\t", "") + value = value.replace("\r", "") + value = value.replace('"', "") self.__dict__[copy(current_section)][copy(attribute)] = copy(value) else: pass @@ -125,7 +125,7 @@ def add_section(self, section): - Guard against wrong type """ self._sections.append(section) - super(BOUTOptions,self).__setattr__(section,{}) + super(BOUTOptions, self).__setattr__(section, {}) def remove_section(self, section): """Remove a section from the options @@ -141,9 +141,9 @@ def remove_section(self, section): """ if section in self._sections: self._sections.pop(self._sections.index(sections)) - super(BOUTOptions,self).__delattr__(section) + super(BOUTOptions, self).__delattr__(section) else: - print("WARNING: Section "+section+" not found.\n") + print("WARNING: Section " + section + " not found.\n") def list_sections(self, verbose=False): """Return all the sections in the options @@ -160,6 +160,6 @@ def list_sections(self, verbose=False): if verbose: print("Sections Contained: \n") for section in self._sections: - print("\t"+section+"\n") + print("\t" + section + "\n") return self._sections diff --git a/boututils/plotdata.py b/boututils/plotdata.py index 72627f4..6bac047 100644 --- a/boututils/plotdata.py +++ b/boututils/plotdata.py @@ -1,25 +1,38 @@ from __future__ import print_function -# Plot a data set -import numpy as np import matplotlib import matplotlib.cm as cm import matplotlib.mlab as mlab import matplotlib.pyplot as plt +import numpy as np + +# Plot a data set -matplotlib.rcParams['xtick.direction'] = 'out' -matplotlib.rcParams['ytick.direction'] = 'out' -def plotdata(data, x=None, y=None, - title=None, xtitle=None, ytitle=None, - output=None, range=None, - fill=True, mono=False, colorbar=True, - xerr=None, yerr=None): +matplotlib.rcParams["xtick.direction"] = "out" +matplotlib.rcParams["ytick.direction"] = "out" + + +def plotdata( + data, + x=None, + y=None, + title=None, + xtitle=None, + ytitle=None, + output=None, + range=None, + fill=True, + mono=False, + colorbar=True, + xerr=None, + yerr=None, +): """Plot 1D or 2D data, with a variety of options.""" - + size = data.shape ndims = len(size) - + if ndims == 1: if (xerr is not None) or (yerr is not None): # Points with error bars @@ -34,38 +47,40 @@ def plotdata(data, x=None, y=None, elif ndims == 2: # A contour plot - + if x is None: x = np.arange(size[1]) if y is None: y = np.arange(size[0]) - + if fill: - #plt.contourf(data, colors=colors) - cmap=None - if mono: cmap = cm.gray - plt.imshow(data, interpolation='bilinear', cmap=cmap) + # plt.contourf(data, colors=colors) + cmap = None + if mono: + cmap = cm.gray + plt.imshow(data, interpolation="bilinear", cmap=cmap) else: colors = None - if mono: colors = 'k' - + if mono: + colors = "k" + plt.contour(x, y, data, colors=colors) # Add a color bar if colorbar: - CB = plt.colorbar(shrink=0.8, extend='both') - + CB = plt.colorbar(shrink=0.8, extend="both") + else: print("Sorry, can't handle %d-D variables" % ndims) return - + if title is not None: plt.title(title) if xtitle is not None: plt.xlabel(xtitle) if ytitle is not None: plt.ylabel(ytitle) - + if output is not None: # Write to a file plt.savefig(output) @@ -73,10 +88,11 @@ def plotdata(data, x=None, y=None, # Plot to screen plt.show() + def test(): """Test the plotdata routine.""" # Generate and plot test data - + delta = 0.025 x = np.arange(-3.0, 3.0, delta) y = np.arange(-2.0, 2.0, delta) @@ -85,6 +101,6 @@ def test(): Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1) # difference of Gaussians Z = 10.0 * (Z2 - Z1) - + plotdata(Z, title="test data", fill=False, mono=False) plotdata(Z, title="Fill in mono", fill=True, mono=True) diff --git a/boututils/plotpolslice.py b/boututils/plotpolslice.py index 924272f..2de9863 100644 --- a/boututils/plotpolslice.py +++ b/boututils/plotpolslice.py @@ -1,72 +1,75 @@ -from __future__ import print_function -from __future__ import division -from builtins import str -from builtins import range -from past.utils import old_div +from __future__ import division, print_function + +import sys +from builtins import range, str + import numpy as np +from past.utils import old_div + from boututils.file_import import file_import -import sys -if sys.version_info[0]>=3: - message = "polplotslice uses the VTK library through mayavi, which"+\ - " is currently only available in python 2" +if sys.version_info[0] >= 3: + message = ( + "polplotslice uses the VTK library through mayavi, which" + + " is currently only available in python 2" + ) raise ImportError(message) else: from mayavi import mlab from tvtk.tools import visual +def zinterp(v, zind): + # v = REFORM(v) + nz = np.size(v) + z0 = np.round(zind) -def zinterp( v, zind): - #v = REFORM(v) - nz = np.size(v) - z0 = np.round(zind) - - p = zind - float(z0) # between -0.5 and 0.5 - - if p < 0.0 : - z0 = z0 - 1 - p = p + 1.0 + p = zind - float(z0) # between -0.5 and 0.5 + if p < 0.0: + z0 = z0 - 1 + p = p + 1.0 - z0 = ((z0 % (nz-1)) + (nz-1)) % (nz-1) + z0 = ((z0 % (nz - 1)) + (nz - 1)) % (nz - 1) - # for now 3-point interpolation + # for now 3-point interpolation - zp = (z0 + 1) % (nz - 1) - zm = (z0 - 1 + (nz-1)) % (nz - 1) + zp = (z0 + 1) % (nz - 1) + zm = (z0 - 1 + (nz - 1)) % (nz - 1) - result = 0.5*p*(p-1.0)*v[zm] \ - + (1.0 - p*p)*v[z0] \ - + 0.5*p*(p+1.0)*v[zp] + result = ( + 0.5 * p * (p - 1.0) * v[zm] + + (1.0 - p * p) * v[z0] + + 0.5 * p * (p + 1.0) * v[zp] + ) - return result + return result -def plotpolslice(var3d,gridfile,period=1,zangle=0.0, rz=1, fig=0): +def plotpolslice(var3d, gridfile, period=1, zangle=0.0, rz=1, fig=0): """ data2d = plotpolslice(data3d, 'gridfile' , period=1, zangle=0.0, rz:return (r,z) grid also=1, fig: to do the graph, set to 1 ) """ - g=file_import(gridfile) + g = file_import(gridfile) - nx=var3d.shape[0] - ny=var3d.shape[1] - nz=var3d.shape[2] + nx = var3d.shape[0] + ny = var3d.shape[1] + nz = var3d.shape[2] + zShift = g.get("zShift") + rxy = g.get("Rxy") + zxy = g.get("Zxy") - zShift=g.get('zShift') - rxy=g.get('Rxy') - zxy=g.get('Zxy') + dz = 2.0 * np.pi / float(period * nz) - dz = 2.0*np.pi / float(period*nz) - - ny2=ny - nskip=np.zeros(ny-1) - for i in range(ny-1): - ip=(i+1)%ny - nskip[i]=0 + ny2 = ny + nskip = np.zeros(ny - 1) + for i in range(ny - 1): + ip = (i + 1) % ny + nskip[i] = 0 for x in range(nx): - ns=old_div(np.max(np.abs(zShift[x,ip]-zShift[x,i])),dz)-1 - if ns > nskip[i] : nskip[i] = ns + ns = old_div(np.max(np.abs(zShift[x, ip] - zShift[x, i])), dz) - 1 + if ns > nskip[i]: + nskip[i] = ns nskip = np.int_(np.round(nskip)) ny2 = np.int_(ny2 + np.sum(nskip)) @@ -78,66 +81,65 @@ def plotpolslice(var3d,gridfile,period=1,zangle=0.0, rz=1, fig=0): z = np.zeros((nx, ny2)) ypos = 0 - for y in range (ny-1) : - # put in the original points - for x in range (nx): - zind = old_div((zangle - zShift[x,y]),dz) - var2d[x,ypos] = zinterp(var3d[x,y,:], zind) - # IF KEYWORD_SET(profile) THEN var2d[x,ypos] = var2d[x,ypos] + profile[x,y] - r[x,ypos] = rxy[x,y] - z[x,ypos] = zxy[x,y] + for y in range(ny - 1): + # put in the original points + for x in range(nx): + zind = old_div((zangle - zShift[x, y]), dz) + var2d[x, ypos] = zinterp(var3d[x, y, :], zind) + # IF KEYWORD_SET(profile) THEN var2d[x,ypos] = var2d[x,ypos] + profile[x,y] + r[x, ypos] = rxy[x, y] + z[x, ypos] = zxy[x, y] ypos = ypos + 1 print((y, ypos)) - # and the extra points + # and the extra points - for x in range (nx): - zi0 = old_div((zangle - zShift[x,y]),dz) - zip1 = old_div((zangle - zShift[x,y+1]),dz) + for x in range(nx): + zi0 = old_div((zangle - zShift[x, y]), dz) + zip1 = old_div((zangle - zShift[x, y + 1]), dz) dzi = old_div((zip1 - zi0), (nskip[y] + 1)) - for i in range (nskip[y]): - zi = zi0 + float(i+1)*dzi # zindex - w = old_div(float(i+1),float(nskip[y]+1)) # weighting - - var2d[x,ypos+i] = w*zinterp(var3d[x,y+1,:], zi) + (1.0-w)*zinterp(var3d[x,y,:], zi) - # IF KEYWORD_SET(profile) THEN var2d[x,ypos+i] = var2d[x,ypos+i] + w*profile[x,y+1] + (1.0-w)*profile[x,y] - r[x,ypos+i] = w*rxy[x,y+1] + (1.0-w)*rxy[x,y] - z[x,ypos+i] = w*zxy[x,y+1] + (1.0-w)*zxy[x,y] - + for i in range(nskip[y]): + zi = zi0 + float(i + 1) * dzi # zindex + w = old_div(float(i + 1), float(nskip[y] + 1)) # weighting + var2d[x, ypos + i] = w * zinterp(var3d[x, y + 1, :], zi) + ( + 1.0 - w + ) * zinterp(var3d[x, y, :], zi) + # IF KEYWORD_SET(profile) THEN var2d[x,ypos+i] = var2d[x,ypos+i] + w*profile[x,y+1] + (1.0-w)*profile[x,y] + r[x, ypos + i] = w * rxy[x, y + 1] + (1.0 - w) * rxy[x, y] + z[x, ypos + i] = w * zxy[x, y + 1] + (1.0 - w) * zxy[x, y] ypos = ypos + nskip[y] - - # FINAL POINT + # FINAL POINT for x in range(nx): - zind = old_div((zangle - zShift[x,ny-1]),dz) - var2d[x,ypos] = zinterp(var3d[x,ny-1,:], zind) - # IF KEYWORD_SET(profile) THEN var2d[x,ypos] = var2d[x,ypos] + profile[x,ny-1] - r[x,ypos] = rxy[x,ny-1] - z[x,ypos] = zxy[x,ny-1] + zind = old_div((zangle - zShift[x, ny - 1]), dz) + var2d[x, ypos] = zinterp(var3d[x, ny - 1, :], zind) + # IF KEYWORD_SET(profile) THEN var2d[x,ypos] = var2d[x,ypos] + profile[x,ny-1] + r[x, ypos] = rxy[x, ny - 1] + z[x, ypos] = zxy[x, ny - 1] + if fig == 1: - if(fig==1): - - f = mlab.figure(size=(600,600)) + f = mlab.figure(size=(600, 600)) # Tell visual to use this as the viewer. visual.set_viewer(f) - - s = mlab.mesh(r,z,var2d, colormap='PuOr')#, wrap_scale='true')#, representation='wireframe') - s.enable_contours=True - s.contour.filled_contours=True - mlab.view(0,0) + s = mlab.mesh( + r, z, var2d, colormap="PuOr" + ) # , wrap_scale='true')#, representation='wireframe') + s.enable_contours = True + s.contour.filled_contours = True + mlab.view(0, 0) else: # return according to opt - if rz==1 : - return r,z,var2d + if rz == 1: + return r, z, var2d else: return var2d diff --git a/boututils/radial_grid.py b/boututils/radial_grid.py index e0cf9c4..0a06c16 100644 --- a/boututils/radial_grid.py +++ b/boututils/radial_grid.py @@ -1,8 +1,10 @@ from __future__ import division -from past.utils import old_div + import numpy -#;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -# +from past.utils import old_div + +# ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +# # radial grid # # n - number of grid points @@ -12,57 +14,54 @@ # in_dp=in_dp - Fix the dx on the lower side # out_dp=out_dp - Fix the dx on the upper side -def radial_grid( n, pin, pout, include_in, include_out, seps, sep_factor, - in_dp=None, out_dp=None): - - if n == 1 : - return [0.5*(pin+pout)] +def radial_grid( + n, pin, pout, include_in, include_out, seps, sep_factor, in_dp=None, out_dp=None +): + + if n == 1: + return [0.5 * (pin + pout)] - x = numpy.arange(0.,n) - m = numpy.float(n-1) - if include_in is None : + x = numpy.arange(0.0, n) + m = numpy.float(n - 1) + if include_in is None: x = x + 0.5 m = m + 0.5 - - + if include_out is None: m = m + 0.5 - - x = old_div(x, m) - - if in_dp is None and out_dp is None : - # Neither inner or outer gradients set. Just return equal spacing - return pin + (pout - pin)*x - - - norm = (x[1] - x[0])*(pout - pin) + x = old_div(x, m) + + if in_dp is None and out_dp is None: + # Neither inner or outer gradients set. Just return equal spacing + return pin + (pout - pin) * x + + norm = (x[1] - x[0]) * (pout - pin) - if in_dp is not None and out_dp is not None : - # Fit to dist = a*i^3 + b*i^2 + c*i - c = old_div(in_dp,norm) - b = 3.*(1. - c) - old_div(out_dp,norm) + c - a = 1. - c - b - elif in_dp is not None : - # Only inner set - c = old_div(in_dp,norm) - a = 0.5*(c-1.) - b = 1. - c - a - - #a = 0 - #c = in_dp/norm - #b = 1. - c + if in_dp is not None and out_dp is not None: + # Fit to dist = a*i^3 + b*i^2 + c*i + c = old_div(in_dp, norm) + b = 3.0 * (1.0 - c) - old_div(out_dp, norm) + c + a = 1.0 - c - b + elif in_dp is not None: + # Only inner set + c = old_div(in_dp, norm) + a = 0.5 * (c - 1.0) + b = 1.0 - c - a + + # a = 0 + # c = in_dp/norm + # b = 1. - c else: - # Only outer set. Used in PF region - # Fit to (1-b)*x^a + bx for fixed b + # Only outer set. Used in PF region + # Fit to (1-b)*x^a + bx for fixed b df = old_div(out_dp, norm) b = 0.25 < df # Make sure a > 0 - a = old_div((df - b), (1. - b)) - vals = pin + (pout - pin)*( (1.-b)*x^a + b*x ) + a = old_div((df - b), (1.0 - b)) + vals = pin + (pout - pin) * ((1.0 - b) * x ^ a + b * x) return vals - - - vals = pin + (pout - pin)*(c*x + b*x^2 + a*x^3) - #STOP + + vals = pin + (pout - pin) * (c * x + b * x ^ 2 + a * x ^ 3) + # STOP return vals diff --git a/boututils/read_geqdsk.py b/boututils/read_geqdsk.py index f665f59..83f4d2b 100644 --- a/boututils/read_geqdsk.py +++ b/boututils/read_geqdsk.py @@ -1,90 +1,103 @@ from __future__ import print_function + from builtins import range + import numpy from geqdsk import Geqdsk + from boututils.bunch import Bunch -def read_geqdsk (file): - - data=Geqdsk() - + +def read_geqdsk(file): + + data = Geqdsk() + data.openFile(file) - nxefit =data.get('nw') - nyefit =data.get('nh') - xdim =data.get('rdim') - zdim =data.get('zdim') - rcentr =data.get('rcentr') - rgrid1 =data.get('rleft') - zmid =data.get('zmid') - - rmagx =data.get('rmaxis') - zmagx =data.get('zmaxis') - simagx =data.get('simag') - sibdry =data.get('sibry') - bcentr =data.get('bcentr') - - cpasma =data.get('current') - #simagx =data.get('simag') - #xdum =data.get() - #rmagx =data.get('rmaxis') - #xdum =data.get() - - #zmagx =data.get('zmaxis') - #xdum =data.get() - #sibdry =data.get('sibry') - #xdum =data.get() - #xdum =data.get() - -# Read arrays - - fpol=data.get('fpol') - pres=data.get('pres') - - f=data.get('psirz') - qpsi=data.get('qpsi') - - nbdry=data.get('nbbbs') - nlim=data.get('limitr') - - if(nlim != 0) : - xlim=data.get('rlim') - ylim=data.get('zlim') + nxefit = data.get("nw") + nyefit = data.get("nh") + xdim = data.get("rdim") + zdim = data.get("zdim") + rcentr = data.get("rcentr") + rgrid1 = data.get("rleft") + zmid = data.get("zmid") + + rmagx = data.get("rmaxis") + zmagx = data.get("zmaxis") + simagx = data.get("simag") + sibdry = data.get("sibry") + bcentr = data.get("bcentr") + + cpasma = data.get("current") + # simagx =data.get('simag') + # xdum =data.get() + # rmagx =data.get('rmaxis') + # xdum =data.get() + + # zmagx =data.get('zmaxis') + # xdum =data.get() + # sibdry =data.get('sibry') + # xdum =data.get() + # xdum =data.get() + + # Read arrays + + fpol = data.get("fpol") + pres = data.get("pres") + + f = data.get("psirz") + qpsi = data.get("qpsi") + + nbdry = data.get("nbbbs") + nlim = data.get("limitr") + + if nlim != 0: + xlim = data.get("rlim") + ylim = data.get("zlim") else: - xlim=[0] - ylim=[0] - - rbdry=data.get('rbbbs') - zbdry=data.get('zbbbs') + xlim = [0] + ylim = [0] + rbdry = data.get("rbbbs") + zbdry = data.get("zbbbs") # Reconstruct the (R,Z) mesh - r=numpy.zeros((nxefit, nyefit), numpy.float64) - z=numpy.zeros((nxefit, nyefit), numpy.float64) - - - for i in range(0,nxefit): - for j in range(0,nyefit): - r[i,j] = rgrid1 + xdim*i/(nxefit-1) - z[i,j] = (zmid-0.5*zdim) + zdim*j/(nyefit-1) - - f=f.T - - print('nxefit = ', nxefit, ' nyefit= ', nyefit) - - return Bunch(nx=nxefit, ny=nyefit, # Number of horizontal and vertical points - r=r, z=z, # Location of the grid-points - xdim=xdim, zdim=zdim, # Size of the domain in meters - rcentr=rcentr, bcentr=bcentr, # Reference vacuum toroidal field (m, T) - rgrid1=rgrid1, # R of left side of domain - zmid=zmid, # Z at the middle of the domain - rmagx=rmagx, zmagx=zmagx, # Location of magnetic axis - simagx=simagx, # Poloidal flux at the axis (Weber / rad) - sibdry=sibdry, # Poloidal flux at plasma boundary (Weber / rad) - cpasma=cpasma, # - psi=f, # Poloidal flux in Weber/rad on grid points - fpol=fpol, # Poloidal current function on uniform flux grid - pres=pres, # Plasma pressure in nt/m^2 on uniform flux grid - qpsi=qpsi, # q values on uniform flux grid - nbdry=nbdry, rbdry=rbdry, zbdry=zbdry, # Plasma boundary - nlim=nlim, xlim=xlim, ylim=ylim) # Wall boundary + r = numpy.zeros((nxefit, nyefit), numpy.float64) + z = numpy.zeros((nxefit, nyefit), numpy.float64) + + for i in range(0, nxefit): + for j in range(0, nyefit): + r[i, j] = rgrid1 + xdim * i / (nxefit - 1) + z[i, j] = (zmid - 0.5 * zdim) + zdim * j / (nyefit - 1) + + f = f.T + + print("nxefit = ", nxefit, " nyefit= ", nyefit) + + return Bunch( + nx=nxefit, + ny=nyefit, # Number of horizontal and vertical points + r=r, + z=z, # Location of the grid-points + xdim=xdim, + zdim=zdim, # Size of the domain in meters + rcentr=rcentr, + bcentr=bcentr, # Reference vacuum toroidal field (m, T) + rgrid1=rgrid1, # R of left side of domain + zmid=zmid, # Z at the middle of the domain + rmagx=rmagx, + zmagx=zmagx, # Location of magnetic axis + simagx=simagx, # Poloidal flux at the axis (Weber / rad) + sibdry=sibdry, # Poloidal flux at plasma boundary (Weber / rad) + cpasma=cpasma, # + psi=f, # Poloidal flux in Weber/rad on grid points + fpol=fpol, # Poloidal current function on uniform flux grid + pres=pres, # Plasma pressure in nt/m^2 on uniform flux grid + qpsi=qpsi, # q values on uniform flux grid + nbdry=nbdry, + rbdry=rbdry, + zbdry=zbdry, # Plasma boundary + nlim=nlim, + xlim=xlim, + ylim=ylim, + ) # Wall boundary diff --git a/boututils/run_wrapper.py b/boututils/run_wrapper.py index 8c0aa65..e7598bf 100644 --- a/boututils/run_wrapper.py +++ b/boututils/run_wrapper.py @@ -1,12 +1,11 @@ """Collection of functions which can be used to make a BOUT++ run""" -from builtins import str import os import pathlib import re import subprocess -from subprocess import call, Popen, STDOUT, PIPE - +from builtins import str +from subprocess import PIPE, STDOUT, Popen, call if os.name == "nt": # Default on Windows @@ -16,22 +15,22 @@ def getmpirun(default=DEFAULT_MPIRUN): - """Return environment variable named MPIRUN, if it exists else return - a default mpirun command + """Return environment variable named MPIRUN, if it exists else return + a default mpirun command - Parameters - ---------- - default : str, optional - An mpirun command to return if ``MPIRUN`` is not set in the environment + Parameters + ---------- + default : str, optional + An mpirun command to return if ``MPIRUN`` is not set in the environment - """ - MPIRUN = os.getenv("MPIRUN") + """ + MPIRUN = os.getenv("MPIRUN") - if MPIRUN is None or MPIRUN == "": - MPIRUN = default - print("getmpirun: using the default " + str(default)) + if MPIRUN is None or MPIRUN == "": + MPIRUN = default + print("getmpirun: using the default " + str(default)) - return MPIRUN + return MPIRUN def shell(command, pipe=False): @@ -88,10 +87,9 @@ def determineNumberOfCPUs(): # cpuset # cpuset may restrict the number of *available* processors try: - m = re.search(r'(?m)^Cpus_allowed:\s*(.*)$', - open('/proc/self/status').read()) + m = re.search(r"(?m)^Cpus_allowed:\s*(.*)$", open("/proc/self/status").read()) if m: - res = bin(int(m.group(1).replace(',', ''), 16)).count('1') + res = bin(int(m.group(1).replace(",", ""), 16)).count("1") if res > 0: return res except IOError: @@ -100,22 +98,23 @@ def determineNumberOfCPUs(): # Python 2.6+ try: import multiprocessing + return multiprocessing.cpu_count() - except (ImportError,NotImplementedError): + except (ImportError, NotImplementedError): pass # POSIX try: - res = int(os.sysconf('SC_NPROCESSORS_ONLN')) + res = int(os.sysconf("SC_NPROCESSORS_ONLN")) if res > 0: return res - except (AttributeError,ValueError): + except (AttributeError, ValueError): pass # Windows try: - res = int(os.environ['NUMBER_OF_PROCESSORS']) + res = int(os.environ["NUMBER_OF_PROCESSORS"]) if res > 0: return res @@ -125,6 +124,7 @@ def determineNumberOfCPUs(): # jython try: from java.lang import Runtime + runtime = Runtime.getRuntime() res = runtime.availableProcessors() if res > 0: @@ -134,8 +134,7 @@ def determineNumberOfCPUs(): # BSD try: - sysctl = subprocess.Popen(['sysctl', '-n', 'hw.ncpu'], - stdout=subprocess.PIPE) + sysctl = subprocess.Popen(["sysctl", "-n", "hw.ncpu"], stdout=subprocess.PIPE) scStdout = sysctl.communicate()[0] res = int(scStdout) @@ -146,7 +145,7 @@ def determineNumberOfCPUs(): # Linux try: - res = open('/proc/cpuinfo').read().count('processor\t:') + res = open("/proc/cpuinfo").read().count("processor\t:") if res > 0: return res @@ -155,8 +154,8 @@ def determineNumberOfCPUs(): # Solaris try: - pseudoDevices = os.listdir('/devices/pseudo/') - expr = re.compile('^cpuid@[0-9]+$') + pseudoDevices = os.listdir("/devices/pseudo/") + expr = re.compile("^cpuid@[0-9]+$") res = 0 for pd in pseudoDevices: @@ -171,13 +170,13 @@ def determineNumberOfCPUs(): # Other UNIXes (heuristic) try: try: - dmesg = open('/var/run/dmesg.boot').read() + dmesg = open("/var/run/dmesg.boot").read() except IOError: - dmesgProcess = subprocess.Popen(['dmesg'], stdout=subprocess.PIPE) + dmesgProcess = subprocess.Popen(["dmesg"], stdout=subprocess.PIPE) dmesg = dmesgProcess.communicate()[0] res = 0 - while '\ncpu' + str(res) + ':' in dmesg: + while "\ncpu" + str(res) + ":" in dmesg: res += 1 if res > 0: @@ -185,11 +184,18 @@ def determineNumberOfCPUs(): except OSError: pass - raise Exception('Can not determine number of CPUs on this system') + raise Exception("Can not determine number of CPUs on this system") -def launch(command, runcmd=None, nproc=None, mthread=None, - output=None, pipe=False, verbose=False): +def launch( + command, + runcmd=None, + nproc=None, + mthread=None, + output=None, + pipe=False, + verbose=False, +): """Launch parallel MPI jobs >>> status = launch(command, nproc, output=None) @@ -229,7 +235,7 @@ def launch(command, runcmd=None, nproc=None, mthread=None, cmd = runcmd + " " + str(nproc) + " " + command if output is not None: - cmd = cmd + " > "+output + cmd = cmd + " > " + output if mthread is not None: if os.name == "nt": @@ -237,9 +243,9 @@ def launch(command, runcmd=None, nproc=None, mthread=None, cmd = 'cmd /C "set OMP_NUM_THREADS={} && {}"'.format(mthread, cmd) else: cmd = "OMP_NUM_THREADS={} {}".format(mthread, cmd) - + if verbose == True: - print(cmd) + print(cmd) return shell(cmd, pipe=pipe) @@ -258,11 +264,12 @@ def shell_safe(command, *args, **kwargs): Optional arguments passed to `shell` """ - s, out = shell(command,*args,**kwargs) + s, out = shell(command, *args, **kwargs) if s: - raise RuntimeError("Run failed with %d.\nCommand was:\n%s\n\n" - "Output was\n\n%s"% - (s,command,out)) + raise RuntimeError( + "Run failed with %d.\nCommand was:\n%s\n\n" + "Output was\n\n%s" % (s, command, out) + ) return s, out @@ -279,11 +286,12 @@ def launch_safe(command, *args, **kwargs): Optional arguments passed to `shell` """ - s, out = launch(command,*args,**kwargs) + s, out = launch(command, *args, **kwargs) if s: - raise RuntimeError("Run failed with %d.\nCommand was:\n%s\n\n" - "Output was\n\n%s"% - (s,command,out)) + raise RuntimeError( + "Run failed with %d.\nCommand was:\n%s\n\n" + "Output was\n\n%s" % (s, command, out) + ) return s, out diff --git a/boututils/showdata.py b/boututils/showdata.py index 4c0eb97..9c63826 100644 --- a/boututils/showdata.py +++ b/boututils/showdata.py @@ -7,36 +7,56 @@ Additional functionality by George Breyiannis 26/12/2014 """ -from __future__ import print_function -from __future__ import division -from builtins import str, chr, range +from __future__ import division, print_function + +from builtins import chr, range, str -from matplotlib import pyplot as plt -from matplotlib import animation -from numpy import linspace, meshgrid, array, min, max, abs, floor, pi, isclose from boutdata.collect import collect +from matplotlib import animation +from matplotlib import pyplot as plt +from numpy import abs, array, floor, isclose, linspace, max, meshgrid, min, pi + from boututils.boutwarnings import alwayswarn #################################################################### # Specify manually ffmpeg path -#plt.rcParams['animation.ffmpeg_path'] = '/usr/bin/ffmpeg' +# plt.rcParams['animation.ffmpeg_path'] = '/usr/bin/ffmpeg' FFwriter = animation.FFMpegWriter() #################################################################### ################### -#https://stackoverflow.com/questions/16732379/stop-start-pause-in-python-matplotlib-animation +# https://stackoverflow.com/questions/16732379/stop-start-pause-in-python-matplotlib-animation # -j=-2 +j = -2 pause = False ################### -def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_array=None, - movie=0, fps=28, dpi=200, intv=1, Ncolors=25, x=[], y=[], - global_colors=False, symmetric_colors=False, hold_aspect=False, - cmap=None, clear_between_frames=None, return_animation=False, window_title=""): +def showdata( + vars, + titles=[], + legendlabels=[], + surf=[], + polar=[], + tslice=0, + t_array=None, + movie=0, + fps=28, + dpi=200, + intv=1, + Ncolors=25, + x=[], + y=[], + global_colors=False, + symmetric_colors=False, + hold_aspect=False, + cmap=None, + clear_between_frames=None, + return_animation=False, + window_title="", +): """A Function to animate time dependent data from BOUT++ To animate multiple variables on different axes: @@ -161,87 +181,100 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar # Sort out titles if len(titles) == 0: - for i in range(0,Nvar): - titles.append(('Var' + str(i+1))) + for i in range(0, Nvar): + titles.append(("Var" + str(i + 1))) elif len(titles) != Nvar: - raise ValueError('The length of the titles input list must match the length of the vars list.') + raise ValueError( + "The length of the titles input list must match the length of the vars list." + ) # Sort out legend labels if len(legendlabels) == 0: - for i in range(0,Nvar): + for i in range(0, Nvar): legendlabels.append([]) - for j in range(0,Nlines[i]): - legendlabels[i].append(chr(97+j)) - elif (isinstance(legendlabels[0], list) != 1): + for j in range(0, Nlines[i]): + legendlabels[i].append(chr(97 + j)) + elif isinstance(legendlabels[0], list) != 1: if Nvar != 1: check = 0 - for i in range(0,Nvar): + for i in range(0, Nvar): if len(legendlabels) != Nlines[i]: - check = check+1 + check = check + 1 if check == 0: - alwayswarn("The legendlabels list does not contain a sublist for each variable, but its length matches the number of lines on each plot. Will apply labels to each plot") + alwayswarn( + "The legendlabels list does not contain a sublist for each variable, but its length matches the number of lines on each plot. Will apply labels to each plot" + ) legendlabelsdummy = [] for i in range(0, Nvar): legendlabelsdummy.append([]) - for j in range(0,Nlines[i]): + for j in range(0, Nlines[i]): legendlabelsdummy[i].append(legendlabels[j]) legendlabels = legendlabelsdummy else: - alwayswarn("The legendlabels list does not contain a sublist for each variable, and it's length does not match the number of lines on each plot. Will default apply labels to each plot") + alwayswarn( + "The legendlabels list does not contain a sublist for each variable, and it's length does not match the number of lines on each plot. Will default apply labels to each plot" + ) legendlabels = [] - for i in range(0,Nvar): + for i in range(0, Nvar): legendlabels.append([]) - for j in range(0,Nlines[i]): - legendlabels[i].append(chr(97+j)) + for j in range(0, Nlines[i]): + legendlabels[i].append(chr(97 + j)) else: - if (Nlines[0] == len(legendlabels)): + if Nlines[0] == len(legendlabels): legendlabels = [legendlabels] elif len(legendlabels) != Nvar: - alwayswarn("The length of the legendlabels list does not match the length of the vars list, will continue with default values") + alwayswarn( + "The length of the legendlabels list does not match the length of the vars list, will continue with default values" + ) legendlabels = [] - for i in range(0,Nvar): + for i in range(0, Nvar): legendlabels.append([]) - for j in range(0,Nlines[i]): - legendlabels[i].append(chr(97+j)) + for j in range(0, Nlines[i]): + legendlabels[i].append(chr(97 + j)) else: - for i in range(0,Nvar): + for i in range(0, Nvar): if isinstance(legendlabels[i], list): if len(legendlabels[i]) != Nlines[i]: - alwayswarn('The length of the legendlabel (sub)list for each plot does not match the number of datasets for each plot. Will continue with default values') + alwayswarn( + "The length of the legendlabel (sub)list for each plot does not match the number of datasets for each plot. Will continue with default values" + ) legendlabels[i] = [] - for j in range(0,Nlines[i]): - legendlabels[i].append(chr(97+j)) + for j in range(0, Nlines[i]): + legendlabels[i].append(chr(97 + j)) else: legendlabels[i] = [legendlabels[i]] if len(legendlabels[i]) != Nlines[i]: - alwayswarn('The length of the legendlabel (sub)list for each plot does not match the number of datasets for each plot. Will continue with default values') + alwayswarn( + "The length of the legendlabel (sub)list for each plot does not match the number of datasets for each plot. Will continue with default values" + ) legendlabels[i] = [] - for j in range(0,Nlines[i]): - legendlabels[i].append(chr(97+j)) - + for j in range(0, Nlines[i]): + legendlabels[i].append(chr(97 + j)) # Sort out surf list if isinstance(surf, list): - if (len(surf) == Nvar): + if len(surf) == Nvar: for i in range(0, Nvar): if surf[i] >= 1: surf[i] = 1 else: surf[i] = 0 - elif (len(surf) == 1): + elif len(surf) == 1: if surf[0] >= 1: surf[0] = 1 else: surf[0] = 0 - if (Nvar > 1): - for i in range(1,Nvar): + if Nvar > 1: + for i in range(1, Nvar): surf.append(surf[0]) - elif (len(surf) == 0): - for i in range(0,Nvar): + elif len(surf) == 0: + for i in range(0, Nvar): surf.append(0) else: - alwayswarn('Length of surf list does not match number of variables. Will default to no polar plots') - for i in range(0,Nvar): + alwayswarn( + "Length of surf list does not match number of variables. Will default to no polar plots" + ) + for i in range(0, Nvar): surf.append(0) else: @@ -250,32 +283,34 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar surf[0] = 1 else: surf[0] = 0 - if (Nvar > 1): - for i in range(1,Nvar): + if Nvar > 1: + for i in range(1, Nvar): surf.append(surf[0]) # Sort out polar list if isinstance(polar, list): - if (len(polar) == Nvar): + if len(polar) == Nvar: for i in range(0, Nvar): if polar[i] >= 1: polar[i] = 1 else: polar[i] = 0 - elif (len(polar) == 1): + elif len(polar) == 1: if polar[0] >= 1: polar[0] = 1 else: polar[0] = 0 - if (Nvar > 1): - for i in range(1,Nvar): + if Nvar > 1: + for i in range(1, Nvar): polar.append(polar[0]) - elif (len(polar) == 0): - for i in range(0,Nvar): + elif len(polar) == 0: + for i in range(0, Nvar): polar.append(0) else: - alwayswarn('Length of polar list does not match number of variables. Will default to no polar plots') - for i in range(0,Nvar): + alwayswarn( + "Length of polar list does not match number of variables. Will default to no polar plots" + ) + for i in range(0, Nvar): polar.append(0) else: polar = [polar] @@ -283,8 +318,8 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar polar[0] = 1 else: polar[0] = 0 - if (Nvar > 1): - for i in range(1,Nvar): + if Nvar > 1: + for i in range(1, Nvar): polar.append(polar[0]) # Determine shapes of arrays @@ -292,40 +327,49 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar Ndims = [] lineplot = [] contour = [] - for i in range(0,Nvar): + for i in range(0, Nvar): dims.append([]) Ndims.append([]) for j in range(0, Nlines[i]): dims[i].append(array((vars[i][j].shape))) Ndims[i].append(dims[i][j].shape[0]) # Perform check to make sure that data is either 2D or 3D - if (Ndims[i][j] < 2): - raise ValueError('data must be either 2 or 3 dimensional. Exiting') + if Ndims[i][j] < 2: + raise ValueError("data must be either 2 or 3 dimensional. Exiting") - if (Ndims[i][j] > 3): - raise ValueError('data must be either 2 or 3 dimensional. Exiting') + if Ndims[i][j] > 3: + raise ValueError("data must be either 2 or 3 dimensional. Exiting") - if ((Ndims[i][j] == 2) & (polar[i] != 0)): - alwayswarn('Data must be 3 dimensional (time, r, theta) for polar plots. Will plot lineplot instead') + if (Ndims[i][j] == 2) & (polar[i] != 0): + alwayswarn( + "Data must be 3 dimensional (time, r, theta) for polar plots. Will plot lineplot instead" + ) - if ((Ndims[i][j] == 2) & (surf[i] != 0)): - alwayswarn('Data must be 3 dimensional (time, x, y) for surface plots. Will plot lineplot instead') + if (Ndims[i][j] == 2) & (surf[i] != 0): + alwayswarn( + "Data must be 3 dimensional (time, x, y) for surface plots. Will plot lineplot instead" + ) - if ((Ndims[i][j] == 3) & (Nlines[i] != 1)): - raise ValueError('cannot have multiple sets of 3D (time + 2 spatial dimensions) on each subplot') + if (Ndims[i][j] == 3) & (Nlines[i] != 1): + raise ValueError( + "cannot have multiple sets of 3D (time + 2 spatial dimensions) on each subplot" + ) + if Ndims[i][j] != Ndims[i][0]: + raise ValueError( + "Error, Number of dimensions must be the same for all variables on each plot." + ) - if ((Ndims[i][j] != Ndims[i][0])): - raise ValueError('Error, Number of dimensions must be the same for all variables on each plot.') - - if (Ndims[i][0] == 2): # Set polar and surf list entries to 0 + if Ndims[i][0] == 2: # Set polar and surf list entries to 0 polar[i] = 0 surf[i] = 0 lineplot.append(1) contour.append(0) else: - if ((polar[i] == 1) & (surf[i] == 1)): - alwayswarn('Cannot do polar and surface plots at the same time. Default to contour plot') + if (polar[i] == 1) & (surf[i] == 1): + alwayswarn( + "Cannot do polar and surface plots at the same time. Default to contour plot" + ) contour.append(1) lineplot.append(0) polar[i] = 0 @@ -348,48 +392,68 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar for j in range(0, Nlines[i]): Nt[i].append(vars[i][j].shape[0]) Nx[i].append(vars[i][j].shape[1]) - if (Nt[i][j] != Nt[0][0]): - raise ValueError('time dimensions must be the same for all variables.') + if Nt[i][j] != Nt[0][0]: + raise ValueError("time dimensions must be the same for all variables.") - #if (Nx[i][j] != Nx[i][0]): + # if (Nx[i][j] != Nx[i][0]): # raise ValueError('Dimensions must be the same for all variables on each plot.') - if (Ndims[i][j] == 3): + if Ndims[i][j] == 3: Ny[i].append(vars[i][j].shape[2]) - #if (Ny[i][j] != Ny[i][0]): + # if (Ny[i][j] != Ny[i][0]): # raise ValueError('Dimensions must be the same for all variables.') # Obtain number of frames - Nframes = int(Nt[0][0]/intv) + Nframes = int(Nt[0][0] / intv) # Generate grids for plotting # Try to use provided grids where possible # If x and/or y are not lists, apply to all variables - if not isinstance(x, (list,tuple)): - x = [x]*Nvar # Make list of x with length Nvar - if not isinstance(y, (list,tuple)): - y = [y]*Nvar # Make list of x with length Nvar + if not isinstance(x, (list, tuple)): + x = [x] * Nvar # Make list of x with length Nvar + if not isinstance(y, (list, tuple)): + y = [y] * Nvar # Make list of x with length Nvar xnew = [] ynew = [] - for i in range(0,Nvar): + for i in range(0, Nvar): xnew.append([]) try: xnew[i].append(x[i]) - if not (x[i].shape==(Nx[i][0],) or x[i].shape==(Nx[i][0],Ny[i][0]) or x[i].shape==(Nt[i][0],Nx[i][0],Ny[i],[0])): - raise ValueError("For variable number "+str(i)+", "+titles[i]+", the shape of x is not compatible with the shape of the variable. Shape of x should be (Nx), (Nx,Ny) or (Nt,Nx,Ny).") + if not ( + x[i].shape == (Nx[i][0],) + or x[i].shape == (Nx[i][0], Ny[i][0]) + or x[i].shape == (Nt[i][0], Nx[i][0], Ny[i], [0]) + ): + raise ValueError( + "For variable number " + + str(i) + + ", " + + titles[i] + + ", the shape of x is not compatible with the shape of the variable. Shape of x should be (Nx), (Nx,Ny) or (Nt,Nx,Ny)." + ) except: for j in range(0, Nlines[i]): - xnew[i].append(linspace(0,Nx[i][j]-1, Nx[i][j])) + xnew[i].append(linspace(0, Nx[i][j] - 1, Nx[i][j])) - #x.append(linspace(0,Nx[i][0]-1, Nx[i][0])) + # x.append(linspace(0,Nx[i][0]-1, Nx[i][0])) - if (Ndims[i][0] == 3): + if Ndims[i][0] == 3: try: ynew.append(y[i]) - if not (y[i].shape==(Ny[i][0],) or y[i].shape==(Nx[i][0],Ny[i][0]) or y[i].shape==(Nt[i][0],Nx[i][0],Ny[i],[0])): - raise ValueError("For variable number "+str(i)+", "+titles[i]+", the shape of y is not compatible with the shape of the variable. Shape of y should be (Ny), (Nx,Ny) or (Nt,Nx,Ny).") + if not ( + y[i].shape == (Ny[i][0],) + or y[i].shape == (Nx[i][0], Ny[i][0]) + or y[i].shape == (Nt[i][0], Nx[i][0], Ny[i], [0]) + ): + raise ValueError( + "For variable number " + + str(i) + + ", " + + titles[i] + + ", the shape of y is not compatible with the shape of the variable. Shape of y should be (Ny), (Nx,Ny) or (Nt,Nx,Ny)." + ) except: - ynew.append(linspace(0, Ny[i][0]-1, Ny[i][0])) + ynew.append(linspace(0, Ny[i][0] - 1, Ny[i][0])) else: ynew.append(0) x = xnew @@ -403,80 +467,78 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar dummymin = [] clevels = [] - for i in range(0,Nvar): + for i in range(0, Nvar): dummymax.append([]) dummymin.append([]) - for j in range(0,Nlines[i]): + for j in range(0, Nlines[i]): dummymax[i].append(max(vars[i][j])) dummymin[i].append(min(vars[i][j])) fmax.append(max(dummymax[i])) fmin.append(min(dummymin[i])) - if(symmetric_colors): - absmax =max(abs(array(fmax[i], fmin[i]))) + if symmetric_colors: + absmax = max(abs(array(fmax[i], fmin[i]))) fmax[i] = absmax fmin[i] = -absmax - for j in range(0,Nlines[i]): + for j in range(0, Nlines[i]): dummymax[i][j] = max(x[i][j]) xmax.append(max(dummymax[i])) - if not (global_colors): if isclose(fmin[i], fmax[i]): # add/subtract very small constant in case fmin=fmax=0 - thiscontourmin = fmin[i] - 3.e-15*abs(fmin[i]) - 1.e-36 - thiscontourmax = fmax[i] + 3.e-15*abs(fmax[i]) + 1.e-36 + thiscontourmin = fmin[i] - 3.0e-15 * abs(fmin[i]) - 1.0e-36 + thiscontourmax = fmax[i] + 3.0e-15 * abs(fmax[i]) + 1.0e-36 alwayswarn("Contour levels too close, adding padding to colorbar range") clevels.append(linspace(thiscontourmin, thiscontourmax, Ncolors)) else: clevels.append(linspace(fmin[i], fmax[i], Ncolors)) - if(global_colors): + if global_colors: fmaxglobal = max(fmax) fminglobal = min(fmin) if isclose(fminglobal, fmaxglobal): - fminglobal = fminglobal - 3.e-15*abs(fminglobal) - 1.e-36 - fmaxglobal = fmaxglobal + 3.e-15*abs(fmaxglobal) + 1.e-36 - for i in range(0,Nvar): + fminglobal = fminglobal - 3.0e-15 * abs(fminglobal) - 1.0e-36 + fmaxglobal = fmaxglobal + 3.0e-15 * abs(fmaxglobal) + 1.0e-36 + for i in range(0, Nvar): clevels.append(linspace(fminglobal, fmaxglobal, Ncolors)) # Create figures for animation plotting - if (Nvar < 2): + if Nvar < 2: row = 1 col = 1 h = 6.0 w = 8.0 - elif (Nvar <3): + elif Nvar < 3: row = 1 col = 2 h = 6.0 w = 12.0 - elif (Nvar < 5): + elif Nvar < 5: row = 2 col = 2 h = 8.0 w = 12.0 - elif (Nvar < 7): + elif Nvar < 7: row = 2 col = 3 h = 8.0 w = 14.0 - elif (Nvar < 10) : + elif Nvar < 10: row = 3 col = 3 h = 12.0 w = 14.0 else: - raise ValueError('too many variables...') - + raise ValueError("too many variables...") - fig = plt.figure(window_title, figsize=(w,h)) - title = fig.suptitle(r' ', fontsize=14 ) + fig = plt.figure(window_title, figsize=(w, h)) + title = fig.suptitle(r" ", fontsize=14) # Initiate all list variables required for plotting here ax = [] @@ -488,28 +550,27 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar r = [] theta = [] - # Initiate figure frame - for i in range(0,Nvar): + for i in range(0, Nvar): lines.append([]) - if (lineplot[i] == 1): - ax.append(fig.add_subplot(row,col,i+1)) - ax[i].set_xlim((0,xmax[i])) + if lineplot[i] == 1: + ax.append(fig.add_subplot(row, col, i + 1)) + ax[i].set_xlim((0, xmax[i])) ax[i].set_ylim((fmin[i], fmax[i])) - for j in range(0,Nlines[i]): - lines[i].append(ax[i].plot([],[],lw=2, label = legendlabels[i][j])[0]) - #Need the [0] to 'unpack' the line object from tuple. Alternatively: - #lines[i], = lines[i] - ax[i].set_xlabel(r'x') + for j in range(0, Nlines[i]): + lines[i].append(ax[i].plot([], [], lw=2, label=legendlabels[i][j])[0]) + # Need the [0] to 'unpack' the line object from tuple. Alternatively: + # lines[i], = lines[i] + ax[i].set_xlabel(r"x") ax[i].set_ylabel(titles[i]) - if (Nlines[i] != 1): + if Nlines[i] != 1: legendneeded = 1 - for k in range(0,i): - if (Nlines[i] == Nlines[k]): + for k in range(0, i): + if Nlines[i] == Nlines[k]: legendneeded = 0 - if (legendneeded == 1): + if legendneeded == 1: plt.axes(ax[i]) - plt.legend(loc = 0) + plt.legend(loc=0) # Pad out unused list variables with zeros plots.append(0) cbars.append(0) @@ -518,26 +579,36 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar r.append(0) theta.append(0) - elif (contour[i] == 1): - ax.append(fig.add_subplot(row,col,i+1)) - #ax[i].set_xlim((0,Nx[i][0]-1)) - #ax[i].set_ylim((0,Ny[i][0]-1)) - ax[i].set_xlim(min(x[i]),max(x[i])) - ax[i].set_ylim(min(y[i]),max(y[i])) - ax[i].set_xlabel(r'x') - ax[i].set_ylabel(r'y') + elif contour[i] == 1: + ax.append(fig.add_subplot(row, col, i + 1)) + # ax[i].set_xlim((0,Nx[i][0]-1)) + # ax[i].set_ylim((0,Ny[i][0]-1)) + ax[i].set_xlim(min(x[i]), max(x[i])) + ax[i].set_ylim(min(y[i]), max(y[i])) + ax[i].set_xlabel(r"x") + ax[i].set_ylabel(r"y") ax[i].set_title(titles[i]) if hold_aspect: - ax[i].set_aspect('equal') + ax[i].set_aspect("equal") thisx = x[i][0] if len(thisx.shape) == 3: thisx = thisx[0] thisy = y[i] if len(thisy.shape) == 3: thisy = thisy[0] - plots.append(ax[i].contourf(thisx.T,thisy.T,vars[i][0][0,:,:].T, Ncolors, cmap=cmap, lw=0, levels=clevels[i] )) + plots.append( + ax[i].contourf( + thisx.T, + thisy.T, + vars[i][0][0, :, :].T, + Ncolors, + cmap=cmap, + lw=0, + levels=clevels[i], + ) + ) plt.axes(ax[i]) - cbars.append(fig.colorbar(plots[i], format='%1.1e')) + cbars.append(fig.colorbar(plots[i], format="%1.1e")) # Pad out unused list variables with zeros lines[i].append(0) xstride.append(0) @@ -545,29 +616,37 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar r.append(0) theta.append(0) - elif (surf[i] == 1): - if (len(x[i][0].shape)==1 and len(y[i].shape)==1): + elif surf[i] == 1: + if len(x[i][0].shape) == 1 and len(y[i].shape) == 1: # plot_wireframe() requires 2d arrays for x and y coordinates - x[i][0],y[i] = meshgrid(x[i][0],y[i]) + x[i][0], y[i] = meshgrid(x[i][0], y[i]) thisx = x[i][0] if len(thisx.shape) == 3: thisx = thisx[0] thisy = y[i] if len(thisy.shape) == 3: thisy = thisy[0] - if (Nx[i][0]<= 20): + if Nx[i][0] <= 20: xstride.append(1) else: - xstride.append(int(floor(Nx[i][0]/20))) - if (Ny[i][0]<=20): + xstride.append(int(floor(Nx[i][0] / 20))) + if Ny[i][0] <= 20: ystride.append(1) else: - ystride.append(int(floor(Ny[i][0]/20))) - ax.append(fig.add_subplot(row,col,i+1, projection='3d')) - plots.append(ax[i].plot_wireframe(thisx, thisy, vars[i][0][0,:,:].T, rstride=ystride[i], cstride=xstride[i])) - title = fig.suptitle(r'', fontsize=14 ) - ax[i].set_xlabel(r'x') - ax[i].set_ylabel(r'y') + ystride.append(int(floor(Ny[i][0] / 20))) + ax.append(fig.add_subplot(row, col, i + 1, projection="3d")) + plots.append( + ax[i].plot_wireframe( + thisx, + thisy, + vars[i][0][0, :, :].T, + rstride=ystride[i], + cstride=xstride[i], + ) + ) + title = fig.suptitle(r"", fontsize=14) + ax[i].set_xlabel(r"x") + ax[i].set_ylabel(r"y") ax[i].set_zlabel(titles[i]) # Pad out unused list variables with zeros lines[i].append(0) @@ -575,128 +654,162 @@ def showdata(vars, titles=[], legendlabels=[], surf=[], polar=[], tslice=0, t_ar r.append(0) theta.append(0) - elif (polar[i] == 1): - r.append(linspace(1,Nx[i][0], Nx[i][0])) - theta.append(linspace(0,2*pi, Ny[i][0])) - r[i],theta[i] = meshgrid(r[i], theta[i]) - ax.append(fig.add_subplot(row,col,i+1, projection='polar')) - plots.append(ax[i].contourf(theta[i], r[i], vars[i][0][0,:,:].T, cmap=cmap, levels=clevels[i])) + elif polar[i] == 1: + r.append(linspace(1, Nx[i][0], Nx[i][0])) + theta.append(linspace(0, 2 * pi, Ny[i][0])) + r[i], theta[i] = meshgrid(r[i], theta[i]) + ax.append(fig.add_subplot(row, col, i + 1, projection="polar")) + plots.append( + ax[i].contourf( + theta[i], r[i], vars[i][0][0, :, :].T, cmap=cmap, levels=clevels[i] + ) + ) plt.axes(ax[i]) - cbars.append(fig.colorbar(plots[i], format='%1.1e')) - ax[i].set_rmax(Nx[i][0]-1) + cbars.append(fig.colorbar(plots[i], format="%1.1e")) + ax[i].set_rmax(Nx[i][0] - 1) ax[i].set_title(titles[i]) # Pad out unused list variables with zeros lines[i].append(0) xstride.append(0) ystride.append(0) - - def onClick(event): global pause pause ^= True - def control(): global j, pause - if j == Nframes-1 : j = -1 + if j == Nframes - 1: + j = -1 if not pause: - j=j+1 + j = j + 1 return j - # Animation function def animate(i): - j=control() - - index = j*intv - - for j in range(0,Nvar): - #Default to clearing axis between frames on all plots except line plots - if (clear_between_frames is None and lineplot[j] != 1 ) or clear_between_frames is True: - ax[j].cla() #Clear axis between frames so that masked arrays can be plotted - if (lineplot[j] == 1): - for k in range(0,Nlines[j]): - lines[j][k].set_data(x[j][k], vars[j][k][index,:]) - elif (contour[j] == 1): + j = control() + + index = j * intv + + for j in range(0, Nvar): + # Default to clearing axis between frames on all plots except line plots + if ( + clear_between_frames is None and lineplot[j] != 1 + ) or clear_between_frames is True: + ax[ + j + ].cla() # Clear axis between frames so that masked arrays can be plotted + if lineplot[j] == 1: + for k in range(0, Nlines[j]): + lines[j][k].set_data(x[j][k], vars[j][k][index, :]) + elif contour[j] == 1: thisx = x[j][0] if len(thisx.shape) == 3: thisx = thisx[index] thisy = y[j] if len(thisy.shape) == 3: thisy = thisy[index] - plots[j] = ax[j].contourf(x[j][0].T,y[j].T,vars[j][0][index,:,:].T, Ncolors, cmap=cmap, lw=0, levels=clevels[j]) - ax[j].set_xlabel(r'x') - ax[j].set_ylabel(r'y') + plots[j] = ax[j].contourf( + x[j][0].T, + y[j].T, + vars[j][0][index, :, :].T, + Ncolors, + cmap=cmap, + lw=0, + levels=clevels[j], + ) + ax[j].set_xlabel(r"x") + ax[j].set_ylabel(r"y") ax[j].set_title(titles[j]) - elif (surf[j] == 1): + elif surf[j] == 1: thisx = x[j][0] if len(thisx.shape) == 3: thisx = thisx[index] thisy = y[j][0] if len(thisy.shape) == 3: thisy = thisy[index] - ax[j] = fig.add_subplot(row,col,j+1, projection='3d') - plots[j] = ax[j].plot_wireframe(thisx, thisy, vars[j][0][index,:,:].T, rstride=ystride[j], cstride=xstride[j]) - ax[j].set_zlim(fmin[j],fmax[j]) - ax[j].set_xlabel(r'x') - ax[j].set_ylabel(r'y') + ax[j] = fig.add_subplot(row, col, j + 1, projection="3d") + plots[j] = ax[j].plot_wireframe( + thisx, + thisy, + vars[j][0][index, :, :].T, + rstride=ystride[j], + cstride=xstride[j], + ) + ax[j].set_zlim(fmin[j], fmax[j]) + ax[j].set_xlabel(r"x") + ax[j].set_ylabel(r"y") ax[j].set_title(titles[j]) - elif (polar[j] == 1): - plots[j] = ax[j].contourf(theta[j], r[j], vars[j][0][index,:,:].T,cmap=cmap, levels=clevels[j]) - ax[j].set_rmax(Nx[j][0]-1) + elif polar[j] == 1: + plots[j] = ax[j].contourf( + theta[j], + r[j], + vars[j][0][index, :, :].T, + cmap=cmap, + levels=clevels[j], + ) + ax[j].set_rmax(Nx[j][0] - 1) ax[j].set_title(titles[j]) if t_array is not None: - title.set_text('t = %1.2e' % t_array[index]) + title.set_text("t = %1.2e" % t_array[index]) else: - title.set_text('t = %i' % index) + title.set_text("t = %i" % index) return plots def init(): global j, pause - j=-2 + j = -2 pause = False return animate(0) - - - - - # Call Animation function - fig.canvas.mpl_connect('button_press_event', onClick) + fig.canvas.mpl_connect("button_press_event", onClick) anim = animation.FuncAnimation(fig, animate, init_func=init, frames=Nframes) - #If movie is not passed as a string assign the default filename - if (movie==1): - movie='animation.mp4' + # If movie is not passed as a string assign the default filename + if movie == 1: + movie = "animation.mp4" # Save movie with given or default name - if ((isinstance(movie,str)==1)): - movietype = movie.split('.')[-1] - if movietype == 'mp4': + if isinstance(movie, str) == 1: + movietype = movie.split(".")[-1] + if movietype == "mp4": try: - anim.save(movie,writer = FFwriter, fps=fps, dpi=dpi, extra_args=['-vcodec', 'libx264']) + anim.save( + movie, + writer=FFwriter, + fps=fps, + dpi=dpi, + extra_args=["-vcodec", "libx264"], + ) except Exception: - #Try specifying writer by string if ffmpeg not found + # Try specifying writer by string if ffmpeg not found try: - anim.save(movie,writer = 'ffmpeg', fps=fps, dpi=dpi, extra_args=['-vcodec', 'libx264']) + anim.save( + movie, + writer="ffmpeg", + fps=fps, + dpi=dpi, + extra_args=["-vcodec", "libx264"], + ) except Exception: - print('Save failed: Check ffmpeg path') - raise - elif movietype == 'gif': - anim.save(movie,writer = 'imagemagick', fps=fps, dpi=dpi) + print("Save failed: Check ffmpeg path") + raise + elif movietype == "gif": + anim.save(movie, writer="imagemagick", fps=fps, dpi=dpi) else: - raise ValueError("Unrecognized file type for movie. Supported types are .mp4 and .gif") + raise ValueError( + "Unrecognized file type for movie. Supported types are .mp4 and .gif" + ) # Show animation if not saved or returned, otherwise close the plot - if (movie==0 and return_animation == 0): + if movie == 0 and return_animation == 0: plt.show() else: plt.close() # Return animation object - if(return_animation == 1): - return(anim) + if return_animation == 1: + return anim diff --git a/boututils/spectrogram.py b/boututils/spectrogram.py index d1c2a36..c6d3a49 100644 --- a/boututils/spectrogram.py +++ b/boututils/spectrogram.py @@ -5,11 +5,11 @@ updated: 23/06/2016 """ -from __future__ import print_function -from __future__ import division +from __future__ import division, print_function + from builtins import range -from numpy import arange, zeros, exp, power, transpose, sin, cos, linspace, min, max +from numpy import arange, cos, exp, linspace, max, min, power, sin, transpose, zeros from scipy import fftpack, pi @@ -58,49 +58,53 @@ def spectrogram(data, dx, sigma, clip=1.0, optimise_clipping=True, nskip=1.0): """ n = data.size - nnew = int(n/nskip) - xx = arange(n)*dx - xxnew = arange(nnew)*dx*nskip + nnew = int(n / nskip) + xx = arange(n) * dx + xxnew = arange(nnew) * dx * nskip sigma = sigma * dx - n_clipped = int(n/clip) + n_clipped = int(n / clip) # check to see if n_clipped is near a 2^n factor for speed - if(optimise_clipping): + if optimise_clipping: nn = n_clipped two_count = 1 - while(1): - nn = nn/2.0 - if(nn <= 2.0): - n_clipped = 2**two_count - print('clipping window length from ',n,' to ',n_clipped,' points') + while 1: + nn = nn / 2.0 + if nn <= 2.0: + n_clipped = 2 ** two_count + print("clipping window length from ", n, " to ", n_clipped, " points") break else: two_count += 1 else: - print('using full window length: ',n_clipped,' points') + print("using full window length: ", n_clipped, " points") - halfclip = int(n_clipped/2) - spectra = zeros((nnew,halfclip)) + halfclip = int(n_clipped / 2) + spectra = zeros((nnew, halfclip)) omega = fftpack.fftfreq(n_clipped, dx) omega = omega[0:halfclip] for i in range(nnew): - beg = i*nskip-halfclip - end = i*nskip+halfclip-1 + beg = i * nskip - halfclip + end = i * nskip + halfclip - 1 if beg < 0: - end = end-beg + end = end - beg beg = 0 elif end >= n: - end = n-1 + end = n - 1 beg = end - n_clipped + 1 - gaussian = 1.0 / (sigma * 2.0 * pi) * exp(-0.5 * power(( xx[beg:end] - xx[i*nskip] ),2.0) / (2.0 * sigma) ) + gaussian = ( + 1.0 + / (sigma * 2.0 * pi) + * exp(-0.5 * power((xx[beg:end] - xx[i * nskip]), 2.0) / (2.0 * sigma)) + ) fftt = abs(fftpack.fft(data[beg:end] * gaussian)) fftt = fftt[:halfclip] - spectra[i,:] = fftt + spectra[i, :] = fftt return (transpose(spectra), omega, xxnew) @@ -123,41 +127,46 @@ def test_spectrogram(n, d, s): import matplotlib.pyplot as plt nskip = 10 - xx = arange(n)/d - test_data = sin(2.0*pi*512.0*xx * ( 1.0 + 0.005*cos(xx*50.0))) + 0.5*exp(xx)*cos(2.0*pi*100.0*power(xx,2)) + xx = arange(n) / d + test_data = sin(2.0 * pi * 512.0 * xx * (1.0 + 0.005 * cos(xx * 50.0))) + 0.5 * exp( + xx + ) * cos(2.0 * pi * 100.0 * power(xx, 2)) test_sigma = s - dx = 1.0/d + dx = 1.0 / d - s1 = test_sigma*0.1 + s1 = test_sigma * 0.1 s2 = test_sigma - s3 = test_sigma*10.0 + s3 = test_sigma * 10.0 - (spec2,omega2,xx) = spectrogram(test_data, dx, s2, clip=5.0, nskip=nskip) - (spec3,omega3,xx) = spectrogram(test_data, dx, s3, clip=5.0, nskip=nskip) - (spec1,omega1,xx) = spectrogram(test_data, dx, s1, clip=5.0, nskip=nskip) + (spec2, omega2, xx) = spectrogram(test_data, dx, s2, clip=5.0, nskip=nskip) + (spec3, omega3, xx) = spectrogram(test_data, dx, s3, clip=5.0, nskip=nskip) + (spec1, omega1, xx) = spectrogram(test_data, dx, s1, clip=5.0, nskip=nskip) - levels = linspace(min(spec1),max(spec1),100) + levels = linspace(min(spec1), max(spec1), 100) plt.subplot(311) - plt.contourf(xx,omega1,spec1,levels=levels) + plt.contourf(xx, omega1, spec1, levels=levels) plt.ylabel("frequency") plt.xlabel(r"$t$") - plt.title(r"Spectrogram of $sin(t + cos(t) )$ with $\sigma=$%3.1f"%s1) + plt.title(r"Spectrogram of $sin(t + cos(t) )$ with $\sigma=$%3.1f" % s1) - levels = linspace(min(spec2),max(spec2),100) + levels = linspace(min(spec2), max(spec2), 100) plt.subplot(312) - plt.contourf(xx,omega2,spec2,levels=levels) + plt.contourf(xx, omega2, spec2, levels=levels) plt.ylabel("frequency") plt.xlabel(r"$t$") - plt.title(r"Spectrogram of $sin(t + cos(t) )$ with $\sigma=$%3.1f"%s2) + plt.title(r"Spectrogram of $sin(t + cos(t) )$ with $\sigma=$%3.1f" % s2) - levels = linspace(min(spec3),max(spec3),100) + levels = linspace(min(spec3), max(spec3), 100) plt.subplot(313) - plt.contourf(xx,omega3,spec3,levels=levels) + plt.contourf(xx, omega3, spec3, levels=levels) plt.ylabel("frequency") plt.xlabel(r"$t$") - plt.title(r"Spectrogram of $sin(t + cos(t) )$ with $\sigma=$%3.1f"%s3) + plt.title(r"Spectrogram of $sin(t + cos(t) )$ with $\sigma=$%3.1f" % s3) plt.tight_layout() plt.show() + if __name__ == "__main__": - test_spectrogram(2048, 2048.0, 0.01) # array size, divisions per unit, sigma of gaussian + test_spectrogram( + 2048, 2048.0, 0.01 + ) # array size, divisions per unit, sigma of gaussian diff --git a/boututils/surface_average.py b/boututils/surface_average.py index 340d252..21ad22f 100644 --- a/boututils/surface_average.py +++ b/boututils/surface_average.py @@ -2,14 +2,16 @@ """ -from __future__ import print_function -from __future__ import absolute_import -from __future__ import division +from __future__ import absolute_import, division, print_function + from builtins import range -from past.utils import old_div + import numpy as np +from past.utils import old_div + from boututils.calculus import deriv from boututils.int_func import int_func + from .idl_tabulate import idl_tabulate @@ -34,60 +36,60 @@ def surface_average(var, grid, area=None): s = np.ndim(var) - if s == 4 : + if s == 4: nx = np.shape(var)[1] ny = np.shape(var)[2] nt = np.shape(var)[0] - result = np.zeros((nx,nt)) - for t in range (nt): - result[:,t] = surface_average(var[t,:,:,:], grid, area=area) + result = np.zeros((nx, nt)) + for t in range(nt): + result[:, t] = surface_average(var[t, :, :, :], grid, area=area) return result - elif s != 3 : + elif s != 3: raise RuntimeError("ERROR: surface_average var must be 3D or 4D") # 3D [x,y,z] nx = np.shape(var)[0] ny = np.shape(var)[1] - # Calculate poloidal angle from grid - theta = np.zeros((nx,ny)) + theta = np.zeros((nx, ny)) - #status = gen_surface(mesh=grid) ; Start generator + # status = gen_surface(mesh=grid) ; Start generator xi = -1 - yi = np.arange(0,ny,dtype=int) + yi = np.arange(0, ny, dtype=int) last = 0 while True: - #yi = gen_surface(last=last, xi=xi, period=periodic) + # yi = gen_surface(last=last, xi=xi, period=periodic) xi = xi + 1 - if xi == nx-1 : + if xi == nx - 1: last = 1 - dtheta = 2.*np.pi / np.float(ny) - r = grid['Rxy'][xi,yi] - z = grid['Zxy'][xi,yi] + dtheta = 2.0 * np.pi / np.float(ny) + r = grid["Rxy"][xi, yi] + z = grid["Zxy"][xi, yi] n = np.size(r) - dl = old_div(np.sqrt( deriv(r)**2 + deriv(z)**2 ), dtheta) + dl = old_div(np.sqrt(deriv(r) ** 2 + deriv(z) ** 2), dtheta) if area: - dA = (old_div(grid['Bxy'][xi,yi],grid['Bpxy'][xi,yi]))*r*dl - A = int_func(np.arange(n),dA) - theta[xi,yi] = 2.*np.pi*A/A[n-1] + dA = (old_div(grid["Bxy"][xi, yi], grid["Bpxy"][xi, yi])) * r * dl + A = int_func(np.arange(n), dA) + theta[xi, yi] = 2.0 * np.pi * A / A[n - 1] else: - nu = dl * (grid['Btxy'][xi,yi]) / ((grid['Bpxy'][xi,yi]) * r ) - theta[xi,yi] = int_func(np.arange(n)*dtheta,nu) - theta[xi,yi] = 2.*np.pi*theta[xi,yi] / theta[xi,yi[n-1]] + nu = dl * (grid["Btxy"][xi, yi]) / ((grid["Bpxy"][xi, yi]) * r) + theta[xi, yi] = int_func(np.arange(n) * dtheta, nu) + theta[xi, yi] = 2.0 * np.pi * theta[xi, yi] / theta[xi, yi[n - 1]] - if last==1 : break + if last == 1: + break vy = np.zeros(ny) result = np.zeros(nx) - for x in range(nx) : - for y in range(ny) : - vy[y] = np.mean(var[x,y,:]) + for x in range(nx): + for y in range(ny): + vy[y] = np.mean(var[x, y, :]) - result[x] = old_div(idl_tabulate(theta[x,:], vy), (2.*np.pi)) + result[x] = old_div(idl_tabulate(theta[x, :], vy), (2.0 * np.pi)) return result diff --git a/boututils/tests/test_import.py b/boututils/tests/test_import.py index ced6a37..1872123 100644 --- a/boututils/tests/test_import.py +++ b/boututils/tests/test_import.py @@ -1,4 +1,5 @@ import unittest + from boututils.datafile import DataFile @@ -8,5 +9,5 @@ def test_import(self): self.assertIsInstance(d, DataFile) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/boututils/volume_integral.py b/boututils/volume_integral.py index 7e5d4da..170971d 100644 --- a/boututils/volume_integral.py +++ b/boututils/volume_integral.py @@ -2,13 +2,16 @@ """ -from __future__ import print_function -from __future__ import division +from __future__ import division, print_function + from builtins import range -from past.utils import old_div + import numpy as np +from past.utils import old_div + from boututils.calculus import deriv + def volume_integral(var, grid, xr=False): """Integrate a variable over a volume @@ -30,73 +33,74 @@ def volume_integral(var, grid, xr=False): s = np.ndim(var) - if s == 4 : + if s == 4: # 4D [t,x,y,z] - integrate for each t nx = np.shape(var)[1] ny = np.shape(var)[2] nt = np.shape(var)[0] result = np.zeros(nt) - for t in range(nt) : - result[t] = volume_integral(var[t,:,:,:],g,xr=xr) + for t in range(nt): + result[t] = volume_integral(var[t, :, :, :], g, xr=xr) return result - elif s == 3 : + elif s == 3: # 3D [x,y,z] - average in Z nx = np.shape(var)[0] ny = np.shape(var)[1] - # nz = np.shape(var)[2] + # nz = np.shape(var)[2] zi = np.zeros((nx, ny)) for x in range(nx): for y in range(ny): - zi[x,y] = np.mean(var[x,y,:]) + zi[x, y] = np.mean(var[x, y, :]) return volume_integral(zi, g, xr=xr) - - elif s != 2 : + elif s != 2: print("ERROR: volume_integral var must be 2, 3 or 4D") - # 2D [x,y] nx = np.shape(var)[0] ny = np.shape(var)[1] - if xr == False : xr=[0,nx-1] + if xr == False: + xr = [0, nx - 1] result = 0.0 - #status = gen_surface(mesh=grid) ; Start generator + # status = gen_surface(mesh=grid) ; Start generator xi = -1 - yi = np.arange(0,ny,dtype=int) + yi = np.arange(0, ny, dtype=int) last = 0 - # iy = np.zeros(nx) + # iy = np.zeros(nx) while True: - #yi = gen_surface(last=last, xi=xi, period=periodic) + # yi = gen_surface(last=last, xi=xi, period=periodic) xi = xi + 1 - if xi == nx-1 : last = 1 + if xi == nx - 1: + last = 1 - if (xi >= np.min(xr)) & (xi <= np.max(xr)) : - dtheta = 2.*np.pi / np.float(ny) - r = grid['Rxy'][xi,yi] - z = grid['Zxy'][xi,yi] + if (xi >= np.min(xr)) & (xi <= np.max(xr)): + dtheta = 2.0 * np.pi / np.float(ny) + r = grid["Rxy"][xi, yi] + z = grid["Zxy"][xi, yi] n = np.size(r) - dl = old_div(np.sqrt( deriv(r)**2 + deriv(z)**2 ), dtheta) + dl = old_div(np.sqrt(deriv(r) ** 2 + deriv(z) ** 2), dtheta) - # Area of flux-surface - dA = (grid['Bxy'][xi,yi]/grid['Bpxy'][xi,yi]*dl) * (r*2.*np.pi) - # Volume - if xi == nx-1 : - dpsi = (grid['psixy'][xi,yi] - grid['psixy'][xi-1,yi]) + # Area of flux-surface + dA = (grid["Bxy"][xi, yi] / grid["Bpxy"][xi, yi] * dl) * (r * 2.0 * np.pi) + # Volume + if xi == nx - 1: + dpsi = grid["psixy"][xi, yi] - grid["psixy"][xi - 1, yi] else: - dpsi = (grid['psixy'][xi+1,yi] - grid['psixy'][xi,yi]) + dpsi = grid["psixy"][xi + 1, yi] - grid["psixy"][xi, yi] - dV = dA * dpsi / (r*(grid['Bpxy'][xi,yi])) # May need factor of 2pi + dV = dA * dpsi / (r * (grid["Bpxy"][xi, yi])) # May need factor of 2pi dV = np.abs(dV) - result = result + np.sum(var[xi,yi] * dV) + result = result + np.sum(var[xi, yi] * dV) - if last==1 : break + if last == 1: + break return result diff --git a/boututils/watch.py b/boututils/watch.py index e7d038c..611661e 100644 --- a/boututils/watch.py +++ b/boututils/watch.py @@ -3,10 +3,10 @@ """ from __future__ import print_function -from builtins import zip -import time import os +import time +from builtins import zip def watch(files, timeout=None, poll=2): @@ -45,9 +45,9 @@ def watch(files, timeout=None, poll=2): # Get modification time of file(s) try: - if hasattr(files, '__iter__'): + if hasattr(files, "__iter__"): # Iterable - lastmod = [ os.stat(f).st_mtime for f in files ] + lastmod = [os.stat(f).st_mtime for f in files] iterable = True else: # Not iterable -> just one file @@ -66,8 +66,8 @@ def watch(files, timeout=None, poll=2): if time.time() - start_time + sleepfor > timeout: # Adjust time so that finish at timeout sleepfor = timeout - (time.time() - start_time) - running = False # Stop after next test - + running = False # Stop after next test + time.sleep(sleepfor) if iterable: diff --git a/setup.cfg b/setup.cfg index d85d118..6a09f5a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,4 +8,4 @@ line_length = 88 [flake8] max-line-length = 88 -extend-ignore = E203, W503 \ No newline at end of file +extend-ignore = E203, W503 diff --git a/setup.py b/setup.py index 78ef91b..f9313f7 100644 --- a/setup.py +++ b/setup.py @@ -2,54 +2,62 @@ # -*- coding: utf-8 -*- import re -import setuptools from pathlib import Path -name = 'boututils' +import setuptools + +name = "boututils" root_path = Path(__file__).parent -init_path = root_path.joinpath(name, '__init__.py') -readme_path = root_path.joinpath('README.md') +init_path = root_path.joinpath(name, "__init__.py") +readme_path = root_path.joinpath("README.md") -with readme_path.open('r') as f: +with readme_path.open("r") as f: long_description = f.read() setuptools.setup( name=name, - author='Ben Dudson et al.', - description='Python package containing BOUT++ utils', + author="Ben Dudson et al.", + description="Python package containing BOUT++ utils", long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/boutproject/boututils', + long_description_content_type="text/markdown", + url="https://github.com/boutproject/boututils", project_urls={ "Bug Tracker": "https://github.com/boutproject/boututils/issues/", "Documentation": "https://bout-dev.readthedocs.io/en/latest/", "Source Code": "https://github.com/boutproject/boututils/", }, packages=setuptools.find_packages(), - keywords=['bout++', - 'bout', - 'plasma', - 'physics', - 'data-extraction', - 'data-analysis', - 'data-visualization'], + keywords=[ + "bout++", + "bout", + "plasma", + "physics", + "data-extraction", + "data-analysis", + "data-visualization", + ], use_scm_version=True, - setup_requires=['setuptools>=42', - 'setuptools_scm[toml]>=3.4', - 'setuptools_scm_git_archive'], - install_requires=['numpy', - 'matplotlib', - 'scipy', - 'h5py', - 'future', - 'netCDF4', - "importlib-metadata ; python_version<'3.8'"], - extras_require={ - 'mayavi': ['mayavi', 'PyQt5']}, + setup_requires=[ + "setuptools>=42", + "setuptools_scm[toml]>=3.4", + "setuptools_scm_git_archive", + ], + install_requires=[ + "numpy", + "matplotlib", + "scipy", + "h5py", + "future", + "netCDF4", + "importlib-metadata ; python_version<'3.8'", + ], + extras_require={"mayavi": ["mayavi", "PyQt5"]}, classifiers=[ - 'Programming Language :: Python :: 3', - ('License :: OSI Approved :: ' - 'GNU Lesser General Public License v3 or later (LGPLv3+)'), - 'Operating System :: OS Independent', + "Programming Language :: Python :: 3", + ( + "License :: OSI Approved :: " + "GNU Lesser General Public License v3 or later (LGPLv3+)" + ), + "Operating System :: OS Independent", ], ) From 2b8818c05ef4bc7ed9fc4ab4520ee39038e80a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Sat, 9 Jan 2021 23:07:18 +0100 Subject: [PATCH 48/98] Fixed flake8 complaints --- boututils/View3D.py | 69 ++++++++++++---------- boututils/__init__.py | 5 +- boututils/analyse_equil_2.py | 5 +- boututils/anim.py | 13 ++-- boututils/boutgrid.py | 6 +- boututils/calculus.py | 6 +- boututils/check_scaling.py | 10 ++-- boututils/contour.py | 7 ++- boututils/crosslines.py | 29 +++++---- boututils/datafile.py | 8 +-- boututils/efit_analyzer.py | 111 ++++++++++++++++++----------------- boututils/fft_integrate.py | 2 +- boututils/geqdsk.py | 6 +- boututils/mode_structure.py | 7 ++- boututils/options.py | 4 +- boututils/plotdata.py | 4 +- boututils/plotpolslice.py | 9 ++- boututils/run_wrapper.py | 2 +- boututils/showdata.py | 57 +++++++++++------- boututils/volume_integral.py | 8 +-- setup.cfg | 2 +- setup.py | 1 - 22 files changed, 208 insertions(+), 163 deletions(-) diff --git a/boututils/View3D.py b/boututils/View3D.py index 9024938..cbd3c76 100644 --- a/boututils/View3D.py +++ b/boututils/View3D.py @@ -1,11 +1,13 @@ """ -View a 3D rendering of the magnetic field lines and the streamlines of the rational surfaces. -The quality of the later can be used as an indicator of the quality of the grid. The magnetic field -is computed from efit_analyzed.py. The script can be used as a template to show additional properties of the field +View a 3D rendering of the magnetic field lines and the streamlines of the rational +surfaces. +The quality of the later can be used as an indicator of the quality of the grid. +The magnetic field +is computed from efit_analyzed.py. +The script can be used as a template to show additional properties of the field based on enthought's example by Gael Varoquaux https://docs.enthought.com/mayavi/mayavi/auto/example_magnetic_field.html#example-magnetic-field - """ from __future__ import absolute_import, division @@ -29,7 +31,7 @@ from boututils.View2D import View2D -from .boutgrid import * +from .boutgrid import create_grid from .read_geqdsk import read_geqdsk @@ -47,25 +49,28 @@ def View3D(g, path=None, gb=None): ############################################################################## # The grid of points on which we want to evaluate the field X, Y, Z = np.mgrid[-rd : rd : n * 1j, -rd : rd : n * 1j, -zd : zd : n * 1j] - ## Avoid rounding issues : + # # Avoid rounding issues : # f = 1e4 # this gives the precision we are interested by : # X = np.round(X * f) / f # Y = np.round(Y * f) / f # Z = np.round(Z * f) / f - r = np.c_[X.ravel(), Y.ravel(), Z.ravel()] + # r = np.c_[X.ravel(), Y.ravel(), Z.ravel()] ############################################################################## # Calculate field # First initialize a container matrix for the field vector : - B = np.empty_like(r) + # B = np.empty_like(r) # Compute Toroidal field # fpol is given between simagx (psi on the axis) and sibdry ( - # psi on limiter or separatrix). So the toroidal field (fpol/R) and the q profile are within these boundaries - # For each r,z we have psi thus we get fpol if (r,z) is within the boundary (limiter or separatrix) and fpol=fpol(outer_boundary) for outside + # psi on limiter or separatrix). So the toroidal field (fpol/R) and the q profile + # are within these boundaries + # For each r,z we have psi thus we get fpol if (r,z) is within the boundary + # (limiter or separatrix) and fpol=fpol(outer_boundary) for outside - # The range of psi is g.psi.max(), g.psi.min() but we have f(psi) up to the limit. Thus we use a new extended variable padded up to max psi + # The range of psi is g.psi.max(), g.psi.min() but we have f(psi) up to the limit. + # Thus we use a new extended variable padded up to max psi # set points between psi_limit and psi_max add_psi = np.linspace(g.sibdry, g.psi.max(), 10) @@ -103,8 +108,8 @@ def View3D(g, path=None, gb=None): g, X, Y, Z, rmin, rmax, zmin, zmax, Br, Bz, Btrz ) - bpnorm = np.sqrt(B1p ** 2 + B2p ** 2 + B3p ** 2) - btnorm = np.sqrt(B1t ** 2 + B2t ** 2 + B3t ** 2) + # bpnorm = np.sqrt(B1p ** 2 + B2p ** 2 + B3p ** 2) + # btnorm = np.sqrt(B1t ** 2 + B2t ** 2 + B3t ** 2) BBx = B1p + B1t BBy = B2p + B2t @@ -129,13 +134,13 @@ def View3D(g, path=None, gb=None): mlab.clf() - fieldp = mlab.pipeline.vector_field( - X, Y, Z, B1p, B2p, B3p, scalars=bpnorm, name="Bp field" - ) - - fieldt = mlab.pipeline.vector_field( - X, Y, Z, B1t, B2t, B3t, scalars=btnorm, name="Bt field" - ) + # fieldp = mlab.pipeline.vector_field( + # X, Y, Z, B1p, B2p, B3p, scalars=bpnorm, name="Bp field" + # ) + # + # fieldt = mlab.pipeline.vector_field( + # X, Y, Z, B1t, B2t, B3t, scalars=btnorm, name="Bt field" + # ) field = mlab.pipeline.vector_field( X, Y, Z, BBx, BBy, BBz, scalars=btotal, name="B field" @@ -212,14 +217,14 @@ def View3D(g, path=None, gb=None): s = mlab.pipeline.streamline(field) s.streamline_type = "line" - ##s.seed.widget = s.seed.widget_list[0] - ##s.seed.widget.center = 0.0, 0.0, 0.0 - ##s.seed.widget.radius = 1.725 - ##s.seed.widget.phi_resolution = 16 - ##s.seed.widget.handle_direction =[ 1., 0., 0.] - ##s.seed.widget.enabled = False - ##s.seed.widget.enabled = True - ##s.seed.widget.enabled = False + # s.seed.widget = s.seed.widget_list[0] + # s.seed.widget.center = 0.0, 0.0, 0.0 + # s.seed.widget.radius = 1.725 + # s.seed.widget.phi_resolution = 16 + # s.seed.widget.handle_direction =[ 1., 0., 0.] + # s.seed.widget.enabled = False + # s.seed.widget.enabled = True + # s.seed.widget.enabled = False # if x[i].size > 1: s.seed.widget = s.seed.widget_list[3] @@ -265,7 +270,7 @@ def View3D(g, path=None, gb=None): # data=data+data0[:,:,None] s = np.shape(data) - nz = s[2] + # nz = s[2] sgrid = create_grid(gb, data, 1) @@ -274,7 +279,7 @@ def View3D(g, path=None, gb=None): # gr=mlab.pipeline.grid_plane(sgrid) # gr.grid_plane.axis='x' - ## pressure scalar cut plane from bout + # pressure scalar cut plane from bout scpb = mlab.pipeline.scalar_cut_plane( sgrid, colormap="jet", plane_orientation="x_axes" ) @@ -335,7 +340,7 @@ def magnetic_field(g, X, Y, Z, rmin, rmax, zmin, zmax, Br, Bz, Btrz): nx, ny, nz = np.shape(X) mask = (rho >= rmin) & (rho <= rmax) & (Z >= zmin) & (Z <= zmax) - k = np.argwhere(mask == True) + k = np.argwhere(mask is True) fr = interpolate.interp2d(g.r[:, 0], g.z[0, :], Br.T) fz = interpolate.interp2d(g.r[:, 0], g.z[0, :], Bz.T) @@ -375,7 +380,7 @@ def psi_field(g, X, Y, Z, rmin, rmax, zmin, zmax): nx, ny, nz = np.shape(X) mask = (rho >= rmin) & (rho <= rmax) & (Z >= zmin) & (Z <= zmax) - k = np.argwhere(mask == True) + k = np.argwhere(mask is True) f = interpolate.interp2d(g.r[:, 0], g.z[0, :], g.psi.T) diff --git a/boututils/__init__.py b/boututils/__init__.py index 811338b..ad5ce74 100644 --- a/boututils/__init__.py +++ b/boututils/__init__.py @@ -3,7 +3,8 @@ import sys try: - from builtins import str + # NOTE: This import will not be needed when python2 support is stopped + from builtins import str # noqa: F401 except ImportError: raise ImportError("Please install the future module to use Python 2") @@ -53,7 +54,7 @@ path = Path(__file__).resolve() __version__ = get_version(root="..", relative_to=path) - except (ModuleNotFoundError, LookupError) as e: + except (ModuleNotFoundError, LookupError): # ModuleNotFoundError if setuptools_scm is not installed. # LookupError if git is not installed, or the code is not in a git repo even # though it has not been installed. diff --git a/boututils/analyse_equil_2.py b/boututils/analyse_equil_2.py index 53fc3b3..5eca465 100644 --- a/boututils/analyse_equil_2.py +++ b/boututils/analyse_equil_2.py @@ -8,6 +8,7 @@ from builtins import range, str, zip import numpy +from bunch import Bunch from crosslines import find_inter from matplotlib.pyplot import annotate, contour, draw, gradient, plot from past.utils import old_div @@ -61,7 +62,7 @@ def analyse_equil(F, R, Z): draw() - ### 1st method - line crossings --------------------------- + # ## 1st method - line crossings --------------------------- res = find_inter(contour1, contour2) # rex1=numpy.interp(res[0], R, numpy.arange(R.size)).astype(int) @@ -77,7 +78,7 @@ def analyse_equil(F, R, Z): rex1 = rex1[w].flatten() zex1 = zex1[w].flatten() - ### 2nd method - local maxima_minima ----------------------- + # ## 2nd method - local maxima_minima ----------------------- res1 = local_min_max.detect_local_minima(F) res2 = local_min_max.detect_local_maxima(F) res = numpy.append(res1, res2, 1) diff --git a/boututils/anim.py b/boututils/anim.py index 0f34e45..22b3bd3 100755 --- a/boututils/anim.py +++ b/boututils/anim.py @@ -13,11 +13,11 @@ try: from enthought.mayavi import mlab - from enthought.mayavi.mlab import * + from enthought.mayavi.mlab import colorbar, contour_surf, surf except ImportError: try: from mayavi import mlab - from mayavi.mlab import * + from mayavi.mlab import colorbar, contour_surf, surf except ImportError: print("No mlab available") @@ -55,7 +55,7 @@ def anim(s, d, *args, **kwargs): nt = d.shape[0] print("animating for ", nt, "timesteps") - if save == True: + if save: print("Saving pics in folder Movie") if not os.path.exists("Movie"): os.makedirs("Movie") @@ -66,7 +66,7 @@ def anim(s, d, *args, **kwargs): s1.mlab_source.scalars = d[i, :, :] title = "t=" + np.string0(i) mlab.title(title, height=1.1, size=0.26) - if save == True: + if save: mlab.savefig("Movie/anim%d.png" % i) yield @@ -83,7 +83,7 @@ def anim(s, d, *args, **kwargs): ne = data.shape[2] nz = data.shape[3] - f = mayavi.mlab.figure(size=(600, 600)) + f = mlab.figure(size=(600, 600)) # Tell visual to use this as the viewer. visual.set_viewer(f) @@ -99,7 +99,8 @@ def anim(s, d, *args, **kwargs): # second way # x, y= mgrid[0:ns:1, 0:ne:1] - # s = mesh(x,y,data[0,:,:,10], colormap='Spectral')#, warp_scale='auto')#, representation='wireframe') + # s = mesh(x,y,data[0,:,:,10], colormap='Spectral')#, warp_scale='auto') + # #, representation='wireframe') s.enable_contours = True s.contour.filled_contours = True # diff --git a/boututils/boutgrid.py b/boututils/boutgrid.py index 4767892..c20e4db 100755 --- a/boututils/boutgrid.py +++ b/boututils/boutgrid.py @@ -21,7 +21,7 @@ def aligned_points(grid, nz=1, period=1.0, maxshift=0.4): print("Missing required data") return None - dz = 2.0 * pi / (period * (nz - 1)) + # dz = 2.0 * pi / (period * (nz - 1)) phi0 = np.linspace(0, 2.0 * pi / period, nz) # Need to insert additional points in Y so mesh looks smooth @@ -94,10 +94,10 @@ def view3d(sgrid): e = Engine() e.start() - s = e.new_scene() + _ = e.new_scene() # Do this if you need to see the MayaVi tree view UI. ev = EngineView(engine=e) - ui = ev.edit_traits() + _ = ev.edit_traits() # mayavi.new_scene() src = VTKDataSource(data=sgrid) diff --git a/boututils/calculus.py b/boututils/calculus.py index 6b6521f..f297c0d 100644 --- a/boututils/calculus.py +++ b/boututils/calculus.py @@ -91,12 +91,14 @@ def deriv2D(data, axis=-1, dx=1.0, noise_suppression=True): result = deriv2D(data, dx) output is 2D (if only one axis specified) - output is 3D if no axis specified [nx,ny,2] with the third dimension being [dfdx, dfdy] + output is 3D if no axis specified [nx,ny,2] with the third dimension being + [dfdx, dfdy] keywords: axis = 0/1 If no axis specified 2D derivative will be returned dx = 1.0 axis spacing, must be 2D if 2D deriv is taken - default is [1.0,1.0] - noise_suppression = True noise suppressing coefficients used to take derivative - default = True + noise_suppression = True + noise suppressing coefficients used to take derivative - default = True """ from scipy.signal import convolve diff --git a/boututils/check_scaling.py b/boututils/check_scaling.py index 3a7922c..52cc41f 100644 --- a/boututils/check_scaling.py +++ b/boututils/check_scaling.py @@ -26,9 +26,8 @@ def get_order(grid_spacing, errors): """ if len(errors) != len(grid_spacing): raise ValueError( - "errors (len: {}) and grid_spacing (len: {}) should be the same length".format( - len(errors), len(grid_spacing) - ) + "errors (len: {}) and grid_spacing (len: {}) " + "should be the same length".format(len(errors), len(grid_spacing)) ) full_range = polyfit(log(grid_spacing), log(errors), 1) @@ -82,9 +81,8 @@ def error_rate_table(errors, grid_sizes, label): """ if len(errors) != len(grid_sizes): raise ValueError( - "errors (len: {}) and grid_sizes (len: {}) should be the same length".format( - len(errors), len(grid_sizes) - ) + "errors (len: {}) and grid_sizes (len: {}) " + "should be the same length".format(len(errors), len(grid_sizes)) ) dx = 1.0 / array(grid_sizes) diff --git a/boututils/contour.py b/boututils/contour.py index f37ea9c..2aae549 100644 --- a/boututils/contour.py +++ b/boututils/contour.py @@ -1,7 +1,8 @@ """ Contour calculation routines -https://web.archive.org/web/20140901225541/https://members.bellatlantic.net/~vze2vrva/thesis.html""" +https://web.archive.org/web/20140901225541/https://members.bellatlantic.net/~vze2vrva/thesis.html +""" from __future__ import division, print_function import numpy as np @@ -33,12 +34,12 @@ def contour(f, level): continue # Check each edge - ncross = 0 + ncross = 0 # noqa: F823 def location(a, b): if (a > level) ^ (a > level): # One of the corners is > level, and the other is <= level - ncross += 1 + ncross += 1 # noqa: F841, F823 # Find location return old_div((level - a), (b - a)) else: diff --git a/boututils/crosslines.py b/boututils/crosslines.py index 58af72e..9d2b37a 100644 --- a/boututils/crosslines.py +++ b/boututils/crosslines.py @@ -24,8 +24,8 @@ def unique(a, atol=1e-08): Notes ----- - Adapted to include tolerance from code at https://stackoverflow.com/questions/8560440/removing-duplicate-columns-and-rows-from-a-numpy-2d-array#answer-8564438 - + Adapted to include tolerance from code at + https://stackoverflow.com/questions/8560440/removing-duplicate-columns-and-rows-from-a-numpy-2d-array#answer-8564438 """ if np.issubdtype(a.dtype, float): @@ -89,8 +89,10 @@ def meshgrid_as_strided(x, y, mask=None): ), ) - # In the following the indices i, j represents the pairing of the ith segment of b and the jth segment of a - # e.g. if ignore[i,j]==True then the ith segment of b and the jth segment of a cannot intersect + # In the following the indices i, j represents the pairing of the ith segment of b + # and the jth segment of a + # e.g. if ignore[i,j]==True then the ith segment of b and the jth segment of a + # cannot intersect ignore = np.zeros([b.shape[0] - 1, a.shape[0] - 1], dtype=bool) x11, x21 = meshgrid_as_strided(a[:-1, 0], b[:-1, 0], mask=ignore) @@ -104,7 +106,8 @@ def meshgrid_as_strided(x, y, mask=None): ignore[np.ma.maximum(y11, y12) < np.ma.minimum(y21, y22)] = True ignore[np.ma.minimum(y11, y12) > np.ma.maximum(y21, y22)] = True - # find intersection of segments, ignoring impossible line segment pairs when new info becomes available + # find intersection of segments, ignoring impossible line segment pairs when new + # info becomes available denom_ = np.empty(ignore.shape, dtype=float) denom = np.ma.array(denom_, mask=ignore) denom_[:, :] = ((y22 - y21) * (x12 - x11)) - ((x22 - x21) * (y12 - y11)) @@ -159,12 +162,15 @@ def meshgrid_as_strided(x, y, mask=None): else: n_nans = np.ma.sum(nans) n_standard = np.ma.count(x11) - n_nans - # I initially tried using a set to get unique points but had problems with floating point equality + # I initially tried using a set to get unique points but had problems with + # floating point equality - # create n by 2 array to hold all possible intersection points, check later for uniqueness + # create n by 2 array to hold all possible intersection points, + # check later for uniqueness points = np.empty( [n_standard + 2 * n_nans, 2], dtype=float - ) # each colinear segment pair has two intersections, hence the extra n_colinear points + ) # each colinear segment pair has two intersections, + # hence the extra n_colinear points # add standard intersection points xi = x11 + ua * (x12 - x11) @@ -173,9 +179,10 @@ def meshgrid_as_strided(x, y, mask=None): points[:n_standard, 1] = np.ma.compressed(yi[~nans]) ignore[~nans] = True - # now add the appropriate intersection points for the colinear overlapping segments - # colinear overlapping segments intersect on the two internal points of the four points describing a straight line - # ua and ub have already serverd their purpose. Reuse them here to mean: + # now add the appropriate intersection points for the colinear overlapping + # segments colinear overlapping segments intersect on the two internal points + # of the four points describing a straight line ua and ub have already + # serverd their purpose. Reuse them here to mean: # ua is relative position of 1st b segment point along segment a # ub is relative position of 2nd b segment point along segment a use_x = x12 != x11 # avoid vertical lines diviiding by zero diff --git a/boututils/datafile.py b/boututils/datafile.py index b3234ea..3343bae 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -25,8 +25,6 @@ from __future__ import print_function -import getpass -import time from builtins import map, object, str, zip import numpy as np @@ -91,7 +89,7 @@ def __init__( NetCDF formats are described here: https://unidata.github.io/netcdf4-python/ - NETCDF3_CLASSIC Limited to 2.1Gb files - - NETCDF3_64BIT_OFFSET or NETCDF3_64BIT is an extension to allow larger file sizes + - NETCDF3_64BIT_OFFSET or NETCDF3_64BIT is an extension to allow larger files - NETCDF3_64BIT_DATA adds 64-bit integer data types and 64-bit dimension sizes - NETCDF4 and NETCDF4_CLASSIC use HDF5 as the disk format """ @@ -955,7 +953,7 @@ def write(self, name, data, info=False): # attributes from data, which it should be if data is a BoutArray. # Otherwise, need to write it explicitly try: - if not "bout_type" in data.attributes: + if "bout_type" not in data.attributes: raise AttributeError("'bout_type' not found in attributes") except AttributeError: self.handle[name].attrs.create( @@ -986,7 +984,7 @@ def attributes(self, varname): attribute = str(attribute, encoding="utf-8") attributes[attrname] = attribute - if not "bout_type" in attributes: + if "bout_type" not in attributes: # bout_type is a required attribute for BOUT++ outputs, so it should # have been found raise ValueError( diff --git a/boututils/efit_analyzer.py b/boututils/efit_analyzer.py index b8dbc7d..d53a30f 100644 --- a/boututils/efit_analyzer.py +++ b/boututils/efit_analyzer.py @@ -22,13 +22,14 @@ ) from scipy import interpolate -from boututils.bunch import Bunch - from .analyse_equil_2 import analyse_equil from .ask import query_yes_no -from .radial_grid import radial_grid + +# from .radial_grid import radial_grid from .read_geqdsk import read_geqdsk +# from boututils.bunch import Bunch + def View2D(g, option=0): @@ -84,7 +85,8 @@ def View2D(g, option=0): # show(block=False) # Function fpol and qpsi are given between simagx (psi on the axis) and sibdry ( - # psi on limiter or separatrix). So the toroidal field (fpol/R) and the q profile are within these boundaries + # psi on limiter or separatrix). So the toroidal field (fpol/R) and the q profile + # are within these boundaries npsigrid = old_div(np.arange(np.size(g.pres)).astype(float), (np.size(g.pres) - 1)) @@ -92,61 +94,61 @@ def View2D(g, option=0): fpsi[0, :] = g.simagx + npsigrid * (g.sibdry - g.simagx) fpsi[1, :] = g.fpol - boundary = np.array([g.xlim, g.ylim]) - - rz_grid = Bunch( - nr=g.nx, - nz=g.ny, # Number of grid points - r=g.r[:, 0], - z=g.z[0, :], # R and Z as 1D arrays - simagx=g.simagx, - sibdry=g.sibdry, # Range of psi - psi=g.psi, # Poloidal flux in Weber/rad on grid points - npsigrid=npsigrid, # Normalised psi grid for fpol, pres and qpsi - fpol=g.fpol, # Poloidal current function on uniform flux grid - pres=g.pres, # Plasma pressure in nt/m^2 on uniform flux grid - qpsi=g.qpsi, # q values on uniform flux grid - nlim=g.nlim, - rlim=g.xlim, - zlim=g.ylim, - ) # Wall boundary + # boundary = np.array([g.xlim, g.ylim]) + # + # rz_grid = Bunch( + # nr=g.nx, + # nz=g.ny, # Number of grid points + # r=g.r[:, 0], + # z=g.z[0, :], # R and Z as 1D arrays + # simagx=g.simagx, + # sibdry=g.sibdry, # Range of psi + # psi=g.psi, # Poloidal flux in Weber/rad on grid points + # npsigrid=npsigrid, # Normalised psi grid for fpol, pres and qpsi + # fpol=g.fpol, # Poloidal current function on uniform flux grid + # pres=g.pres, # Plasma pressure in nt/m^2 on uniform flux grid + # qpsi=g.qpsi, # q values on uniform flux grid + # nlim=g.nlim, + # rlim=g.xlim, + # zlim=g.ylim, + # ) # Wall boundary critical = analyse_equil(g.psi, g.r[:, 0], g.z[0, :]) - n_opoint = critical.n_opoint - n_xpoint = critical.n_xpoint + # n_opoint = critical.n_opoint + # n_xpoint = critical.n_xpoint primary_opt = critical.primary_opt - inner_sep = critical.inner_sep + # inner_sep = critical.inner_sep opt_ri = critical.opt_ri opt_zi = critical.opt_zi - opt_f = critical.opt_f - xpt_ri = critical.xpt_ri - xpt_zi = critical.xpt_zi - xpt_f = critical.xpt_f - - psi_inner = 0.6 - psi_outer = (0.8,) - nrad = 68 - npol = 64 - rad_peaking = [0.0] - pol_peaking = [0.0] - parweight = 0.0 + # opt_f = critical.opt_f + # xpt_ri = critical.xpt_ri + # xpt_zi = critical.xpt_zi + # xpt_f = critical.xpt_f + # + # psi_inner = 0.6 + # psi_outer = (0.8,) + # nrad = 68 + # npol = 64 + # rad_peaking = [0.0] + # pol_peaking = [0.0] + # parweight = 0.0 - boundary = np.array([rz_grid.rlim, rz_grid.zlim]) + # boundary = np.array([rz_grid.rlim, rz_grid.zlim]) # Psi normalisation factors - faxis = critical.opt_f[critical.primary_opt] - - fnorm = critical.xpt_f[critical.inner_sep] - critical.opt_f[critical.primary_opt] + # faxis = critical.opt_f[critical.primary_opt] + # + # fnorm = critical.xpt_f[critical.inner_sep] - critical.opt_f[critical.primary_opt] # From normalised psi, get range of f - f_inner = faxis + np.min(psi_inner) * fnorm - f_outer = faxis + np.max(psi_outer) * fnorm + # f_inner = faxis + np.min(psi_inner) * fnorm + # f_outer = faxis + np.max(psi_outer) * fnorm - fvals = radial_grid(nrad, f_inner, f_outer, 1, 1, [xpt_f[inner_sep]], rad_peaking) + # fvals = radial_grid(nrad, f_inner, f_outer, 1, 1, [xpt_f[inner_sep]], rad_peaking) - ## Create a starting surface + # # Create a starting surface # sind = np.int(nrad / 2) # start_f = 0. #fvals[sind] @@ -161,7 +163,8 @@ def View2D(g, option=0): ) fpsiq = interpolate.interp1d(g.qpsi, psiq) - # Find how many rational surfaces we have within the boundary and locate x,y position of curves + # Find how many rational surfaces we have within the boundary and locate x,y + # position of curves nmax = g.qpsi.max().astype(int) nmin = g.qpsi.min().astype(int) @@ -224,7 +227,7 @@ def View2D(g, option=0): if option == 0: sm = query_yes_no("Overplot vector field") if sm: - lw = 50 * Bprz / Bprz.max() + # lw = 50 * Bprz / Bprz.max() streamplot( g.r.T, g.z.T, Br.T, Bz.T, color=Bprz, linewidth=2, cmap=cm.bone ) # density =[.5, 1], color='k')#, linewidth=lw) @@ -281,7 +284,7 @@ def View2D(g, option=0): ax = subplot2grid((3, 3), (2, 1), colspan=2, rowspan=1) ax.plot(psiq, g.qpsi) - ax.set_xlabel("$\psi$") + ax.set_xlabel(r"$\psi$") ax.set_ylabel("$q$") ax.yaxis.label.set_rotation("horizontal") ax.yaxis.label.set_size(20) @@ -320,7 +323,7 @@ def View2D(g, option=0): return Br, Bz, x, y, psi -## output to files +# # output to files # np.savetxt('../data.in', np.reshape([g.nx, g.ny],(1,2)), fmt='%i, %i') # f_handle = open('../data.in', 'a') # np.savetxt(f_handle,np.reshape([g.rmagx, g.zmagx],(1,2)), fmt='%e, %e') @@ -349,7 +352,8 @@ def View2D(g, option=0): def surface(cs, i, f, opt_ri, opt_zi, style, iplot=0): - # contour_lines( F, np.arange(nx).astype(float), np.arange(ny).astype(float), levels=[start_f]) + # contour_lines( F, np.arange(nx).astype(float), np.arange(ny).astype(float), + # levels=[start_f]) # cs=contour( g.r, g.z, g.psi, levels=[f]) # proxy = [Rectangle((0,0),1,1,fc = 'b') # for pc in cs.collections] @@ -358,8 +362,9 @@ def surface(cs, i, f, opt_ri, opt_zi, style, iplot=0): p = cs.collections[i].get_paths() # - # You might get more than one contours for the same start_f. We need to keep the closed one - vn = np.zeros(np.size(p)) + # You might get more than one contours for the same start_f. We need to keep the + # closed one + # vn = np.zeros(np.size(p)) # find the closed contour @@ -386,7 +391,7 @@ def surface(cs, i, f, opt_ri, opt_zi, style, iplot=0): # yy = [yy,v[:,1]] # if np.shape(vn)[0] > 1 : - ## Find the surface closest to the o-point + # # Find the surface closest to the o-point # ind = closest_line(np.size(xx), xx, yy, opt_ri, opt_zi) # x=xx[ind] # y=yy[ind] diff --git a/boututils/fft_integrate.py b/boututils/fft_integrate.py index c012fee..cae5495 100644 --- a/boututils/fft_integrate.py +++ b/boututils/fft_integrate.py @@ -16,7 +16,7 @@ def fft_integrate(y, loop=None): imag = np.complex(0.0, 1.0) result = np.arange(n) * f[0] - loop = np.float(n) * f[0] # return the loop integral + # loop = np.float(n) * f[0] # return the loop integral f[0] = 0.0 diff --git a/boututils/geqdsk.py b/boututils/geqdsk.py index b339b83..7c8f95d 100755 --- a/boututils/geqdsk.py +++ b/boututils/geqdsk.py @@ -39,7 +39,11 @@ def openFile(self, filename): self.data["nw"] = int(m.group(2)), "Number of horizontal R grid points" self.data["nh"] = int(m.group(3)), "Number of vertical Z grid points" - fltsPat = r"^\s*([ \-]\d\.\d+[Ee][\+\-]\d\d)([ \-]\d\.\d+[Ee][\+\-]\d\d)([ \-]\d\.\d+[Ee][\+\-]\d\d)([ \-]\d\.\d+[Ee][\+\-]\d\d)([ \-]\d\.\d+[Ee][\+\-]\d\d)\s*$" + fltsPat = ( + r"^\s*([ \-]\d\.\d+[Ee][\+\-]\d\d)([ \-]\d\.\d+[Ee][\+\-]\d\d)([ \-]" + r"\d\.\d+[Ee][\+\-]\d\d)([ \-]\d\.\d+[Ee][\+\-]\d\d)([ \-]\d\.\d+[Ee]" + r"[\+\-]\d\d)\s*$" + ) # 2nd line m = re.search(fltsPat, lines[1]) diff --git a/boututils/mode_structure.py b/boututils/mode_structure.py index 32357d5..1ca0ad6 100644 --- a/boututils/mode_structure.py +++ b/boututils/mode_structure.py @@ -18,6 +18,7 @@ # ; for producing plots similar to the ERGOS # ; vacuum RMP code + # interpolates a 1D periodic function def zinterp(v, zind): @@ -82,7 +83,8 @@ def mode_structure( else: n = period - # if (grid_in.JYSEPS1_1 GE 0) OR (grid_in.JYSEPS1_2 NE grid_in.JYSEPS2_1) OR (grid_in.JYSEPS2_2 NE grid_in.ny-1) THEN BEGIN + # if (grid_in.JYSEPS1_1 GE 0) OR (grid_in.JYSEPS1_2 NE grid_in.JYSEPS2_1) OR + # (grid_in.JYSEPS2_2 NE grid_in.ny-1) THEN BEGIN # PRINT, "Mesh contains branch-cuts. Keeping only core" # # grid = core_mesh(grid_in) @@ -404,7 +406,8 @@ def mode_structure( # IF KEYWORD_SET(addq) THEN BEGIN # # FOR i=0, pmodes-1 DO BEGIN -# PRINT, "m = "+STRTRIM(STRING(inds[i]+1), 2)+" amp = "+STRTRIM(STRING(fmax[inds[i]]),2) +# PRINT, "m = "+STRTRIM(STRING(inds[i]+1), 2)+" amp = " +# +STRTRIM(STRING(fmax[inds[i]]),2) # q = FLOAT(inds[i]+1) / FLOAT(n) # # pos = INTERPOL(xarr, qprof, q) diff --git a/boututils/options.py b/boututils/options.py index de471af..b3bbc8c 100644 --- a/boututils/options.py +++ b/boututils/options.py @@ -91,7 +91,7 @@ def read_inp(self, inp_path=""): # remove white space line = line.replace(" ", "") - if len(line) > 0 and line[0] is not "#": + if len(line) > 0 and line[0] != "#": # Only read lines that are not comments or blank if "[" in line: # Section header @@ -140,7 +140,7 @@ def remove_section(self, section): - Fix undefined variable """ if section in self._sections: - self._sections.pop(self._sections.index(sections)) + self._sections.pop(self._sections.index(section)) super(BOUTOptions, self).__delattr__(section) else: print("WARNING: Section " + section + " not found.\n") diff --git a/boututils/plotdata.py b/boututils/plotdata.py index 6bac047..67ddeb8 100644 --- a/boututils/plotdata.py +++ b/boututils/plotdata.py @@ -38,7 +38,7 @@ def plotdata( # Points with error bars if x is None: x = np.arange(size) - errorbar(x, data, xerr, yerr) + plt.errorbar(x, data, xerr, yerr) # Line plot if x is None: plt.plot(data) @@ -68,7 +68,7 @@ def plotdata( # Add a color bar if colorbar: - CB = plt.colorbar(shrink=0.8, extend="both") + _ = plt.colorbar(shrink=0.8, extend="both") else: print("Sorry, can't handle %d-D variables" % ndims) diff --git a/boututils/plotpolslice.py b/boututils/plotpolslice.py index 2de9863..984d033 100644 --- a/boututils/plotpolslice.py +++ b/boututils/plotpolslice.py @@ -47,7 +47,8 @@ def zinterp(v, zind): def plotpolslice(var3d, gridfile, period=1, zangle=0.0, rz=1, fig=0): - """ data2d = plotpolslice(data3d, 'gridfile' , period=1, zangle=0.0, rz:return (r,z) grid also=1, fig: to do the graph, set to 1 ) """ + """data2d = plotpolslice(data3d, 'gridfile' , period=1, zangle=0.0, + rz:return (r,z) grid also=1, fig: to do the graph, set to 1 )""" g = file_import(gridfile) @@ -86,7 +87,8 @@ def plotpolslice(var3d, gridfile, period=1, zangle=0.0, rz=1, fig=0): for x in range(nx): zind = old_div((zangle - zShift[x, y]), dz) var2d[x, ypos] = zinterp(var3d[x, y, :], zind) - # IF KEYWORD_SET(profile) THEN var2d[x,ypos] = var2d[x,ypos] + profile[x,y] + # IF KEYWORD_SET(profile) THEN var2d[x,ypos] = + # var2d[x,ypos] + profile[x,y] r[x, ypos] = rxy[x, y] z[x, ypos] = zxy[x, y] @@ -109,7 +111,8 @@ def plotpolslice(var3d, gridfile, period=1, zangle=0.0, rz=1, fig=0): var2d[x, ypos + i] = w * zinterp(var3d[x, y + 1, :], zi) + ( 1.0 - w ) * zinterp(var3d[x, y, :], zi) - # IF KEYWORD_SET(profile) THEN var2d[x,ypos+i] = var2d[x,ypos+i] + w*profile[x,y+1] + (1.0-w)*profile[x,y] + # IF KEYWORD_SET(profile) THEN var2d[x,ypos+i] = + # var2d[x,ypos+i] + w*profile[x,y+1] + (1.0-w)*profile[x,y] r[x, ypos + i] = w * rxy[x, y + 1] + (1.0 - w) * rxy[x, y] z[x, ypos + i] = w * zxy[x, y + 1] + (1.0 - w) * zxy[x, y] diff --git a/boututils/run_wrapper.py b/boututils/run_wrapper.py index e7598bf..9b93fd7 100644 --- a/boututils/run_wrapper.py +++ b/boututils/run_wrapper.py @@ -244,7 +244,7 @@ def launch( else: cmd = "OMP_NUM_THREADS={} {}".format(mthread, cmd) - if verbose == True: + if verbose: print(cmd) return shell(cmd, pipe=pipe) diff --git a/boututils/showdata.py b/boututils/showdata.py index 9c63826..bf3fd68 100644 --- a/boututils/showdata.py +++ b/boututils/showdata.py @@ -11,7 +11,6 @@ from builtins import chr, range, str -from boutdata.collect import collect from matplotlib import animation from matplotlib import pyplot as plt from numpy import abs, array, floor, isclose, linspace, max, meshgrid, min, pi @@ -185,7 +184,8 @@ def showdata( titles.append(("Var" + str(i + 1))) elif len(titles) != Nvar: raise ValueError( - "The length of the titles input list must match the length of the vars list." + "The length of the titles input list must match the length of the vars " + "list." ) # Sort out legend labels @@ -202,7 +202,9 @@ def showdata( check = check + 1 if check == 0: alwayswarn( - "The legendlabels list does not contain a sublist for each variable, but its length matches the number of lines on each plot. Will apply labels to each plot" + "The legendlabels list does not contain a sublist for each " + "variable, but its length matches the number of lines on each plot." + " Will apply labels to each plot" ) legendlabelsdummy = [] for i in range(0, Nvar): @@ -212,7 +214,9 @@ def showdata( legendlabels = legendlabelsdummy else: alwayswarn( - "The legendlabels list does not contain a sublist for each variable, and it's length does not match the number of lines on each plot. Will default apply labels to each plot" + "The legendlabels list does not contain a sublist for each " + "variable, and it's length does not match the number of lines on " + "each plot. Will default apply labels to each plot" ) legendlabels = [] for i in range(0, Nvar): @@ -224,7 +228,8 @@ def showdata( legendlabels = [legendlabels] elif len(legendlabels) != Nvar: alwayswarn( - "The length of the legendlabels list does not match the length of the vars list, will continue with default values" + "The length of the legendlabels list does not match the length of the vars " + "list, will continue with default values" ) legendlabels = [] for i in range(0, Nvar): @@ -236,7 +241,9 @@ def showdata( if isinstance(legendlabels[i], list): if len(legendlabels[i]) != Nlines[i]: alwayswarn( - "The length of the legendlabel (sub)list for each plot does not match the number of datasets for each plot. Will continue with default values" + "The length of the legendlabel (sub)list for each plot does not" + " match the number of datasets for each plot. " + "Will continue with default values" ) legendlabels[i] = [] for j in range(0, Nlines[i]): @@ -245,7 +252,9 @@ def showdata( legendlabels[i] = [legendlabels[i]] if len(legendlabels[i]) != Nlines[i]: alwayswarn( - "The length of the legendlabel (sub)list for each plot does not match the number of datasets for each plot. Will continue with default values" + "The length of the legendlabel (sub)list for each plot does not " + "match the number of datasets for each plot. " + "Will continue with default values" ) legendlabels[i] = [] for j in range(0, Nlines[i]): @@ -272,7 +281,8 @@ def showdata( surf.append(0) else: alwayswarn( - "Length of surf list does not match number of variables. Will default to no polar plots" + "Length of surf list does not match number of variables. " + "Will default to no polar plots" ) for i in range(0, Nvar): surf.append(0) @@ -308,7 +318,8 @@ def showdata( polar.append(0) else: alwayswarn( - "Length of polar list does not match number of variables. Will default to no polar plots" + "Length of polar list does not match number of variables. " + "Will default to no polar plots" ) for i in range(0, Nvar): polar.append(0) @@ -342,22 +353,26 @@ def showdata( if (Ndims[i][j] == 2) & (polar[i] != 0): alwayswarn( - "Data must be 3 dimensional (time, r, theta) for polar plots. Will plot lineplot instead" + "Data must be 3 dimensional (time, r, theta) for polar plots. " + "Will plot lineplot instead" ) if (Ndims[i][j] == 2) & (surf[i] != 0): alwayswarn( - "Data must be 3 dimensional (time, x, y) for surface plots. Will plot lineplot instead" + "Data must be 3 dimensional (time, x, y) for surface plots. " + "Will plot lineplot instead" ) if (Ndims[i][j] == 3) & (Nlines[i] != 1): raise ValueError( - "cannot have multiple sets of 3D (time + 2 spatial dimensions) on each subplot" + "cannot have multiple sets of 3D (time + 2 spatial dimensions) on " + "each subplot" ) if Ndims[i][j] != Ndims[i][0]: raise ValueError( - "Error, Number of dimensions must be the same for all variables on each plot." + "Error, Number of dimensions must be the same for all variables on" + " each plot." ) if Ndims[i][0] == 2: # Set polar and surf list entries to 0 @@ -368,7 +383,8 @@ def showdata( else: if (polar[i] == 1) & (surf[i] == 1): alwayswarn( - "Cannot do polar and surface plots at the same time. Default to contour plot" + "Cannot do polar and surface plots at the same time. " + "Default to contour plot" ) contour.append(1) lineplot.append(0) @@ -396,7 +412,8 @@ def showdata( raise ValueError("time dimensions must be the same for all variables.") # if (Nx[i][j] != Nx[i][0]): - # raise ValueError('Dimensions must be the same for all variables on each plot.') + # raise ValueError('Dimensions must be the same for all variables on + # each plot.') if Ndims[i][j] == 3: Ny[i].append(vars[i][j].shape[2]) @@ -429,7 +446,8 @@ def showdata( + str(i) + ", " + titles[i] - + ", the shape of x is not compatible with the shape of the variable. Shape of x should be (Nx), (Nx,Ny) or (Nt,Nx,Ny)." + + ", the shape of x is not compatible with the shape of the " + "variable. Shape of x should be (Nx), (Nx,Ny) or (Nt,Nx,Ny)." ) except: for j in range(0, Nlines[i]): @@ -450,7 +468,8 @@ def showdata( + str(i) + ", " + titles[i] - + ", the shape of y is not compatible with the shape of the variable. Shape of y should be (Ny), (Nx,Ny) or (Nt,Nx,Ny)." + + ", the shape of y is not compatible with the shape of the " + "variable. Shape of y should be (Ny), (Nx,Ny) or (Nt,Nx,Ny)." ) except: ynew.append(linspace(0, Ny[i][0] - 1, Ny[i][0])) @@ -697,9 +716,7 @@ def animate(i): if ( clear_between_frames is None and lineplot[j] != 1 ) or clear_between_frames is True: - ax[ - j - ].cla() # Clear axis between frames so that masked arrays can be plotted + ax[j].cla() # Clear axis between frames so masked arrays can be plotted if lineplot[j] == 1: for k in range(0, Nlines[j]): lines[j][k].set_data(x[j][k], vars[j][k][index, :]) diff --git a/boututils/volume_integral.py b/boututils/volume_integral.py index 170971d..efed5cd 100644 --- a/boututils/volume_integral.py +++ b/boututils/volume_integral.py @@ -41,7 +41,7 @@ def volume_integral(var, grid, xr=False): result = np.zeros(nt) for t in range(nt): - result[t] = volume_integral(var[t, :, :, :], g, xr=xr) + result[t] = volume_integral(var[t, :, :, :], grid, xr=xr) return result elif s == 3: @@ -55,7 +55,7 @@ def volume_integral(var, grid, xr=False): for y in range(ny): zi[x, y] = np.mean(var[x, y, :]) - return volume_integral(zi, g, xr=xr) + return volume_integral(zi, grid, xr=xr) elif s != 2: print("ERROR: volume_integral var must be 2, 3 or 4D") @@ -64,7 +64,7 @@ def volume_integral(var, grid, xr=False): nx = np.shape(var)[0] ny = np.shape(var)[1] - if xr == False: + if not xr: xr = [0, nx - 1] result = 0.0 @@ -84,7 +84,7 @@ def volume_integral(var, grid, xr=False): dtheta = 2.0 * np.pi / np.float(ny) r = grid["Rxy"][xi, yi] z = grid["Zxy"][xi, yi] - n = np.size(r) + # n = np.size(r) dl = old_div(np.sqrt(deriv(r) ** 2 + deriv(z) ** 2), dtheta) # Area of flux-surface diff --git a/setup.cfg b/setup.cfg index 6a09f5a..3a0e34b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,4 +8,4 @@ line_length = 88 [flake8] max-line-length = 88 -extend-ignore = E203, W503 +extend-ignore = E203, W503, E722 diff --git a/setup.py b/setup.py index f9313f7..6263f94 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import re from pathlib import Path import setuptools From c52d6162964d8414ca214eb34ae1423fa4b2bc02 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 10 Jan 2021 10:11:33 +0000 Subject: [PATCH 49/98] Remove contour.py as it seems to be unfinished function bodies not implemented or only partly. Fixes issue #27 --- boututils/contour.py | 80 -------------------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 boututils/contour.py diff --git a/boututils/contour.py b/boututils/contour.py deleted file mode 100644 index 1d3fc97..0000000 --- a/boututils/contour.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Contour calculation routines - -https://web.archive.org/web/20140901225541/https://members.bellatlantic.net/~vze2vrva/thesis.html""" -from __future__ import print_function -from __future__ import division -from past.utils import old_div - -import numpy as np - - -def contour(f, level): - """Return a list of contours matching the given level""" - - if len(f.shape) != 2: - print("Contour only works on 2D data") - return None - nx,ny = f.shape - - # Go through each cell edge and mark which ones contain - # a level crossing. Approximating function as - # f = axy + bx + cy + d - # Hence linear interpolation along edges. - - edgecross = {} # Dictionary: (cell number, edge number) to crossing location - - for i in np.arange(nx-1): - for j in np.arange(ny-1): - # Lower-left corner of cell is (i,j) - if (np.max(f[i:(i+2),j:(j+2)]) < level) or (np.min(f[i:(i+2),j:(j+2)]) > level): - # not in the correct range - skip - continue - - # Check each edge - ncross = 0 - def location(a, b): - if (a > level) ^ (a > level): - # One of the corners is > level, and the other is <= level - ncross += 1 - # Find location - return old_div((level - a), (b - a)) - else: - return None - - loc = [ - location(f[i,j], f[i+1,j]), - location(f[i+1,j], f[i+1,j+1]), - location(f[i+1,j+1], f[i,j+1]), - location(f[i,j+1], f[i,j])] - - if ncross != 0: # Only put into dictionary if has a crossing - cellnr = (ny-1)*i + j # The cell number - edgecross[cellnr] = [loc,ncross] # Tack ncross onto the end - - # Process crossings into contour lines - - while True: - # Start from an arbitrary location and follow until - # it goes out of the domain or closes - try: - startcell, cross = edgecross.popitem() - except KeyError: - # No keys left so finished - break - - def follow(): - return - - # Follow - - return - -def find_opoints(var2d): - """Find O-points in psi i.e. local minima/maxima""" - return - -def find_xpoints(var2d): - """Find X-points in psi i.e. inflection points""" - return - From 62709af41992fdec7c4f7ac6eb70d8322cf28d09 Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Sun, 10 Jan 2021 11:02:20 +0000 Subject: [PATCH 50/98] Change non-working example into docstring boutgrid __main__ used hard-coded paths which aren't shipped with the code. This is probably more useful as a docstring example. --- boututils/boutgrid.py | 55 ++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 30 deletions(-) mode change 100755 => 100644 boututils/boutgrid.py diff --git a/boututils/boutgrid.py b/boututils/boutgrid.py old mode 100755 new mode 100644 index ace6766..a2c9aa7 --- a/boututils/boutgrid.py +++ b/boututils/boutgrid.py @@ -56,7 +56,32 @@ def aligned_points(grid, nz=1, period=1.0, maxshift=0.4): return points def create_grid(grid, data, period=1): + """ + Create a structured grid which can be plotted with Mayavi + or saved to a VTK file. + Example + ------- + from boutdata.collect import collect + from boututils.file_import import file_import + from boututile import boutgrid + + # Load grid file + g = file_import("bout.grd.nc") + + # Load 3D data (x,y,z) + data = collect("P", tind=-1)[0,:,:,:] + + # Create a structured grid + sgrid = boutgrid.create_grid(g, data, 1) + + # Write structured grid to file + w = tvtk.XMLStructuredGridWriter(input=sgrid, file_name='sgrid.vts') + w.write() + + # View the structured grid + boutgrid.view3d(sgrid) + """ s = np.shape(data) nx = grid["nx"]#[0] @@ -107,33 +132,3 @@ def view3d(sgrid): g = GridPlane() g.grid_plane.axis = 'x' e.add_module(g) - -if __name__ == '__main__': - from boutdata.collect import collect - from boututils.file_import import file_import - - #path = "/media/449db594-b2fe-4171-9e79-2d9b76ac69b6/runs/data_33/" - path="../data" - - g = file_import("../bout.grd.nc") - #g = file_import("../cbm18_8_y064_x516_090309.nc") - #g = file_import("/home/ben/run4/reduced_y064_x256.nc") - - data = collect("P", tind=10, path=path) - data = data[0,:,:,:] - s = np.shape(data) - nz = s[2] - - #bkgd = collect("P0", path=path) - #for z in range(nz): - # data[:,:,z] += bkgd - - # Create a structured grid - sgrid = create_grid(g, data, 1) - - - w = tvtk.XMLStructuredGridWriter(input=sgrid, file_name='sgrid.vts') - w.write() - - # View the structured grid - view3d(sgrid) From 9f0abfdcfc91e6c3497fe9045cc7e81d61b15867 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 17 Jan 2021 16:17:05 +0100 Subject: [PATCH 51/98] Remove #! and +x permission The files are not installed as user facing scripts, thus the shebang and the permissions can be dropped --- boututils/anim.py | 5 +---- boututils/boutgrid.py | 1 - boututils/geqdsk.py | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) mode change 100755 => 100644 boututils/anim.py mode change 100755 => 100644 boututils/boutgrid.py mode change 100755 => 100644 boututils/geqdsk.py diff --git a/boututils/anim.py b/boututils/anim.py old mode 100755 new mode 100644 index d2f7838..94a2cfe --- a/boututils/anim.py +++ b/boututils/anim.py @@ -1,7 +1,4 @@ -#!/usr/bin/env python3 -"""Animate graph with mayavi - -""" +"""Animate graph with mayavi""" from __future__ import print_function from builtins import range diff --git a/boututils/boutgrid.py b/boututils/boutgrid.py old mode 100755 new mode 100644 index ace6766..9a805eb --- a/boututils/boutgrid.py +++ b/boututils/boutgrid.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 from __future__ import print_function from builtins import range diff --git a/boututils/geqdsk.py b/boututils/geqdsk.py old mode 100755 new mode 100644 index d63caf1..9a2ac88 --- a/boututils/geqdsk.py +++ b/boututils/geqdsk.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - from __future__ import print_function from __future__ import division from builtins import str From 0630c0832f069e2709e9729bc784072f98fcdb57 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Mon, 18 Jan 2021 22:53:43 +0000 Subject: [PATCH 52/98] Read and write file-level attributes in DataFile --- boututils/datafile.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/boututils/datafile.py b/boututils/datafile.py index 3343bae..e31af4a 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -104,7 +104,7 @@ def __init__( write=write, create=create, format=format, - **kwargs + **kwargs, ) elif format == "HDF5": self.impl = DataFile_HDF5( @@ -309,6 +309,12 @@ def write(self, name, data, info=False): """ return self.impl.write(name, data, info) + def read_file_attribute(self, name): + return self.impl.read_file_attribute(name) + + def write_file_attribute(self, name, value): + return self.impl.write_file_attribute(name, value) + def __getitem__(self, name): return self.impl.__getitem__(name) @@ -356,7 +362,7 @@ def __init__( write=False, create=False, format="NETCDF3_CLASSIC", - **kwargs + **kwargs, ): self._kwargs = kwargs if not has_netCDF: @@ -669,6 +675,15 @@ def find_dim(dim): except AttributeError: pass + def read_file_attribute(self, name): + try: + return getattr(self.handle, name) + except AttributeError: + raise AttributeError(f"DataFile (netCDF4) has no file attribute {name}") + + def write_file_attribute(self, name, value): + setattr(self.handle, name, value) + def attributes(self, varname): try: return self._attributes_cache[varname] @@ -970,6 +985,15 @@ def write(self, name, data, info=False): # data is not a BoutArray, so doesn't have attributes to write pass + def read_file_attribute(self, name): + try: + return self.handle.attrs[name] + except KeyError: + raise AttributeError(f"DataFile (HDF5) has no file attribute {name}") + + def write_file_attribute(self, name, value): + self.handle.attrs[name] = value + def attributes(self, varname): try: From 9cf1392b2208fff2fb3691da3ba7fe3d421630ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Schw=C3=B6rer?= Date: Mon, 15 Feb 2021 11:12:07 +0100 Subject: [PATCH 53/98] fix __all__ We do not import anything by default, thus __all__ should be empty --- boututils/__init__.py | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/boututils/__init__.py b/boututils/__init__.py index ad5ce74..ca7cba7 100644 --- a/boututils/__init__.py +++ b/boututils/__init__.py @@ -1,41 +1,6 @@ """ Generic routines, useful for all data """ -import sys - -try: - # NOTE: This import will not be needed when python2 support is stopped - from builtins import str # noqa: F401 -except ImportError: - raise ImportError("Please install the future module to use Python 2") - -# Modules to be imported independent of version -for_all_versions = [ - "calculus", - "closest_line", - "datafile", # 'efit_analyzer',\ # bunch pkg required - "fft_deriv", - "fft_integrate", - "file_import", - "int_func", - "linear_regression", - "mode_structure", # 'moment_xyzt',\ # bunch pkg requried - "run_wrapper", - "shell", - "showdata", # 'surface_average',\ - # 'volume_integral',\ #bunch pkg required -] - -# Check the current python version -if sys.version_info[0] >= 3: - do_import = for_all_versions - __all__ = do_import -else: - do_import = for_all_versions - do_import.append("anim") - do_import.append("plotpolslice") - do_import.append("View3D") - __all__ = do_import - +__all__ = [] __name__ = "boututils" try: From f8753829674bb4349ebf71aa644aac3e8bfaff3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Schw=C3=B6rer?= Date: Wed, 17 Feb 2021 10:06:12 +0100 Subject: [PATCH 54/98] raise errors that cannot be handeled --- boututils/boutgrid.py | 14 ++++------- boututils/fft_integrate.py | 48 +++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/boututils/boutgrid.py b/boututils/boutgrid.py index 6e04702..5f649a6 100644 --- a/boututils/boutgrid.py +++ b/boututils/boutgrid.py @@ -10,15 +10,11 @@ def aligned_points(grid, nz=1, period=1.0, maxshift=0.4): - try: - nx = grid["nx"] # [0] - ny = grid["ny"] # [0] - zshift = grid["zShift"] - Rxy = grid["Rxy"] - Zxy = grid["Zxy"] - except: - print("Missing required data") - return None + nx = grid["nx"] # [0] + ny = grid["ny"] # [0] + zshift = grid["zShift"] + Rxy = grid["Rxy"] + Zxy = grid["Zxy"] # dz = 2.0 * pi / (period * (nz - 1)) phi0 = np.linspace(0, 2.0 * pi / period, nz) diff --git a/boututils/fft_integrate.py b/boututils/fft_integrate.py index cae5495..b1b3f53 100644 --- a/boututils/fft_integrate.py +++ b/boututils/fft_integrate.py @@ -8,42 +8,38 @@ def fft_integrate(y, loop=None): - try: # If an error occurs, return to caller + n = np.size(y) - n = np.size(y) + f = old_div(np.fft.fft(y), n) + imag = np.complex(0.0, 1.0) - f = old_div(np.fft.fft(y), n) - imag = np.complex(0.0, 1.0) + result = np.arange(n) * f[0] + # loop = np.float(n) * f[0] # return the loop integral - result = np.arange(n) * f[0] - # loop = np.float(n) * f[0] # return the loop integral + f[0] = 0.0 - f[0] = 0.0 + if (n % 2) == 0: + # even number of points - if (n % 2) == 0: - # even number of points + for i in range(1, old_div(n, 2)): + a = imag * 2.0 * np.pi * np.float(i) / np.float(n) + f[i] = old_div(f[i], a) # positive frequencies + f[n - i] = old_div(-f[n - i], a) # negative frequencies - for i in range(1, old_div(n, 2)): - a = imag * 2.0 * np.pi * np.float(i) / np.float(n) - f[i] = old_div(f[i], a) # positive frequencies - f[n - i] = old_div(-f[n - i], a) # negative frequencies + f[old_div(n, 2)] = old_div(f[old_div(n, 2)], (imag * np.pi)) + else: + # odd number of points - f[old_div(n, 2)] = old_div(f[old_div(n, 2)], (imag * np.pi)) - else: - # odd number of points + for i in range(1, old_div((n - 1), 2) + 1): + a = imag * 2.0 * np.pi * np.float(i) / np.float(n) + f[i] = old_div(f[i], a) + f[n - i] = old_div(-f[n - i], a) - for i in range(1, old_div((n - 1), 2) + 1): - a = imag * 2.0 * np.pi * np.float(i) / np.float(n) - f[i] = old_div(f[i], a) - f[n - i] = old_div(-f[n - i], a) + result = result + np.fft.ifft(f) * n - result = result + np.fft.ifft(f) * n + result = result - result[0] # just to make sure - result = result - result[0] # just to make sure - - return result - except: - print("fft_integrate_fail") + return result def test_integrate(): From d45df12315c33bd2fa0d8320452d51871fb9d224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Schw=C3=B6rer?= Date: Wed, 17 Feb 2021 10:06:38 +0100 Subject: [PATCH 55/98] using .get with default value avoids need for excpetion --- boututils/anim.py | 5 +---- boututils/calculus.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/boututils/anim.py b/boututils/anim.py index 5ee22e5..1478615 100644 --- a/boututils/anim.py +++ b/boututils/anim.py @@ -44,10 +44,7 @@ def anim(s, d, *args, **kwargs): else: s1 = None - try: - save = kwargs["save"] - except: - save = False + save = kwargs.get("save", False) nt = d.shape[0] diff --git a/boututils/calculus.py b/boututils/calculus.py index f297c0d..226b54a 100644 --- a/boututils/calculus.py +++ b/boututils/calculus.py @@ -41,10 +41,7 @@ def deriv(*args, **kwargs): else: raise RuntimeError("deriv must be given 1 or 2 arguments") - try: - periodic = kwargs["periodic"] - except: - periodic = False + periodic = kwargs.get("periodic", False) n = var.size if periodic: From 6e76bee996ef56bd1e590dfc47e37615d36b4b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Schw=C3=B6rer?= Date: Fri, 23 Apr 2021 22:00:13 +0200 Subject: [PATCH 56/98] disable comments --- .codecov.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..fab78fe --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,14 @@ +comment: false + +ignore: + - "test" + +codecov: + branch: next + +coverage: + status: + project: + default: + target: 30% + patch: off From 05d4c20b4fadea5f6915aab00dda8c227a80de1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Schw=C3=B6rer?= Date: Mon, 10 May 2021 10:35:37 +0200 Subject: [PATCH 57/98] Cleanup Remove python2 code Remove commented out code use appropriate division --- boututils/fft_integrate.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/boututils/fft_integrate.py b/boututils/fft_integrate.py index b1b3f53..2b53f29 100644 --- a/boututils/fft_integrate.py +++ b/boututils/fft_integrate.py @@ -1,39 +1,35 @@ -from __future__ import division, print_function - from builtins import range import numpy as np -from past.utils import old_div from pylab import plot, show def fft_integrate(y, loop=None): n = np.size(y) - f = old_div(np.fft.fft(y), n) + f = np.fft.fft(y) / n imag = np.complex(0.0, 1.0) result = np.arange(n) * f[0] - # loop = np.float(n) * f[0] # return the loop integral f[0] = 0.0 if (n % 2) == 0: # even number of points - for i in range(1, old_div(n, 2)): + for i in range(1, n // 2): a = imag * 2.0 * np.pi * np.float(i) / np.float(n) - f[i] = old_div(f[i], a) # positive frequencies - f[n - i] = old_div(-f[n - i], a) # negative frequencies + f[i] = f[i] / a # positive frequencies + f[n - i] = -f[n - i] / a # negative frequencies - f[old_div(n, 2)] = old_div(f[old_div(n, 2)], (imag * np.pi)) + f[n // 2] = f[n // 2] / (imag * np.pi) else: # odd number of points - for i in range(1, old_div((n - 1), 2) + 1): + for i in range(1, (n + 1) // 2): a = imag * 2.0 * np.pi * np.float(i) / np.float(n) - f[i] = old_div(f[i], a) - f[n - i] = old_div(-f[n - i], a) + f[i] = f[i] / a + f[n - i] = -f[n - i] / a result = result + np.fft.ifft(f) * n From 1b4615740613ff074808ef9f935856cd91f2761f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Schw=C3=B6rer?= Date: Mon, 10 May 2021 10:37:28 +0200 Subject: [PATCH 58/98] run on all branches --- .codecov.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index fab78fe..06b8db1 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -3,9 +3,6 @@ comment: false ignore: - "test" -codecov: - branch: next - coverage: status: project: From dff94ba3edd0a4907350522b3d6979a859e010e2 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Tue, 25 May 2021 10:15:40 +0100 Subject: [PATCH 59/98] Empty commit to trigger Github Actions From e2008c54d9f8e23f25bc247ee83b7cd5a045dad4 Mon Sep 17 00:00:00 2001 From: dschwoerer Date: Wed, 7 Jul 2021 10:16:40 +0200 Subject: [PATCH 60/98] Use internal bunch --- boututils/analyse_equil_2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boututils/analyse_equil_2.py b/boututils/analyse_equil_2.py index 5eca465..7380321 100644 --- a/boututils/analyse_equil_2.py +++ b/boututils/analyse_equil_2.py @@ -8,7 +8,7 @@ from builtins import range, str, zip import numpy -from bunch import Bunch +from boututils.bunch import Bunch from crosslines import find_inter from matplotlib.pyplot import annotate, contour, draw, gradient, plot from past.utils import old_div From ccfc68891321e6105316a20ca888989be344a253 Mon Sep 17 00:00:00 2001 From: dschwoerer Date: Wed, 7 Jul 2021 10:21:14 +0200 Subject: [PATCH 61/98] Fix import --- boututils/read_geqdsk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boututils/read_geqdsk.py b/boututils/read_geqdsk.py index 83f4d2b..7c502bf 100644 --- a/boututils/read_geqdsk.py +++ b/boututils/read_geqdsk.py @@ -3,8 +3,8 @@ from builtins import range import numpy -from geqdsk import Geqdsk +from boututils.geqdsk import Geqdsk from boututils.bunch import Bunch From 11bb66d96a61e4cf5dd1b160d51ba4b556cc4aee Mon Sep 17 00:00:00 2001 From: johnomotani Date: Wed, 11 Aug 2021 13:22:36 +0100 Subject: [PATCH 62/98] Use 'NETCDF4' as default format for DataFile `NETCDF4` format is pretty universally supported now, and is probably the least surprising default. --- boututils/datafile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boututils/datafile.py b/boututils/datafile.py index e31af4a..9524897 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -83,7 +83,7 @@ class DataFile(object): impl = None def __init__( - self, filename=None, write=False, create=False, format="NETCDF3_64BIT", **kwargs + self, filename=None, write=False, create=False, format="NETCDF4", **kwargs ): """ From 9f16d574e1d3a5b0afba6bf1f779ea45176277b7 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 29 Oct 2021 17:13:09 +0200 Subject: [PATCH 63/98] Remove future --- boututils/View3D.py | 1 - boututils/analyse_equil_2.py | 1 - boututils/anim.py | 1 - boututils/boutgrid.py | 1 - boututils/calculus.py | 1 - boututils/crosslines.py | 1 - boututils/datafile.py | 5 ++--- boututils/efit_analyzer.py | 1 - boututils/fft_deriv.py | 1 - boututils/geqdsk.py | 1 - boututils/int_func.py | 1 - boututils/linear_regression.py | 1 - boututils/mode_structure.py | 1 - boututils/moment_xyzt.py | 1 - boututils/plotdata.py | 1 - boututils/plotpolslice.py | 1 - boututils/radial_grid.py | 1 - boututils/read_geqdsk.py | 1 - boututils/showdata.py | 1 - boututils/spectrogram.py | 1 - boututils/surface_average.py | 1 - boututils/volume_integral.py | 1 - boututils/watch.py | 3 +-- requirements.txt | 1 - setup.py | 1 - 25 files changed, 3 insertions(+), 28 deletions(-) diff --git a/boututils/View3D.py b/boututils/View3D.py index cbd3c76..82fe867 100644 --- a/boututils/View3D.py +++ b/boututils/View3D.py @@ -9,7 +9,6 @@ based on enthought's example by Gael Varoquaux https://docs.enthought.com/mayavi/mayavi/auto/example_magnetic_field.html#example-magnetic-field """ -from __future__ import absolute_import, division import sys from builtins import range diff --git a/boututils/analyse_equil_2.py b/boututils/analyse_equil_2.py index 5eca465..a2e3aaf 100644 --- a/boututils/analyse_equil_2.py +++ b/boututils/analyse_equil_2.py @@ -3,7 +3,6 @@ Takes a RZ psi grid, and finds x-points and o-points """ -from __future__ import division, print_function from builtins import range, str, zip diff --git a/boututils/anim.py b/boututils/anim.py index 1478615..88244f0 100644 --- a/boututils/anim.py +++ b/boututils/anim.py @@ -1,6 +1,5 @@ """Animate graph with mayavi""" -from __future__ import print_function import os from builtins import range diff --git a/boututils/boutgrid.py b/boututils/boutgrid.py index 5f649a6..9bbd680 100644 --- a/boututils/boutgrid.py +++ b/boututils/boutgrid.py @@ -1,4 +1,3 @@ -from __future__ import print_function from builtins import range diff --git a/boututils/calculus.py b/boututils/calculus.py index 226b54a..c94be47 100644 --- a/boututils/calculus.py +++ b/boututils/calculus.py @@ -4,7 +4,6 @@ B.Dudson, University of York, Nov 2009 """ -from __future__ import division, print_function from builtins import range diff --git a/boututils/crosslines.py b/boututils/crosslines.py index 9d2b37a..a6f2c54 100644 --- a/boututils/crosslines.py +++ b/boututils/crosslines.py @@ -1,4 +1,3 @@ -from __future__ import division, print_function import itertools diff --git a/boututils/datafile.py b/boututils/datafile.py index e31af4a..ac23bf2 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -23,7 +23,6 @@ """ -from __future__ import print_function from builtins import map, object, str, zip @@ -584,8 +583,8 @@ def write(self, name, data, info=False): if var.shape != s: print("DataFile: Variable already exists with different size: " + name) # Fallthrough to the exception - raise - except: + raise KeyError + except KeyError: # Not found, so add. # Get dimensions diff --git a/boututils/efit_analyzer.py b/boututils/efit_analyzer.py index d53a30f..19a4b58 100644 --- a/boututils/efit_analyzer.py +++ b/boututils/efit_analyzer.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import, division from builtins import range diff --git a/boututils/fft_deriv.py b/boututils/fft_deriv.py index 7d43f68..33e380f 100644 --- a/boututils/fft_deriv.py +++ b/boututils/fft_deriv.py @@ -1,4 +1,3 @@ -from __future__ import division from builtins import range diff --git a/boututils/geqdsk.py b/boututils/geqdsk.py index 62ba2b4..365ed41 100644 --- a/boututils/geqdsk.py +++ b/boututils/geqdsk.py @@ -1,4 +1,3 @@ -from __future__ import division, print_function import re from builtins import object, range, str diff --git a/boututils/int_func.py b/boututils/int_func.py index ba15b35..25543c1 100644 --- a/boututils/int_func.py +++ b/boututils/int_func.py @@ -1,4 +1,3 @@ -from __future__ import division import copy from builtins import range diff --git a/boututils/linear_regression.py b/boututils/linear_regression.py index a861a42..5640399 100644 --- a/boututils/linear_regression.py +++ b/boututils/linear_regression.py @@ -1,4 +1,3 @@ -from __future__ import division from numpy import mean diff --git a/boututils/mode_structure.py b/boututils/mode_structure.py index 1ca0ad6..148d915 100644 --- a/boututils/mode_structure.py +++ b/boututils/mode_structure.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import, division, print_function import sys from builtins import range diff --git a/boututils/moment_xyzt.py b/boututils/moment_xyzt.py index ef906d0..78fd97c 100644 --- a/boututils/moment_xyzt.py +++ b/boututils/moment_xyzt.py @@ -1,4 +1,3 @@ -from __future__ import division, print_function from builtins import range diff --git a/boututils/plotdata.py b/boututils/plotdata.py index 67ddeb8..e70fc90 100644 --- a/boututils/plotdata.py +++ b/boututils/plotdata.py @@ -1,4 +1,3 @@ -from __future__ import print_function import matplotlib import matplotlib.cm as cm diff --git a/boututils/plotpolslice.py b/boututils/plotpolslice.py index 984d033..aab00a7 100644 --- a/boututils/plotpolslice.py +++ b/boututils/plotpolslice.py @@ -1,4 +1,3 @@ -from __future__ import division, print_function import sys from builtins import range, str diff --git a/boututils/radial_grid.py b/boututils/radial_grid.py index 0a06c16..74ea077 100644 --- a/boututils/radial_grid.py +++ b/boututils/radial_grid.py @@ -1,4 +1,3 @@ -from __future__ import division import numpy from past.utils import old_div diff --git a/boututils/read_geqdsk.py b/boututils/read_geqdsk.py index 83f4d2b..0df2f9d 100644 --- a/boututils/read_geqdsk.py +++ b/boututils/read_geqdsk.py @@ -1,4 +1,3 @@ -from __future__ import print_function from builtins import range diff --git a/boututils/showdata.py b/boututils/showdata.py index bf3fd68..6163bc1 100644 --- a/boututils/showdata.py +++ b/boututils/showdata.py @@ -7,7 +7,6 @@ Additional functionality by George Breyiannis 26/12/2014 """ -from __future__ import division, print_function from builtins import chr, range, str diff --git a/boututils/spectrogram.py b/boututils/spectrogram.py index c6d3a49..4ab6972 100644 --- a/boututils/spectrogram.py +++ b/boututils/spectrogram.py @@ -5,7 +5,6 @@ updated: 23/06/2016 """ -from __future__ import division, print_function from builtins import range diff --git a/boututils/surface_average.py b/boututils/surface_average.py index 21ad22f..cc85c01 100644 --- a/boututils/surface_average.py +++ b/boututils/surface_average.py @@ -2,7 +2,6 @@ """ -from __future__ import absolute_import, division, print_function from builtins import range diff --git a/boututils/volume_integral.py b/boututils/volume_integral.py index efed5cd..c86e081 100644 --- a/boututils/volume_integral.py +++ b/boututils/volume_integral.py @@ -2,7 +2,6 @@ """ -from __future__ import division, print_function from builtins import range diff --git a/boututils/watch.py b/boututils/watch.py index 611661e..4ac73c6 100644 --- a/boututils/watch.py +++ b/boututils/watch.py @@ -2,7 +2,6 @@ Routines for watching files for changes """ -from __future__ import print_function import os import time @@ -53,7 +52,7 @@ def watch(files, timeout=None, poll=2): # Not iterable -> just one file lastmod = os.stat(files).st_mtime iterable = False - except: + except FileNotFoundError: print("Can't test modified time. Wrong file name?") raise diff --git a/requirements.txt b/requirements.txt index 9e85c88..5959343 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ numpy==1.18.2 matplotlib==3.2.1 h5py==2.10.0 scipy==1.4.1 -future==0.18.2 PyQt5==5.12.2 [mayavi] mayavi==4.6.2 [mayavi] netCDF4==1.5.3 diff --git a/setup.py b/setup.py index 6263f94..d33dbd0 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,6 @@ "matplotlib", "scipy", "h5py", - "future", "netCDF4", "importlib-metadata ; python_version<'3.8'", ], From 15305a6002096fd018234f6c0a522b91c267fe55 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 29 Oct 2021 17:13:39 +0200 Subject: [PATCH 64/98] make h5py optional --- requirements.txt | 2 +- setup.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5959343..2beaae0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ numpy==1.18.2 matplotlib==3.2.1 -h5py==2.10.0 +h5py==2.10.0 [hdf5] scipy==1.4.1 PyQt5==5.12.2 [mayavi] mayavi==4.6.2 [mayavi] diff --git a/setup.py b/setup.py index d33dbd0..03b6934 100644 --- a/setup.py +++ b/setup.py @@ -45,11 +45,13 @@ "numpy", "matplotlib", "scipy", - "h5py", "netCDF4", "importlib-metadata ; python_version<'3.8'", ], - extras_require={"mayavi": ["mayavi", "PyQt5"]}, + extras_require={ + "mayavi": ["mayavi", "PyQt5"], + "hdf5": ["h5py"], + }, classifiers=[ "Programming Language :: Python :: 3", ( From be3949291a41f249e522bceb12c3ef42ebbfe4f1 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 29 Oct 2021 17:20:14 +0200 Subject: [PATCH 65/98] black --- boututils/boutgrid.py | 1 - boututils/crosslines.py | 1 - boututils/fft_deriv.py | 1 - boututils/geqdsk.py | 1 - boututils/int_func.py | 1 - boututils/linear_regression.py | 1 - boututils/mode_structure.py | 1 - boututils/moment_xyzt.py | 1 - boututils/plotdata.py | 1 - boututils/plotpolslice.py | 1 - boututils/radial_grid.py | 1 - boututils/read_geqdsk.py | 1 - 12 files changed, 12 deletions(-) diff --git a/boututils/boutgrid.py b/boututils/boutgrid.py index 9bbd680..1dcb6ca 100644 --- a/boututils/boutgrid.py +++ b/boututils/boutgrid.py @@ -1,4 +1,3 @@ - from builtins import range import numpy as np diff --git a/boututils/crosslines.py b/boututils/crosslines.py index a6f2c54..aed6186 100644 --- a/boututils/crosslines.py +++ b/boututils/crosslines.py @@ -1,4 +1,3 @@ - import itertools import numpy as np diff --git a/boututils/fft_deriv.py b/boututils/fft_deriv.py index 33e380f..683fea2 100644 --- a/boututils/fft_deriv.py +++ b/boututils/fft_deriv.py @@ -1,4 +1,3 @@ - from builtins import range import numpy diff --git a/boututils/geqdsk.py b/boututils/geqdsk.py index 365ed41..8d172ca 100644 --- a/boututils/geqdsk.py +++ b/boututils/geqdsk.py @@ -1,4 +1,3 @@ - import re from builtins import object, range, str diff --git a/boututils/int_func.py b/boututils/int_func.py index 25543c1..8eb75c9 100644 --- a/boututils/int_func.py +++ b/boututils/int_func.py @@ -1,4 +1,3 @@ - import copy from builtins import range diff --git a/boututils/linear_regression.py b/boututils/linear_regression.py index 5640399..8b2a98b 100644 --- a/boututils/linear_regression.py +++ b/boututils/linear_regression.py @@ -1,4 +1,3 @@ - from numpy import mean # diff --git a/boututils/mode_structure.py b/boututils/mode_structure.py index 148d915..19dd69e 100644 --- a/boututils/mode_structure.py +++ b/boututils/mode_structure.py @@ -1,4 +1,3 @@ - import sys from builtins import range diff --git a/boututils/moment_xyzt.py b/boututils/moment_xyzt.py index 78fd97c..ba8f2a3 100644 --- a/boututils/moment_xyzt.py +++ b/boututils/moment_xyzt.py @@ -1,4 +1,3 @@ - from builtins import range import numpy as np diff --git a/boututils/plotdata.py b/boututils/plotdata.py index e70fc90..0e75476 100644 --- a/boututils/plotdata.py +++ b/boututils/plotdata.py @@ -1,4 +1,3 @@ - import matplotlib import matplotlib.cm as cm import matplotlib.mlab as mlab diff --git a/boututils/plotpolslice.py b/boututils/plotpolslice.py index aab00a7..4e96a69 100644 --- a/boututils/plotpolslice.py +++ b/boututils/plotpolslice.py @@ -1,4 +1,3 @@ - import sys from builtins import range, str diff --git a/boututils/radial_grid.py b/boututils/radial_grid.py index 74ea077..db96f54 100644 --- a/boututils/radial_grid.py +++ b/boututils/radial_grid.py @@ -1,4 +1,3 @@ - import numpy from past.utils import old_div diff --git a/boututils/read_geqdsk.py b/boututils/read_geqdsk.py index 0df2f9d..5196214 100644 --- a/boututils/read_geqdsk.py +++ b/boututils/read_geqdsk.py @@ -1,4 +1,3 @@ - from builtins import range import numpy From 46ea522834f92c33c5182b8d46b3d83d1a6d6f8d Mon Sep 17 00:00:00 2001 From: David Bold Date: Mon, 1 Nov 2021 15:13:14 +0100 Subject: [PATCH 66/98] isort imports --- boututils/analyse_equil_2.py | 3 ++- boututils/read_geqdsk.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/boututils/analyse_equil_2.py b/boututils/analyse_equil_2.py index 7380321..0ce24ac 100644 --- a/boututils/analyse_equil_2.py +++ b/boututils/analyse_equil_2.py @@ -8,12 +8,13 @@ from builtins import range, str, zip import numpy -from boututils.bunch import Bunch from crosslines import find_inter from matplotlib.pyplot import annotate, contour, draw, gradient, plot from past.utils import old_div from scipy.interpolate import RectBivariateSpline +from boututils.bunch import Bunch + from . import local_min_max diff --git a/boututils/read_geqdsk.py b/boututils/read_geqdsk.py index 7c502bf..e74e5fe 100644 --- a/boututils/read_geqdsk.py +++ b/boututils/read_geqdsk.py @@ -4,8 +4,8 @@ import numpy -from boututils.geqdsk import Geqdsk from boututils.bunch import Bunch +from boututils.geqdsk import Geqdsk def read_geqdsk(file): From 1a6e6359241f22efac780fd301d8cd6a19bb2f63 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Mon, 14 Mar 2022 12:20:35 +0000 Subject: [PATCH 67/98] DataFile.get() method Means DataFile behaves more like a dict, and allows passing a default in case the value is missing. --- boututils/datafile.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/boututils/datafile.py b/boututils/datafile.py index 76dd318..659e72a 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -314,6 +314,12 @@ def read_file_attribute(self, name): def write_file_attribute(self, name, value): return self.impl.write_file_attribute(name, value) + def get(self, name, default=None): + if default is None or name in self.keys(): + return self[name] + else: + return default + def __getitem__(self, name): return self.impl.__getitem__(name) From 449e2a41e8d1b90f4e86585cf4813cd3719481c8 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Wed, 6 Apr 2022 16:37:24 +0100 Subject: [PATCH 68/98] Method to list file attributes of a DataFile --- boututils/datafile.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/boututils/datafile.py b/boututils/datafile.py index 659e72a..8e9befe 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -314,6 +314,9 @@ def read_file_attribute(self, name): def write_file_attribute(self, name, value): return self.impl.write_file_attribute(name, value) + def list_file_attributes(self): + return self.impl.list_file_attributes() + def get(self, name, default=None): if default is None or name in self.keys(): return self[name] @@ -689,6 +692,9 @@ def read_file_attribute(self, name): def write_file_attribute(self, name, value): setattr(self.handle, name, value) + def list_file_attributes(self): + return self.handle.ncattrs() + def attributes(self, varname): try: return self._attributes_cache[varname] @@ -999,6 +1005,9 @@ def read_file_attribute(self, name): def write_file_attribute(self, name, value): self.handle.attrs[name] = value + def list_file_attributes(self): + return self.handle.attrs.keys() + def attributes(self, varname): try: From 989951acda4930737c1af80f76bc000cfc898752 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Wed, 6 Apr 2022 16:38:00 +0100 Subject: [PATCH 69/98] Formatting changes from updated black --- boututils/View3D.py | 6 +++--- boututils/linear_regression.py | 2 +- boututils/spectrogram.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/boututils/View3D.py b/boututils/View3D.py index 82fe867..4028da6 100644 --- a/boututils/View3D.py +++ b/boututils/View3D.py @@ -113,7 +113,7 @@ def View3D(g, path=None, gb=None): BBx = B1p + B1t BBy = B2p + B2t BBz = B3p + B3t - btotal = np.sqrt(BBx ** 2 + BBy ** 2 + BBz ** 2) + btotal = np.sqrt(BBx**2 + BBy**2 + BBz**2) Psi = psi_field(g, X, Y, Z, rmin, rmax, zmin, zmax) @@ -329,7 +329,7 @@ def View3D(g, path=None, gb=None): def magnetic_field(g, X, Y, Z, rmin, rmax, zmin, zmax, Br, Bz, Btrz): - rho = np.sqrt(X ** 2 + Y ** 2) + rho = np.sqrt(X**2 + Y**2) phi = np.arctan2(Y, X) br = np.zeros(np.shape(X)) @@ -372,7 +372,7 @@ def magnetic_field(g, X, Y, Z, rmin, rmax, zmin, zmax, Br, Bz, Btrz): def psi_field(g, X, Y, Z, rmin, rmax, zmin, zmax): - rho = np.sqrt(X ** 2 + Y ** 2) + rho = np.sqrt(X**2 + Y**2) psi = np.zeros(np.shape(X)) diff --git a/boututils/linear_regression.py b/boututils/linear_regression.py index 8b2a98b..543dfa6 100644 --- a/boututils/linear_regression.py +++ b/boututils/linear_regression.py @@ -20,7 +20,7 @@ def linear_regression(x, y): mx = mean(x) my = mean(y) - b = (mean(x * y) - mx * my) / (mean(x ** 2) - mx ** 2) + b = (mean(x * y) - mx * my) / (mean(x**2) - mx**2) a = my - b * mx return a, b diff --git a/boututils/spectrogram.py b/boututils/spectrogram.py index 4ab6972..aeb76a3 100644 --- a/boututils/spectrogram.py +++ b/boututils/spectrogram.py @@ -71,7 +71,7 @@ def spectrogram(data, dx, sigma, clip=1.0, optimise_clipping=True, nskip=1.0): while 1: nn = nn / 2.0 if nn <= 2.0: - n_clipped = 2 ** two_count + n_clipped = 2**two_count print("clipping window length from ", n, " to ", n_clipped, " points") break else: From f13d9a3d2734e484ab6c1412a5b88008a37896e2 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Wed, 6 Apr 2022 17:30:01 +0100 Subject: [PATCH 70/98] Update tool versions for pre-commit checks --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c816694..0b37abe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,17 +2,17 @@ # pre-commit autoupdate repos: - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 22.3.0 hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + rev: 3.9.2 hooks: - id: flake8 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.1.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -24,6 +24,6 @@ repos: args: [--prose-wrap=always, --print-width=88] - repo: https://github.com/PyCQA/isort - rev: 5.7.0 + rev: 5.10.1 hooks: - id: isort From cb077d57caf576f41cf664bc68486193971c51ed Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 8 Apr 2022 16:21:39 +0100 Subject: [PATCH 71/98] Update README with current relationship to BOUT++ and boututils --- README.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index cdfce51..2b34d01 100644 --- a/README.md +++ b/README.md @@ -7,21 +7,15 @@ [![PEP8](https://img.shields.io/badge/code%20style-PEP8-brightgreen.svg)](https://www.python.org/dev/peps/pep-0008/) [![License](https://img.shields.io/badge/license-LGPL--3.0-blue.svg)](https://github.com/boutproject/boutdata/blob/master/LICENSE) -pip-package of what is found in `BOUT-dev/tools/pylib/boutdata`. -Note that `BOUT-dev/tools/pylib/boutdata` will likely be replaced by this repo -in `BOUT++ v4.3.0`. -See [this issue](https://github.com/boutproject/BOUT-dev/issues/1347), -[this pull request](https://github.com/boutproject/BOUT-dev/pull/1766) and -[this pull request](https://github.com/boutproject/BOUT-dev/pull/1740) for details. +Python tools for working with [BOUT++](https://github.com/boutproject/BOUT-dev.git). > **NOTE**: This package will likely be superseded by [`xBOUT`](https://github.com/boutproject/xBOUT) in the near future # Dependencies -`boutdata` depends on `boututils` which again depends on -[`netcfd4`](https://github.com/Unidata/netcdf4-python) which requires -[`HDF5`](https://www.h5py.org) and +`boutdata` uses [`netcfd4`](https://github.com/Unidata/netcdf4-python) +which requires [`HDF5`](https://www.h5py.org) and [`netcdf-4`](https://github.com/Unidata/netcdf-c/releases) are installed, and that the `nc-config` utility is in your `PATH`. This can be install with @@ -40,7 +34,7 @@ in ubuntu Reading data from dump files: ``` -from boutdata import * +from boutdata import collect ni = collect("Ni") ``` From 6d82ab8a3e6471e4f53e3e074438baea5c642746 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 8 Apr 2022 16:45:58 +0100 Subject: [PATCH 72/98] Import the most useful `boututils` functions/classes into `boutdata` --- boutdata/__init__.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/boutdata/__init__.py b/boutdata/__init__.py index 7edd2f0..c5688f5 100644 --- a/boutdata/__init__.py +++ b/boutdata/__init__.py @@ -1,9 +1,32 @@ """ Routines for exchanging data to/from BOUT++ """ -# Import this, as this almost always used when calling this package from boutdata.collect import collect, attributes +from boututils.boutarray import BoutArray +from boututils.boutwarnings import alwayswarn +from boututils.run_wrapper import ( + launch, + launch_safe, + shell, + shell_safe, + determineNumberOfCPUs, + build_and_log, +) -__all__ = ["attributes", "collect", "gen_surface", "pol_slice"] + +__all__ = [ + "attributes", + "collect", + "gen_surface", + "pol_slice", + "BoutArray", + "alwayswarn", + "launch", + "launch_safe", + "shell", + "shell_safe", + "determineNumberOfCPUs", + "build_and_log", +] __name__ = "boutdata" From c33120561f821f1eeb0029a4dbe5780c6c113c08 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 8 Apr 2022 17:23:42 +0100 Subject: [PATCH 73/98] CI: Remove duplicated lint workflow --- .github/workflows/lint.yml | 42 -------------------------------------- .pre-commit-config.yaml | 29 -------------------------- 2 files changed, 71 deletions(-) delete mode 100644 .github/workflows/lint.yml delete mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 5ae524b..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Lint - -on: - # Run each time we push and pull requests - push: - pull_request: - # Cron job - # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows#scheduled-events-schedule - schedule: - # https://crontab.guru/#0_0_1_*_* - - cron: "0 0 1 * *" - -jobs: - linting: - runs-on: ubuntu-latest - - steps: - # Use the v2 tag of: https://github.com/actions/checkout - - name: Checkout repo - uses: actions/checkout@v2 - - - name: Set up Python 3.x - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - - name: Set up Python ${{ matrix.python-version }} - # Use the v2 tag of: https://github.com/actions/setup-python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - # https://github.com/PyCQA/pylint/issues/352 - # pylint need the requirements to do the checks as the pre-commit repo is local - run: | - python -m pip install --upgrade pip - python -m pip install --upgrade pre-commit - pip install . - - - name: Lint - run: pre-commit run --all-files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 0b37abe..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# NOTE: The versions can be updated by calling -# pre-commit autoupdate -repos: - - repo: https://github.com/psf/black - rev: 22.3.0 - hooks: - - id: black - - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 - hooks: - - id: flake8 - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - - repo: https://github.com/prettier/pre-commit - rev: 57f39166b5a5a504d6808b87ab98d41ebf095b46 - hooks: - - id: prettier - args: [--prose-wrap=always, --print-width=88] - - - repo: https://github.com/PyCQA/isort - rev: 5.10.1 - hooks: - - id: isort From d8e834edfd1fda3dfa8f160e06c21c361137cf7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Tue, 23 Aug 2022 11:36:34 +0200 Subject: [PATCH 74/98] Update requirements.txt Update numpy in order to patch vulnerabilities --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2beaae0..933542d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.18.2 +numpy==1.19 matplotlib==3.2.1 h5py==2.10.0 [hdf5] scipy==1.4.1 From 6a3f607e3f5da3699ea7db8bca526c73e5d2da3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20L=C3=B8iten?= Date: Thu, 1 Sep 2022 15:47:45 +0200 Subject: [PATCH 75/98] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 933542d..686d285 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.19 +numpy==1.23.2 matplotlib==3.2.1 h5py==2.10.0 [hdf5] scipy==1.4.1 From c29b8932c373965062fdf141c4f58f41ffe17dbf Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 28 Sep 2022 11:46:00 +0200 Subject: [PATCH 76/98] Move the definition of all bout_types of BoutArray to BoutArray That makes it easier to be consistent what types are supported. Also add ArrayY and some others. --- boututils/boutarray.py | 28 +++++++++++++++++++++++++ boututils/datafile.py | 47 +++++------------------------------------- 2 files changed, 33 insertions(+), 42 deletions(-) diff --git a/boututils/boutarray.py b/boututils/boutarray.py index 9b20df8..04cbefa 100644 --- a/boututils/boutarray.py +++ b/boututils/boutarray.py @@ -71,3 +71,31 @@ def __format__(self, str): return super().__format__(str) except TypeError: return float(self).__format__(str) + + @staticmethod + def get_dims_type(): + return { + "Field3D_t": ("t", "x", "y", "z"), + "Field2D_t": ("t", "x", "y"), + "FieldPerp_t": ("t", "x", "z"), + "ArrayX_t": ("t", "x"), + "ArrayY_t": ("t", "y"), + "ArrayZ_t": ("t", "z"), + "scalar_t": ("t",), + "Field3D": ("x", "y", "z"), + "Field2D": ("x", "y"), + "FieldPerp": ("x", "z"), + "ArrayX": ("x",), + "ArrayY": ("y",), + "ArrayZ": ("z",), + "scalar": (), + "string": ("char",), + } + + @staticmethod + def dims_from_type(type): + return BoutArray.get_dims_type().get(type, None) + + @staticmethod + def type_from_dims(dims): + return {v: k for k, v in BoutArray.get_dims_type().items()}.get(dims, None) diff --git a/boututils/datafile.py b/boututils/datafile.py index 8e9befe..b14cf1e 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -505,19 +505,7 @@ def _bout_type_from_dimensions(self, varname): else: return "string" - dims_dict = { - ("t", "x", "y", "z"): "Field3D_t", - ("t", "x", "y"): "Field2D_t", - ("t", "x", "z"): "FieldPerp_t", - ("t",): "scalar_t", - ("x", "y", "z"): "Field3D", - ("x", "y"): "Field2D", - ("x", "z"): "FieldPerp", - ("x",): "ArrayX", - (): "scalar", - } - - return dims_dict.get(dims, None) + return BoutArray.type_from_dims(dims) def _bout_dimensions_from_var(self, data): try: @@ -542,19 +530,7 @@ def _bout_dimensions_from_var(self, data): string_length = len(data) return ("char" + str(string_length),) - dims_dict = { - "Field3D_t": ("t", "x", "y", "z"), - "Field2D_t": ("t", "x", "y"), - "FieldPerp_t": ("t", "x", "z"), - "scalar_t": ("t",), - "Field3D": ("x", "y", "z"), - "Field2D": ("x", "y"), - "FieldPerp": ("x", "z"), - "ArrayX": ("x",), - "scalar": (), - } - - return dims_dict.get(bout_type, None) + return BoutArray.dims_from_type(bout_type) def write(self, name, data, info=False): @@ -843,25 +819,12 @@ def keys(self): def dimensions(self, varname): bout_type = self.bout_type(varname) - dims_dict = { - "Field3D_t": ("t", "x", "y", "z"), - "FieldPerp_t": ("t", "x", "z"), - "Field2D_t": ("t", "x", "y"), - "scalar_t": ("t",), - "string_t": ("t", "char"), - "Field3D": ("x", "y", "z"), - "FieldPerp": ("x", "z"), - "Field2D": ("x", "y"), - "ArrayX": ("x",), - "scalar": (), - "string": ("char",), - } - try: - return dims_dict[bout_type] - except KeyError: + dims = BoutArray.dims_from_type(bout_type) + if dims is None: raise ValueError( "Variable bout_type not recognized (got {})".format(bout_type) ) + return dims def _bout_type_from_array(self, data): """Get the bout_type from the array 'data' From c02d6c6c9a715c6170b3362e0dd024f4150e03bb Mon Sep 17 00:00:00 2001 From: David Bold Date: Wed, 28 Sep 2022 11:46:32 +0200 Subject: [PATCH 77/98] Do ignore file we shouldn't track --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6fd2411..c8c7b73 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,4 @@ dmypy.json .DS_Store .idea/ *.sw[po] +/boututils/_version.py From fbb0a5c809c920bfa7ca040c51cfe32b8bbf65fc Mon Sep 17 00:00:00 2001 From: Michael Redenti <43955379+mredenti@users.noreply.github.com> Date: Fri, 28 Oct 2022 21:25:38 +0100 Subject: [PATCH 78/98] Fix bug in computation of derivative with DFT --- boututils/calculus.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/boututils/calculus.py b/boututils/calculus.py index c94be47..3cdb233 100644 --- a/boututils/calculus.py +++ b/boututils/calculus.py @@ -44,18 +44,20 @@ def deriv(*args, **kwargs): n = var.size if periodic: + # assume uniform grid + dx = x[1] - x[0] # Use FFTs to take derivatives f = rfft(var) f[0] = 0.0 # Zero constant term if n % 2 == 0: # Even n for i in arange(1, old_div(n, 2)): - f[i] *= 2.0j * pi * float(i) / float(n) + f[i] *= 2.0j * pi * float(i) / (float(n)*dx) f[-1] = 0.0 # Nothing from Nyquist frequency else: # Odd n for i in arange(1, old_div((n - 1), 2) + 1): - f[i] *= 2.0j * pi * float(i) / float(n) + f[i] *= 2.0j * pi * float(i) / (float(n)*dx) return irfft(f) else: # Non-periodic function From c2e97a226a8a7728b8f5085792f7744484943512 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 23 Mar 2023 09:50:54 +0100 Subject: [PATCH 79/98] Ensure to always write int32 BOUT++ does not handle reading int64 attributes --- boututils/datafile.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/boututils/datafile.py b/boututils/datafile.py index 8e9befe..595b162 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -678,8 +678,10 @@ def find_dim(dim): # Write attributes, if present try: - for attrname in data.attributes: - var.setncattr(attrname, data.attributes[attrname]) + for attrname, attrval in data.attributes.items(): + if isinstance(attrval, int): + attrval = np.int32(attrval) + var.setncattr(attrname, attrval) except AttributeError: pass @@ -987,8 +989,7 @@ def write(self, name, data, info=False): ) try: - for attrname in data.attributes: - attrval = data.attributes[attrname] + for attrname, attrval in data.attributes.items(): if type(attrval) == str: attrval = attrval.encode(encoding="utf-8") self.handle[name].attrs.create(attrname, attrval) From 1ab5e72a95f2e05e3075d0dc5b6ba80531358988 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 23 Mar 2023 15:09:38 +0100 Subject: [PATCH 80/98] Add basic xarray support Allows to write DataArrays and preserve their dimensions --- boututils/datafile.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/boututils/datafile.py b/boututils/datafile.py index b14cf1e..0072c88 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -511,6 +511,8 @@ def _bout_dimensions_from_var(self, data): try: bout_type = data.attributes["bout_type"] except AttributeError: + if hasattr(data, "dims"): + return data.dims defdims_list = [ (), ("t",), @@ -542,6 +544,9 @@ def write(self, name, data, info=False): # Get the variable type t = type(data).__name__ + if t == "DataArray": + t = data.dtype.str + if t == "NoneType": print("DataFile: None passed as data to write. Ignoring") return From 0a34513731c38ac7e934abc174d75e7602f3a1f6 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 23 Mar 2023 15:15:53 +0100 Subject: [PATCH 81/98] CI: Update flake8 to github repo --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0b37abe..157d346 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: hooks: - id: black - - repo: https://gitlab.com/pycqa/flake8 + - repo: https://github.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 From bf2335d80c66df19c7c43492a0a2fd7b8bfac4c7 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 23 Mar 2023 15:19:59 +0100 Subject: [PATCH 82/98] Auto update pre-commit-config --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 157d346..96c0be0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,17 +2,17 @@ # pre-commit autoupdate repos: - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 23.1.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 3.9.2 + rev: 6.0.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -24,6 +24,6 @@ repos: args: [--prose-wrap=always, --print-width=88] - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort From 7ba5147b6256a5c021f2a3b1fc6be8270d69ff99 Mon Sep 17 00:00:00 2001 From: David Bold Date: Thu, 23 Mar 2023 15:20:08 +0100 Subject: [PATCH 83/98] black format --- boututils/View3D.py | 3 --- boututils/analyse_equil_2.py | 2 -- boututils/anim.py | 1 - boututils/boutgrid.py | 1 - boututils/closest_line.py | 1 - boututils/crosslines.py | 1 - boututils/datafile.py | 3 --- boututils/efit_analyzer.py | 4 ---- boututils/fft_integrate.py | 1 - boututils/int_func.py | 1 - boututils/mode_structure.py | 3 --- boututils/options.py | 1 - boututils/plotpolslice.py | 1 - boututils/radial_grid.py | 1 - boututils/read_geqdsk.py | 1 - boututils/showdata.py | 1 - 16 files changed, 26 deletions(-) diff --git a/boututils/View3D.py b/boututils/View3D.py index 4028da6..f9aa417 100644 --- a/boututils/View3D.py +++ b/boututils/View3D.py @@ -213,7 +213,6 @@ def View3D(g, path=None, gb=None): # q=i surfaces for i in range(np.shape(x)[0]): - s = mlab.pipeline.streamline(field) s.streamline_type = "line" # s.seed.widget = s.seed.widget_list[0] @@ -328,7 +327,6 @@ def View3D(g, path=None, gb=None): def magnetic_field(g, X, Y, Z, rmin, rmax, zmin, zmax, Br, Bz, Btrz): - rho = np.sqrt(X**2 + Y**2) phi = np.arctan2(Y, X) @@ -371,7 +369,6 @@ def magnetic_field(g, X, Y, Z, rmin, rmax, zmin, zmax, Br, Bz, Btrz): def psi_field(g, X, Y, Z, rmin, rmax, zmin, zmax): - rho = np.sqrt(X**2 + Y**2) psi = np.zeros(np.shape(X)) diff --git a/boututils/analyse_equil_2.py b/boututils/analyse_equil_2.py index 4014679..5e43d03 100644 --- a/boututils/analyse_equil_2.py +++ b/boututils/analyse_equil_2.py @@ -209,7 +209,6 @@ def analyse_equil(F, R, Z): print("") if n_xpoint > 0: - # Find the primary separatrix # First remove non-monotonic separatrices @@ -265,7 +264,6 @@ def analyse_equil(F, R, Z): inner_sep = 0 else: - # No x-points. Pick mid-point in f xpt_f = 0.5 * (numpy.max(F) + numpy.min(F)) diff --git a/boututils/anim.py b/boututils/anim.py index 88244f0..679c2d4 100644 --- a/boututils/anim.py +++ b/boututils/anim.py @@ -65,7 +65,6 @@ def anim(s, d, *args, **kwargs): if __name__ == "__main__": - path = "../../../examples/elm-pb/data" data = collect("P", path=path) diff --git a/boututils/boutgrid.py b/boututils/boutgrid.py index 1dcb6ca..6e62ad9 100644 --- a/boututils/boutgrid.py +++ b/boututils/boutgrid.py @@ -28,7 +28,6 @@ def aligned_points(grid, nz=1, period=1.0, maxshift=0.4): start = 0 for y in range(ny): - end = start + nx * nz phi = zshift[:, y] + phi0[:, None] diff --git a/boututils/closest_line.py b/boututils/closest_line.py index 5d6f136..7f627d5 100644 --- a/boututils/closest_line.py +++ b/boututils/closest_line.py @@ -5,7 +5,6 @@ # Find the closest contour line to a given point def closest_line(n, x, y, ri, zi, mind=None): - mind = numpy.min((x[0] - ri) ** 2 + (y[0] - zi) ** 2) ind = 0 diff --git a/boututils/crosslines.py b/boututils/crosslines.py index aed6186..ae32251 100644 --- a/boututils/crosslines.py +++ b/boututils/crosslines.py @@ -133,7 +133,6 @@ def meshgrid_as_strided(x, y, mask=None): nans_[:, :] = np.isnan(ua) if not np.ma.any(nans): - # remove duplicate cases where intersection happens on an endpoint # ignore[np.ma.where((ua[:, :-1] == 1) & (ua[:, 1:] == 0))] = True # ignore[np.ma.where((ub[:-1, :] == 1) & (ub[1:, :] == 0))] = True diff --git a/boututils/datafile.py b/boututils/datafile.py index 0072c88..2e3d783 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -535,7 +535,6 @@ def _bout_dimensions_from_var(self, data): return BoutArray.dims_from_type(bout_type) def write(self, name, data, info=False): - if not self.writeable: raise Exception("File not writeable. Open with write=True keyword") @@ -914,7 +913,6 @@ def size(self, varname): return var.shape def write(self, name, data, info=False): - if not self.writeable: raise Exception("File not writeable. Open with write=True keyword") @@ -977,7 +975,6 @@ def list_file_attributes(self): return self.handle.attrs.keys() def attributes(self, varname): - try: return self._attributes_cache[varname] except KeyError: diff --git a/boututils/efit_analyzer.py b/boututils/efit_analyzer.py index 19a4b58..6433653 100644 --- a/boututils/efit_analyzer.py +++ b/boututils/efit_analyzer.py @@ -31,7 +31,6 @@ def View2D(g, option=0): - # plot and check the field fig = figure(num=2, figsize=(16, 6)) # fig.suptitle('Efit Analysis', fontsize=20) @@ -350,7 +349,6 @@ def View2D(g, option=0): def surface(cs, i, f, opt_ri, opt_zi, style, iplot=0): - # contour_lines( F, np.arange(nx).astype(float), np.arange(ny).astype(float), # levels=[start_f]) # cs=contour( g.r, g.z, g.psi, levels=[f]) @@ -400,7 +398,6 @@ def surface(cs, i, f, opt_ri, opt_zi, style, iplot=0): # y=yy # if iplot == 0: - # plot the start_f line zc = cs.collections[i] setp(zc, linewidth=4, linestyle=style[i]) @@ -417,7 +414,6 @@ def surface(cs, i, f, opt_ri, opt_zi, style, iplot=0): if __name__ == "__main__": - path = "../../tokamak_grids/pyGridGen/" g = read_geqdsk(path + "g118898.03400") diff --git a/boututils/fft_integrate.py b/boututils/fft_integrate.py index 2b53f29..d693891 100644 --- a/boututils/fft_integrate.py +++ b/boututils/fft_integrate.py @@ -39,7 +39,6 @@ def fft_integrate(y, loop=None): def test_integrate(): - n = 10 dx = 2.0 * np.pi / np.float(n) x = dx * np.arange(n) diff --git a/boututils/int_func.py b/boututils/int_func.py index 8eb75c9..6c0e436 100644 --- a/boututils/int_func.py +++ b/boututils/int_func.py @@ -37,7 +37,6 @@ def int_func(xin, fin=None, simple=None): g[i] = g[i - 1] + 0.5 * (x[i] - x[i - 1]) * (f[i] + f[i - 1]) else: - n2 = numpy.int(old_div(n, 2)) g[0] = 0.0 diff --git a/boututils/mode_structure.py b/boututils/mode_structure.py index 19dd69e..e2d50dc 100644 --- a/boututils/mode_structure.py +++ b/boututils/mode_structure.py @@ -19,7 +19,6 @@ # interpolates a 1D periodic function def zinterp(v, zind): - v = numpy.ravel(v) nz = numpy.size(v) @@ -70,7 +69,6 @@ def mode_structure( pmodes=None, _extra=None, ): - # ON_ERROR, 2 # # period = 1 ; default = full torus @@ -362,7 +360,6 @@ def mode_structure( # # if subset is not None: - # get number of modes larger than 5% of the maximum count = numpy.size(numpy.where(fmax > 0.10 * numpy.max(fmax))) diff --git a/boututils/options.py b/boututils/options.py index b3bbc8c..2487315 100644 --- a/boututils/options.py +++ b/boututils/options.py @@ -57,7 +57,6 @@ class BOUTOptions(object): """ def __init__(self, inp_path=None): - self._sections = ["root"] for section in self._sections: diff --git a/boututils/plotpolslice.py b/boututils/plotpolslice.py index 4e96a69..cfd9750 100644 --- a/boututils/plotpolslice.py +++ b/boututils/plotpolslice.py @@ -126,7 +126,6 @@ def plotpolslice(var3d, gridfile, period=1, zangle=0.0, rz=1, fig=0): z[x, ypos] = zxy[x, ny - 1] if fig == 1: - f = mlab.figure(size=(600, 600)) # Tell visual to use this as the viewer. visual.set_viewer(f) diff --git a/boututils/radial_grid.py b/boututils/radial_grid.py index db96f54..6dbeb3d 100644 --- a/boututils/radial_grid.py +++ b/boututils/radial_grid.py @@ -16,7 +16,6 @@ def radial_grid( n, pin, pout, include_in, include_out, seps, sep_factor, in_dp=None, out_dp=None ): - if n == 1: return [0.5 * (pin + pout)] diff --git a/boututils/read_geqdsk.py b/boututils/read_geqdsk.py index 2172cb6..95f7c7a 100644 --- a/boututils/read_geqdsk.py +++ b/boututils/read_geqdsk.py @@ -7,7 +7,6 @@ def read_geqdsk(file): - data = Geqdsk() data.openFile(file) diff --git a/boututils/showdata.py b/boututils/showdata.py index 6163bc1..350cb65 100644 --- a/boututils/showdata.py +++ b/boututils/showdata.py @@ -486,7 +486,6 @@ def showdata( clevels = [] for i in range(0, Nvar): - dummymax.append([]) dummymin.append([]) for j in range(0, Nlines[i]): From 57047398d887704bfecad74de54f25c3fd076738 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 12 May 2023 14:31:49 +0100 Subject: [PATCH 84/98] Move to pyproject.toml package config --- pyproject.toml | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 8 ------ setup.py | 63 --------------------------------------------- 3 files changed, 69 insertions(+), 71 deletions(-) delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml index ec95545..99febf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,71 @@ +[build-system] +requires = [ + "setuptools >= 61.0.0", + "setuptools_scm[toml] >= 6.2", +] +build-backend = "setuptools.build_meta" + +[project] +name = "boututils" +description = "Python utilities for BOUT++" +readme = "README.md" +authors = [{name = "Ben Dudson"}, {name = "BOUT++ team"}] +license = {file = "LICENSE"} +dynamic = ["version"] +classifiers = [ + "Programming Language :: Python :: 3", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", + "Operating System :: OS Independent", + "Topic :: Software Development :: Libraries :: Python Modules", +] +keywords=[ + "bout++", + "bout", + "plasma", + "physics", + "data-extraction", + "data-analysis", + "data-visualization", +] +requires-python = ">=3.8" +dependencies = [ + "numpy>=1.22.0", + "matplotlib>=3.2.1", + "scipy>=1.4.1", + "netCDF4", +] + +[project.optional-dependencies] +tests = [ + "pytest", + "pytest-cov", +] +docs = [ + "sphinx>=3.4,<5", +] + +[project.urls] +Tracker = "https://github.com/boutproject/boututils/issues/" +Documentation = "https://bout-dev.readthedocs.io/en/latest/" +Source = "https://github.com/boutproject/boututils/" + +[tool.setuptools] +packages = ["boututils"] + +[tool.setuptools.dynamic] +version = { attr = "setuptools_scm.get_version" } + [tool.setuptools_scm] write_to = "boututils/_version.py" + +[tool.pytest.ini_options] +addopts = "--cov=boututils" + +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true +line_length = 88 diff --git a/setup.cfg b/setup.cfg index 3a0e34b..22cf2e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,3 @@ -[isort] -multi_line_output = 3 -include_trailing_comma = True -force_grid_wrap = 0 -use_parentheses = True -ensure_newline_before_comments = True -line_length = 88 - [flake8] max-line-length = 88 extend-ignore = E203, W503, E722 diff --git a/setup.py b/setup.py deleted file mode 100644 index 03b6934..0000000 --- a/setup.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from pathlib import Path - -import setuptools - -name = "boututils" -root_path = Path(__file__).parent -init_path = root_path.joinpath(name, "__init__.py") -readme_path = root_path.joinpath("README.md") - -with readme_path.open("r") as f: - long_description = f.read() - -setuptools.setup( - name=name, - author="Ben Dudson et al.", - description="Python package containing BOUT++ utils", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/boutproject/boututils", - project_urls={ - "Bug Tracker": "https://github.com/boutproject/boututils/issues/", - "Documentation": "https://bout-dev.readthedocs.io/en/latest/", - "Source Code": "https://github.com/boutproject/boututils/", - }, - packages=setuptools.find_packages(), - keywords=[ - "bout++", - "bout", - "plasma", - "physics", - "data-extraction", - "data-analysis", - "data-visualization", - ], - use_scm_version=True, - setup_requires=[ - "setuptools>=42", - "setuptools_scm[toml]>=3.4", - "setuptools_scm_git_archive", - ], - install_requires=[ - "numpy", - "matplotlib", - "scipy", - "netCDF4", - "importlib-metadata ; python_version<'3.8'", - ], - extras_require={ - "mayavi": ["mayavi", "PyQt5"], - "hdf5": ["h5py"], - }, - classifiers=[ - "Programming Language :: Python :: 3", - ( - "License :: OSI Approved :: " - "GNU Lesser General Public License v3 or later (LGPLv3+)" - ), - "Operating System :: OS Independent", - ], -) From 4f771bbb2a9196be3195bd771dcd58b261785541 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 12 May 2023 14:45:47 +0100 Subject: [PATCH 85/98] Refactor CI - Use ruff instead of flake8 - Auto-apply black and isort on commits to master - Bump CI action versions - Drop support for Python 3.7 - Test on Python 3.8, 3.11 - Don't run cron jobs Fixes #39 --- .github/workflows/formatting.yml | 38 +++++++++++++++++++++++++++++ .github/workflows/lint.yml | 42 -------------------------------- .github/workflows/test.yml | 41 ++++++++++++------------------- .pre-commit-config.yaml | 29 ---------------------- setup.cfg | 3 --- 5 files changed, 53 insertions(+), 100 deletions(-) create mode 100644 .github/workflows/formatting.yml delete mode 100644 .github/workflows/lint.yml delete mode 100644 .pre-commit-config.yaml delete mode 100644 setup.cfg diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml new file mode 100644 index 0000000..d160e3c --- /dev/null +++ b/.github/workflows/formatting.yml @@ -0,0 +1,38 @@ +name: Formatting (black & isort) +on: + push: + branches: master + paths: + - '**.py' + +defaults: + run: + shell: bash + +jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: 3.x + - name: Install black + run: | + python -m pip install --upgrade pip + pip install black isort + - name: Version + run: | + python --version + black --version + isort --version + - name: Run black + run: | + black boututils + - name: Run isort + run: | + isort boututils + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: "[skip ci] Apply black/isort changes" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 5ae524b..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Lint - -on: - # Run each time we push and pull requests - push: - pull_request: - # Cron job - # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows#scheduled-events-schedule - schedule: - # https://crontab.guru/#0_0_1_*_* - - cron: "0 0 1 * *" - -jobs: - linting: - runs-on: ubuntu-latest - - steps: - # Use the v2 tag of: https://github.com/actions/checkout - - name: Checkout repo - uses: actions/checkout@v2 - - - name: Set up Python 3.x - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - - name: Set up Python ${{ matrix.python-version }} - # Use the v2 tag of: https://github.com/actions/setup-python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - # https://github.com/PyCQA/pylint/issues/352 - # pylint need the requirements to do the checks as the pre-commit repo is local - run: | - python -m pip install --upgrade pip - python -m pip install --upgrade pre-commit - pip install . - - - name: Lint - run: pre-commit run --all-files diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index af00372..52a1a3e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,32 +1,21 @@ -name: Test - -on: - # Run each time we push and pull requests - push: - pull_request: - # Cron job - # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows#scheduled-events-schedule - schedule: - # https://crontab.guru/#0_0_1_*_* - - cron: "0 0 1 * *" +name: Test and lint + +on: [push, pull_request] jobs: - # As we are running on different environments, we are splitting the jobs - # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobs - local: - runs-on: ${{ matrix.os }} + test: + runs-on: ubuntu-latest strategy: fail-fast: true matrix: - python-version: [3.7, 3.8] - os: [ubuntu-latest] + python-version: ["3.8", "3.11"] steps: - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -38,14 +27,14 @@ jobs: - name: Install dependencies run: | - pip install setuptools_scm pytest-cov - pip install . + pip install .[tests] - name: Test local run run: | - pytest --cov=./ + pytest - - name: Upload to codecov - run: | - pip install codecov - codecov + lint-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: chartboost/ruff-action@v1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 96c0be0..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# NOTE: The versions can be updated by calling -# pre-commit autoupdate -repos: - - repo: https://github.com/psf/black - rev: 23.1.0 - hooks: - - id: black - - - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - - repo: https://github.com/prettier/pre-commit - rev: 57f39166b5a5a504d6808b87ab98d41ebf095b46 - hooks: - - id: prettier - args: [--prose-wrap=always, --print-width=88] - - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 22cf2e6..0000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -max-line-length = 88 -extend-ignore = E203, W503, E722 From 80d1fb178d9ce1680d636e1c145bafff4ab1b09f Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 12 May 2023 14:50:16 +0100 Subject: [PATCH 86/98] Fix bare `except` Closes #29 --- boututils/datafile.py | 4 ++-- boututils/options.py | 2 +- boututils/showdata.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/boututils/datafile.py b/boututils/datafile.py index 2906cb7..3747fea 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -652,7 +652,7 @@ def find_dim(dim): try: # Some libraries allow this for arrays var.assignValue(data) - except: + except Exception: # And some others only this var[:] = data @@ -706,7 +706,7 @@ def attributes(self, varname): attributes[attrname] = var.getncattr( attrname ) # Get all values and insert into map - except: + except Exception: print("Error reading attributes for " + varname) # Result will be an empty map diff --git a/boututils/options.py b/boututils/options.py index 2487315..78ccf7b 100644 --- a/boututils/options.py +++ b/boututils/options.py @@ -77,7 +77,7 @@ def read_inp(self, inp_path=""): try: inpfile = open(os.path.join(inp_path, "BOUT.inp"), "r") - except: + except OSError: raise TypeError( "ERROR: Could not read file " + os.path.join(inp_path, "BOUT.inp") ) diff --git a/boututils/showdata.py b/boututils/showdata.py index 350cb65..9e72f4b 100644 --- a/boututils/showdata.py +++ b/boututils/showdata.py @@ -448,7 +448,7 @@ def showdata( + ", the shape of x is not compatible with the shape of the " "variable. Shape of x should be (Nx), (Nx,Ny) or (Nt,Nx,Ny)." ) - except: + except ValueError: for j in range(0, Nlines[i]): xnew[i].append(linspace(0, Nx[i][j] - 1, Nx[i][j])) @@ -470,7 +470,7 @@ def showdata( + ", the shape of y is not compatible with the shape of the " "variable. Shape of y should be (Ny), (Nx,Ny) or (Nt,Nx,Ny)." ) - except: + except ValueError: ynew.append(linspace(0, Ny[i][0] - 1, Ny[i][0])) else: ynew.append(0) From 098c1ab186e53c2811aa7412de399a1f52fd764b Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 12 May 2023 15:01:23 +0100 Subject: [PATCH 87/98] CI: Update publishing workflow Use PyPA's new "Trusted Publisher" thing: https://docs.pypi.org/trusted-publishers/ Fixes #49 --- .github/workflows/python_publish.yml | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/.github/workflows/python_publish.yml b/.github/workflows/python_publish.yml index 4c0c66b..1a519fb 100644 --- a/.github/workflows/python_publish.yml +++ b/.github/workflows/python_publish.yml @@ -1,27 +1,26 @@ name: Upload Python Package on: - # Only run when a release is created release: - types: [created] + types: [published] jobs: deploy: runs-on: ubuntu-latest + permissions: + # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: - python-version: "3.x" + python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* + pip install build + - name: Build package + run: python -m build --sdist --wheel + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 From a967915f144c80619f97bda468f3d394fdaf499f Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 12 May 2023 15:04:32 +0100 Subject: [PATCH 88/98] Remove fallback backport of importlib.metadata We now only support Python 3.8+ --- boututils/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/boututils/__init__.py b/boututils/__init__.py index ca7cba7..e30e1c9 100644 --- a/boututils/__init__.py +++ b/boututils/__init__.py @@ -3,10 +3,8 @@ __all__ = [] __name__ = "boututils" -try: - from importlib.metadata import PackageNotFoundError, version -except ModuleNotFoundError: - from importlib_metadata import PackageNotFoundError, version +from importlib.metadata import PackageNotFoundError, version + try: # This gives the version if the boututils package was installed __version__ = version(__name__) From 70540aa00ef14f92e36a9915b47c19245048313e Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 12 May 2023 15:10:49 +0100 Subject: [PATCH 89/98] Apply black --- boututils/calculus.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boututils/calculus.py b/boututils/calculus.py index 3cdb233..5554163 100644 --- a/boututils/calculus.py +++ b/boututils/calculus.py @@ -44,7 +44,7 @@ def deriv(*args, **kwargs): n = var.size if periodic: - # assume uniform grid + # assume uniform grid dx = x[1] - x[0] # Use FFTs to take derivatives f = rfft(var) @@ -52,12 +52,12 @@ def deriv(*args, **kwargs): if n % 2 == 0: # Even n for i in arange(1, old_div(n, 2)): - f[i] *= 2.0j * pi * float(i) / (float(n)*dx) + f[i] *= 2.0j * pi * float(i) / (float(n) * dx) f[-1] = 0.0 # Nothing from Nyquist frequency else: # Odd n for i in arange(1, old_div((n - 1), 2) + 1): - f[i] *= 2.0j * pi * float(i) / (float(n)*dx) + f[i] *= 2.0j * pi * float(i) / (float(n) * dx) return irfft(f) else: # Non-periodic function From 5fdba8e798f0ca3944a33f71cf19fd45a10c0d2c Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 12 May 2023 15:27:41 +0100 Subject: [PATCH 90/98] Replace most common uses of string concatenation with f-strings --- boututils/datafile.py | 46 +++++++++++++++------------------------- boututils/options.py | 9 ++++---- boututils/run_wrapper.py | 21 ++++++++---------- 3 files changed, 31 insertions(+), 45 deletions(-) diff --git a/boututils/datafile.py b/boututils/datafile.py index 3747fea..b251433 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -402,7 +402,7 @@ def read(self, name, ranges=None, asBoutArray=True): var = None for n in list(self.handle.variables.keys()): if n.lower() == name.lower(): - print("WARNING: Reading '" + n + "' instead of '" + name + "'") + print(f"WARNING: Reading '{n}' instead of '{name}'") var = self.handle.variables[n] if var is None: return None @@ -424,9 +424,7 @@ def read(self, name, ranges=None, asBoutArray=True): elif len(ranges) != ndims: raise ValueError( "Incorrect number of elements in ranges argument " - "(got {}, expected {} or {})".format( - len(ranges), ndims, 2 * ndims - ) + f"(got {ranges}, expected {ndims} or {2 * ndims})" ) data = var[ranges[:ndims]] @@ -442,7 +440,7 @@ def read(self, name, ranges=None, asBoutArray=True): def __getitem__(self, name): var = self.read(name) if var is None: - raise KeyError("No variable found: " + name) + raise KeyError(f"No variable found: {name}") return var def __setitem__(self, key, value): @@ -524,13 +522,9 @@ def _bout_dimensions_from_var(self, data): if bout_type == "string_t": nt, string_length = data.shape - return ( - "t", - "char" + str(string_length), - ) + return ("t", f"char{string_length}") elif bout_type == "string": - string_length = len(data) - return ("char" + str(string_length),) + return (f"char{len(data)}",) return BoutArray.dims_from_type(bout_type) @@ -570,7 +564,7 @@ def write(self, name, data, info=False): # Check the shape of the variable if var.shape != s: - print("DataFile: Variable already exists with different size: " + name) + print(f"DataFile: Variable already exists with different size: {name}") # Fallthrough to the exception raise KeyError except KeyError: @@ -608,16 +602,14 @@ def find_dim(dim): # None found, so create a new one i = 2 while True: - dn = name + str(i) + dn = f"{name}{i}" try: d = self.handle.dimensions[dn] # Already exists, so keep going except KeyError: # Not found. Create if info: - print( - "Defining dimension {} of size {}".format(dn, size) - ) + print(f"Defining dimension {dn} of size {size}") self.handle.createDimension(dn, size) return dn @@ -626,7 +618,7 @@ def find_dim(dim): except KeyError: # Doesn't exist, so add if info: - print("Defining dimension " + name + " of size %d" % size) + print(f"Defining dimension {name} of size {size}") if name == "t": size = None @@ -691,9 +683,7 @@ def attributes(self, varname): var = None for n in list(self.handle.variables.keys()): if n.lower() == varname.lower(): - print( - "WARNING: Reading '" + n + "' instead of '" + varname + "'" - ) + print(f"WARNING: Reading '{n}' instead of '{varname}'") var = self.handle.variables[n] if var is None: return None @@ -707,7 +697,7 @@ def attributes(self, varname): attrname ) # Get all values and insert into map except Exception: - print("Error reading attributes for " + varname) + print(f"Error reading attributes for {varname}") # Result will be an empty map if "bout_type" not in attributes: @@ -766,7 +756,7 @@ def read(self, name, ranges=None, asBoutArray=True): var = None for n in self.handle: if n.lower() == name.lower(): - print("WARNING: Reading '" + n + "' instead of '" + name + "'") + print(f"WARNING: Reading '{n}' instead of '{name}'") var = self.handle[n] if var is None: return None @@ -789,9 +779,7 @@ def read(self, name, ranges=None, asBoutArray=True): elif len(ranges) != ndims: raise ValueError( "Incorrect number of elements in ranges argument " - "(got {}, expected {} or {})".format( - len(ranges), ndims, 2 * ndims - ) + f"(got {ranges}, expected {ndims} or {2 * ndims})" ) # Probably a bug in h5py, work around by passing tuple data = var[tuple(ranges[:ndims])] @@ -807,7 +795,7 @@ def read(self, name, ranges=None, asBoutArray=True): def __getitem__(self, name): var = self.read(name) if var is None: - raise KeyError("No variable found: " + name) + raise KeyError(f"No variable found: {name}") return var def __setitem__(self, key, value): @@ -886,7 +874,7 @@ def _bout_type_from_array(self, data): elif ndim == 0: return "scalar" else: - raise ValueError("Unrecognized variable bout_type, ndims=" + str(ndim)) + raise ValueError(f"Unrecognized variable bout_type, ndims={ndim}") def ndims(self, varname): if self.handle is None: @@ -924,7 +912,7 @@ def write(self, name, data, info=False): bout_type = self._bout_type_from_array(data) if info: - print("Creating variable '" + name + "' with bout_type '" + bout_type + "'") + print(f"Creating variable '{name}' with bout_type '{bout_type}'") if bout_type[-2:] == "_t": # time evolving fields @@ -992,7 +980,7 @@ def attributes(self, varname): # bout_type is a required attribute for BOUT++ outputs, so it should # have been found raise ValueError( - "Error: bout_type not found in attributes of " + varname + f"Error: bout_type not found in attributes of {varname}" ) # Save the attributes for this variable to the cache diff --git a/boututils/options.py b/boututils/options.py index 78ccf7b..110be0f 100644 --- a/boututils/options.py +++ b/boututils/options.py @@ -75,11 +75,12 @@ def read_inp(self, inp_path=""): """ + filename = os.path.join(inp_path, "BOUT.inp") try: - inpfile = open(os.path.join(inp_path, "BOUT.inp"), "r") + inpfile = open(filename, "r") except OSError: raise TypeError( - "ERROR: Could not read file " + os.path.join(inp_path, "BOUT.inp") + f"ERROR: Could not read file {filename}" ) current_section = "root" @@ -142,7 +143,7 @@ def remove_section(self, section): self._sections.pop(self._sections.index(section)) super(BOUTOptions, self).__delattr__(section) else: - print("WARNING: Section " + section + " not found.\n") + print(f"WARNING: Section {section} not found.\n") def list_sections(self, verbose=False): """Return all the sections in the options @@ -159,6 +160,6 @@ def list_sections(self, verbose=False): if verbose: print("Sections Contained: \n") for section in self._sections: - print("\t" + section + "\n") + print("\t{section}\n") return self._sections diff --git a/boututils/run_wrapper.py b/boututils/run_wrapper.py index 9b93fd7..3575327 100644 --- a/boututils/run_wrapper.py +++ b/boututils/run_wrapper.py @@ -28,7 +28,7 @@ def getmpirun(default=DEFAULT_MPIRUN): if MPIRUN is None or MPIRUN == "": MPIRUN = default - print("getmpirun: using the default " + str(default)) + print(f"getmpirun: using the default {default}") return MPIRUN @@ -176,7 +176,7 @@ def determineNumberOfCPUs(): dmesg = dmesgProcess.communicate()[0] res = 0 - while "\ncpu" + str(res) + ":" in dmesg: + while f"\ncpu{res}:" in dmesg: res += 1 if res > 0: @@ -232,17 +232,17 @@ def launch( # Determine number of CPUs on this machine nproc = determineNumberOfCPUs() - cmd = runcmd + " " + str(nproc) + " " + command + cmd = f"{runcmd} {nproc} {command}" if output is not None: - cmd = cmd + " > " + output + cmd = f"{cmd} > {output}" if mthread is not None: if os.name == "nt": # We're on windows, so we have to do it a little different - cmd = 'cmd /C "set OMP_NUM_THREADS={} && {}"'.format(mthread, cmd) + cmd = f'cmd /C "set OMP_NUM_THREADS={mthread} && {cmd}"' else: - cmd = "OMP_NUM_THREADS={} {}".format(mthread, cmd) + cmd = f"OMP_NUM_THREADS={mthread} {cmd}" if verbose: print(cmd) @@ -289,8 +289,7 @@ def launch_safe(command, *args, **kwargs): s, out = launch(command, *args, **kwargs) if s: raise RuntimeError( - "Run failed with %d.\nCommand was:\n%s\n\n" - "Output was\n\n%s" % (s, command, out) + f"Run failed with {s}.\nCommand was:\n{command}\n\nOutput was\n\n{out}" ) return s, out @@ -306,7 +305,7 @@ def build_and_log(test): if os.name == "nt": return - print("Making {}".format(test)) + print(f"Making {test}") if os.path.exists("makefile") or os.path.exists("Makefile"): return shell_safe("make > make.log") @@ -330,9 +329,7 @@ def build_and_log(test): here = pathlib.Path(".").absolute() for parent in here.parents: if (parent / "CMakeCache.txt").exists(): - return shell_safe( - "cmake --build {} --target {} > make.log".format(parent, test_name) - ) + return shell_safe(f"cmake --build {parent} --target {test_name} > make.log") # We've just looked up the entire directory structure and not # found the build directory, this could happen if CMakeCache was From 5dc35adbb3c55b0dc7be5f91f79dab9379426789 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Fri, 12 May 2023 15:33:14 +0100 Subject: [PATCH 91/98] Remove imports from `builtin` --- boututils/View3D.py | 1 - boututils/analyse_equil_2.py | 2 -- boututils/anim.py | 1 - boututils/ask.py | 1 - boututils/boutgrid.py | 2 -- boututils/calculus.py | 1 - boututils/closest_line.py | 2 -- boututils/datafile.py | 2 -- boututils/efit_analyzer.py | 1 - boututils/fft_deriv.py | 2 -- boututils/fft_integrate.py | 2 -- boututils/geqdsk.py | 1 - boututils/idl_tabulate.py | 2 -- boututils/int_func.py | 1 - boututils/mode_structure.py | 1 - boututils/moment_xyzt.py | 2 -- boututils/options.py | 4 +--- boututils/plotpolslice.py | 1 - boututils/read_geqdsk.py | 2 -- boututils/run_wrapper.py | 1 - boututils/showdata.py | 1 - boututils/spectrogram.py | 1 - boututils/surface_average.py | 2 -- boututils/volume_integral.py | 2 -- boututils/watch.py | 1 - 25 files changed, 1 insertion(+), 38 deletions(-) diff --git a/boututils/View3D.py b/boututils/View3D.py index f9aa417..e1dc597 100644 --- a/boututils/View3D.py +++ b/boututils/View3D.py @@ -11,7 +11,6 @@ """ import sys -from builtins import range import numpy as np from boutdata.collect import collect diff --git a/boututils/analyse_equil_2.py b/boututils/analyse_equil_2.py index 5e43d03..ed2519a 100644 --- a/boututils/analyse_equil_2.py +++ b/boututils/analyse_equil_2.py @@ -4,8 +4,6 @@ """ -from builtins import range, str, zip - import numpy from crosslines import find_inter from matplotlib.pyplot import annotate, contour, draw, gradient, plot diff --git a/boututils/anim.py b/boututils/anim.py index 679c2d4..bb895cc 100644 --- a/boututils/anim.py +++ b/boututils/anim.py @@ -2,7 +2,6 @@ import os -from builtins import range import numpy as np from boutdata.collect import collect diff --git a/boututils/ask.py b/boututils/ask.py index cae018c..301dca7 100644 --- a/boututils/ask.py +++ b/boututils/ask.py @@ -3,7 +3,6 @@ """ import sys -from builtins import input def query_yes_no(question, default="yes"): diff --git a/boututils/boutgrid.py b/boututils/boutgrid.py index 6e62ad9..6172dd3 100644 --- a/boututils/boutgrid.py +++ b/boututils/boutgrid.py @@ -1,5 +1,3 @@ -from builtins import range - import numpy as np from numpy import cos, pi, sin from tvtk.api import tvtk diff --git a/boututils/calculus.py b/boututils/calculus.py index 5554163..ddd0207 100644 --- a/boututils/calculus.py +++ b/boututils/calculus.py @@ -5,7 +5,6 @@ B.Dudson, University of York, Nov 2009 """ -from builtins import range try: from past.utils import old_div diff --git a/boututils/closest_line.py b/boututils/closest_line.py index 7f627d5..d8335c0 100644 --- a/boututils/closest_line.py +++ b/boututils/closest_line.py @@ -1,5 +1,3 @@ -from builtins import range - import numpy diff --git a/boututils/datafile.py b/boututils/datafile.py index b251433..86c7eea 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -24,8 +24,6 @@ """ -from builtins import map, object, str, zip - import numpy as np from boututils.boutarray import BoutArray diff --git a/boututils/efit_analyzer.py b/boututils/efit_analyzer.py index 6433653..8822524 100644 --- a/boututils/efit_analyzer.py +++ b/boututils/efit_analyzer.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from builtins import range import numpy as np from past.utils import old_div diff --git a/boututils/fft_deriv.py b/boututils/fft_deriv.py index 683fea2..921e6b9 100644 --- a/boututils/fft_deriv.py +++ b/boututils/fft_deriv.py @@ -1,5 +1,3 @@ -from builtins import range - import numpy from past.utils import old_div diff --git a/boututils/fft_integrate.py b/boututils/fft_integrate.py index d693891..2ef9197 100644 --- a/boututils/fft_integrate.py +++ b/boututils/fft_integrate.py @@ -1,5 +1,3 @@ -from builtins import range - import numpy as np from pylab import plot, show diff --git a/boututils/geqdsk.py b/boututils/geqdsk.py index 8d172ca..16d8b96 100644 --- a/boututils/geqdsk.py +++ b/boututils/geqdsk.py @@ -1,5 +1,4 @@ import re -from builtins import object, range, str import numpy diff --git a/boututils/idl_tabulate.py b/boututils/idl_tabulate.py index a7ded1e..a1b1c8b 100644 --- a/boututils/idl_tabulate.py +++ b/boututils/idl_tabulate.py @@ -1,5 +1,3 @@ -from builtins import range - import numpy as np import scipy diff --git a/boututils/int_func.py b/boututils/int_func.py index 6c0e436..6d701ce 100644 --- a/boututils/int_func.py +++ b/boututils/int_func.py @@ -1,5 +1,4 @@ import copy -from builtins import range import numpy from past.utils import old_div diff --git a/boututils/mode_structure.py b/boututils/mode_structure.py index e2d50dc..8f4fe60 100644 --- a/boututils/mode_structure.py +++ b/boututils/mode_structure.py @@ -1,5 +1,4 @@ import sys -from builtins import range import numpy as numpy from past.utils import old_div diff --git a/boututils/moment_xyzt.py b/boututils/moment_xyzt.py index ba8f2a3..dbee422 100644 --- a/boututils/moment_xyzt.py +++ b/boututils/moment_xyzt.py @@ -1,5 +1,3 @@ -from builtins import range - import numpy as np from past.utils import old_div diff --git a/boututils/options.py b/boututils/options.py index 110be0f..24f1492 100644 --- a/boututils/options.py +++ b/boututils/options.py @@ -79,9 +79,7 @@ def read_inp(self, inp_path=""): try: inpfile = open(filename, "r") except OSError: - raise TypeError( - f"ERROR: Could not read file {filename}" - ) + raise TypeError(f"ERROR: Could not read file {filename}") current_section = "root" inplines = inpfile.read().splitlines() diff --git a/boututils/plotpolslice.py b/boututils/plotpolslice.py index cfd9750..9fcd87d 100644 --- a/boututils/plotpolslice.py +++ b/boututils/plotpolslice.py @@ -1,5 +1,4 @@ import sys -from builtins import range, str import numpy as np from past.utils import old_div diff --git a/boututils/read_geqdsk.py b/boututils/read_geqdsk.py index 95f7c7a..e804c59 100644 --- a/boututils/read_geqdsk.py +++ b/boututils/read_geqdsk.py @@ -1,5 +1,3 @@ -from builtins import range - import numpy from boututils.bunch import Bunch diff --git a/boututils/run_wrapper.py b/boututils/run_wrapper.py index 3575327..666dcf9 100644 --- a/boututils/run_wrapper.py +++ b/boututils/run_wrapper.py @@ -4,7 +4,6 @@ import pathlib import re import subprocess -from builtins import str from subprocess import PIPE, STDOUT, Popen, call if os.name == "nt": diff --git a/boututils/showdata.py b/boututils/showdata.py index 9e72f4b..5de90f0 100644 --- a/boututils/showdata.py +++ b/boututils/showdata.py @@ -8,7 +8,6 @@ """ -from builtins import chr, range, str from matplotlib import animation from matplotlib import pyplot as plt diff --git a/boututils/spectrogram.py b/boututils/spectrogram.py index aeb76a3..9465e0c 100644 --- a/boututils/spectrogram.py +++ b/boututils/spectrogram.py @@ -6,7 +6,6 @@ """ -from builtins import range from numpy import arange, cos, exp, linspace, max, min, power, sin, transpose, zeros from scipy import fftpack, pi diff --git a/boututils/surface_average.py b/boututils/surface_average.py index cc85c01..854e52d 100644 --- a/boututils/surface_average.py +++ b/boututils/surface_average.py @@ -3,8 +3,6 @@ """ -from builtins import range - import numpy as np from past.utils import old_div diff --git a/boututils/volume_integral.py b/boututils/volume_integral.py index c86e081..87cd225 100644 --- a/boututils/volume_integral.py +++ b/boututils/volume_integral.py @@ -3,8 +3,6 @@ """ -from builtins import range - import numpy as np from past.utils import old_div diff --git a/boututils/watch.py b/boututils/watch.py index 4ac73c6..e499aeb 100644 --- a/boututils/watch.py +++ b/boututils/watch.py @@ -5,7 +5,6 @@ import os import time -from builtins import zip def watch(files, timeout=None, poll=2): From 6ba636f45f489648914aeb66ecc7797bc7ade859 Mon Sep 17 00:00:00 2001 From: David Bold Date: Fri, 5 Jan 2024 10:53:23 +0100 Subject: [PATCH 92/98] Update README.md BOUT++ 4.3.0 was released a while ago xBOUT depends on this, so I doubt it is going to replace it anytime soon. Whether it will ever happen is a different story ... --- README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9c87cc9..f627bde 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,8 @@ [![License](https://img.shields.io/badge/license-LGPL--3.0-blue.svg)](https://github.com/boutproject/boututils/blob/master/LICENSE) pip-package of what was previously found in `BOUT-dev/tools/pylib/boututils` Note that -`BOUT-dev/tools/pylib/boututils` will likely be replaced by this repo in -`BOUT++ v4.3.0`. See [this issue](https://github.com/boutproject/BOUT-dev/issues/1347), -[this pull request](https://github.com/boutproject/BOUT-dev/pull/1766) and -[this pull request](https://github.com/boutproject/BOUT-dev/pull/1740) for details. - -> **NOTE**: This package will likely be superseded by -> [`xBOUT`](https://github.com/boutproject/xBOUT) in the near future +`BOUT-dev/tools/pylib/boututils` was replaced by this repo in +`BOUT++ v4.3.0`. # Dependencies From 2c30f24ab200908503417a1c2684baee59527589 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 30 Apr 2024 09:59:17 +0200 Subject: [PATCH 93/98] Do not compare types, use `isinstance()` --- boututils/datafile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/boututils/datafile.py b/boututils/datafile.py index 86c7eea..b14d960 100644 --- a/boututils/datafile.py +++ b/boututils/datafile.py @@ -23,7 +23,6 @@ """ - import numpy as np from boututils.boutarray import BoutArray @@ -942,7 +941,7 @@ def write(self, name, data, info=False): try: for attrname, attrval in data.attributes.items(): - if type(attrval) == str: + if isinstance(attrval, str): attrval = attrval.encode(encoding="utf-8") self.handle[name].attrs.create(attrname, attrval) except AttributeError: From 0dd5d960ba8fdf57ccb1d3cf80d8d27431cfdc28 Mon Sep 17 00:00:00 2001 From: David Bold Date: Tue, 30 Apr 2024 10:02:04 +0200 Subject: [PATCH 94/98] black --- boututils/analyse_equil_2.py | 1 - boututils/anim.py | 1 - boututils/calculus.py | 1 - boututils/showdata.py | 1 - boututils/spectrogram.py | 1 - boututils/surface_average.py | 1 - boututils/volume_integral.py | 1 - 7 files changed, 7 deletions(-) diff --git a/boututils/analyse_equil_2.py b/boututils/analyse_equil_2.py index ed2519a..4a79822 100644 --- a/boututils/analyse_equil_2.py +++ b/boututils/analyse_equil_2.py @@ -3,7 +3,6 @@ Takes a RZ psi grid, and finds x-points and o-points """ - import numpy from crosslines import find_inter from matplotlib.pyplot import annotate, contour, draw, gradient, plot diff --git a/boututils/anim.py b/boututils/anim.py index bb895cc..15fc61f 100644 --- a/boututils/anim.py +++ b/boututils/anim.py @@ -1,6 +1,5 @@ """Animate graph with mayavi""" - import os import numpy as np diff --git a/boututils/calculus.py b/boututils/calculus.py index ddd0207..649a32b 100644 --- a/boututils/calculus.py +++ b/boututils/calculus.py @@ -5,7 +5,6 @@ B.Dudson, University of York, Nov 2009 """ - try: from past.utils import old_div except ImportError: diff --git a/boututils/showdata.py b/boututils/showdata.py index 5de90f0..7ff9330 100644 --- a/boututils/showdata.py +++ b/boututils/showdata.py @@ -8,7 +8,6 @@ """ - from matplotlib import animation from matplotlib import pyplot as plt from numpy import abs, array, floor, isclose, linspace, max, meshgrid, min, pi diff --git a/boututils/spectrogram.py b/boututils/spectrogram.py index 9465e0c..1da6e18 100644 --- a/boututils/spectrogram.py +++ b/boututils/spectrogram.py @@ -6,7 +6,6 @@ """ - from numpy import arange, cos, exp, linspace, max, min, power, sin, transpose, zeros from scipy import fftpack, pi diff --git a/boututils/surface_average.py b/boututils/surface_average.py index 854e52d..0e4c1c0 100644 --- a/boututils/surface_average.py +++ b/boututils/surface_average.py @@ -2,7 +2,6 @@ """ - import numpy as np from past.utils import old_div diff --git a/boututils/volume_integral.py b/boututils/volume_integral.py index 87cd225..82c834c 100644 --- a/boututils/volume_integral.py +++ b/boututils/volume_integral.py @@ -2,7 +2,6 @@ """ - import numpy as np from past.utils import old_div From 8211cce2b47aeda4f3bfa51bde3161d3534d1399 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Tue, 23 Jul 2024 09:53:31 +0100 Subject: [PATCH 95/98] Make `boututils` a local package --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2cc8159..f24bdcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ keywords = [ "data-visualization", ] license = {text = "GNU Lesser General Public License v3 or later (LGPLv3+)"} -authors = [{name = "Ben Dudson", email = "benjamin.dudson@york.ac.uk"}] +authors = [{name = "Ben Dudson", email = "benjamin.dudson@york.ac.uk"}, {name = "BOUT++ team"}] urls = {project = "https://github.com/boutproject/boutdata"} dependencies = [ "sympy>=1.5.1", @@ -36,7 +36,7 @@ dependencies = [ "matplotlib>=3.2.1", "natsort>=8.1.0", "scipy>=1.4.1", - "boututils>=0.1.9", + "netCDF4", ] dynamic = ["version"] @@ -53,7 +53,7 @@ docs = [ bout-squashoutput = "boutdata.scripts.bout_squashoutput:main" [tool.setuptools] -packages = ["boutdata"] +packages = ["boutdata", "boututils"] [tool.setuptools.dynamic] version = { attr = "setuptools_scm.get_version" } From e15a3f9490e71bd672b77c1212d31471f51b4d96 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Tue, 23 Jul 2024 10:00:33 +0100 Subject: [PATCH 96/98] Apply black --- boutdata/data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boutdata/data.py b/boutdata/data.py index 7d4fcc3..32e614f 100644 --- a/boutdata/data.py +++ b/boutdata/data.py @@ -695,9 +695,9 @@ def __init__( comments = [] if inline_comment is not None: parent_section.inline_comments[sectionname] = inline_comment - parent_section._comment_whitespace[sectionname] = ( - comment_whitespace - ) + parent_section._comment_whitespace[ + sectionname + ] = comment_whitespace else: # A key=value pair From 716aeccc4da1cee9a53c9c32e50c24d38663d99e Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Tue, 23 Jul 2024 10:23:55 +0100 Subject: [PATCH 97/98] Move both modules under `src` --- .gitignore | 4 ++-- pyproject.toml | 5 +---- {boutdata => src/boutdata}/__init__.py | 0 {boutdata => src/boutdata}/cbdtoeqdsk.py | 0 {boutdata => src/boutdata}/collect.py | 0 {boutdata => src/boutdata}/data.py | 0 {boutdata => src/boutdata}/gen_surface.py | 0 {boutdata => src/boutdata}/griddata.py | 0 {boutdata => src/boutdata}/input.py | 0 {boutdata => src/boutdata}/mayavi2.py | 0 {boutdata => src/boutdata}/mms.py | 0 {boutdata => src/boutdata}/pol_slice.py | 0 {boutdata => src/boutdata}/processor_rearrange.py | 0 {boutdata => src/boutdata}/restart.py | 0 {boutdata => src/boutdata}/scripts/__init__.py | 0 {boutdata => src/boutdata}/scripts/bout_squashoutput.py | 0 {boutdata => src/boutdata}/settings.py | 0 {boutdata => src/boutdata}/shiftz.py | 0 {boutdata => src/boutdata}/squashoutput.py | 0 {boutdata => src/boutdata}/tests/__init__.py | 0 {boutdata => src/boutdata}/tests/make_test_data.py | 0 {boutdata => src/boutdata}/tests/test_boutoptions.py | 0 {boutdata => src/boutdata}/tests/test_collect.py | 0 {boutdata => src/boutdata}/tests/test_import.py | 0 {boutdata => src/boutdata}/tests/test_restart.py | 0 {boututils => src/boututils}/View3D.py | 0 {boututils => src/boututils}/__init__.py | 0 {boututils => src/boututils}/analyse_equil_2.py | 0 {boututils => src/boututils}/anim.py | 0 {boututils => src/boututils}/ask.py | 0 {boututils => src/boututils}/boutarray.py | 0 {boututils => src/boututils}/boutgrid.py | 0 {boututils => src/boututils}/boutwarnings.py | 0 {boututils => src/boututils}/bunch.py | 0 {boututils => src/boututils}/calculus.py | 0 {boututils => src/boututils}/check_scaling.py | 0 {boututils => src/boututils}/closest_line.py | 0 {boututils => src/boututils}/crosslines.py | 0 {boututils => src/boututils}/datafile.py | 0 {boututils => src/boututils}/efit_analyzer.py | 0 {boututils => src/boututils}/fft_deriv.py | 0 {boututils => src/boututils}/fft_integrate.py | 0 {boututils => src/boututils}/file_import.py | 0 {boututils => src/boututils}/geqdsk.py | 0 {boututils => src/boututils}/idl_tabulate.py | 0 {boututils => src/boututils}/int_func.py | 0 {boututils => src/boututils}/linear_regression.py | 0 {boututils => src/boututils}/local_min_max.py | 0 {boututils => src/boututils}/mode_structure.py | 0 {boututils => src/boututils}/moment_xyzt.py | 0 {boututils => src/boututils}/options.py | 0 {boututils => src/boututils}/plotdata.py | 0 {boututils => src/boututils}/plotpolslice.py | 0 {boututils => src/boututils}/radial_grid.py | 0 {boututils => src/boututils}/read_geqdsk.py | 0 {boututils => src/boututils}/run_wrapper.py | 0 {boututils => src/boututils}/showdata.py | 0 {boututils => src/boututils}/spectrogram.py | 0 {boututils => src/boututils}/surface_average.py | 0 {boututils => src/boututils}/tests/__init__.py | 0 {boututils => src/boututils}/tests/test_import.py | 0 {boututils => src/boututils}/volume_integral.py | 0 {boututils => src/boututils}/watch.py | 0 63 files changed, 3 insertions(+), 6 deletions(-) rename {boutdata => src/boutdata}/__init__.py (100%) rename {boutdata => src/boutdata}/cbdtoeqdsk.py (100%) rename {boutdata => src/boutdata}/collect.py (100%) rename {boutdata => src/boutdata}/data.py (100%) rename {boutdata => src/boutdata}/gen_surface.py (100%) rename {boutdata => src/boutdata}/griddata.py (100%) rename {boutdata => src/boutdata}/input.py (100%) rename {boutdata => src/boutdata}/mayavi2.py (100%) rename {boutdata => src/boutdata}/mms.py (100%) rename {boutdata => src/boutdata}/pol_slice.py (100%) rename {boutdata => src/boutdata}/processor_rearrange.py (100%) rename {boutdata => src/boutdata}/restart.py (100%) rename {boutdata => src/boutdata}/scripts/__init__.py (100%) rename {boutdata => src/boutdata}/scripts/bout_squashoutput.py (100%) rename {boutdata => src/boutdata}/settings.py (100%) rename {boutdata => src/boutdata}/shiftz.py (100%) rename {boutdata => src/boutdata}/squashoutput.py (100%) rename {boutdata => src/boutdata}/tests/__init__.py (100%) rename {boutdata => src/boutdata}/tests/make_test_data.py (100%) rename {boutdata => src/boutdata}/tests/test_boutoptions.py (100%) rename {boutdata => src/boutdata}/tests/test_collect.py (100%) rename {boutdata => src/boutdata}/tests/test_import.py (100%) rename {boutdata => src/boutdata}/tests/test_restart.py (100%) rename {boututils => src/boututils}/View3D.py (100%) rename {boututils => src/boututils}/__init__.py (100%) rename {boututils => src/boututils}/analyse_equil_2.py (100%) rename {boututils => src/boututils}/anim.py (100%) rename {boututils => src/boututils}/ask.py (100%) rename {boututils => src/boututils}/boutarray.py (100%) rename {boututils => src/boututils}/boutgrid.py (100%) rename {boututils => src/boututils}/boutwarnings.py (100%) rename {boututils => src/boututils}/bunch.py (100%) rename {boututils => src/boututils}/calculus.py (100%) rename {boututils => src/boututils}/check_scaling.py (100%) rename {boututils => src/boututils}/closest_line.py (100%) rename {boututils => src/boututils}/crosslines.py (100%) rename {boututils => src/boututils}/datafile.py (100%) rename {boututils => src/boututils}/efit_analyzer.py (100%) rename {boututils => src/boututils}/fft_deriv.py (100%) rename {boututils => src/boututils}/fft_integrate.py (100%) rename {boututils => src/boututils}/file_import.py (100%) rename {boututils => src/boututils}/geqdsk.py (100%) rename {boututils => src/boututils}/idl_tabulate.py (100%) rename {boututils => src/boututils}/int_func.py (100%) rename {boututils => src/boututils}/linear_regression.py (100%) rename {boututils => src/boututils}/local_min_max.py (100%) rename {boututils => src/boututils}/mode_structure.py (100%) rename {boututils => src/boututils}/moment_xyzt.py (100%) rename {boututils => src/boututils}/options.py (100%) rename {boututils => src/boututils}/plotdata.py (100%) rename {boututils => src/boututils}/plotpolslice.py (100%) rename {boututils => src/boututils}/radial_grid.py (100%) rename {boututils => src/boututils}/read_geqdsk.py (100%) rename {boututils => src/boututils}/run_wrapper.py (100%) rename {boututils => src/boututils}/showdata.py (100%) rename {boututils => src/boututils}/spectrogram.py (100%) rename {boututils => src/boututils}/surface_average.py (100%) rename {boututils => src/boututils}/tests/__init__.py (100%) rename {boututils => src/boututils}/tests/test_import.py (100%) rename {boututils => src/boututils}/volume_integral.py (100%) rename {boututils => src/boututils}/watch.py (100%) diff --git a/.gitignore b/.gitignore index 4d8d455..205d856 100644 --- a/.gitignore +++ b/.gitignore @@ -127,5 +127,5 @@ dmypy.json .idea/ *.sw[po] -boutdata/_version.py -boututils/_version.py +src/boutdata/_version.py +src/boututils/_version.py diff --git a/pyproject.toml b/pyproject.toml index f24bdcb..6259b77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,14 +52,11 @@ docs = [ [project.scripts] bout-squashoutput = "boutdata.scripts.bout_squashoutput:main" -[tool.setuptools] -packages = ["boutdata", "boututils"] - [tool.setuptools.dynamic] version = { attr = "setuptools_scm.get_version" } [tool.setuptools_scm] -write_to = "boutdata/_version.py" +write_to = "src/boutdata/_version.py" [tool.pytest.ini_options] addopts = "--cov=boutdata" diff --git a/boutdata/__init__.py b/src/boutdata/__init__.py similarity index 100% rename from boutdata/__init__.py rename to src/boutdata/__init__.py diff --git a/boutdata/cbdtoeqdsk.py b/src/boutdata/cbdtoeqdsk.py similarity index 100% rename from boutdata/cbdtoeqdsk.py rename to src/boutdata/cbdtoeqdsk.py diff --git a/boutdata/collect.py b/src/boutdata/collect.py similarity index 100% rename from boutdata/collect.py rename to src/boutdata/collect.py diff --git a/boutdata/data.py b/src/boutdata/data.py similarity index 100% rename from boutdata/data.py rename to src/boutdata/data.py diff --git a/boutdata/gen_surface.py b/src/boutdata/gen_surface.py similarity index 100% rename from boutdata/gen_surface.py rename to src/boutdata/gen_surface.py diff --git a/boutdata/griddata.py b/src/boutdata/griddata.py similarity index 100% rename from boutdata/griddata.py rename to src/boutdata/griddata.py diff --git a/boutdata/input.py b/src/boutdata/input.py similarity index 100% rename from boutdata/input.py rename to src/boutdata/input.py diff --git a/boutdata/mayavi2.py b/src/boutdata/mayavi2.py similarity index 100% rename from boutdata/mayavi2.py rename to src/boutdata/mayavi2.py diff --git a/boutdata/mms.py b/src/boutdata/mms.py similarity index 100% rename from boutdata/mms.py rename to src/boutdata/mms.py diff --git a/boutdata/pol_slice.py b/src/boutdata/pol_slice.py similarity index 100% rename from boutdata/pol_slice.py rename to src/boutdata/pol_slice.py diff --git a/boutdata/processor_rearrange.py b/src/boutdata/processor_rearrange.py similarity index 100% rename from boutdata/processor_rearrange.py rename to src/boutdata/processor_rearrange.py diff --git a/boutdata/restart.py b/src/boutdata/restart.py similarity index 100% rename from boutdata/restart.py rename to src/boutdata/restart.py diff --git a/boutdata/scripts/__init__.py b/src/boutdata/scripts/__init__.py similarity index 100% rename from boutdata/scripts/__init__.py rename to src/boutdata/scripts/__init__.py diff --git a/boutdata/scripts/bout_squashoutput.py b/src/boutdata/scripts/bout_squashoutput.py similarity index 100% rename from boutdata/scripts/bout_squashoutput.py rename to src/boutdata/scripts/bout_squashoutput.py diff --git a/boutdata/settings.py b/src/boutdata/settings.py similarity index 100% rename from boutdata/settings.py rename to src/boutdata/settings.py diff --git a/boutdata/shiftz.py b/src/boutdata/shiftz.py similarity index 100% rename from boutdata/shiftz.py rename to src/boutdata/shiftz.py diff --git a/boutdata/squashoutput.py b/src/boutdata/squashoutput.py similarity index 100% rename from boutdata/squashoutput.py rename to src/boutdata/squashoutput.py diff --git a/boutdata/tests/__init__.py b/src/boutdata/tests/__init__.py similarity index 100% rename from boutdata/tests/__init__.py rename to src/boutdata/tests/__init__.py diff --git a/boutdata/tests/make_test_data.py b/src/boutdata/tests/make_test_data.py similarity index 100% rename from boutdata/tests/make_test_data.py rename to src/boutdata/tests/make_test_data.py diff --git a/boutdata/tests/test_boutoptions.py b/src/boutdata/tests/test_boutoptions.py similarity index 100% rename from boutdata/tests/test_boutoptions.py rename to src/boutdata/tests/test_boutoptions.py diff --git a/boutdata/tests/test_collect.py b/src/boutdata/tests/test_collect.py similarity index 100% rename from boutdata/tests/test_collect.py rename to src/boutdata/tests/test_collect.py diff --git a/boutdata/tests/test_import.py b/src/boutdata/tests/test_import.py similarity index 100% rename from boutdata/tests/test_import.py rename to src/boutdata/tests/test_import.py diff --git a/boutdata/tests/test_restart.py b/src/boutdata/tests/test_restart.py similarity index 100% rename from boutdata/tests/test_restart.py rename to src/boutdata/tests/test_restart.py diff --git a/boututils/View3D.py b/src/boututils/View3D.py similarity index 100% rename from boututils/View3D.py rename to src/boututils/View3D.py diff --git a/boututils/__init__.py b/src/boututils/__init__.py similarity index 100% rename from boututils/__init__.py rename to src/boututils/__init__.py diff --git a/boututils/analyse_equil_2.py b/src/boututils/analyse_equil_2.py similarity index 100% rename from boututils/analyse_equil_2.py rename to src/boututils/analyse_equil_2.py diff --git a/boututils/anim.py b/src/boututils/anim.py similarity index 100% rename from boututils/anim.py rename to src/boututils/anim.py diff --git a/boututils/ask.py b/src/boututils/ask.py similarity index 100% rename from boututils/ask.py rename to src/boututils/ask.py diff --git a/boututils/boutarray.py b/src/boututils/boutarray.py similarity index 100% rename from boututils/boutarray.py rename to src/boututils/boutarray.py diff --git a/boututils/boutgrid.py b/src/boututils/boutgrid.py similarity index 100% rename from boututils/boutgrid.py rename to src/boututils/boutgrid.py diff --git a/boututils/boutwarnings.py b/src/boututils/boutwarnings.py similarity index 100% rename from boututils/boutwarnings.py rename to src/boututils/boutwarnings.py diff --git a/boututils/bunch.py b/src/boututils/bunch.py similarity index 100% rename from boututils/bunch.py rename to src/boututils/bunch.py diff --git a/boututils/calculus.py b/src/boututils/calculus.py similarity index 100% rename from boututils/calculus.py rename to src/boututils/calculus.py diff --git a/boututils/check_scaling.py b/src/boututils/check_scaling.py similarity index 100% rename from boututils/check_scaling.py rename to src/boututils/check_scaling.py diff --git a/boututils/closest_line.py b/src/boututils/closest_line.py similarity index 100% rename from boututils/closest_line.py rename to src/boututils/closest_line.py diff --git a/boututils/crosslines.py b/src/boututils/crosslines.py similarity index 100% rename from boututils/crosslines.py rename to src/boututils/crosslines.py diff --git a/boututils/datafile.py b/src/boututils/datafile.py similarity index 100% rename from boututils/datafile.py rename to src/boututils/datafile.py diff --git a/boututils/efit_analyzer.py b/src/boututils/efit_analyzer.py similarity index 100% rename from boututils/efit_analyzer.py rename to src/boututils/efit_analyzer.py diff --git a/boututils/fft_deriv.py b/src/boututils/fft_deriv.py similarity index 100% rename from boututils/fft_deriv.py rename to src/boututils/fft_deriv.py diff --git a/boututils/fft_integrate.py b/src/boututils/fft_integrate.py similarity index 100% rename from boututils/fft_integrate.py rename to src/boututils/fft_integrate.py diff --git a/boututils/file_import.py b/src/boututils/file_import.py similarity index 100% rename from boututils/file_import.py rename to src/boututils/file_import.py diff --git a/boututils/geqdsk.py b/src/boututils/geqdsk.py similarity index 100% rename from boututils/geqdsk.py rename to src/boututils/geqdsk.py diff --git a/boututils/idl_tabulate.py b/src/boututils/idl_tabulate.py similarity index 100% rename from boututils/idl_tabulate.py rename to src/boututils/idl_tabulate.py diff --git a/boututils/int_func.py b/src/boututils/int_func.py similarity index 100% rename from boututils/int_func.py rename to src/boututils/int_func.py diff --git a/boututils/linear_regression.py b/src/boututils/linear_regression.py similarity index 100% rename from boututils/linear_regression.py rename to src/boututils/linear_regression.py diff --git a/boututils/local_min_max.py b/src/boututils/local_min_max.py similarity index 100% rename from boututils/local_min_max.py rename to src/boututils/local_min_max.py diff --git a/boututils/mode_structure.py b/src/boututils/mode_structure.py similarity index 100% rename from boututils/mode_structure.py rename to src/boututils/mode_structure.py diff --git a/boututils/moment_xyzt.py b/src/boututils/moment_xyzt.py similarity index 100% rename from boututils/moment_xyzt.py rename to src/boututils/moment_xyzt.py diff --git a/boututils/options.py b/src/boututils/options.py similarity index 100% rename from boututils/options.py rename to src/boututils/options.py diff --git a/boututils/plotdata.py b/src/boututils/plotdata.py similarity index 100% rename from boututils/plotdata.py rename to src/boututils/plotdata.py diff --git a/boututils/plotpolslice.py b/src/boututils/plotpolslice.py similarity index 100% rename from boututils/plotpolslice.py rename to src/boututils/plotpolslice.py diff --git a/boututils/radial_grid.py b/src/boututils/radial_grid.py similarity index 100% rename from boututils/radial_grid.py rename to src/boututils/radial_grid.py diff --git a/boututils/read_geqdsk.py b/src/boututils/read_geqdsk.py similarity index 100% rename from boututils/read_geqdsk.py rename to src/boututils/read_geqdsk.py diff --git a/boututils/run_wrapper.py b/src/boututils/run_wrapper.py similarity index 100% rename from boututils/run_wrapper.py rename to src/boututils/run_wrapper.py diff --git a/boututils/showdata.py b/src/boututils/showdata.py similarity index 100% rename from boututils/showdata.py rename to src/boututils/showdata.py diff --git a/boututils/spectrogram.py b/src/boututils/spectrogram.py similarity index 100% rename from boututils/spectrogram.py rename to src/boututils/spectrogram.py diff --git a/boututils/surface_average.py b/src/boututils/surface_average.py similarity index 100% rename from boututils/surface_average.py rename to src/boututils/surface_average.py diff --git a/boututils/tests/__init__.py b/src/boututils/tests/__init__.py similarity index 100% rename from boututils/tests/__init__.py rename to src/boututils/tests/__init__.py diff --git a/boututils/tests/test_import.py b/src/boututils/tests/test_import.py similarity index 100% rename from boututils/tests/test_import.py rename to src/boututils/tests/test_import.py diff --git a/boututils/volume_integral.py b/src/boututils/volume_integral.py similarity index 100% rename from boututils/volume_integral.py rename to src/boututils/volume_integral.py diff --git a/boututils/watch.py b/src/boututils/watch.py similarity index 100% rename from boututils/watch.py rename to src/boututils/watch.py From 3d737da75e92626ee2f412f6e247a765c95851a5 Mon Sep 17 00:00:00 2001 From: Peter Hill Date: Tue, 23 Jul 2024 10:27:29 +0100 Subject: [PATCH 98/98] Apply black --- src/boutdata/data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/boutdata/data.py b/src/boutdata/data.py index 32e614f..7d4fcc3 100644 --- a/src/boutdata/data.py +++ b/src/boutdata/data.py @@ -695,9 +695,9 @@ def __init__( comments = [] if inline_comment is not None: parent_section.inline_comments[sectionname] = inline_comment - parent_section._comment_whitespace[ - sectionname - ] = comment_whitespace + parent_section._comment_whitespace[sectionname] = ( + comment_whitespace + ) else: # A key=value pair