diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6428b881..449b4695 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,11 +25,13 @@ build:centos7: case1: stage: test - image: garrettwrong/spec_centos-7 + image: csmiet/testspec script: + - source source.sh - cd ci/G1V03L2Fi - - ../../xspec G1V03L2Fi.001.sp - #- comparison + - xspec G1V03L2Fi.001.sp + - python3 -m py_spec.ci.test G1V03L2Fi.001.sp.h5 comparison.h5 + #comparison python3 -m py_spec.ci.testspec f1 f2 artifacts: when: always paths: diff --git a/Utilities/pythontools/py_spec/__init__.py b/Utilities/pythontools/py_spec/__init__.py index 378782d4..ae8824dc 100644 --- a/Utilities/pythontools/py_spec/__init__.py +++ b/Utilities/pythontools/py_spec/__init__.py @@ -4,5 +4,7 @@ from .proc_spec import * from . import plot from . import math +from . import external +from . import ci diff --git a/Utilities/pythontools/py_spec/ci/__init.py b/Utilities/pythontools/py_spec/ci/__init.py new file mode 100644 index 00000000..9a02442e --- /dev/null +++ b/Utilities/pythontools/py_spec/ci/__init.py @@ -0,0 +1,3 @@ +# import the test script + +import test diff --git a/Utilities/pythontools/py_spec/ci/test.py b/Utilities/pythontools/py_spec/ci/test.py new file mode 100755 index 00000000..85879aed --- /dev/null +++ b/Utilities/pythontools/py_spec/ci/test.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +@author: Caoxiang Zhu (czhu@ppp.gov) +For any help, type ./compare_spec.py -h +""" +import numpy as np +from py_spec import SPEC +import argparse + +# parse command line arguments +parser = argparse.ArgumentParser(description="Compare two SPEC HDF5 outputs") +parser.add_argument("filename", type=str, help="file name to be compared") +parser.add_argument("reference", type=str, help="reference data") +parser.add_argument("-t", "--tol", type=float, default=1E-12, help="difference tolerance") + +args = parser.parse_args() +print('Compare SPEC outputs in {:s} and {:s} with tolerance {:12.5E}'.format( + args.filename, args.reference, args.tol)) +data_A = SPEC(args.filename) +data_B = SPEC(args.reference) +tol = args.tol +match = True + + +def compare(data, reference): + global match + for key, value in vars(data).items(): + if isinstance(value, SPEC): # recurse data + print('------------------') + print('Elements in '+key) + compare(value, reference.__dict__[key]) + else: + if key in ['filename', 'version']: # not compare filename and version + continue + elif key == 'iterations': # skip iteration data (might be revised) + continue + else: + # print(key) + diff = np.linalg.norm(np.abs(np.array(value) - np.array(reference.__dict__[key]))) + unmatch = diff > tol + if unmatch: + match = False + print('UNMATCHED: '+key, ', diff={:12.5E}'.format(diff)) + else : + print('ok: '+key) + return + +compare(data_A, data_B) +print('===================') +if match : + print('All the terms are within tolerance.') +else : + print('Differences in some elements are larger than the tolerence.') + +exit + diff --git a/Utilities/pythontools/py_spec/math/__init__.py b/Utilities/pythontools/py_spec/math/__init__.py index 72dcd793..dbd08e3a 100644 --- a/Utilities/pythontools/py_spec/math/__init__.py +++ b/Utilities/pythontools/py_spec/math/__init__.py @@ -1,4 +1,4 @@ # import the math functions -from .fourier_surface import * +from .fourier_surface import fourier_surface from .interface_current import * diff --git a/Utilities/pythontools/py_spec/math/fourier_surface.py b/Utilities/pythontools/py_spec/math/fourier_surface.py index 0ae6aa10..15650996 100644 --- a/Utilities/pythontools/py_spec/math/fourier_surface.py +++ b/Utilities/pythontools/py_spec/math/fourier_surface.py @@ -3,17 +3,15 @@ # coded by @zhucaoxiang (czhu@pppl.gov) # adapted by @smiet (csmiet@pppl.gov) # - +import numpy as np class fourier_surface(object): - import numpy as np ''' toroidal surface in Fourier representation R = \sum RBC cos(mu-nv) + RBS sin(mu-nv) Z = \sum ZBC cos(mu-nv) + ZBS sin(mu-nv) ''' def __init__(self, xm=[], xn=[], rbc=[], zbs=[], rbs=[], zbc=[]): - import numpy as np """Initialization with Fourier harmonics. Parameters: @@ -45,7 +43,6 @@ def read_spec_output(cls, spec_out, ns=-1): Returns: fourier_surface class """ - import numpy as np # check if spec_out is in correct format #if not isinstance(spec_out, SPEC): # raise TypeError("Invalid type of input data, should be SPEC type.") @@ -74,7 +71,6 @@ def read_vmec_output(cls, woutfile, ns=-1): Returns: fourier_surface class """ - import numpy as np import xarray as ncdata # read netcdf file vmec = ncdata.open_dataset(woutfile) xm = vmec['xm'].values @@ -108,7 +104,6 @@ def read_focus_input(cls, filename, Mpol=9999, Ntor=9999): Returns: fourier_surface class """ - import numpy as np with open(filename, 'r') as f: line = f.readline() #skip one line line = f.readline() @@ -152,7 +147,6 @@ def read_winding_surfce(cls, filename, Mpol=9999, Ntor=9999): Returns: fourier_surface class """ - import numpy as np with open(filename, 'r') as f: line = '' while "phip_edge" not in line: @@ -205,7 +199,6 @@ def rz(self, theta, zeta, normal=False): r, z -- float array_like r, z, [rt, zt], [rz, zz] -- if normal """ - import numpy as np assert len(np.atleast_1d(theta)) == len(np.atleast_1d(zeta)), "theta, zeta should be equal size" # mt - nz (in matrix) _mtnz = np.matmul( np.reshape(self.xm, (-1,1)), np.reshape(theta, (1,-1)) ) \ @@ -243,7 +236,6 @@ def xyz(self, theta, zeta, normal=False): x, y, z -- float array_like x, y, z, [nx, ny, nz] -- if normal """ - import numpy as np data = self.rz(theta, zeta, normal) r = data[0] z = data[1] @@ -277,7 +269,6 @@ def areaVolume(self, theta0=0.0, theta1=2*np.pi, zeta0=0.0, zeta1=2*np.pi, \ area -- surface area volume -- surface volume """ - import numpy as np # get mesh data _theta = np.linspace(theta0, theta1, npol, endpoint=False) _zeta = np.linspace(zeta0, zeta1, ntor, endpoint=False) @@ -299,7 +290,6 @@ def get_area(self): Returns: area """ - import numpy as np self.area, _volume = self._areaVolume() return self.area @@ -325,7 +315,6 @@ def plot(self, zeta=0.0, npoints=360, **kwargs): line class in matplotlib.pyplot """ import matplotlib.pyplot as plt - import numpy as np # get figure and ax data if plt.get_fignums(): fig = plt.gcf() @@ -363,7 +352,6 @@ def plot3d(self, engine='pyplot', theta0=0.0, theta1=2*np.pi, zeta0=0.0, zeta1=2 Returns: xsurf, ysurf, zsurf -- arrays of x,y,z coordinates on the surface """ - import numpy as np # get mesh data _theta = np.linspace(theta0, theta1, npol) _zeta = np.linspace(zeta0, zeta1, ntor) @@ -405,7 +393,6 @@ def tovtk(self, vtkname, npol=360, ntor=360, **kwargs): Returns: """ - import numpy as np from pyevtk.hl import gridToVTK # save to binary vtk _xx, _yy, _zz = self.plot3d('noplot', zeta0=0.0, zeta1=2*np.pi, theta0=0.0, theta1=2*np.pi, npol=npol, ntor=ntor) diff --git a/Utilities/pythontools/py_spec/math/interface_current.py b/Utilities/pythontools/py_spec/math/interface_current.py index 3f28b547..170ebd9a 100644 --- a/Utilities/pythontools/py_spec/math/interface_current.py +++ b/Utilities/pythontools/py_spec/math/interface_current.py @@ -4,7 +4,7 @@ def interface_current(SPEC, interface_number): """ - returns the ruccent on the interface given by interface_number. + returns the current on the interface given by interface_number. Not working yet We are calculating a loop integral over the difference in magnetic field on the surface. @@ -21,13 +21,15 @@ def interface_current(SPEC, interface_number): outer_volume = SPEC.output.Btemn[0:Ntor, 0, interface_number] inner_volume = SPEC.output.Btemn[0:Ntor, 1, interface_number-1] - current = np.sum(outer_volume-inner_volume)* np.pi * 2 # check if idl's pi2 is pi squared or just two pi + current = np.sum(outer_volume-inner_volume) * np.pi * 2 # check if idl's pi2 is pi squared or just two pi return current + def interface_current_total(SPEC): """ - returns the ruccent on the interface given by interface_number. + NOT TESTED! + returns the current on the interface given by interface_number. Not working yet We are calculating a loop integral over the difference in magnetic field on the surface. diff --git a/Utilities/pythontools/py_spec/plot/plot_kam_surface.py b/Utilities/pythontools/py_spec/plot/plot_kam_surface.py index b601991f..31b55670 100644 --- a/Utilities/pythontools/py_spec/plot/plot_kam_surface.py +++ b/Utilities/pythontools/py_spec/plot/plot_kam_surface.py @@ -31,7 +31,7 @@ def plot_kam_surface(SPEC, ns=None, zeta=0.0, **kwargs): kwargs.update({'label': 'SPEC_KAM'}) # default label # plot all the surfaces for i in ns: - _surf = math.fourier_surface.read_spec_output(SPEC, i) #look into which spec fourier_surface needs. + _surf = math.fourier_surface.read_spec_output(SPEC, i) # look into which spec fourier_surface needs. if i == 0: # plot axis as a curve _r, _z = _surf.rz(0.0, zeta) diff --git a/Utilities/pythontools/py_spec/plot/plot_poincare.py b/Utilities/pythontools/py_spec/plot/plot_poincare.py index 75fd8ccc..b7fabd32 100644 --- a/Utilities/pythontools/py_spec/plot/plot_poincare.py +++ b/Utilities/pythontools/py_spec/plot/plot_poincare.py @@ -17,33 +17,33 @@ def plot_poincare(SPEC, toroidalIdx=0, prange='full', **kwargs): import matplotlib.pyplot as plt import matplotlib.lines as mlines # extract slice corresponding to the given toroidal cutplane - rr = SPEC.poincare.R[:,:,toroidalIdx] - zz = SPEC.poincare.Z[:,:,toroidalIdx] + rr = SPEC.poincare.R[:, :, toroidalIdx] + zz = SPEC.poincare.Z[:, :, toroidalIdx] # get current figure or build new one; if plt.get_fignums(): fig = plt.gcf() ax = plt.gca() - else : + else: fig, ax = plt.subplots() # set default plotting parameters # use dots - if kwargs.get('marker') == None: + if kwargs.get('marker') is None: kwargs.update({'marker': '.'}) # use gray color - if kwargs.get('c') == None: + if kwargs.get('c') is None: kwargs.update({'c': 'gray'}) # make plot depending on the 'range' if prange == 'full': dots = ax.scatter(rr, zz, **kwargs) elif prange == 'upper': - dots = ax.scatter(rr[zz>=0], zz[zz>=0], **kwargs) + dots = ax.scatter(rr[zz >= 0], zz[zz >= 0], **kwargs) elif prange == 'lower': - dots = ax.scatter(rr[zz<=0], zz[zz<=0], **kwargs) - else : + dots = ax.scatter(rr[zz <= 0], zz[zz <= 0], **kwargs) + else: raise ValueError("prange should be one of ['full'(default), 'upper', 'lower'].") # adjust figure properties - plt.xlabel('R [m]',fontsize=20) - plt.ylabel('Z [m]',fontsize=20) + plt.xlabel('R [m]', fontsize=20) + plt.ylabel('Z [m]', fontsize=20) plt.xticks(fontsize=16) plt.yticks(fontsize=16) plt.axis('equal') diff --git a/ci/Dockerfile.centos7 b/ci/Dockerfile.centos7 index 0b87335d..69551c3c 100644 --- a/ci/Dockerfile.centos7 +++ b/ci/Dockerfile.centos7 @@ -6,6 +6,8 @@ RUN yum -y install epel-release RUN yum install gcc-gfortran openmpi openmpi-devel hdf5 hdf5-devel -y RUN yum install fftw3 fftw3-devel openblas openblas-devel -y RUN yum install blas blas-devel lapack lapack-devel -y +RUN yum install -y python3 python3-devel +RUN pip3 install h5py matplotlib # direct the existing makefile to the system install location ENV FFTW_HOME=/usr diff --git a/ci/G1V03L2Fi/compare.h5 b/ci/G1V03L2Fi/compare.h5 new file mode 100644 index 00000000..f913a80e Binary files /dev/null and b/ci/G1V03L2Fi/compare.h5 differ diff --git a/source.sh b/source.sh new file mode 100644 index 00000000..152b4fda --- /dev/null +++ b/source.sh @@ -0,0 +1,20 @@ +# +# source.sh +# This file sets the PATH to the current directory, which +# is the directory where the xspec file is located. +# +# It also updates the $PYTHONPATH variable so that the +# regression testing can be executed. +# + +# Test if this is the folder where xspec lives then export paths or exit with error +if ( [ -e ./xspec ] && \ + [ -e ./xspech.f90 ] ); then + export PATH=${PATH}:$PWD + export PYTHONPATH=${PYTHONPATH}:$PWD/Utilities/pythontools +else + echo "xspec executable does not exist, will not modify your path" + exit 1 +fi + +