Commit 4651c191 authored by Martin Bauer's avatar Martin Bauer
Browse files

Documentation overhaul Part2

parent 01ab38e8
...@@ -3,7 +3,7 @@ from . import sympy_gmpy_bug_workaround # NOQA ...@@ -3,7 +3,7 @@ from . import sympy_gmpy_bug_workaround # NOQA
from .field import Field, FieldType, fields from .field import Field, FieldType, fields
from .data_types import TypedSymbol from .data_types import TypedSymbol
from .slicing import make_slice from .slicing import make_slice
from .kernelcreation import create_kernel, create_indexed_kernel from .kernelcreation import create_kernel, create_indexed_kernel, create_staggered_kernel
from .display_utils import show_code, to_dot from .display_utils import show_code, to_dot
from .simp import AssignmentCollection from .simp import AssignmentCollection
from .assignment import Assignment from .assignment import Assignment
...@@ -15,7 +15,7 @@ from . import fd ...@@ -15,7 +15,7 @@ from . import fd
__all__ = ['Field', 'FieldType', 'fields', __all__ = ['Field', 'FieldType', 'fields',
'TypedSymbol', 'TypedSymbol',
'make_slice', 'make_slice',
'create_kernel', 'create_indexed_kernel', 'create_kernel', 'create_indexed_kernel', 'create_staggered_kernel',
'show_code', 'to_dot', 'show_code', 'to_dot',
'AssignmentCollection', 'AssignmentCollection',
'Assignment', 'Assignment',
...@@ -23,4 +23,3 @@ __all__ = ['Field', 'FieldType', 'fields', ...@@ -23,4 +23,3 @@ __all__ = ['Field', 'FieldType', 'fields',
'create_data_handling', 'create_data_handling',
'kernel', 'kernel',
'fd'] 'fd']
r""" r"""
*pystencils* automatically searches for a compiler, so in most cases no explicit configuration is required.
On Linux make sure that 'gcc' and 'g++' are installed and in your path.
On Windows a recent Visual Studio installation is required.
In case anything does not work as expected or a special compiler should be used, changes can be specified
in a configuration file.
*pystencils* looks for a configuration file in JSON format at the following locations in the listed order. *pystencils* looks for a configuration file in JSON format at the following locations in the listed order.
1. at the path specified in the environment variable ``PYSTENCILS_CONFIG`` 1. at the path specified in the environment variable ``PYSTENCILS_CONFIG``
......
...@@ -33,7 +33,7 @@ def highlight_cpp(code: str): ...@@ -33,7 +33,7 @@ def highlight_cpp(code: str):
def show_code(ast: KernelFunction): def show_code(ast: KernelFunction):
"""Returns an object to display C code. """Returns an object to display generated code (C/C++ or CUDA)
Can either be displayed as HTML in Jupyter notebooks or printed as normal string. Can either be displayed as HTML in Jupyter notebooks or printed as normal string.
""" """
......
...@@ -9,38 +9,40 @@ from pystencils.alignedarray import aligned_empty ...@@ -9,38 +9,40 @@ from pystencils.alignedarray import aligned_empty
from pystencils.data_types import TypedSymbol, create_type, create_composite_type_from_string, StructType from pystencils.data_types import TypedSymbol, create_type, create_composite_type_from_string, StructType
from pystencils.sympyextensions import is_integer_sequence from pystencils.sympyextensions import is_integer_sequence
__all__ = ['Field', 'fields', 'FieldType']
def fields(description=None, index_dimensions=0, layout=None, **kwargs): def fields(description=None, index_dimensions=0, layout=None, **kwargs):
"""Creates pystencils fields from a string description. """Creates pystencils fields from a string description.
Examples: Examples:
Create a 2D scalar and vector field Create a 2D scalar and vector field:
>>> s, v = fields("s, v(2): double[2D]") >>> s, v = fields("s, v(2): double[2D]")
>>> assert s.spatial_dimensions == 2 and s.index_dimensions == 0 >>> assert s.spatial_dimensions == 2 and s.index_dimensions == 0
>>> assert v.spatial_dimensions == 2 and v.index_dimensions == 1 and v.index_shape == (2,) >>> assert (v.spatial_dimensions, v.index_dimensions, v.index_shape) == (2, 1, (2,))
Create an integer field of shape (10, 20)
>>> f = fields("f : int32[10, 20]")
>>> f.has_fixed_shape, f.shape
(True, (10, 20))
Numpy arrays can be used as template for shape and data type of field Create an integer field of shape (10, 20):
>>> arr_s, arr_v = np.zeros([20, 20]), np.zeros([20, 20, 2]) >>> f = fields("f : int32[10, 20]")
>>> s, v = fields("s, v(2)", s=arr_s, v=arr_v) >>> f.has_fixed_shape, f.shape
>>> assert s.index_dimensions == 0 and v.index_shape == (2,) and s.dtype.numpy_dtype == arr_s.dtype (True, (10, 20))
Format string can be left out, field names are taken from keyword arguments. Numpy arrays can be used as template for shape and data type of field:
>>> fields(f1=arr_s, f2=arr_s) >>> arr_s, arr_v = np.zeros([20, 20]), np.zeros([20, 20, 2])
[f1, f2] >>> s, v = fields("s, v(2)", s=arr_s, v=arr_v)
>>> assert s.index_dimensions == 0 and s.dtype.numpy_dtype == arr_s.dtype
>>> assert v.index_shape == (2,)
The keyword names 'index_dimension' and 'layout' have special meaning and thus can not be used to pass
numpy arrays:
>>> f = fields(f=arr_v, index_dimensions=1)
>>> assert f.index_dimensions == 1
>>> f = fields("pdfs(19) : float32[3D]", layout='fzyx') Format string can be left out, field names are taken from keyword arguments.
>>> f.layout >>> fields(f1=arr_s, f2=arr_s)
(2, 1, 0) [f1, f2]
The keyword names ``index_dimension`` and ``layout`` have special meaning, don't use them for field names
>>> f = fields(f=arr_v, index_dimensions=1)
>>> assert f.index_dimensions == 1
>>> f = fields("pdfs(19) : float32[3D]", layout='fzyx')
>>> f.layout
(2, 1, 0)
""" """
result = [] result = []
if description: if description:
...@@ -108,36 +110,41 @@ class Field: ...@@ -108,36 +110,41 @@ class Field:
This Field class knows about the dimension, memory layout (strides) and optionally about the size of an array. This Field class knows about the dimension, memory layout (strides) and optionally about the size of an array.
Creating Fields: Creating Fields:
The preferred method to create fields is the `fields` function.
To create a field use one of the static create* members. There are two options: Alternatively one can use one of the static functions `Field.create_generic`, `Field.create_from_numpy_array`
and `Field.create_fixed_size`. Don't instantiate the Field directly!
1. create a kernel with fixed loop sizes i.e. the shape of the array is already known. This is usually the Fields can be created with known or unknown shapes:
case if just-in-time compilation directly from Python is done. (see :func:`Field.create_from_numpy_array`)
1. If you want to create a kernel with fixed loop sizes i.e. the shape of the array is already known.
This is usually the case if just-in-time compilation directly from Python is done.
(see `Field.create_from_numpy_array`
2. create a more general kernel that works for variable array sizes. This can be used to create kernels 2. create a more general kernel that works for variable array sizes. This can be used to create kernels
beforehand for a library. (see :func:`Field.create_generic`) beforehand for a library. (see `Field.create_generic`)
Dimensions: Dimensions and Indexing:
A field has spatial and index dimensions, where the spatial dimensions come first. A field has spatial and index dimensions, where the spatial dimensions come first.
The interpretation is that the field has multiple cells in (usually) two or three dimensional space which are The interpretation is that the field has multiple cells in (usually) two or three dimensional space which are
looped over. Additionally N values are stored per cell. In this case spatial_dimensions is two or three, looped over. Additionally N values are stored per cell. In this case spatial_dimensions is two or three,
and index_dimensions equals N. If you want to store a matrix on each point in a two dimensional grid, there and index_dimensions equals N. If you want to store a matrix on each point in a two dimensional grid, there
are four dimensions, two spatial and two index dimensions: ``len(arr.shape) == spatial_dims + index_dims`` are four dimensions, two spatial and two index dimensions: ``len(arr.shape) == spatial_dims + index_dims``
Indexing: The shape of the index dimension does not have to be specified. Just use the 'index_dimensions' parameter.
When accessing (indexing) a field the result is a FieldAccess which is derived from sympy Symbol. However, it is good practice to define the shape, since out of bounds accesses can be directly detected in this
case. The shape can be passed with the 'index_shape' parameter of the field creation functions.
When accessing (indexing) a field the result is a `Field.Access` which is derived from sympy Symbol.
First specify the spatial offsets in [], then in case index_dimension>0 the indices in () First specify the spatial offsets in [], then in case index_dimension>0 the indices in ()
e.g. ``f[-1,0,0](7)`` e.g. ``f[-1,0,0](7)``
Example without index dimensions: Example using no index dimensions:
>>> a = np.zeros([10, 10]) >>> a = np.zeros([10, 10])
>>> f = Field.create_from_numpy_array("f", a, index_dimensions=0) >>> f = Field.create_from_numpy_array("f", a, index_dimensions=0)
>>> jacobi = ( f[-1,0] + f[1,0] + f[0,-1] + f[0,1] ) / 4 >>> jacobi = (f[-1,0] + f[1,0] + f[0,-1] + f[0,1]) / 4
Example with index dimensions: LBM D2Q9 stream pull Examples for index dimensions to create LB field and implement stream pull:
>>> from pystencils import Assignment >>> from pystencils import Assignment
>>> stencil = np.array([[0,0], [0,1], [0,-1]]) >>> stencil = np.array([[0,0], [0,1], [0,-1]])
>>> src = Field.create_generic("src", spatial_dimensions=2, index_dimensions=1) >>> src, dst = fields("src(3), dst(3) : double[2D]")
>>> dst = Field.create_generic("dst", spatial_dimensions=2, index_dimensions=1)
>>> for i, offset in enumerate(stencil): >>> for i, offset in enumerate(stencil):
... Assignment(dst[0,0](i), src[-offset](i)) ... Assignment(dst[0,0](i), src[-offset](i))
Assignment(dst_C^0, src_C^0) Assignment(dst_C^0, src_C^0)
...@@ -380,6 +387,24 @@ class Field: ...@@ -380,6 +387,24 @@ class Field:
# noinspection PyAttributeOutsideInit,PyUnresolvedReferences # noinspection PyAttributeOutsideInit,PyUnresolvedReferences
class Access(sp.Symbol): class Access(sp.Symbol):
"""Class representing a relative access into a `Field`.
This class behaves like a normal sympy Symbol, it is actually derived from it. One can built up
sympy expressions using field accesses, solve for them, etc.
Examples:
>>> vector_field_2d = fields("v(2): double[2D]") # create a 2D vector field
>>> northern_neighbor_y_component = vector_field_2d[0, 1](1)
>>> northern_neighbor_y_component
v_N^1
>>> central_y_component = vector_field_2d(1)
>>> central_y_component
v_C^1
>>> central_y_component.get_shifted(1, 0) # move the existing access
v_E^1
>>> central_y_component.at_index(0) # change component
v_C^0
"""
def __new__(cls, name, *args, **kwargs): def __new__(cls, name, *args, **kwargs):
obj = Field.Access.__xnew_cached_(cls, name, *args, **kwargs) obj = Field.Access.__xnew_cached_(cls, name, *args, **kwargs)
return obj return obj
...@@ -458,15 +483,18 @@ class Field: ...@@ -458,15 +483,18 @@ class Field:
raise TypeError("Field access is not iterable") raise TypeError("Field access is not iterable")
@property @property
def field(self): def field(self) -> 'Field':
"""Field that the Access points to"""
return self._field return self._field
@property @property
def offsets(self): def offsets(self) -> Tuple:
"""Spatial offset as tuple"""
return tuple(self._offsets) return tuple(self._offsets)
@property @property
def required_ghost_layers(self): def required_ghost_layers(self) -> int:
"""Largest spatial distance that is accessed."""
return int(np.max(np.abs(self._offsets))) return int(np.max(np.abs(self._offsets)))
@property @property
...@@ -475,21 +503,54 @@ class Field: ...@@ -475,21 +503,54 @@ class Field:
@property @property
def offset_name(self) -> str: def offset_name(self) -> str:
"""Spatial offset as string, East-West for x, North-South for y and Top-Bottom for z coordinate.
Example:
>>> f = fields("f: double[2D]")
>>> f[1, 1].offset_name # north-east
'NE'
"""
return self._offsetName return self._offsetName
@property @property
def index(self): def index(self):
"""Value of index coordinates as tuple."""
return self._index return self._index
def neighbor(self, coord_id: int, offset: Sequence[int]) -> 'Field.Access': def neighbor(self, coord_id: int, offset: Sequence[int]) -> 'Field.Access':
"""Returns a new Access with changed spatial coordinates.
Args:
coord_id: index of the coordinate to change (0 for x, 1 for y,...)
offset: incremental change of this coordinate
Example:
>>> f = fields('f: [2D]')
>>> f[0,0].neighbor(coord_id=1, offset=-1)
f_S
"""
offset_list = list(self.offsets) offset_list = list(self.offsets)
offset_list[coord_id] += offset offset_list[coord_id] += offset
return Field.Access(self.field, tuple(offset_list), self.index) return Field.Access(self.field, tuple(offset_list), self.index)
def get_shifted(self, *shift)-> 'Field.Access': def get_shifted(self, *shift)-> 'Field.Access':
"""Returns a new Access with changed spatial coordinates
Example:
>>> f = fields("f: [2D]")
>>> f[0,0].get_shifted(1, 1)
f_NE
"""
return Field.Access(self.field, tuple(a + b for a, b in zip(shift, self.offsets)), self.index) return Field.Access(self.field, tuple(a + b for a, b in zip(shift, self.offsets)), self.index)
def at_index(self, *idx_tuple): def at_index(self, *idx_tuple) -> 'Field.Access':
"""Returns new Access with changed index.
Example:
>>> f = fields("f(9): [2D]")
>>> f(0).at_index(8)
f_C^8
"""
return Field.Access(self.field, self.offsets, idx_tuple) return Field.Access(self.field, self.offsets, idx_tuple)
def _hashable_content(self): def _hashable_content(self):
...@@ -729,12 +790,12 @@ def direction_string_to_offset(direction: str, dim: int = 3): ...@@ -729,12 +790,12 @@ def direction_string_to_offset(direction: str, dim: int = 3):
direction = direction[1:] direction = direction[1:]
return offset[:dim] return offset[:dim]
def _parse_type_description(type_description): def _parse_type_description(type_description):
if not type_description: if not type_description:
return np.float64, None return np.float64, None
elif '[' in type_description: elif '[' in type_description:
assert type_description[-1] == ']' assert type_description[-1] == ']'
splitted = type_description[:-1].split("[", )
type_part, size_part = type_description[:-1].split("[", ) type_part, size_part = type_description[:-1].split("[", )
if not type_part: if not type_part:
type_part = "float64" type_part = "float64"
......
from pystencils.gpucuda.kernelcreation import create_cuda_kernel, created_indexed_cuda_kernel from pystencils.gpucuda.kernelcreation import create_cuda_kernel, created_indexed_cuda_kernel
from pystencils.gpucuda.cudajit import make_python_function from pystencils.gpucuda.cudajit import make_python_function
from .indexing import AbstractIndexing, BlockIndexing, LineIndexing
__all__ = ['create_cuda_kernel', 'created_indexed_cuda_kernel', 'make_python_function'] __all__ = ['create_cuda_kernel', 'created_indexed_cuda_kernel', 'make_python_function',
'AbstractIndexing', 'BlockIndexing', 'LineIndexing']
import abc import abc
from typing import Tuple from typing import Tuple # noqa
import sympy as sp import sympy as sp
from pystencils.astnodes import Conditional, Block from pystencils.astnodes import Conditional, Block
from pystencils.slicing import normalize_slice from pystencils.slicing import normalize_slice
......
from types import MappingProxyType from types import MappingProxyType
import sympy as sp import sympy as sp
from .assignment import Assignment from pystencils.assignment import Assignment
from .astnodes import LoopOverCoordinate, Conditional, Block, SympyAssignment from pystencils.astnodes import LoopOverCoordinate, Conditional, Block, SympyAssignment
from .simp.assignment_collection import AssignmentCollection from pystencils.simp.assignment_collection import AssignmentCollection
from .gpucuda.indexing import indexing_creator_from_params from pystencils.gpucuda.indexing import indexing_creator_from_params
from .transformations import remove_conditionals_in_staggered_kernel from pystencils.transformations import remove_conditionals_in_staggered_kernel
def create_kernel(assignments, target='cpu', data_type="double", iteration_slice=None, ghost_layers=None, def create_kernel(assignments, target='cpu', data_type="double", iteration_slice=None, ghost_layers=None,
...@@ -14,7 +14,7 @@ def create_kernel(assignments, target='cpu', data_type="double", iteration_slice ...@@ -14,7 +14,7 @@ def create_kernel(assignments, target='cpu', data_type="double", iteration_slice
Creates abstract syntax tree (AST) of kernel, using a list of update equations. Creates abstract syntax tree (AST) of kernel, using a list of update equations.
Args: Args:
assignments: either be a plain list of equations or a AssignmentCollection object assignments: can be a single assignment, sequence of assignments or an `AssignmentCollection`
target: 'cpu', 'llvm' or 'gpu' target: 'cpu', 'llvm' or 'gpu'
data_type: data type used for all untyped symbols (i.e. non-fields), can also be a dict from symbol name data_type: data type used for all untyped symbols (i.e. non-fields), can also be a dict from symbol name
to type to type
...@@ -22,18 +22,34 @@ def create_kernel(assignments, target='cpu', data_type="double", iteration_slice ...@@ -22,18 +22,34 @@ def create_kernel(assignments, target='cpu', data_type="double", iteration_slice
part of the field is iterated over part of the field is iterated over
ghost_layers: if left to default, the number of necessary ghost layers is determined automatically ghost_layers: if left to default, the number of necessary ghost layers is determined automatically
a single integer specifies the ghost layer count at all borders, can also be a sequence of a single integer specifies the ghost layer count at all borders, can also be a sequence of
pairs [(x_lower_gl, x_upper_gl), .... ] pairs ``[(x_lower_gl, x_upper_gl), .... ]``
cpu_openmp: True or number of threads for OpenMP parallelization, False for no OpenMP cpu_openmp: True or number of threads for OpenMP parallelization, False for no OpenMP
cpu_vectorize_info: pair of instruction set name ('sse, 'avx', 'avx512') and data type ('float', 'double') cpu_vectorize_info: pair of instruction set name, i.e. one of 'sse, 'avx' or 'avx512'
and data type 'float' or 'double'. For example ``('avx', 'double')``
gpu_indexing: either 'block' or 'line' , or custom indexing class (see gpucuda/indexing.py) gpu_indexing: either 'block' or 'line' , or custom indexing class, see `pystencils.gpucuda.AbstractIndexing`
gpu_indexing_params: dict with indexing parameters (constructor parameters of indexing class) gpu_indexing_params: dict with indexing parameters (constructor parameters of indexing class)
e.g. for 'block' one can specify {'block_size': (20, 20, 10) } e.g. for 'block' one can specify '{'block_size': (20, 20, 10) }'
Returns: Returns:
abstract syntax tree object, that can either be printed as source code with `show_code` or can be compiled with abstract syntax tree (AST) object, that can either be printed as source code with `show_code` or
through its `compile()` member can be compiled with through its `compile()` member
Example:
>>> import pystencils as ps
>>> import numpy as np
>>> s, d = ps.fields('s, d: [2D]')
>>> assignment = ps.Assignment(d[0,0], s[0, 1] + s[0, -1] + s[1, 0] + s[-1, 0])
>>> ast = ps.create_kernel(assignment, target='cpu', cpu_openmp=True)
>>> kernel = ast.compile()
>>> d_arr = np.zeros([5, 5])
>>> kernel(d=d_arr, s=np.ones([5, 5]))
>>> d_arr
array([[0., 0., 0., 0., 0.],
[0., 4., 4., 4., 0.],
[0., 4., 4., 4., 0.],
[0., 4., 4., 4., 0.],
[0., 0., 0., 0., 0.]])
""" """
# ---- Normalizing parameters # ---- Normalizing parameters
...@@ -89,9 +105,33 @@ def create_indexed_kernel(assignments, index_fields, target='cpu', data_type="do ...@@ -89,9 +105,33 @@ def create_indexed_kernel(assignments, index_fields, target='cpu', data_type="do
index_fields: list of index fields, i.e. 1D fields with struct data type index_fields: list of index fields, i.e. 1D fields with struct data type
coordinate_names: name of the coordinate fields in the struct data type coordinate_names: name of the coordinate fields in the struct data type
"""
if isinstance(assignments, AssignmentCollection): Example:
>>> import pystencils as ps
>>> import numpy as np
>>>
>>> # Index field stores the indices of the cell to visit together with optional values
>>> index_arr_dtype = np.dtype([('x', np.int32), ('y', np.int32), ('val', np.double)])
>>> index_arr = np.array([(1, 1, 0.1), (2, 2, 0.2), (3, 3, 0.3)], dtype=index_arr_dtype)
>>> idx_field = ps.fields(idx=index_arr)
>>>
>>> # Additional values stored in index field can be accessed in the kernel as well
>>> s, d = ps.fields('s, d: [2D]')
>>> assignment = ps.Assignment(d[0,0], 2 * s[0, 1] + 2 * s[1, 0] + idx_field('val'))
>>> ast = create_indexed_kernel(assignment, [idx_field], coordinate_names=('x', 'y'))
>>> kernel = ast.compile()
>>> d_arr = np.zeros([5, 5])
>>> kernel(s=np.ones([5, 5]), d=d_arr, idx=index_arr)
>>> d_arr
array([[0. , 0. , 0. , 0. , 0. ],
[0. , 4.1, 0. , 0. , 0. ],
[0. , 0. , 4.2, 0. , 0. ],
[0. , 0. , 0. , 4.3, 0. ],
[0. , 0. , 0. , 0. , 0. ]])
"""
if isinstance(assignments, Assignment):
assignments = [assignments]
elif isinstance(assignments, AssignmentCollection):
assignments = assignments.all_assignments assignments = assignments.all_assignments
if target == 'cpu': if target == 'cpu':
from pystencils.cpu import create_indexed_kernel from pystencils.cpu import create_indexed_kernel
...@@ -116,10 +156,12 @@ def create_indexed_kernel(assignments, index_fields, target='cpu', data_type="do ...@@ -116,10 +156,12 @@ def create_indexed_kernel(assignments, index_fields, target='cpu', data_type="do
def create_staggered_kernel(staggered_field, expressions, subexpressions=(), target='cpu', **kwargs): def create_staggered_kernel(staggered_field, expressions, subexpressions=(), target='cpu', **kwargs):
"""Kernel that updates a staggered field. """Kernel that updates a staggered field.
.. image:: /img/staggered_grid.svg
Args: Args:
staggered_field: field that has one index coordinate and staggered_field: field that has one index coordinate and
where e.g. f[0,0](0) is interpreted as value at the left cell boundary, f[1,0](0) the right cell where e.g. ``f[0,0](0)`` is interpreted as value at the left cell boundary, ``f[1,0](0)`` the right cell
boundary and f[0,0](1) the southern cell boundary etc. boundary and ``f[0,0](1)`` the southern cell boundary etc.
expressions: sequence of expressions of length dim, defining how the east, southern, (bottom) cell boundary expressions: sequence of expressions of length dim, defining how the east, southern, (bottom) cell boundary
should be update should be update
subexpressions: optional sequence of Assignments, that define subexpressions used in the main expressions subexpressions: optional sequence of Assignments, that define subexpressions used in the main expressions
......
...@@ -253,6 +253,36 @@ def vector_field_magnitude_animation(run_function, plot_setup_function=lambda *_ ...@@ -253,6 +253,36 @@ def vector_field_magnitude_animation(run_function, plot_setup_function=lambda *_
return animation.FuncAnimation(fig, update_figure, interval=interval, frames=frames) return animation.FuncAnimation(fig, update_figure, interval=interval, frames=frames)
def scalar_field_animation(run_function, plot_setup_function=lambda *_: None, rescale=True,
plot_update_function=lambda *_: None, interval=30, frames=180, **kwargs):
import matplotlib.animation as animation
fig = gcf()
im = None
field = run_function()
if rescale:
f_min, f_max = np.min(field), np.max(field)
field = (field - f_min) / (f_max - f_min)
im = scalar_field(field, vmin=0.0, vmax=1.0, **kwargs)
else:
im = scalar_field(field, **kwargs)
plot_setup_function(im)
def update_figure(*_):
f = run_function()
if rescale:
f_min, f_max = np.min(f), np.max(f)
f = (f - f_min) / (f_max - f_min)
if hasattr(f, 'mask'):
f = np.ma.masked_array(f, mask=f.mask[:, :, 0])
f = np.swapaxes(f, 0, 1)
im.set_array(f)
plot_update_function(im)
return im,
return animation.FuncAnimation(fig, update_figure, interval=interval, frames=frames)
def surface_plot_animation(run_function, frames=90, interval=30): def surface_plot_animation(run_function, frames=90, interval=30):
from mpl_toolkits.mplot3d import Axes3D from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation import matplotlib.animation as animation
......
...@@ -687,6 +687,7 @@ class KernelConstraintsCheck: ...@@ -687,6 +687,7 @@ class KernelConstraintsCheck:
def process_expression(self, rhs): def process_expression(self, rhs):
self._update_accesses_rhs(rhs) self._update_accesses_rhs(rhs)
if isinstance(rhs, Field.Access): if isinstance(rhs, Field.Access):
self.fields_read.add(rhs.field)
return rhs return rhs
elif isinstance(rhs, TypedSymbol): elif isinstance(rhs, TypedSymbol):
return rhs return rhs
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment