Commit 63e6dce2 authored by Pierre Augier's avatar Pierre Augier

More unittest + debug + version 0.1.0

parent 882a69a030a0
0.1.0
-----
- Clean-up code.
- More unittests (coverage = 60%).
- Compatible Python 2.7 and Python >= 3.4.
- fluiddyn.util.easypyfft.
0.0.13
------
......
......@@ -16,24 +16,17 @@ FluidDyn
:target: https://codecov.io/bb/fluiddyn/fluiddyn/branch/default/
:alt: Code coverage
FluidDyn is a framework for studying fluid dynamics. Most of the features are
actually implemented in other packages (`fluidlab
<http://fluidlab.readthedocs.io>`_, `fluidimage
<http://fluidimage.readthedocs.io>`_, `fluidsim
<http://fluidsim.readthedocs.io>`_). The base package fluiddyn contains basic
utilities.
FluidDyn is the evolution of two other codes previously developed by `Pierre
Augier <http://www.legi.grenoble-inp.fr/people/Pierre.Augier/>`_ (CNRS
researcher at `LEGI <http://www.legi.grenoble-inp.fr>`_, Grenoble): Solveq2d (a
numerical code to solve fluid equations in a periodic two-dimensional space
with a pseudo-spectral method, developed at KTH, Stockholm) and FluidLab (a
toolkit to do experiments, developed in the G. K. Batchelor Fluid Dynamics
Laboratory at DAMTP, University of Cambridge).
*Key words and ambitions*: fluid dynamics research with Python (2.7 or
>= 3.3); modular, object-oriented, collaborative, tested and
documented, free and open-source software.
FluidDyn is a framework for research and teaching in fluid dynamics. The Python
package fluiddyn contains basic utilities (file io, figures, clusters, mpi,
etc.). Most of the features of the FluidDyn project are actually implemented
in `other packages <https://bitbucket.org/fluiddyn>`_ (in particular in
`fluidlab <http://fluidlab.readthedocs.io>`_, `fluidimage
<http://fluidimage.readthedocs.io>`_ and `fluidsim
<http://fluidsim.readthedocs.io>`_).
*Key words and ambitions*: fluid dynamics research with Python (2.7 or >= 3.4);
modular, object-oriented, collaborative, tested and documented, free and
open-source software.
License
-------
......@@ -46,9 +39,14 @@ french license.
Installation
------------
It is recommended to `install numpy <http://scipy.org/install.html>`_ before
installing fluiddyn. The simplest way to install fluiddyn is by using pip::
pip install fluiddyn
You can get the source code from `Bitbucket
<https://bitbucket.org/fluiddyn/fluiddyn>`_ or from `the Python
Package Index <https://pypi.python.org/pypi/fluiddyn/>`_.
<https://bitbucket.org/fluiddyn/fluiddyn>`_ or from `the Python Package Index
<https://pypi.python.org/pypi/fluiddyn/>`_.
The development mode is often useful. From the root directory, run::
......@@ -63,4 +61,15 @@ From the root directory::
Or, from the root directory or any of the "test" directories::
python -m unittest discover
\ No newline at end of file
python -m unittest discover
History
-------
FluidDyn is the evolution of two other codes previously developed by `Pierre
Augier <http://www.legi.grenoble-inp.fr/people/Pierre.Augier/>`_ (CNRS
researcher at `LEGI <http://www.legi.grenoble-inp.fr>`_, Grenoble): Solveq2d (a
numerical code to solve fluid equations in a periodic two-dimensional space
with a pseudo-spectral method, developed at KTH, Stockholm) and FluidLab (a
toolkit to do experiments, developed in the G. K. Batchelor Fluid Dynamics
Laboratory at DAMTP, University of Cambridge).
......@@ -11,4 +11,4 @@ http://legacy.python.org/dev/peps/pep-0386/
'a' or 'alpha' means alpha version (internal testing),
'b' or 'beta' means beta version (external testing).
"""
__version__ = '0.0.14'
__version__ = '0.1.0'
......@@ -11,19 +11,23 @@ Provides:
"""
import csv
import io as _io
import numpy as np
class CSVFile(_io.FileIO):
class CSVFile(object):
def __init__(self, *args, **kargs):
super(CSVFile, self).__init__(*args, **kargs)
self.reader = csv.DictReader(self)
self._textio = open(*args, **kargs)
self.reader = csv.DictReader(self._textio)
self.fieldnames = self.reader.fieldnames
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._textio.close()
def load_as_dict(self, keys=None, skiptimes=0):
if keys is None:
keys = self.fieldnames
......@@ -54,8 +58,8 @@ class CSVFile(_io.FileIO):
dtype = np.int32
dtypes.append(dtype)
self.seek(0)
arr = np.loadtxt(self, delimiter=',', skiprows=1+skiptimes,
self._textio.seek(0)
arr = np.loadtxt(self._textio, delimiter=',', skiprows=1+skiptimes,
usecols=usecols).T
ret = {}
......
......@@ -3,12 +3,13 @@
"""
from future import standard_library
standard_library.install_aliases()
from builtins import range
from builtins import object
import os
import configparser
try:
import configparser
except ImportError:
import ConfigParser as configparser
try:
from PIL import Image
......
"""
Test dantec module
====================
"""
import unittest
import os
from shutil import rmtree
from .. import dantec
class TestDantex(unittest.TestCase):
"""Test fluiddyn.io.dantec module."""
def setUp(self):
self._work_dir = 'test_fluiddyn_io_dantec'
if not os.path.exists(self._work_dir):
os.mkdir(self._work_dir)
os.chdir(self._work_dir)
def tearDown(self):
os.chdir('..')
rmtree(self._work_dir)
if __name__ == '__main__':
unittest.main()
......@@ -16,7 +16,7 @@ from ..digiflow import DigiflowImage, DigiflowMovie
class TestDigiflow(unittest.TestCase):
"""Test fluiddyn.io.digiflow module."""
def setUp(self):
self._work_dir = 'test_fluiddyn_io_hdf5'
self._work_dir = 'test_fluiddyn_io_digiflow'
if not os.path.exists(self._work_dir):
os.mkdir(self._work_dir)
......@@ -29,5 +29,6 @@ class TestDigiflow(unittest.TestCase):
os.chdir('..')
rmtree(self._work_dir)
if __name__ == '__main__':
unittest.main()
......@@ -30,7 +30,8 @@ class TestImage(unittest.TestCase):
self.paths = {
None: 'test_image_none.png',
'PNG': 'test_image.png',
# 'TIFF': 'test_image.tif', TIFF image saving with Pillow gives error
# TIFF image saving with Pillow gives error
# 'TIFF': 'test_image.tif',
}
self.paths_h5 = {
......
"""
Test mycsv module
====================
"""
import unittest
import os
from shutil import rmtree
from ..mycsv import CSVFile
class TestDantex(unittest.TestCase):
"""Test fluiddyn.io.mycsv module."""
def setUp(self):
self._work_dir = 'test_fluiddyn_io_mycsv'
if not os.path.exists(self._work_dir):
os.mkdir(self._work_dir)
os.chdir(self._work_dir)
with open('myfile.csv', 'w') as f:
f.write('a,b,c\n1,2,3\n')
def tearDown(self):
os.chdir('..')
rmtree(self._work_dir)
def test_csv(self):
with CSVFile('myfile.csv') as f:
f.get_fieldnames()
f.load_as_dict(['a', 'b'])
if __name__ == '__main__':
unittest.main()
"""
Test rdvision module
====================
"""
import unittest
import os
from shutil import rmtree
from .. import rdvision
class TestDantex(unittest.TestCase):
"""Test fluiddyn.io.rdvision module."""
def setUp(self):
self._work_dir = 'test_fluiddyn_io_rdvision'
if not os.path.exists(self._work_dir):
os.mkdir(self._work_dir)
os.chdir(self._work_dir)
def tearDown(self):
os.chdir('..')
rmtree(self._work_dir)
if __name__ == '__main__':
unittest.main()
......@@ -5,6 +5,7 @@ import os
from shutil import rmtree
from ..figs import Figures
from ..util import gradient_colors
class TestFigs(unittest.TestCase):
......@@ -20,6 +21,10 @@ class TestFigs(unittest.TestCase):
def tearDown(self):
rmtree(self._work_dir)
def test_gradient_colors(self):
gradient_colors(4)
gradient_colors(5)
def test_save(self):
fig = self.figures.new_figure(
......
......@@ -47,9 +47,9 @@ class FFTP2D(object):
raise ValueError('nx and ny should be even')
self.nx = nx
self.ny = ny
self.shapeX = [ny, nx]
self.shapeX = (ny, nx)
self.nkx = int(float(nx)/2+1)
self.shapeK = [ny, self.nkx]
self.shapeK = (ny, self.nkx)
self.coef_norm = nx*ny
self.fft2d = self.fftp2d
......@@ -65,20 +65,27 @@ class FFTP2D(object):
def ifftp2d(self, small_ff_fft, ARG_IS_COMPLEX=False):
if not (isinstance(small_ff_fft[0, 0], complex)):
print('Warning: not array of complexes')
print('small_ff_fft\n', small_ff_fft)
# print('small_ff_fft\n', small_ff_fft)
big_ff_fft = np.empty(self.shapeX, dtype=np.complex128)
big_ff_fft[:, 0:self.nkx] = small_ff_fft
for iky in range(self.ny):
big_ff_fft[iky, self.nkx:] = \
small_ff_fft[-iky, self.nkx-2:0:-1].conj()
print('big_ff_fft final\n', big_ff_fft)
# print('big_ff_fft final\n', big_ff_fft)
result_ifft = fftp.ifft2(big_ff_fft*self.coef_norm)
if np.max(np.imag(result_ifft)) > 10**(-8):
print('ifft2: imaginary part of ifft not equal to zero,',
np.max(np.imag(result_ifft)))
return np.real(result_ifft)
def compute_energy_from_Fourier(self, ff_fft):
return (np.sum(abs(ff_fft[:, 0])**2 + abs(ff_fft[:, -1])**2) +
2*np.sum(abs(ff_fft[:, 1:-1])**2))/2
def compute_energy_from_spatial(self, ff):
return np.mean(abs(ff)**2)/2
class FFTW2DReal2Complex(object):
""" A class to use fftw """
......@@ -90,8 +97,8 @@ class FFTW2DReal2Complex(object):
'ImportError {0}. Instead fftpack can be used (?)', err)
if nx % 2 != 0 or ny % 2 != 0:
raise ValueError('nx and ny should be even')
shapeX = [ny, nx]
shapeK = [ny, nx//2 + 1]
shapeX = (ny, nx)
shapeK = (ny, nx//2 + 1)
self.shapeX = shapeX
self.shapeK = shapeK
......@@ -112,8 +119,8 @@ class FFTW2DReal2Complex(object):
self.coef_norm = nx*ny
self.get_shapeK_seq = self.get_shapeK_loc = lambda s: shapeK
self.get_shapeX_seq = self.get_shapeX_loc = lambda s: shapeX
self.get_shapeK_seq = self.get_shapeK_loc = lambda: shapeK
self.get_shapeX_seq = self.get_shapeX_loc = lambda: shapeX
def fft2d(self, ff):
self.arrayX[:] = ff
......@@ -146,8 +153,8 @@ class FFTW3DReal2Complex(object):
"ImportError {0}. Instead fftpack can be used (?)", err)
if nx % 2 != 0 or ny % 2 != 0 or nz % 2 != 0:
raise ValueError('nx, ny and nz should be even')
shapeX = [nz, ny, nx]
shapeK = [nz, ny, nx//2 + 1]
shapeX = (nz, ny, nx)
shapeK = (nz, ny, nx//2 + 1)
self.shapeX = shapeX
self.shapeK = shapeK
......@@ -229,7 +236,7 @@ class FFTW3DReal2Complex(object):
raise ValueError('Not the same physical shape...')
# check that the 2d fft is not with distributed memory...
if o2d.get_shapeX_loc() != o2d.get_shapeX_loc():
if o2d.get_shapeX_loc() != o2d.get_shapeX_seq():
raise ValueError('2d fft is with distributed memory...')
ind0seq_first, ind1seq_first = self.get_seq_indices_first_K()
......@@ -265,8 +272,8 @@ class FFTW1D(object):
if n % 2 != 0:
raise ValueError('n should be even')
shapeX = [n]
shapeK = [n]
shapeX = (n,)
shapeK = (n,)
self.shapeX = shapeX
self.shapeK = shapeK
self.arrayX = pyfftw.empty_aligned(shapeX, 'complex128')
......@@ -304,13 +311,14 @@ class FFTW1DReal2Complex(object):
if isinstance(arg, int):
n = arg
shapeX = [n]
shapeK = [n//2+1]
shapeX = (n,)
shapeK = (n//2+1,)
else:
n = arg[axis]
shapeX = arg
shapeK = list(copy(arg))
shapeK[axis] = n//2+1
shapeK = tuple(shapeK)
if n % 2 != 0:
raise ValueError('n should be even')
......
......@@ -92,7 +92,7 @@ class Logger(object):
def send_email(self, name_exception=None, figures=None):
"""Sends the content of the storage file as an email"""
with open(self.path, 'rb') as f:
with open(self.path, 'r') as f:
txt = f.read()
if name_exception is None:
......@@ -101,7 +101,7 @@ class Logger(object):
subject = name_exception + ', ' + self.email_title
if os.path.exists(self.path_logerr):
with open(self.path_logerr, 'rb') as f:
with open(self.path_logerr, 'r') as f:
txt += f.read()
if figures is None:
......@@ -130,8 +130,8 @@ class Logger(object):
if __name__ == '__main__':
logger = Logger('storage_file', 'pierre.augier@ens-lyon.org',
'pierre.augier@legi.cnrs.fr',
logger = Logger('storage_file', '@ens-lyon.org',
'@legi.cnrs.fr',
email_title='itworks', email_delay=9)
print = logger.print_log
print('fluiddyn, blablabla')
......
......@@ -10,7 +10,6 @@ Provides:
from __future__ import print_function
import os
import time
import smtplib
import socket
......@@ -69,8 +68,6 @@ def send_email(subject, txt, address_recipients, address_sender=None,
"""
print(address_recipients)
if isinstance(address_recipients, str):
address_recipients = [address_recipients]
......@@ -91,7 +88,7 @@ def send_email(subject, txt, address_recipients, address_sender=None,
if files is None:
files = []
# sub parts: the enclosed files
for path in files:
# Guess the content type based on the file's extension. Encoding
......
......@@ -80,8 +80,7 @@ class SerieOfArrays(object):
else:
l = glob(path)
if len(l) == 0:
raise ValueError('The provided path does not exist:\n'
+ path)
raise ValueError('The provided path does not exist:\n' + path)
self.path_dir, self.filename_given = os.path.split(l[0])
......
......@@ -88,7 +88,7 @@ class FunctionLinInterp(object):
def deriv(f, x=None, dx=None, method='diff'):
"""Derive a 1D signal."""
if dx is None:
dx = np.diff(x)
......@@ -96,7 +96,6 @@ def deriv(f, x=None, dx=None, method='diff'):
x = (x[:-1]+x[1:])/2
return x, np.diff(f) / dx
elif method == 'convolve':
return np.convolve(f, [1, -1]) / dx
elif method == 'gaussian_filter':
return ndimage.gaussian_filter1d(f, sigma=1, order=1, mode='wrap')/dx
......@@ -113,7 +112,9 @@ def smooth(x, window_len=11, window='hanning'):
transient parts are minimized in the begining and end part of the
output signal.
input:
Parameters
----------
x: the input signal
window_len: the dimension of the smoothing window; should be
an odd integer
......@@ -121,7 +122,9 @@ def smooth(x, window_len=11, window='hanning'):
'bartlett', 'blackman'
flat window will produce a moving average smoothing.
output:
Returns
-------
the smoothed signal
example:
......@@ -159,7 +162,8 @@ def smooth(x, window_len=11, window='hanning'):
if window not in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']:
raise ValueError(
"Window is on of 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'")
"Window is on of 'flat', 'hanning', "
"'hamming', 'bartlett', 'blackman'")
s = np.r_[x[window_len-1:0:-1], x, x[-1:-window_len:-1]]
if window == 'flat': # moving average
......@@ -168,7 +172,7 @@ def smooth(x, window_len=11, window='hanning'):
w = eval('np.'+window+'(window_len)')
y = np.convolve(w/w.sum(), s, mode='valid')
return y[(window_len/2-1):-(window_len/2+1)]
return y[(window_len//2-1):-(window_len//2+1)]
if __name__ == '__main__':
......
......@@ -3,17 +3,23 @@ import unittest
import numpy as np
from ...io import stdout_redirected
from .. import easypyfft
# from fluiddyn.io import stdout_redirected
class TestFFTW1D(unittest.TestCase):
def test_fft(self):
op = easypyfft.FFTW1D(4)
func_fft = np.zeros(op.shapeK, dtype=np.complex128)
func = op.ifft(func_fft)
op.fft(func)
class TestFFTW1DReal2Complex(unittest.TestCase):
def test_fft(self):
"""Should be able to..."""
nx = 128
nx = 64
op = easypyfft.FFTW1DReal2Complex(nx)
func_fft = np.zeros(op.shapeK, dtype=np.complex128)
......@@ -39,7 +45,7 @@ class TestFFTW1DReal2Complex(unittest.TestCase):
def test_fft_random(self):
"""Should be able to..."""
nx = 128
nx = 64
op = easypyfft.FFTW1DReal2Complex(nx)
func_fft = (np.random.random(op.shapeK)
......@@ -51,12 +57,13 @@ class TestFFTW1DReal2Complex(unittest.TestCase):
class TestFFTW2DReal2Complex(unittest.TestCase):
cls = easypyfft.FFTW2DReal2Complex
def test_fft(self):
"""Should be able to..."""
nx = 4
ny = 2
op = easypyfft.FFTW2DReal2Complex(nx, ny)
op = self.cls(nx, ny)
func_fft = np.zeros(op.shapeK, dtype=np.complex128)
func_fft[0, 1] = 1
......@@ -82,22 +89,28 @@ class TestFFTW2DReal2Complex(unittest.TestCase):
def test_fft_random(self):
"""Should be able to..."""
nx = 64
ny = 128
op = easypyfft.FFTW2DReal2Complex(nx, ny)
nx = 16
ny = 32
op = self.cls(nx, ny)
func_fft = (np.random.random(op.shapeK)
+ 1.j*np.random.random(op.shapeK))
func = op.ifft2d(func_fft)
with stdout_redirected():
func = op.ifft2d(func_fft)
func_fft = op.fft2d(func)
self.compute_and_check(func_fft, op)
class TestFFTP2D(TestFFTW2DReal2Complex):
cls = easypyfft.FFTP2D
class TestFFTW3DReal2Complex(unittest.TestCase):
def test_fft(self):
"""Should be able to..."""
"""Test easypyfft.FFTW3DReal2Complex"""
nx = 4
ny = 2
nz = 8
......@@ -108,6 +121,22 @@ class TestFFTW3DReal2Complex(unittest.TestCase):
self.compute_and_check(func_fft, op)
op.get_shapeX_loc()
nX0, nX1, nX2 = op.get_shapeX_seq()
op.get_shapeK_loc()
nK0, nK1, nK2 = op.get_shapeK_seq()
op.get_k_adim()
op.get_k_adim_loc