Commit de3489c4 authored by Stephan Seitz's avatar Stephan Seitz
Browse files

Merge branch 'oldsympy' into 'master'

CI: Replace minimal-ubuntu job with ubuntu

Closes #19

See merge request pycodegen/pystencils!135
parents 1cabb407 f46b5976
......@@ -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
......
......@@ -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')]
......@@ -129,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>
......
......@@ -291,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):
......
......@@ -387,6 +387,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:
......
......@@ -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,
......
......@@ -316,7 +316,8 @@ def expand_diff_full(expr, functions=None, constants=None):
functions.difference_update(constants)
def visit(e):
e = e.expand()
if not isinstance(e, sp.Tuple):
e = e.expand()
if e.func == Diff:
result = 0
......@@ -341,6 +342,9 @@ def expand_diff_full(expr, functions=None, constants=None):
return result
elif isinstance(e, sp.Piecewise):
return sp.Piecewise(*((expand_diff_full(a, functions, constants), b) for a, b in e.args))
elif isinstance(expr, sp.Tuple):
new_args = [visit(arg) for arg in e.args]
return sp.Tuple(*new_args)
else:
new_args = [visit(arg) for arg in e.args]
return e.func(*new_args) if new_args else e
......@@ -380,6 +384,9 @@ def expand_diff_linear(expr, functions=None, constants=None):
return diff.split_linear(functions)
elif isinstance(expr, sp.Piecewise):
return sp.Piecewise(*((expand_diff_linear(a, functions, constants), b) for a, b in expr.args))
elif isinstance(expr, sp.Tuple):
new_args = [expand_diff_linear(e, functions) for e in expr.args]
return sp.Tuple(*new_args)
else:
new_args = [expand_diff_linear(e, functions) for e in expr.args]
result = sp.expand(expr.func(*new_args) if new_args else expr)
......
......@@ -1173,53 +1173,53 @@ operator<<(std::basic_ostream<_CharT, _Traits> &__os, const complex<_Tp> &__x) {
template <class U, class V>
CUDA_CALLABLE_MEMBER auto operator*(const complex<U> &complexNumber,
const V &scalar) -> complex<U> {
return complex<U>{real(complexNumber) * scalar, imag(complexNumber) * scalar};
return complex<U>(real(complexNumber) * scalar, imag(complexNumber) * scalar);
}
template <class U, class V>
CUDA_CALLABLE_MEMBER auto operator*(const V &scalar,
const complex<U> &complexNumber)
-> complex<U> {
return complex<U>{real(complexNumber) * scalar, imag(complexNumber) * scalar};
return complex<U>(real(complexNumber) * scalar, imag(complexNumber) * scalar);
}
template <class U, class V>
CUDA_CALLABLE_MEMBER auto operator+(const complex<U> &complexNumber,
const V &scalar) -> complex<U> {
return complex<U>{real(complexNumber) + scalar, imag(complexNumber)};
return complex<U>(real(complexNumber) + scalar, imag(complexNumber));
}
template <class U, class V>
CUDA_CALLABLE_MEMBER auto operator+(const V &scalar,
const complex<U> &complexNumber)
-> complex<U> {
return complex<U>{real(complexNumber) + scalar, imag(complexNumber)};
return complex<U>(real(complexNumber) + scalar, imag(complexNumber));
}
template <class U, class V>
CUDA_CALLABLE_MEMBER auto operator-(const complex<U> &complexNumber,
const V &scalar) -> complex<U> {
return complex<U>{real(complexNumber) - scalar, imag(complexNumber)};
return complex<U>(real(complexNumber) - scalar, imag(complexNumber));
}
template <class U, class V>
CUDA_CALLABLE_MEMBER auto operator-(const V &scalar,
const complex<U> &complexNumber)
-> complex<U> {
return complex<U>{scalar - real(complexNumber), imag(complexNumber)};
return complex<U>(scalar - real(complexNumber), imag(complexNumber));
}
template <class U, class V>
CUDA_CALLABLE_MEMBER auto operator/(const complex<U> &complexNumber,
const V scalar) -> complex<U> {
return complex<U>{real(complexNumber) / scalar, imag(complexNumber) / scalar};
return complex<U>(real(complexNumber) / scalar, imag(complexNumber) / scalar);
}
template <class U, class V>
CUDA_CALLABLE_MEMBER auto operator/(const V scalar,
const complex<U> &complexNumber)
-> complex<U> {
return complex<U>{scalar, 0} / complexNumber;
return complex<U>(scalar, 0) / complexNumber;
}
using ComplexDouble = complex<double>;
......
......@@ -79,14 +79,14 @@ def create_kernel(assignments,
[0., 0., 0., 0., 0.]])
"""
# ---- Normalizing parameters
if isinstance(assignments, Assignment):
assignments = [assignments]
assert assignments, "Assignments must not be empty!"
split_groups = ()
if isinstance(assignments, AssignmentCollection):
if 'split_groups' in assignments.simplification_hints:
split_groups = assignments.simplification_hints['split_groups']
assignments = assignments.all_assignments
if isinstance(assignments, Assignment):
assignments = [assignments]
# ---- Creating ast
if target == 'cpu':
......
......@@ -3,6 +3,8 @@ from tempfile import TemporaryDirectory
import numpy as np
import pytest
from pystencils import Assignment, create_kernel
from pystencils.boundaries import BoundaryHandling, Neumann, add_neumann_boundary
from pystencils.datahandling import SerialDataHandling
......@@ -83,5 +85,6 @@ def test_kernel_vs_copy_boundary():
np.testing.assert_almost_equal(python_copy_result, handling_result)
with TemporaryDirectory() as tmp_dir:
pytest.importorskip('pyevtk')
boundary_handling.geometry_to_vtk(file_name=os.path.join(tmp_dir, 'test_output1'), ghost_layers=False)
boundary_handling.geometry_to_vtk(file_name=os.path.join(tmp_dir, 'test_output2'), ghost_layers=True)
......@@ -22,6 +22,7 @@ FIELD_SIZES = [(4, 3), (9, 3, 7)]
def _generate_fields(dt=np.uint8, stencil_directions=1, layout='numpy'):
pytest.importorskip('pycuda')
field_sizes = FIELD_SIZES
if stencil_directions > 1:
field_sizes = [s + (stencil_directions,) for s in field_sizes]
......@@ -44,7 +45,6 @@ def _generate_fields(dt=np.uint8, stencil_directions=1, layout='numpy'):
return fields
@pytest.mark.gpu
def test_full_scalar_field():
"""Tests fully (un)packing a scalar field (from)to a GPU buffer."""
fields = _generate_fields()
......@@ -73,7 +73,6 @@ def test_full_scalar_field():
np.testing.assert_equal(src_arr, dst_arr)
@pytest.mark.gpu
def test_field_slice():
"""Tests (un)packing slices of a scalar field (from)to a buffer."""
fields = _generate_fields()
......@@ -109,7 +108,6 @@ def test_field_slice():
np.testing.assert_equal(src_arr[pack_slice], dst_arr[unpack_slice])
@pytest.mark.gpu
def test_all_cell_values():
"""Tests (un)packing all cell values of the a field (from)to a buffer."""
num_cell_values = 7
......@@ -148,7 +146,6 @@ def test_all_cell_values():
np.testing.assert_equal(src_arr, dst_arr)
@pytest.mark.gpu
def test_subset_cell_values():
"""Tests (un)packing a subset of cell values of the a field (from)to a buffer."""
num_cell_values = 7
......@@ -190,7 +187,6 @@ def test_subset_cell_values():
np.testing.assert_equal(dst_arr, mask_arr.filled(int(0)))
@pytest.mark.gpu
def test_field_layouts():
num_cell_values = 7
for layout_str in ['numpy', 'fzyx', 'zyxf', 'reverse_numpy']:
......
......@@ -57,6 +57,9 @@ def test_complex_numbers(assignment, scalar_dtypes, target):
print(code)
assert "Not supported" not in code
if target == 'gpu':
pytest.importorskip('pycuda')
kernel = ast.compile()
assert kernel is not None
......@@ -100,6 +103,9 @@ def test_complex_numbers_64(assignment, target):
print(code)
assert "Not supported" not in code
if target == 'gpu':
pytest.importorskip('pycuda')
kernel = ast.compile()
assert kernel is not None
......@@ -125,6 +131,7 @@ def test_complex_execution(dtype, target, with_complex_argument):
})
if target == 'gpu':
pytest.importorskip('pycuda')
from pycuda.gpuarray import zeros
x_arr = zeros((20, 30), complex_dtype)
y_arr = zeros((20, 30), complex_dtype)
......
import sympy
import pytest
import pystencils
from pystencils.astnodes import get_dummy_symbol
from pystencils.backends.cuda_backend import CudaSympyPrinter
......@@ -18,6 +20,7 @@ def test_cuda_known_functions():
})
ast = pystencils.create_kernel(assignments, 'gpu')
pytest.importorskip('pycuda')
pystencils.show_code(ast)
kernel = ast.compile()
assert(kernel is not None)
......
......@@ -25,7 +25,7 @@ class ScreamingGpuBackend(CudaBackend):
return normal_code.upper()
def test_custom_backends():
def test_custom_backends_cpu():
z, x, y = pystencils.fields("z, y, x: [2d]")
normal_assignments = pystencils.AssignmentCollection([pystencils.Assignment(
......@@ -36,6 +36,16 @@ def test_custom_backends():
with pytest.raises(CalledProcessError):
pystencils.cpu.cpujit.make_python_function(ast, custom_backend=ScreamingBackend())
def test_custom_backends_gpu():
pytest.importorskip('pycuda')
import pycuda.driver
z, x, y = pystencils.fields("z, y, x: [2d]")
normal_assignments = pystencils.AssignmentCollection([pystencils.Assignment(
z[0, 0], x[0, 0] * sympy.log(x[0, 0] * y[0, 0]))], [])
ast = pystencils.create_kernel(normal_assignments, target='gpu')
pystencils.show_code(ast, ScreamingGpuBackend())
with pytest.raises(pycuda.driver.CompileError):
......
......@@ -128,6 +128,7 @@ def kernel_execution_jacobi(dh, target):
def vtk_output(dh):
pytest.importorskip('pyevtk')
dh.add_array('scalar_field')
dh.add_array('vector_field', values_per_cell=dh.dim)
dh.add_array('multiple_scalar_field', values_per_cell=9)
......@@ -223,6 +224,7 @@ def test_kernel_param(target):
def test_vtk_output():
pytest.importorskip('pyevtk')
for domain_shape in [(4, 5), (3, 4, 5)]:
dh = create_data_handling(domain_size=domain_shape, periodicity=True)
vtk_output(dh)
......
......@@ -44,7 +44,7 @@ def test_error_handling():
Field.create_generic('f', spatial_dimensions=2, index_dimensions=1, dtype=struct_dtype)
assert 'index dimension' in str(e.value)
arr = np.array([[1, 2.0, 3], [1, 2.0, 3]], dtype=struct_dtype)
arr = np.array([[[(1,)*3, (2,)*3, (3,)*3]]*2], dtype=struct_dtype)
Field.create_from_numpy_array('f', arr, index_dimensions=0)
with pytest.raises(ValueError) as e:
Field.create_from_numpy_array('f', arr, index_dimensions=1)
......
......@@ -14,8 +14,6 @@ import numpy as np
import pytest
import sympy
import pycuda.autoinit # NOQA
import pycuda.gpuarray as gpuarray
import pystencils
from pystencils.interpolation_astnodes import LinearInterpolator
from pystencils.spatial_coordinates import x_, y_
......@@ -51,7 +49,7 @@ def test_interpolation():
print(assignments)
ast = pystencils.create_kernel(assignments)
print(ast)
pystencils.show_code(ast)
print(pystencils.show_code(ast))
kernel = ast.compile()
pyconrad.imshow(lenna)
......@@ -71,7 +69,7 @@ def test_scale_interpolation():
print(assignments)
ast = pystencils.create_kernel(assignments)
print(ast)
pystencils.show_code(ast)
print(pystencils.show_code(ast))
kernel = ast.compile()
out = np.zeros_like(lenna)
......@@ -83,9 +81,9 @@ def test_scale_interpolation():
['border',
'clamp',
pytest.param('warp', marks=pytest.mark.xfail(
reason="Fails on newer SymPy version due to complex conjugate()")),
reason="requires interpolation-refactoring branch")),
pytest.param('mirror', marks=pytest.mark.xfail(
reason="Fails on newer SymPy version due to complex conjugate()")),
reason="requires interpolation-refactoring branch")),
])
def test_rotate_interpolation(address_mode):
"""
......@@ -102,7 +100,7 @@ def test_rotate_interpolation(address_mode):
print(assignments)
ast = pystencils.create_kernel(assignments)
print(ast)
pystencils.show_code(ast)
print(pystencils.show_code(ast))
kernel = ast.compile()