Commit b1e9b1e4 authored by Michael Kuron's avatar Michael Kuron
Browse files

Merge branch 'master' of i10git.cs.fau.de:pycodegen/pystencils into abs

parents 659319bd a5ab1070
......@@ -65,16 +65,21 @@ minimal-windows:
- python -c "import numpy"
- python setup.py quicktest
minimal-ubuntu:
ubuntu:
stage: test
except:
variables:
- $ENABLE_NIGHTLY_BUILDS
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/minimal_ubuntu
image: i10git.cs.fau.de:5005/pycodegen/pycodegen/ubuntu
script:
- python3 setup.py quicktest
- mkdir -p ~/.config/matplotlib
- echo "backend:template" > ~/.config/matplotlib/matplotlibrc
- sed -i 's/--doctest-modules //g' pytest.ini
- pytest-3 -v -m "not longrun"
tags:
- docker
- cuda
- AVX
minimal-conda:
stage: test
......
import os
import runpy
import sys
import warnings
import tempfile
import warnings
import nbformat
import pytest
......@@ -34,7 +34,7 @@ def add_path_to_ignore(path):
collect_ignore = [os.path.join(SCRIPT_FOLDER, "doc", "conf.py"),
os.path.join(SCRIPT_FOLDER, "pystencils", "opencl", "opencl.autoinit")]
os.path.join(SCRIPT_FOLDER, "pystencils", "opencl", "opencl.autoinit")]
add_path_to_ignore('pystencils_tests/benchmark')
add_path_to_ignore('_local_tmp')
......@@ -44,7 +44,7 @@ collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils/autodiff.py")]
try:
import pycuda
except ImportError:
collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils/pystencils_tests/test_cudagpu.py")]
collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils_tests/test_cudagpu.py")]
add_path_to_ignore('pystencils/gpucuda')
try:
......@@ -73,7 +73,22 @@ try:
import blitzdb
except ImportError:
add_path_to_ignore('pystencils/runhelper')
collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils_tests/test_parameterstudy.py")]
try:
import islpy
except ImportError:
collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils/integer_set_analysis.py")]
try:
import graphviz
except ImportError:
collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils/backends/dot.py")]
try:
import pyevtk
except ImportError:
collect_ignore += [os.path.join(SCRIPT_FOLDER, "pystencils/datahandling/vtk.py")]
collect_ignore += [os.path.join(SCRIPT_FOLDER, 'setup.py')]
......@@ -83,8 +98,6 @@ for root, sub_dirs, files in os.walk('.'):
collect_ignore.append(f)
class IPythonMockup:
def run_line_magic(self, *args, **kwargs):
pass
......@@ -131,7 +144,7 @@ class IPyNbFile(pytest.File):
exporter.exclude_markdown = True
exporter.exclude_input_prompt = True
notebook_contents = self.fspath.open()
notebook_contents = self.fspath.open(encoding='utf-8')
with warnings.catch_warnings():
warnings.filterwarnings("ignore", "IPython.core.inputsplitter is deprecated")
......
......@@ -441,18 +441,22 @@
Now lets grab an image to apply this filter to:
%% Cell type:code id: tags:
``` python
import requests
import imageio
from io import BytesIO
try:
import requests
import imageio
from io import BytesIO
response = requests.get("https://www.python.org/static/img/python-logo.png")
img = imageio.imread(BytesIO(response.content)).astype(np.double)
img /= img.max()
plt.imshow(img);
response = requests.get("https://www.python.org/static/img/python-logo.png")
img = imageio.imread(BytesIO(response.content)).astype(np.double)
img /= img.max()
plt.imshow(img);
except ImportError:
print("No requests installed")
img = np.random.random((82, 290, 4))
```
%%%% Output: display_data
![](data:image/png;base64,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AAC6SK6uuWbWr1sHoX8QQviTyt3nzWwkhHDWzEYkXWjXJLuKuaxtKkKViE35brohmKurWvHWfczbormp9eVZvuD41LKN57fwXKueNwC00HKNuC7XeQNon3379i045p577ql7/44dO3KtY/369YXmBEj5uuaapM9JeiWE8E/dl74q6dFK/aikp1o/PQAAAABAt8nziej7JP0dSS+Z2YuV+35b0qclfcnMHpN0UtJH2jNFAAAAAEA3ydM19z9KssSXH27tdLpfb29vrFe7zmX9vXkirj6C6x60aDxWkuZT3Wez+sb1G7GenZlJrLoN8d2cy/vnAAAAAGD5KNQ1FwAAAACAZnEgCgAAAAAoVa6uuWidn3jwr8T694ZPx7rXUunnVskbY83m8U++/O1YvzC2KtbzM9mFjot2xM0V5W24jBvjnlOYp4PuSnT16tVY+wtw9/Vlb21r1qyJ9cDAQDkTWyJ+G3T7c22Hnp76f5vN02XW73NLia65y9f169fr1v77tXr16ljTpbS41DaWqrezf/8cGhpq/8SAFYpPRAEAAAAApeJAFAAAAABQqs7IEnWgS2+9FetTZ87H+ubNm9mgRcSbrCeLvq7q71/c5Fou1C2np2fdGPc3i2Y64ubphnvb8v4L9TsH+1jcpUtjsT516lisey3H7u5W1lMTtRvZsSvWm7duX/ixkMvExETd+saNrGvzjOvafOedd8baRykHXRfqlAsXLlTdHh8fj7WP8O7du3fBxyrbpUuXYv2We3/y+765iP/+/ftLmVcR/nV98eLFWPtIsee/v9PT07FezHPz32u/b/k5rVqVnYJQG9urx49Zu3ZtrE+fzk67aBSD9c/P73+bNm1acN2dwm/X8+ezn5Xr1q2L9bVr12I9706j8M/fxx9rY87btm1rzWSX0BtvvBFrvy9v3Lixbp16P/Od9/32rn1v89tzx44dse62yP7Jkydj7d//xsay3wN27cp+dufZxlL1fup/7zt16lTd8X4deX4WAbiFT0QBAAAAAKXiQBQAAAAAUKoVH8098sPjsf7ai1mc6tRs1o3u4uUsxjWbO1o6X/9rfpn5PLHWHI9TcHzj9WX13Ez2d4owd9ONqf+weeK4ueK7DVaSetzBwSzWdunMkVg/8I5nsvGTWRxqUXqzSM/pN7Ko0+qhX4j1li07hPpOnDgRax972rlzZ6w3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)
......
%% Cell type:code id: tags:
 
``` python
from pystencils.session import *
import shutil
```
 
%% Cell type:markdown id: tags:
 
# Plotting and Animation
......@@ -211,13 +213,16 @@
```
 
%% Cell type:code id: tags:
 
``` python
plt.figure()
animation = plt.scalar_field_animation(run_func, frames=60)
ps.jupyter.display_as_html_video(animation)
if shutil.which("ffmpeg") is not None:
plt.figure()
animation = plt.scalar_field_animation(run_func, frames=60)
ps.jupyter.display_as_html_video(animation)
else:
print("No ffmpeg installed")
```
 
%%%% Output: execute_result
 
<IPython.core.display.HTML object>
......@@ -243,12 +248,15 @@
For surface plots there is also an animated version:
 
%% Cell type:code id: tags:
 
``` python
animation = plt.surface_plot_animation(run_func, frames=60)
ps.jupyter.display_as_html_video(animation)
if shutil.which("ffmpeg") is not None:
animation = plt.surface_plot_animation(run_func, frames=60)
ps.jupyter.display_as_html_video(animation)
else:
print("No ffmpeg installed")
```
 
%%%% Output: execute_result
 
<IPython.core.display.HTML object>
......@@ -332,13 +340,16 @@
```
 
%% Cell type:code id: tags:
 
``` python
plt.figure()
animation = plt.vector_field_animation(run_func, frames=60)
ps.jupyter.display_as_html_video(animation)
if shutil.which("ffmpeg") is not None:
plt.figure()
animation = plt.vector_field_animation(run_func, frames=60)
ps.jupyter.display_as_html_video(animation)
else:
print("No ffmpeg installed")
```
 
%%%% Output: execute_result
 
<IPython.core.display.HTML object>
......@@ -348,12 +359,15 @@
...and magnitude plots
 
%% Cell type:code id: tags:
 
``` python
animation = plt.vector_field_magnitude_animation(run_func, frames=60)
ps.jupyter.display_as_html_video(animation)
if shutil.which("ffmpeg") is not None:
animation = plt.vector_field_magnitude_animation(run_func, frames=60)
ps.jupyter.display_as_html_video(animation)
else:
print("No ffmpeg installed")
```
 
%%%% Output: execute_result
 
<IPython.core.display.HTML object>
%% Cell type:code id: tags:
``` python
from pystencils.session import *
import shutil
```
%% Cell type:markdown id: tags:
# Demo: Finite differences - 2D wave equation
......@@ -183,12 +185,15 @@
Lets create an animation of the solution:
%% Cell type:code id: tags:
``` python
ani = plt.surface_plot_animation(run, zlim=(-1, 1))
ps.jupyter.display_as_html_video(ani)
if shutil.which("ffmpeg") is not None:
ani = plt.surface_plot_animation(run, zlim=(-1, 1))
ps.jupyter.display_as_html_video(ani)
else:
print("No ffmpeg installed")
```
%%%% Output: execute_result
<IPython.core.display.HTML object>
......@@ -230,13 +235,16 @@
for t in range(timesteps):
kernel(u0=u_arrays[0], u1=u_arrays[1], u2=u_arrays[2])
u_arrays[0], u_arrays[1], u_arrays[2] = u_arrays[1], u_arrays[2], u_arrays[0]
return u_arrays[2]
ani = plt.surface_plot_animation(run_LLVM, zlim=(-1, 1))
assert np.isfinite(np.max(u_arrays[2]))
ps.jupyter.display_as_html_video(ani)
if shutil.which("ffmpeg") is not None:
ani = plt.surface_plot_animation(run_LLVM, zlim=(-1, 1))
ps.jupyter.display_as_html_video(ani)
else:
print("No ffmpeg installed")
```
%%%% Output: execute_result
<IPython.core.display.HTML object>
......
......@@ -5,7 +5,7 @@ from . import stencil as stencil
from .assignment import Assignment, assignment_from_stencil
from .data_types import TypedSymbol
from .datahandling import create_data_handling
from .display_utils import show_code, to_dot
from .display_utils import show_code, get_code_obj, get_code_str, to_dot
from .field import Field, FieldType, fields
from .kernel_decorator import kernel
from .kernelcreation import create_indexed_kernel, create_kernel, create_staggered_kernel
......@@ -25,7 +25,7 @@ __all__ = ['Field', 'FieldType', 'fields',
'TypedSymbol',
'make_slice',
'create_kernel', 'create_indexed_kernel', 'create_staggered_kernel',
'show_code', 'to_dot',
'show_code', 'to_dot', 'get_code_obj', 'get_code_str',
'AssignmentCollection',
'Assignment',
'assignment_from_stencil',
......
# -*- coding: utf-8 -*-
import numpy as np
import sympy as sp
from sympy.printing.latex import LatexPrinter
......@@ -24,9 +23,20 @@ def assignment_str(assignment):
if Assignment:
_old_new = sp.codegen.ast.Assignment.__new__
def _Assignment__new__(cls, lhs, rhs, *args, **kwargs):
if isinstance(lhs, (list, tuple, sp.Matrix)) and isinstance(rhs, (list, tuple, sp.Matrix)):
assert len(lhs) == len(rhs), f'{lhs} and {rhs} must have same length when performing vector assignment!'
return tuple(_old_new(cls, a, b, *args, **kwargs) for a, b in zip(lhs, rhs))
return _old_new(cls, lhs, rhs, *args, **kwargs)
Assignment.__str__ = assignment_str
Assignment.__new__ = _Assignment__new__
LatexPrinter._print_Assignment = print_assignment_latex
sp.MutableDenseMatrix.__hash__ = lambda self: hash(tuple(self))
else:
# back port for older sympy versions that don't have Assignment yet
......
......@@ -110,10 +110,17 @@ class Conditional(Node):
return result
def __str__(self):
return 'if:({!s}) '.format(self.condition_expr)
return self.__repr__()
def __repr__(self):
return 'if:({!r}) '.format(self.condition_expr)
repr = 'if:({!r}) '.format(self.condition_expr)
if self.true_block:
repr += '\n\t{}) '.format(self.true_block)
if self.false_block:
repr = 'else: '.format(self.false_block)
repr += '\n\t{} '.format(self.false_block)
return repr
def replace_by_true_block(self):
"""Replaces the conditional by its True block"""
......@@ -284,7 +291,10 @@ class Block(Node):
self._nodes = nodes
self.parent = None
for n in self._nodes:
n.parent = self
try:
n.parent = self
except AttributeError:
pass
@property
def args(self):
......
import re
from collections import namedtuple
from typing import Set
......@@ -10,11 +11,12 @@ from sympy.printing.ccode import C89CodePrinter
from pystencils.astnodes import KernelFunction, Node
from pystencils.cpu.vectorization import vec_all, vec_any
from pystencils.data_types import (
PointerType, VectorType, address_of, cast_func, create_type, get_type_of_expression, reinterpret_cast_func,
vector_memory_access)
PointerType, VectorType, address_of, cast_func, create_type, get_type_of_expression,
reinterpret_cast_func, vector_memory_access)
from pystencils.fast_approximation import fast_division, fast_inv_sqrt, fast_sqrt
from pystencils.integer_functions import (
bit_shift_left, bit_shift_right, bitwise_and, bitwise_or, bitwise_xor, int_div, int_power_of_2, modulo_ceil)
bit_shift_left, bit_shift_right, bitwise_and, bitwise_or, bitwise_xor,
int_div, int_power_of_2, modulo_ceil)
try:
from sympy.printing.ccode import C99CodePrinter as CCodePrinter
......@@ -23,6 +25,9 @@ except ImportError:
__all__ = ['generate_c', 'CustomCodeNode', 'PrintNode', 'get_headers', 'CustomSympyPrinter']
HEADER_REGEX = re.compile(r'^[<"].*[">]$')
KERNCRAFT_NO_TERNARY_MODE = False
......@@ -91,7 +96,7 @@ def get_global_declarations(ast):
visit_node(ast)
return sorted(set(global_declarations), key=lambda x: str(x))
return sorted(set(global_declarations), key=str)
def get_headers(ast_node: Node) -> Set[str]:
......@@ -111,6 +116,9 @@ def get_headers(ast_node: Node) -> Set[str]:
if isinstance(g, Node):
headers.update(get_headers(g))
for h in headers:
assert HEADER_REGEX.match(h), f'header /{h}/ does not follow the pattern /"..."/ or /<...>/'
return sorted(headers)
......@@ -392,6 +400,13 @@ class CustomSympyPrinter(CCodePrinter):
return self._print(expr.args[0])
elif isinstance(expr, fast_inv_sqrt):
return "({})".format(self._print(1 / sp.sqrt(expr.args[0])))
elif isinstance(expr, sp.Abs):
return "abs({})".format(self._print(expr.args[0]))
elif isinstance(expr, sp.Mod):
if expr.args[0].is_integer and expr.args[1].is_integer:
return "({} % {})".format(self._print(expr.args[0]), self._print(expr.args[1]))
else:
return "fmod({}, {})".format(self._print(expr.args[0]), self._print(expr.args[1]))
elif expr.func in infix_functions:
return "(%s %s %s)" % (self._print(expr.args[0]), infix_functions[expr.func], self._print(expr.args[1]))
elif expr.func == int_power_of_2:
......
......@@ -3,7 +3,7 @@ from os.path import dirname, join
from pystencils.astnodes import Node
from pystencils.backends.cbackend import CBackend, CustomSympyPrinter, generate_c
from pystencils.fast_approximation import fast_division, fast_inv_sqrt, fast_sqrt
from pystencils.interpolation_astnodes import InterpolationMode
from pystencils.interpolation_astnodes import DiffInterpolatorAccess, InterpolationMode
with open(join(dirname(__file__), 'cuda_known_functions.txt')) as f:
lines = f.readlines()
......@@ -70,10 +70,13 @@ class CudaSympyPrinter(CustomSympyPrinter):
super(CudaSympyPrinter, self).__init__()
self.known_functions.update(CUDA_KNOWN_FUNCTIONS)
def _print_TextureAccess(self, node):
dtype = node.texture.field.dtype.numpy_dtype
def _print_InterpolatorAccess(self, node):
dtype = node.interpolator.field.dtype.numpy_dtype
if node.texture.interpolation_mode == InterpolationMode.CUBIC_SPLINE:
if type(node) == DiffInterpolatorAccess:
# cubicTex3D_1st_derivative_x(texture tex, float3 coord)
template = f"cubicTex%iD_1st_derivative_{list(reversed('xyz'[:node.ndim]))[node.diff_coordinate_idx]}(%s, %s)" # noqa
elif node.interpolator.interpolation_mode == InterpolationMode.CUBIC_SPLINE:
template = "cubicTex%iDSimple(%s, %s)"
else:
if dtype.itemsize > 4:
......@@ -84,8 +87,8 @@ class CudaSympyPrinter(CustomSympyPrinter):
template = "tex%iD(%s, %s)"
code = template % (
node.texture.field.spatial_dimensions,
str(node.texture),
node.interpolator.field.spatial_dimensions,
str(node.interpolator),
# + 0.5 comes from Nvidia's staggered indexing
', '.join(self._print(o + 0.5) for o in reversed(node.offsets))
)
......
......@@ -8,6 +8,8 @@ from pystencils.boundaries.createindexlist import (
create_boundary_index_array, numpy_data_type_for_boundary_object)
from pystencils.cache import memorycache
from pystencils.data_types import TypedSymbol, create_type
from pystencils.datahandling import ParallelDataHandling
from pystencils.datahandling.pycuda import PyCudaArrayHandler
from pystencils.field import Field
from pystencils.kernelparameters import FieldPointerSymbol
......@@ -96,16 +98,23 @@ class BoundaryHandling:
def to_gpu(gpu_version, cpu_version):
gpu_version = gpu_version.boundary_object_to_index_list
cpu_version = cpu_version.boundary_object_to_index_list
if ParallelDataHandling and isinstance(self.data_handling, ParallelDataHandling):
array_handler = PyCudaArrayHandler()
else:
array_handler = self.data_handling.array_handler
for obj, cpu_arr in cpu_version.items():
if obj not in gpu_version or gpu_version[obj].shape != cpu_arr.shape:
gpu_version[obj] = self.data_handling.array_handler.to_gpu(cpu_arr)
gpu_version[obj] = array_handler.to_gpu(cpu_arr)
else:
self.data_handling.array_handler.upload(gpu_version[obj], cpu_arr)
array_handler.upload(gpu_version[obj], cpu_arr)
class_ = self.IndexFieldBlockData
class_.to_cpu = to_cpu
class_.to_gpu = to_gpu
data_handling.add_custom_class(self._index_array_name, class_)
gpu = self._target in data_handling._GPU_LIKE_TARGETS
data_handling.add_custom_class(self._index_array_name, class_, cpu=True, gpu=gpu)
@property
def data_handling(self):
......@@ -253,11 +262,13 @@ class BoundaryHandling:
"""
Writes a VTK field where each cell with the given boundary is marked with 1, other cells are 0
This can be used to display the simulation geometry in Paraview
:param file_name: vtk filename
:param boundaries: boundary object, or special string 'domain' for domain cells or special string 'all' for all
boundary conditions.
can also be a sequence, to write multiple boundaries to VTK file
:param ghost_layers: number of ghost layers to write, or True for all, False for none
Params:
file_name: vtk filename
boundaries: boundary object, or special string 'domain' for domain cells or special string 'all' for all
boundary conditions.
can also be a sequence, to write multiple boundaries to VTK file
ghost_layers: number of ghost layers to write, or True for all, False for none
"""
if boundaries == 'all':
boundaries = list(self._boundary_object_to_boundary_info.keys()) + ['domain']
......@@ -329,7 +340,7 @@ class BoundaryHandling:
self.kernel = kernel
class IndexFieldBlockData:
def __init__(self):
def __init__(self, *args, **kwargs):
self.boundary_object_to_index_list = {}
self.boundary_object_to_data_setter = {}
......
......@@ -270,7 +270,8 @@ if( PyErr_Occurred() ) {{ return NULL; }}
template_extract_complex = """
PyObject * obj_{name} = PyDict_GetItemString(kwargs, "{name}");
if( obj_{name} == NULL) {{ PyErr_SetString(PyExc_TypeError, "Keyword argument '{name}' missing"); return NULL; }};
{target_type} {name}{{ {extract_function_real}( obj_{name} ), {extract_function_imag}( obj_{name} ) }};
{target_type} {name}{{ ({real_type}) {extract_function_real}( obj_{name} ),
({real_type}) {extract_function_imag}( obj_{name} ) }};
if( PyErr_Occurred() ) {{ return NULL; }}
"""
......@@ -409,6 +410,8 @@ def create_function_boilerplate_code(parameter_info, name, insert_checks=True):
pre_call_code += template_extract_complex.format(extract_function_real=extract_function[0],
extract_function_imag=extract_function[1],
target_type=target_type,
real_type="float" if target_type == "ComplexFloat"
else "double",
name=param.symbol.name)
else:
pre_call_code += template_extract_scalar.format(extract_function=extract_function,
......
......@@ -575,9 +575,7 @@ def get_type_of_expression(expr,
raise NotImplementedError("Could not determine type for", expr, type(expr))
class Type(sp.Basic):
is_Atom = True
class Type(sp.Atom):
def __new__(cls, *args, **kwargs):
return sp.Basic.__new__(cls)
......
from typing import Any, Dict, Optional
from typing import Any, Dict, Optional, Union
import sympy as sp
......@@ -35,10 +35,10 @@ def highlight_cpp(code: str):
return HTML(highlight(code, CppLexer(), HtmlFormatter()))
def show_code(ast: KernelFunction, custom_backend=None):
def get_code_obj(ast: Union[KernelFunction, KernelWrapper], custom_backend=None):
"""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.
"""
from pystencils.backends.cbackend import generate_c
......@@ -65,3 +65,37 @@ def show_code(ast: KernelFunction, custom_backend=None):
def __repr__(self):
return generate_c(self.ast, dialect=dialect, custom_backend=custom_backend)
return CodeDisplay(ast)
def get_code_str(ast, custom_backend=None):
return str(get_code_obj(ast, custom_backend))
def _isnotebook():
try:
shell = get_ipython().__class__.__name__
if shell == 'ZMQInteractiveShell':
return True # Jupyter notebook or qtconsole
elif shell == 'TerminalInteractiveShell':
return False # Terminal running IPython
else:
return False # Other type (?)
except NameError:
return False
def show_code(ast: Union[KernelFunction, KernelWrapper], custom_backend=None):
code = get_code_obj(ast, custom_backend)
if _isnotebook():
from IPython.display import display
display(code)
else:
try:
import rich.syntax
import rich.console
syntax = rich.syntax.Syntax(str(code), "c++", theme="monokai", line_numbers=True)
console = rich.console.Console()
console.print(syntax)
except ImportError:
print(code)
import itertools
import warnings
from collections import defaultdict
import itertools
import numpy as np
import sympy as sp
......@@ -184,6 +184,9 @@ class FiniteDifferenceStencilDerivation:
result[max_offset - direction[1], max_offset + direction[0]] = weight
return result
def __array__(self):
return np.array(self.as_array().tolist())
def as_array(self):
dim = len(self.stencil[0])
assert (dim == 2 or dim == 3), "Only 2D or 3D matrix representations are available"
......@@ -205,12 +208,12 @@ class FiniteDifferenceStencilDerivation: