Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Frederik Hennig
pystencils
Commits
0b7de74d
Commit
0b7de74d
authored
May 02, 2022
by
Markus Holzer
Browse files
Merged master
parents
826f21ab
37518a47
Changes
14
Hide whitespace changes
Inline
Side-by-side
doc/notebooks/02_tutorial_basic_kernels.ipynb
View file @
0b7de74d
...
...
@@ -140,11 +140,11 @@
ac
```
%%%% Output: execute_result
AssignmentCollection: dst_C, <- f(src_
E
, src_
C
, src_
N
, src_
W
, src_S)
AssignmentCollection: dst_C, <- f(src_
N
, src_
E
, src_
W
, src_
C
, src_S)
%% Cell type:code id: tags:
```
python
ac
.
operation_count
...
...
@@ -173,11 +173,11 @@
opt_ac
```
%%%% Output: execute_result
AssignmentCollection: dst_C, <- f(src_
E
, src_
C
, src_
N
, src_
W
, src_S)
AssignmentCollection: dst_C, <- f(src_
N
, src_
E
, src_
W
, src_
C
, src_S)
%% Cell type:code id: tags:
```
python
opt_ac
.
operation_count
...
...
pystencils/cache.py
View file @
0b7de74d
import
os
from
collections.abc
import
Hashable
from
functools
import
partial
,
lru_cache
from
functools
import
partial
,
wraps
,
lru_cache
from
itertools
import
chain
from
joblib
import
Memory
...
...
@@ -27,6 +27,36 @@ def memorycache_if_hashable(maxsize=128, typed=False):
return
wrapper
def
sharedmethodcache
(
cache_id
:
str
):
"""Decorator for memoization of instance methods, allowing multiple methods to use the same cache.
This decorator caches results of instance methods per instantiated object of the surrounding class.
It allows multiple methods to use the same cache, by passing them the same `cache_id` string.
Cached values are stored in a dictionary, which is added as a member `self.<cache_id>` to the
`self` object instance. Make sure that this doesn't cause any naming conflicts with other members!
Of course, for this to be useful, said methods must have the same signature (up to additional kwargs)
and must return the same result when called with the same arguments."""
def
_decorator
(
user_method
):
@
wraps
(
user_method
)
def
_decorated_func
(
self
,
*
args
,
**
kwargs
):
objdict
=
self
.
__dict__
cache
=
objdict
.
setdefault
(
cache_id
,
dict
())
key
=
args
for
item
in
kwargs
.
items
():
key
+=
item
if
key
not
in
cache
:
result
=
user_method
(
self
,
*
args
,
**
kwargs
)
cache
[
key
]
=
result
return
result
else
:
return
cache
[
key
]
return
_decorated_func
return
_decorator
# Disable memory cache:
# disk_cache = lambda o: o
# disk_cache_no_fallback = lambda o: o
pystencils/cpu/vectorization.py
View file @
0b7de74d
...
...
@@ -270,7 +270,7 @@ def insert_vector_casts(ast_node, instruction_set, default_float_type='double'):
new_arg
=
visit_expr
(
expr
.
args
[
0
],
default_type
)
base_type
=
get_type_of_expression
(
expr
.
args
[
0
]).
base_type
if
type
(
expr
.
args
[
0
])
is
VectorMemoryAccess
\
else
get_type_of_expression
(
expr
.
args
[
0
])
pw
=
sp
.
Piecewise
((
-
new_arg
,
new_arg
<
base_type
.
numpy_dtype
.
type
(
0
)),
pw
=
sp
.
Piecewise
((
-
new_arg
,
new_arg
<
cast_func
(
0
,
base_type
.
numpy_dtype
)),
(
new_arg
,
True
))
return
visit_expr
(
pw
,
default_type
)
elif
expr
.
func
in
handled_functions
or
isinstance
(
expr
,
sp
.
Rel
)
or
isinstance
(
expr
,
BooleanFunction
):
...
...
pystencils/simp/assignment_collection.py
View file @
0b7de74d
...
...
@@ -311,7 +311,7 @@ class AssignmentCollection:
if
eq
.
lhs
in
symbols_to_extract
:
new_assignments
.
append
(
eq
)
new_sub_expr
=
[
eq
for
eq
in
self
.
subexpression
s
new_sub_expr
=
[
eq
for
eq
in
self
.
all_assignment
s
if
eq
.
lhs
in
dependent_symbols
and
eq
.
lhs
not
in
symbols_to_extract
]
return
self
.
copy
(
new_assignments
,
new_sub_expr
)
...
...
pystencils/sympyextensions.py
View file @
0b7de74d
...
...
@@ -235,6 +235,9 @@ def subs_additive(expr: sp.Expr, replacement: sp.Expr, subexpression: sp.Expr,
normalized_replacement_match
=
normalize_match_parameter
(
required_match_replacement
,
len
(
subexpression
.
args
))
if
isinstance
(
subexpression
,
sp
.
Number
):
return
expr
.
subs
({
replacement
:
subexpression
})
def
visit
(
current_expr
):
if
current_expr
.
is_Add
:
expr_max_length
=
max
(
len
(
current_expr
.
args
),
len
(
subexpression
.
args
))
...
...
@@ -263,7 +266,7 @@ def subs_additive(expr: sp.Expr, replacement: sp.Expr, subexpression: sp.Expr,
return
current_expr
else
:
if
current_expr
.
func
==
sp
.
Mul
and
Zero
()
in
param_list
:
return
Zero
(
)
return
sp
.
simplify
(
current_expr
)
else
:
return
current_expr
.
func
(
*
param_list
,
evaluate
=
False
)
...
...
@@ -359,7 +362,7 @@ def remove_higher_order_terms(expr: sp.Expr, symbols: Sequence[sp.Symbol], order
if
velocity_factors_in_product
(
expr
)
<=
order
:
return
expr
else
:
return
sp
.
Rational
(
0
,
1
)
return
Zero
(
)
if
type
(
expr
)
!=
Add
:
return
expr
...
...
@@ -453,6 +456,72 @@ def recursive_collect(expr, symbols, order_by_occurences=False):
return
rec_sum
def
summands
(
expr
):
return
set
(
expr
.
args
)
if
isinstance
(
expr
,
sp
.
Add
)
else
{
expr
}
def
simplify_by_equality
(
expr
,
a
,
b
,
c
):
"""
Uses the equality a = b + c, where a and b must be symbols, to simplify expr
by attempting to express additive combinations of two quantities by the third.
This works on expressions that are reducible to the form
:math:`a * (...) + b * (...) + c * (...)`,
without any mixed terms of a, b and c.
"""
if
not
isinstance
(
a
,
sp
.
Symbol
)
or
not
isinstance
(
b
,
sp
.
Symbol
):
raise
ValueError
(
"a and b must be symbols."
)
c
=
sp
.
sympify
(
c
)
if
not
(
isinstance
(
c
,
sp
.
Symbol
)
or
is_constant
(
c
)):
raise
ValueError
(
"c must be either a symbol or a constant!"
)
expr
=
sp
.
sympify
(
expr
)
expr_expanded
=
sp
.
expand
(
expr
)
a_coeff
=
expr_expanded
.
coeff
(
a
,
1
)
expr_expanded
-=
(
a
*
a_coeff
).
expand
()
b_coeff
=
expr_expanded
.
coeff
(
b
,
1
)
expr_expanded
-=
(
b
*
b_coeff
).
expand
()
if
isinstance
(
c
,
sp
.
Symbol
):
c_coeff
=
expr_expanded
.
coeff
(
c
,
1
)
rest
=
expr_expanded
-
(
c
*
c_coeff
).
expand
()
else
:
c_coeff
=
expr_expanded
/
c
rest
=
0
a_summands
=
summands
(
a_coeff
)
b_summands
=
summands
(
b_coeff
)
c_summands
=
summands
(
c_coeff
)
# replace b + c by a
b_plus_c_coeffs
=
b_summands
&
c_summands
for
coeff
in
b_plus_c_coeffs
:
rest
+=
a
*
coeff
b_summands
-=
b_plus_c_coeffs
c_summands
-=
b_plus_c_coeffs
# replace a - b by c
neg_b_summands
=
{
-
x
for
x
in
b_summands
}
a_minus_b_coeffs
=
a_summands
&
neg_b_summands
for
coeff
in
a_minus_b_coeffs
:
rest
+=
c
*
coeff
a_summands
-=
a_minus_b_coeffs
b_summands
-=
{
-
x
for
x
in
a_minus_b_coeffs
}
# replace a - c by b
neg_c_summands
=
{
-
x
for
x
in
c_summands
}
a_minus_c_coeffs
=
a_summands
&
neg_c_summands
for
coeff
in
a_minus_c_coeffs
:
rest
+=
b
*
coeff
a_summands
-=
a_minus_c_coeffs
c_summands
-=
{
-
x
for
x
in
a_minus_c_coeffs
}
# put it back together
return
(
rest
+
a
*
sum
(
a_summands
)
+
b
*
sum
(
b_summands
)
+
c
*
sum
(
c_summands
)).
expand
()
def
count_operations
(
term
:
Union
[
sp
.
Expr
,
List
[
sp
.
Expr
],
List
[
Assignment
]],
only_type
:
Optional
[
str
]
=
'real'
)
->
Dict
[
str
,
int
]:
"""Counts the number of additions, multiplications and division.
...
...
pystencils_tests/test_aligned_array.py
View file @
0b7de74d
...
...
@@ -59,13 +59,13 @@ def test_alignment_of_different_layouts():
byte_offset
=
8
for
tries
in
range
(
16
):
# try a few times, since we might get lucky and get randomly a correct alignment
arr
=
create_numpy_array_with_layout
((
3
,
4
,
5
),
layout
=
(
0
,
1
,
2
),
alignment
=
True
,
byte_offset
=
byte_offset
)
alignment
=
8
*
4
,
byte_offset
=
byte_offset
)
assert
is_aligned
(
arr
[
offset
,
...],
8
*
4
,
byte_offset
)
arr
=
create_numpy_array_with_layout
((
3
,
4
,
5
),
layout
=
(
2
,
1
,
0
),
alignment
=
True
,
byte_offset
=
byte_offset
)
alignment
=
8
*
4
,
byte_offset
=
byte_offset
)
assert
is_aligned
(
arr
[...,
offset
],
8
*
4
,
byte_offset
)
arr
=
create_numpy_array_with_layout
((
3
,
4
,
5
),
layout
=
(
2
,
0
,
1
),
alignment
=
True
,
byte_offset
=
byte_offset
)
alignment
=
8
*
4
,
byte_offset
=
byte_offset
)
assert
is_aligned
(
arr
[:,
0
,
:],
8
*
4
,
byte_offset
)
pystencils_tests/test_field_access_poly.py
View file @
0b7de74d
...
...
@@ -7,7 +7,7 @@
"""
import
pytest
from
pystencils.session
import
*
from
sympy
import
poly
...
...
@@ -22,8 +22,14 @@ def test_field_access_poly():
def
test_field_access_piecewise
():
dh
=
ps
.
create_data_handling
((
20
,
20
))
ρ
=
dh
.
add_array
(
'rho'
)
pw
=
sp
.
Piecewise
((
0
,
1
<
sp
.
Max
(
-
0.5
,
ρ
.
center
+
0.5
)),
(
1
,
True
))
a
=
sp
.
simplify
(
pw
)
print
(
a
)
try
:
a
=
sp
.
Piecewise
((
0
,
1
<
sp
.
Max
(
-
0.5
,
sp
.
Symbol
(
"test"
)
+
0.5
)),
(
1
,
True
))
a
.
simplify
()
except
Exception
as
e
:
pytest
.
skip
(
f
"Bug in SymPy 1.10:
{
e
}
"
)
else
:
dh
=
ps
.
create_data_handling
((
20
,
20
))
ρ
=
dh
.
add_array
(
'rho'
)
pw
=
sp
.
Piecewise
((
0
,
1
<
sp
.
Max
(
-
0.5
,
ρ
.
center
+
0.5
)),
(
1
,
True
))
a
=
sp
.
simplify
(
pw
)
print
(
a
)
pystencils_tests/test_fvm.py
View file @
0b7de74d
...
...
@@ -317,9 +317,7 @@ def diffusion_reaction(fluctuations: bool):
fluct
=
sp
.
sqrt
(
2
*
dens
*
D
)
*
sp
.
sqrt
(
1
/
length
)
*
stencil_factor
# add fluctuations
fluct
*=
2
*
(
next
(
rng_symbol_gen
)
-
0.5
)
*
sp
.
sqrt
(
3
)
flux
.
main_assignments
[
i
]
=
ps
.
Assignment
(
flux
.
main_assignments
[
i
].
lhs
,
flux
.
main_assignments
[
i
].
rhs
+
fluct
)
flux
.
main_assignments
[
i
]
=
ps
.
Assignment
(
flux
.
main_assignments
[
i
].
lhs
,
flux
.
main_assignments
[
i
].
rhs
+
fluct
)
# Add the folding to the flux, so that the random numbers persist through the ghostlayers.
fold
=
{
ps
.
astnodes
.
LoopOverCoordinate
.
get_loop_counter_symbol
(
i
):
...
...
@@ -419,7 +417,7 @@ advection_diffusion_fluctuations.runners = {}
@
pytest
.
mark
.
parametrize
(
"density"
,
[
27.0
,
56.5
])
@
pytest
.
mark
.
parametrize
(
"fluctuations"
,
[
False
,
True
])
@
pytest
.
mark
.
longrun
def
test_diffusion_reaction
(
velocity
,
density
,
fluctuations
):
def
test_diffusion_reaction
(
fluctuations
,
density
,
velocity
):
diffusion_reaction
.
runner
=
diffusion_reaction
(
fluctuations
)
diffusion_reaction
.
runner
(
density
,
velocity
)
...
...
pystencils_tests/test_random.py
View file @
0b7de74d
...
...
@@ -24,7 +24,7 @@ if get_compiler_config()['os'] == 'windows':
instruction_sets
.
remove
(
'avx512'
)
@
pytest
.
mark
.
parametrize
(
'target,rng'
,
((
Target
.
CPU
,
'philox'
),
(
Target
.
CPU
,
'aesni'
),
(
Target
.
GPU
,
'philox'
)))
@
pytest
.
mark
.
parametrize
(
'target,
rng'
,
((
Target
.
CPU
,
'philox'
),
(
Target
.
CPU
,
'aesni'
),
(
Target
.
GPU
,
'philox'
)))
@
pytest
.
mark
.
parametrize
(
'precision'
,
(
'float'
,
'double'
))
@
pytest
.
mark
.
parametrize
(
'dtype'
,
(
'float'
,
'double'
))
def
test_rng
(
target
,
rng
,
precision
,
dtype
,
t
=
124
,
offsets
=
(
0
,
0
),
keys
=
(
0
,
0
),
offset_values
=
None
):
...
...
pystencils_tests/test_sharedmethodcache.py
0 → 100644
View file @
0b7de74d
from
pystencils.cache
import
sharedmethodcache
class
Fib
:
def
__init__
(
self
):
self
.
fib_rec_called
=
0
self
.
fib_iter_called
=
0
@
sharedmethodcache
(
"fib_cache"
)
def
fib_rec
(
self
,
n
):
self
.
fib_rec_called
+=
1
return
1
if
n
<=
1
else
self
.
fib_rec
(
n
-
1
)
+
self
.
fib_rec
(
n
-
2
)
@
sharedmethodcache
(
"fib_cache"
)
def
fib_iter
(
self
,
n
):
self
.
fib_iter_called
+=
1
f1
,
f2
=
0
,
1
for
i
in
range
(
n
):
f2
=
f1
+
f2
f1
=
f2
-
f1
return
f2
def
test_fib_memoization_1
():
fib
=
Fib
()
assert
"fib_cache"
not
in
fib
.
__dict__
f13
=
fib
.
fib_rec
(
13
)
assert
fib
.
fib_rec_called
==
14
assert
"fib_cache"
in
fib
.
__dict__
assert
fib
.
fib_cache
[(
13
,)]
==
f13
for
k
in
range
(
14
):
# fib_iter should use cached results from fib_rec
fib
.
fib_iter
(
k
)
assert
fib
.
fib_iter_called
==
0
def
test_fib_memoization_2
():
fib
=
Fib
()
f11
=
fib
.
fib_iter
(
11
)
f12
=
fib
.
fib_iter
(
12
)
assert
fib
.
fib_iter_called
==
2
f13
=
fib
.
fib_rec
(
13
)
# recursive calls should be cached
assert
fib
.
fib_rec_called
==
1
class
Triad
:
def
__init__
(
self
):
self
.
triad_called
=
0
@
sharedmethodcache
(
"triad_cache"
)
def
triad
(
self
,
a
,
b
,
c
=
0
):
"""Computes the triad a*b+c."""
self
.
triad_called
+=
1
return
a
*
b
+
c
def
test_triad_memoization
():
triad
=
Triad
()
assert
triad
.
triad
.
__doc__
==
"Computes the triad a*b+c."
t
=
triad
.
triad
(
12
,
4
,
15
)
assert
triad
.
triad_called
==
1
assert
triad
.
triad_cache
[(
12
,
4
,
15
)]
==
t
t
=
triad
.
triad
(
12
,
4
,
c
=
15
)
assert
triad
.
triad_called
==
2
assert
triad
.
triad_cache
[(
12
,
4
,
'c'
,
15
)]
==
t
t
=
triad
.
triad
(
12
,
4
,
15
)
assert
triad
.
triad_called
==
2
t
=
triad
.
triad
(
12
,
4
,
c
=
15
)
assert
triad
.
triad_called
==
2
pystencils_tests/test_sympyextensions.py
View file @
0b7de74d
import
sympy
import
numpy
as
np
import
sympy
as
sp
import
pystencils
from
pystencils.sympyextensions
import
replace_second_order_products
from
pystencils.sympyextensions
import
remove_higher_order_terms
from
pystencils.sympyextensions
import
complete_the_squares_in_exp
from
pystencils.sympyextensions
import
extract_most_common_factor
from
pystencils.sympyextensions
import
simplify_by_equality
from
pystencils.sympyextensions
import
count_operations
from
pystencils.sympyextensions
import
common_denominator
from
pystencils.sympyextensions
import
get_symmetric_part
...
...
@@ -176,3 +178,26 @@ def test_get_symmetric_part():
sym_part
=
get_symmetric_part
(
expr
,
sympy
.
symbols
(
f
'y z'
))
assert
sym_part
==
expected_result
def
test_simplify_by_equality
():
x
,
y
,
z
=
sp
.
symbols
(
'x, y, z'
)
p
,
q
=
sp
.
symbols
(
'p, q'
)
# Let x = y + z
expr
=
x
*
p
-
y
*
p
+
z
*
q
expr
=
simplify_by_equality
(
expr
,
x
,
y
,
z
)
assert
expr
==
z
*
p
+
z
*
q
expr
=
x
*
(
p
-
2
*
q
)
+
2
*
q
*
z
expr
=
simplify_by_equality
(
expr
,
x
,
y
,
z
)
assert
expr
==
x
*
p
-
2
*
q
*
y
expr
=
x
*
(
y
+
z
)
-
y
*
z
expr
=
simplify_by_equality
(
expr
,
x
,
y
,
z
)
assert
expr
==
x
*
y
+
z
**
2
# Let x = y + 2
expr
=
x
*
p
-
2
*
p
expr
=
simplify_by_equality
(
expr
,
x
,
y
,
2
)
assert
expr
==
y
*
p
pystencils_tests/test_timeloop.py
View file @
0b7de74d
...
...
@@ -59,4 +59,6 @@ def test_timeloop():
timeloop
.
run_time_span
(
seconds
=
seconds
)
end
=
time
.
perf_counter
()
np
.
testing
.
assert_almost_equal
(
seconds
,
end
-
start
,
decimal
=
2
)
# This test case fails often due to time measurements. It is not a good idea to assert here
# np.testing.assert_almost_equal(seconds, end - start, decimal=2)
print
(
"timeloop: "
,
seconds
,
" own meassurement: "
,
end
-
start
)
pytest.ini
View file @
0b7de74d
...
...
@@ -53,7 +53,7 @@ exclude_lines =
if
__name__
=
= .__main__.:
skip_covered
=
True
fail_under
=
8
6
fail_under
=
8
5
[html]
directory
=
coverage_report
setup.py
View file @
0b7de74d
...
...
@@ -90,7 +90,7 @@ setuptools.setup(name='pystencils',
author_email
=
'cs10-codegen@fau.de'
,
url
=
'https://i10git.cs.fau.de/pycodegen/pystencils/'
,
packages
=
[
'pystencils'
]
+
[
'pystencils.'
+
s
for
s
in
setuptools
.
find_packages
(
'pystencils'
)],
install_requires
=
[
'sympy>=1.6,<=1.
9
'
,
'numpy>=1.8.0'
,
'appdirs'
,
'joblib'
],
install_requires
=
[
'sympy>=1.6,<=1.
10
'
,
'numpy>=1.8.0'
,
'appdirs'
,
'joblib'
],
package_data
=
{
'pystencils'
:
[
'include/*.h'
,
'backends/cuda_known_functions.txt'
,
'backends/opencl1.1_known_functions.txt'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment