diff --git a/.travis.yml b/.travis.yml index bb8eb6021529f523bf92fe53f38baf432310dd70..5aed0ffd7d0dfaef4b5465f1a57e284fb42748ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_install: - chmod +x miniconda.sh - ./miniconda.sh -b -p $HOME/miniconda - export PATH=$HOME/miniconda/bin:$PATH - - conda update --yes conda +# - conda update --yes conda - conda install --yes python=$TRAVIS_PYTHON_VERSION atlas numpy scipy matplotlib nose cython h5py numexpr install: diff --git a/CHANGELOG.md b/CHANGELOG.md index 221b52d6d35bc2126225e0f17188d37151f511dd..f5ee2e8209b5d264b6c9ddbd30e2b27aa7ff0b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog of postpic +## v0.3 +2017-09-28 + +Many improvements in terms of speed and features. Unfortunately some changes are not backwards-compatible to v0.2.3, so you may have to adapt your code to the new interface. For details, see the corresponding section below. + + +**Highlights** +* kspace reconstruction and propagation of EM waves. +* `postpic.Field` properly handles operator overloading and slicing. Slicing can be index based (integers) or referring the actual physical extent on the axis of a Field object (using floats). +* Expression based interface to particle properties (see below) + +**Incompatible adjustments to last version** +* New dependency: Postpic requires the `numexpr` package to be installed now. +* Expression based interface of for particles: If `ms` is a `postpic.MultiSpecies` object, then the call `ms.X()` has been deprecated. Use `ms('x')` instead. This new particle interface can handle expressions that the `numexpr` package understands. Also `ms('sqrt(x**2 + gamma - id)')` is valid. This interface is easier to use, has better functionality and is faster due to `numexpr`. +The list of known per particle scalars and their definitions is available at `postpic.particle_scalars`. In addition all constants of `scipy.constants.*` can be used. +In case you find particle scalar that you use regularly which is not in the list, please open an issue and let us know! +* The `postpic.Field` class now behaves more like an `numpy.ndarray` which means that almost all functions return a new field object instead of modifying the current. This change affects the following functions: `half_resolution`, `autoreduce`, `cutout`, `mean`. + + +**Other improvements and new features** +* `postpic.helper.kspace` can reconstruct the correct k-space from three EM fields provided to distinguish between forward and backward propagating waves (thanks to @Ablinne) +* `postpic.helper.kspace_propagate` will turn the phases in k-space to propagate the EM-wave. +* List of new functions in `postpic` from `postpic.helper` (thanks to @Ablinne): `kspace_epoch_like`, `kspace`, `kspace_propagate`. +* `Field.fft` function for fft optimized with pyfftw (thanks to @Ablinne). +* `Field.__getitem__` to slice a Field object. If integers are provided, it will interpret them as gridpoints. If float are provided they are interpreted as the physical region of the data and slice along the corresponding axis positions (thanks to @Ablinne). +* `Field` class has been massively impoved (thanks to @Ablinne): The operator overloading is now properly implemented and thanks to `__array__` method, it can be interpreted by numpy as an ndarray whenever neccessary. +* List of new functions of the `Field` class (thanks to @Ablinne): `meshgrid`, `conj`, `replace_data`, `pad`, `transform`, `squeeze`, `integrate`, `fft`, `shift_grid_by`, `__getitem__`, `__setitem__`. +* List of new properties of the `Field` class (thanks to @Ablinne): `matrix`, `real`, `imag`, `angle`. +* Many performance optimizations using pyfftw library (optional) or numexpr (now required by postpic) or by avoiding in memory data copying. +* Lots of fixes + + + + ## v0.2.3 2017-02-17 diff --git a/postpic/__init__.py b/postpic/__init__.py index c61917a10b0673178fdac28ce0a55f5df73b0ffa..df40a9990c4e3bab762360d8a009f0d2afb24f71 100644 --- a/postpic/__init__.py +++ b/postpic/__init__.py @@ -27,7 +27,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera from . import helper from . import datahandling from .datahandling import * -from .helper import PhysicalConstants +from .helper import * from .particles import * from . import datareader from .datareader import chooseCode, readDump, readSim @@ -36,7 +36,7 @@ from . import _postpic_version __all__ = ['helper'] __all__ += datahandling.__all__ -__all__ += ['PhysicalConstants'] +__all__ += helper.__all__ __all__ += particles.__all__ __all__ += ['datareader', 'plotting'] # high level functions diff --git a/postpic/_postpic_version.py b/postpic/_postpic_version.py index 1c2c1c8df7370e39291dabeb39ac7207c7a9c17c..61202daf42bdf6cf8aa967e6b014fc92c4e87573 100644 --- a/postpic/_postpic_version.py +++ b/postpic/_postpic_version.py @@ -22,7 +22,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals -__version__ = 'v0.2.3' +__version__ = 'v0.3.0' try: FileNotFoundError # python3 diff --git a/postpic/datahandling.py b/postpic/datahandling.py index b074a04ba165496f648a6f94585f55872d0ff0a6..d18eafdc159891eabca7dc7c76ad6a53a1fbf7a7 100644 --- a/postpic/datahandling.py +++ b/postpic/datahandling.py @@ -501,6 +501,8 @@ class Field(object): pad_width_numpy = [] + padded_axes = [] + for i, axis_pad in enumerate(pad_width): if not isinstance(axis_pad, collections.Iterable): axis_pad = [axis_pad, axis_pad] @@ -521,16 +523,22 @@ class Field(object): in axis_pad] pad_width_numpy.append(axis_pad) - extent = axis.extent - newextent = [extent[0] - axis_pad[0]*dx, extent[1] + axis_pad[1]*dx] - gridpoints = len(axis.grid_node) - 1 + axis_pad[0] + axis_pad[1] + totalpad_axis = sum(axis_pad) + + if totalpad_axis: + padded_axes.append(i) + + extent = axis.extent + newextent = [extent[0] - axis_pad[0]*dx, extent[1] + axis_pad[1]*dx] + gridpoints = len(axis.grid_node) - 1 + axis_pad[0] + axis_pad[1] - axis.setextent(newextent, gridpoints) + axis.setextent(newextent, gridpoints) ret._matrix = np.pad(self, pad_width_numpy, mode, **kwargs) - # This info is invalidated - ret.transformed_axes_origins = [None]*ret.dimensions + # This info is invalidated for all axes which have actually been padded + for i in padded_axes: + ret.transformed_axes_origins[i] = None return ret @@ -558,7 +566,7 @@ class Field(object): ret.setaxisobj(axis, ret.axes[axis].half_resolution()) # This info is invalidated - ret.transformed_axes_origins = [None]*ret.dimensions + ret.transformed_axes_origins[axis] = None return ret @@ -656,13 +664,8 @@ class Field(object): ''' ret = self # half_resolution will take care for the copy for i in range(len(ret.axes)): - if len(ret.axes[i]) > maxlen: + while len(ret.axes[i]) > maxlen: ret = ret.half_resolution(i) - ret = ret.autoreduce(maxlen=maxlen) - break - - # This info is invalidated by reducing the grid - ret.transformed_axes_origins = [None]*ret.dimensions return ret @@ -1012,14 +1015,20 @@ class Field(object): # Operator overloading def __getitem__(self, key): + old_shape = self.shape + key = self._normalize_slices(key) field = copy.copy(self) field._matrix = field.matrix[key] for i, sl in enumerate(key): field.setaxisobj(i, field.axes[i][sl]) + new_shape = field.shape + # This info is invalidated - field.transformed_axes_origins = [None]*field.dimensions + for i, (o, n) in enumerate(zip(old_shape, new_shape)): + if o != n: + field.transformed_axes_origins[i] = None return field diff --git a/postpic/helper.py b/postpic/helper.py index e5047af3bbce183e55f2fc6bbb6ddb5a0ebe1287..3f8e96aebaa7b1b5594f4310591256faeab47659 100644 --- a/postpic/helper.py +++ b/postpic/helper.py @@ -40,6 +40,10 @@ except(ImportError): particleshapes = [0] +__all__ = ['PhysicalConstants', 'kspace_epoch_like', 'kspace', + 'kspace_propagate'] + + def isnotebook(): return 'ipykernel' in sys.modules @@ -114,6 +118,17 @@ def append_doc_of(obj): return ret +def prepend_doc_of(obj): + ''' + decorator to append the doc of `obj` to decorated object/class. + ''' + def ret(a): + doc = '' if a.__doc__ is None else a.__doc__ + a.__doc__ = obj.__doc__ + doc + return a + return ret + + class float_with_name(float): def __new__(self, value, name): return float.__new__(self, value) @@ -593,7 +608,7 @@ def kspace(component, fields, extent=None, interpolation=None, omega_func=None): def linear_phase(field, dx): ''' Calculates the linear phase as used in Field._apply_linear_phase and - kspace_propagate_generator. + _kspace_propagate_generator. ''' import numexpr as ne transform_state = field._transform_state(dx.keys()) @@ -629,10 +644,10 @@ def linear_phase(field, dx): return exp_ikdx -def kspace_propagate_generator(kspace, dt, moving_window_vect=None, - move_window=None, - remove_antipropagating_waves=None, - yield_zeroth_step=False): +def _kspace_propagate_generator(kspace, dt, moving_window_vect=None, + move_window=None, + remove_antipropagating_waves=None, + yield_zeroth_step=False): ''' Evolve time on a field. This function checks the transform_state of the field and transforms first from spatial @@ -761,29 +776,16 @@ def kspace_propagate_generator(kspace, dt, moving_window_vect=None, yield kspace +@prepend_doc_of(_kspace_propagate_generator) def kspace_propagate(kspace, dt, nsteps=1, **kwargs): ''' - Evolve time on a field. - This function checks the transform_state of the field and transforms first from spatial - domain to frequency domain if necessary. In this case the inverse transform will also - be applied to the result before returning it. This works, however, only correctly with - fields that are the inverse transforms of a k-space reconstruction, i.e. with complex - fields. - - dt: time in seconds - nsteps: number of steps to take If nsteps == 1, this function will just return the result. If nsteps > 1, this function will return a generator that will generate the results. If you want a list, just put list(...) around the return value. - - For additional arguments, see the documentation of kspace_propagate_generator, e.g.: - - If yield_zeroth_step is True, then the kspace will also be yielded after removing the - antipropagating waves, but before the first actual step is done. ''' - gen = kspace_propagate_generator(kspace, dt, **kwargs) + gen = _kspace_propagate_generator(kspace, dt, **kwargs) if nsteps == 1: return next(gen)