From 73e1ffc13b29a827f924aa170e661ba5286548bc Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 28 Aug 2017 11:47:53 -0500 Subject: [PATCH 01/15] Point CI prereqs at sumpy rscale branch --- .test-conda-env-py3-requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.test-conda-env-py3-requirements.txt b/.test-conda-env-py3-requirements.txt index e3fac95d..419f3d15 100644 --- a/.test-conda-env-py3-requirements.txt +++ b/.test-conda-env-py3-requirements.txt @@ -1,5 +1,5 @@ git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/pymbolic git+https://github.com/inducer/loopy -git+https://github.com/inducer/sumpy +git+https://github.com/inducer/sumpy@rscale git+https://github.com/inducer/meshmode diff --git a/requirements.txt b/requirements.txt index 7360a7c7..b9c221e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,5 @@ git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode -git+https://github.com/inducer/sumpy +git+https://github.com/inducer/sumpy@rscale git+https://github.com/inducer/pyfmmlib -- GitLab From d8e7a6a1418dd4b8e576c9ba40563c22ee81900d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 28 Aug 2017 11:48:35 -0500 Subject: [PATCH 02/15] Establish basic interoperability with rscale'd sumpy --- pytential/qbx/fmm.py | 18 ++++++++++++-- pytential/qbx/interactions.py | 46 ++++++++++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index d03affe3..74ec203b 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -27,7 +27,11 @@ from six.moves import range, zip import numpy as np # noqa import pyopencl as cl # noqa import pyopencl.array # noqa -from sumpy.fmm import SumpyExpansionWranglerCodeContainer, SumpyExpansionWrangler +from sumpy.fmm import (SumpyExpansionWranglerCodeContainer, + SumpyExpansionWrangler, level_to_rscale) + +# FIXME: This should be replaced with the radius of the QBX expansions +_QBX_RSCALE = 1 from pytools import memoize_method from pytential.qbx.interactions import P2QBXLFromCSR, M2QBXL, L2QBXL, QBXL2P @@ -59,7 +63,7 @@ class QBXSumpyExpansionWranglerCodeContainer(SumpyExpansionWranglerCodeContainer @memoize_method def qbx_local_expansion(self, order): - return self.qbx_local_expansion_factory(order) + return self.qbx_local_expansion_factory(order, self.use_rscale) @memoize_method def p2qbxl(self, order): @@ -219,6 +223,8 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), strengths=src_weights, qbx_expansions=local_exps, + rscale=_QBX_RSCALE, + **kwargs) assert local_exps is result @@ -258,6 +264,9 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), src_box_starts=ssn.starts, src_box_lists=ssn.lists, + src_rscale=level_to_rscale(self.tree, isrc_level), + tgt_rscale=_QBX_RSCALE, + wait_for=wait_for, **self.kernel_extra_kwargs) @@ -299,6 +308,9 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), expansions=target_locals_view, qbx_expansions=qbx_expansions, + src_rscale=level_to_rscale(self.tree, isrc_level), + tgt_rscale=_QBX_RSCALE, + wait_for=wait_for, **self.kernel_extra_kwargs) @@ -333,6 +345,8 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), qbx_expansions=qbx_expansions, result=pot, + rscale=_QBX_RSCALE, + **self.kernel_extra_kwargs.copy()) for pot_i, pot_res_i in zip(pot, pot_res): diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 172953f3..2290f4e9 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -54,6 +54,7 @@ class P2QBXLFromCSR(P2EBase): None, shape=None), lp.GlobalArg("qbx_centers", None, shape="dim, ncenters", dim_tags="sep,c"), + lp.ValueArg("rscale", None), lp.GlobalArg("qbx_expansions", None, shape=("ncenters", ncoeffs)), lp.ValueArg("ncenters", np.int32), @@ -118,7 +119,15 @@ class P2QBXLFromCSR(P2EBase): return knl def __call__(self, queue, **kwargs): - return self.get_cached_optimized_kernel()(queue, **kwargs) + qbx_centers = kwargs.pop("qbx_centers") + # "1" may be passed for rscale, which won't have its type + # meaningfully inferred. Make the type of rscale explicit. + rscale = qbx_centers[0].dtype.type(kwargs.pop("rscale")) + + return self.get_cached_optimized_kernel()(queue, + qbx_centers=qbx_centers, + rscale=rscale, + **kwargs) # }}} @@ -179,6 +188,7 @@ class M2QBXL(E2EBase): """], [ lp.GlobalArg("centers", None, shape="dim, aligned_nboxes"), + lp.ValueArg("src_rscale,tgt_rscale", None), lp.GlobalArg("src_box_starts, src_box_lists", None, shape=None, strides=(1,)), lp.GlobalArg("qbx_centers", None, shape="dim, ncenters", @@ -211,7 +221,16 @@ class M2QBXL(E2EBase): return knl def __call__(self, queue, **kwargs): - return self.get_cached_optimized_kernel()(queue, **kwargs) + centers = kwargs.pop("centers") + # "1" may be passed for rscale, which won't have its type + # meaningfully inferred. Make the type of rscale explicit. + src_rscale = centers.dtype.type(kwargs.pop("src_rscale")) + tgt_rscale = centers.dtype.type(kwargs.pop("tgt_rscale")) + + return self.get_cached_optimized_kernel()(queue, + centers=centers, + src_rscale=src_rscale, tgt_rscale=tgt_rscale, + **kwargs) # }}} @@ -268,6 +287,7 @@ class L2QBXL(E2EBase): lp.GlobalArg("target_boxes", None, shape=None, offset=lp.auto), lp.GlobalArg("centers", None, shape="dim, naligned_boxes"), + lp.ValueArg("src_rscale,tgt_rscale", None), lp.GlobalArg("qbx_centers", None, shape="dim, ncenters", dim_tags="sep,c"), lp.ValueArg("naligned_boxes,target_base_ibox,nboxes", np.int32), @@ -298,7 +318,16 @@ class L2QBXL(E2EBase): return knl def __call__(self, queue, **kwargs): - return self.get_cached_optimized_kernel()(queue, **kwargs) + centers = kwargs.pop("centers") + # "1" may be passed for rscale, which won't have its type + # meaningfully inferred. Make the type of rscale explicit. + src_rscale = centers.dtype.type(kwargs.pop("src_rscale")) + tgt_rscale = centers.dtype.type(kwargs.pop("tgt_rscale")) + + return self.get_cached_optimized_kernel()(queue, + centers=centers, + src_rscale=src_rscale, tgt_rscale=tgt_rscale, + **kwargs) # }}} @@ -352,6 +381,7 @@ class QBXL2P(E2PBase): dim_tags="sep,C"), lp.GlobalArg("qbx_centers", None, shape="dim, ncenters", dim_tags="sep,c"), + lp.ValueArg("rscale", None), lp.GlobalArg("center_to_targets_starts,center_to_targets_lists", None, shape=None), lp.GlobalArg("qbx_expansions", None, @@ -382,7 +412,15 @@ class QBXL2P(E2PBase): return knl def __call__(self, queue, **kwargs): - return self.get_cached_optimized_kernel()(queue, **kwargs) + qbx_centers = kwargs.pop("qbx_centers") + # "1" may be passed for rscale, which won't have its type + # meaningfully inferred. Make the type of rscale explicit. + rscale = qbx_centers[0].dtype.type(kwargs.pop("rscale")) + + return self.get_cached_optimized_kernel()(queue, + qbx_centers=qbx_centers, + rscale=rscale, + **kwargs) # }}} -- GitLab From 6d6440cd250c9f9f518dcb9b81e114f8e68f8336 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 28 Aug 2017 11:52:10 -0500 Subject: [PATCH 03/15] Fix: Point CI prereqs at sumpy rscale branch --- .test-conda-env-py3-requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.test-conda-env-py3-requirements.txt b/.test-conda-env-py3-requirements.txt index 419f3d15..9b143e43 100644 --- a/.test-conda-env-py3-requirements.txt +++ b/.test-conda-env-py3-requirements.txt @@ -1,5 +1,5 @@ git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/pymbolic git+https://github.com/inducer/loopy -git+https://github.com/inducer/sumpy@rscale +git+https://gitlab.tiker.net/inducer/sumpy@rscale git+https://github.com/inducer/meshmode diff --git a/requirements.txt b/requirements.txt index b9c221e8..71e9d6a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,5 @@ git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode -git+https://github.com/inducer/sumpy@rscale +git+https://gitlab.tiker.net/inducer/sumpy@rscale git+https://github.com/inducer/pyfmmlib -- GitLab From c978977f09de51208c316eee49163360249faab3 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 28 Aug 2017 12:10:26 -0500 Subject: [PATCH 04/15] Establish fmmlib interop with rscale --- pytential/qbx/fmmlib.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index 25ecbed9..68a270e9 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -26,10 +26,14 @@ import numpy as np from pytools import memoize_method, Record import pyopencl as cl # noqa import pyopencl.array # noqa: F401 -from boxtree.pyfmmlib_integration import FMMLibExpansionWrangler +from boxtree.pyfmmlib_integration import ( + FMMLibExpansionWrangler, level_to_rscale) from sumpy.kernel import HelmholtzKernel +# FIXME: This should be replaced with the radius of the QBX expansions +_QBX_RSCALE = 1 + import logging logger = logging.getLogger(__name__) @@ -334,7 +338,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): centers = qbx_centers[:, geo_data.global_qbx_centers()] - rscale = 1 # FIXME + rscale = _QBX_RSCALE # FIXME rscale_vec = np.empty(len(center_source_counts) - 1, dtype=np.float64) rscale_vec.fill(rscale) # FIXME @@ -441,7 +445,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): icontaining_tgt_box_vec = qbx_center_to_target_box[tgt_icenter_vec] # FIXME - rscale2 = np.ones(ngqbx_centers, np.float64) + rscale2 = np.ones(ngqbx_centers, np.float64) * _QBX_RSCALE kwargs = {} if self.dim == 3: @@ -457,8 +461,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): src_boxes_starts[0] = 0 src_boxes_starts[1:] = np.cumsum(nsrc_boxes_per_gqbx_center) - # FIXME - rscale1 = np.ones(nsrc_boxes) + rscale1 = np.ones(nsrc_boxes) * level_to_rscale(self.tree, isrc_level) rscale1_offsets = np.arange(nsrc_boxes) src_ibox = np.empty(nsrc_boxes, dtype=np.int32) @@ -523,8 +526,6 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): qbx_center_to_target_box = geo_data.qbx_center_to_target_box() qbx_centers = geo_data.centers() - rscale = 1 # FIXME - locloc = self.get_translation_routine("%ddlocloc") for isrc_level in range(geo_data.tree().nlevels): @@ -561,11 +562,11 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): if in_range: src_center = self.tree.box_centers[:, src_ibox] tmp_loc_exp = locloc( - rscale1=rscale, + rscale1=level_to_rscale(self.tree, isrc_level), center1=src_center, expn1=local_exps[src_ibox].T, - rscale2=rscale, + rscale2=_QBX_RSCALE, center2=tgt_center, nterms2=local_order, @@ -585,8 +586,6 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): all_targets = geo_data.all_targets() - rscale = 1 # FIXME - taeval = self.get_expn_eval_routine("ta") for isrc_center, src_icenter in enumerate(global_qbx_centers): @@ -599,7 +598,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): center = qbx_centers[:, src_icenter] pot, grad = taeval( - rscale=rscale, + rscale=_QBX_RSCALE, center=center, expn=qbx_expansions[src_icenter].T, ztarg=all_targets[:, center_itgt], -- GitLab From ec58e7594c4540938321c96c7100c1faa79b7a90 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 28 Aug 2017 12:55:27 -0500 Subject: [PATCH 05/15] Give pytential its own sumpy kernel version to allow easy kernel cache busting --- pytential/qbx/interactions.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 2290f4e9..4d693bf9 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -32,11 +32,18 @@ from sumpy.e2e import E2EBase from sumpy.e2p import E2PBase +PYTENTIAL_KERNEL_VERSION = 5 + + # {{{ form qbx expansions from points class P2QBXLFromCSR(P2EBase): default_name = "p2qbxl_from_csr" + def get_cache_key(self): + return super(P2QBXLFromCSR, self).get_cache_key() + ( + PYTENTIAL_KERNEL_VERSION,) + def get_kernel(self): ncoeffs = len(self.expansion) @@ -141,6 +148,9 @@ class M2QBXL(E2EBase): default_name = "m2qbxl_from_csr" + def get_cache_key(self): + return super(M2QBXL, self).get_cache_key() + (PYTENTIAL_KERNEL_VERSION,) + def get_kernel(self): ncoeff_src = len(self.src_expansion) ncoeff_tgt = len(self.tgt_expansion) @@ -240,6 +250,9 @@ class M2QBXL(E2EBase): class L2QBXL(E2EBase): default_name = "l2qbxl" + def get_cache_key(self): + return super(L2QBXL, self).get_cache_key() + (PYTENTIAL_KERNEL_VERSION,) + def get_kernel(self): ncoeff_src = len(self.src_expansion) ncoeff_tgt = len(self.tgt_expansion) @@ -337,6 +350,9 @@ class L2QBXL(E2EBase): class QBXL2P(E2PBase): default_name = "qbx_potential_from_local" + def get_cache_key(self): + return super(QBXL2P, self).get_cache_key() + (PYTENTIAL_KERNEL_VERSION,) + def get_kernel(self): ncoeffs = len(self.expansion) -- GitLab From 69081982f199d95b1d77604c59e040b2e6b4b898 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 28 Aug 2017 12:56:09 -0500 Subject: [PATCH 06/15] Sumpy QBX FMM: Use expansion radii for rscale --- pytential/qbx/fmm.py | 14 ++++------ pytential/qbx/interactions.py | 51 +++++++++++++++-------------------- 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 74ec203b..caa70c61 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -30,9 +30,6 @@ import pyopencl.array # noqa from sumpy.fmm import (SumpyExpansionWranglerCodeContainer, SumpyExpansionWrangler, level_to_rscale) -# FIXME: This should be replaced with the radius of the QBX expansions -_QBX_RSCALE = 1 - from pytools import memoize_method from pytential.qbx.interactions import P2QBXLFromCSR, M2QBXL, L2QBXL, QBXL2P @@ -217,14 +214,13 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), global_qbx_centers=geo_data.global_qbx_centers(), qbx_center_to_target_box=geo_data.qbx_center_to_target_box(), qbx_centers=geo_data.centers(), + qbx_expansion_radii=geo_data.expansion_radii(), source_box_starts=starts, source_box_lists=lists, strengths=src_weights, qbx_expansions=local_exps, - rscale=_QBX_RSCALE, - **kwargs) assert local_exps is result @@ -256,6 +252,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), centers=self.tree.box_centers, qbx_centers=geo_data.centers(), + qbx_expansion_radii=geo_data.expansion_radii(), src_expansions=source_mpoles_view, src_base_ibox=source_level_start_ibox, @@ -265,7 +262,6 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), src_box_lists=ssn.lists, src_rscale=level_to_rscale(self.tree, isrc_level), - tgt_rscale=_QBX_RSCALE, wait_for=wait_for, @@ -304,12 +300,12 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), centers=self.tree.box_centers, qbx_centers=geo_data.centers(), + qbx_expansion_radii=geo_data.expansion_radii(), expansions=target_locals_view, qbx_expansions=qbx_expansions, src_rscale=level_to_rscale(self.tree, isrc_level), - tgt_rscale=_QBX_RSCALE, wait_for=wait_for, @@ -335,6 +331,8 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), evt, pot_res = qbxl2p(self.queue, qbx_centers=geo_data.centers(), + qbx_expansion_radii=geo_data.expansion_radii(), + global_qbx_centers=geo_data.global_qbx_centers(), center_to_targets_starts=ctt.starts, @@ -345,8 +343,6 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), qbx_expansions=qbx_expansions, result=pot, - rscale=_QBX_RSCALE, - **self.kernel_extra_kwargs.copy()) for pot_i, pot_res_i in zip(pot, pot_res): diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 4d693bf9..0d78dcdb 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -61,7 +61,7 @@ class P2QBXLFromCSR(P2EBase): None, shape=None), lp.GlobalArg("qbx_centers", None, shape="dim, ncenters", dim_tags="sep,c"), - lp.ValueArg("rscale", None), + lp.GlobalArg("qbx_expansion_radii", None, shape="ncenters"), lp.GlobalArg("qbx_expansions", None, shape=("ncenters", ncoeffs)), lp.ValueArg("ncenters", np.int32), @@ -73,12 +73,16 @@ class P2QBXLFromCSR(P2EBase): [ "{[itgt_center]: 0<=itgt_center tgt_icenter = global_qbx_centers[itgt_center] + <> center[idim] = qbx_centers[idim, tgt_icenter] {dup=idim} + <> rscale = qbx_expansion_radii[tgt_icenter] + <> itgt_box = qbx_center_to_target_box[tgt_icenter] <> isrc_box_start = source_box_starts[itgt_box] @@ -89,8 +93,6 @@ class P2QBXLFromCSR(P2EBase): <> isrc_start = box_source_starts[src_ibox] <> isrc_end = isrc_start+box_source_counts_nonchild[src_ibox] - <> center[idim] = qbx_centers[idim, tgt_icenter] - for isrc <> a[idim] = center[idim] - sources[idim, isrc] \ {dup=idim} @@ -126,15 +128,7 @@ class P2QBXLFromCSR(P2EBase): return knl def __call__(self, queue, **kwargs): - qbx_centers = kwargs.pop("qbx_centers") - # "1" may be passed for rscale, which won't have its type - # meaningfully inferred. Make the type of rscale explicit. - rscale = qbx_centers[0].dtype.type(kwargs.pop("rscale")) - - return self.get_cached_optimized_kernel()(queue, - qbx_centers=qbx_centers, - rscale=rscale, - **kwargs) + return self.get_cached_optimized_kernel()(queue, **kwargs) # }}} @@ -168,6 +162,7 @@ class M2QBXL(E2EBase): <> tgt_center[idim] = qbx_centers[idim, icenter] \ {id=fetch_tgt_center} + <> tgt_rscale = qbx_expansion_radii[icenter] <> isrc_start = src_box_starts[icontaining_tgt_box] <> isrc_stop = src_box_starts[icontaining_tgt_box+1] @@ -198,11 +193,12 @@ class M2QBXL(E2EBase): """], [ lp.GlobalArg("centers", None, shape="dim, aligned_nboxes"), - lp.ValueArg("src_rscale,tgt_rscale", None), + lp.ValueArg("src_rscale", None), lp.GlobalArg("src_box_starts, src_box_lists", None, shape=None, strides=(1,)), lp.GlobalArg("qbx_centers", None, shape="dim, ncenters", dim_tags="sep,c"), + lp.GlobalArg("qbx_expansion_radii", None, shape="ncenters"), lp.ValueArg("aligned_nboxes,nsrc_level_boxes", np.int32), lp.ValueArg("src_base_ibox", np.int32), lp.GlobalArg("src_expansions", None, @@ -235,11 +231,10 @@ class M2QBXL(E2EBase): # "1" may be passed for rscale, which won't have its type # meaningfully inferred. Make the type of rscale explicit. src_rscale = centers.dtype.type(kwargs.pop("src_rscale")) - tgt_rscale = centers.dtype.type(kwargs.pop("tgt_rscale")) return self.get_cached_optimized_kernel()(queue, centers=centers, - src_rscale=src_rscale, tgt_rscale=tgt_rscale, + src_rscale=src_rscale, **kwargs) # }}} @@ -281,6 +276,9 @@ class L2QBXL(E2EBase): if in_range <> tgt_center[idim] = qbx_centers[idim, icenter] <> src_center[idim] = centers[idim, src_ibox] {dup=idim} + + <> tgt_rscale = qbx_expansion_radii[icenter] + <> d[idim] = tgt_center[idim] - src_center[idim] {dup=idim} """] + [""" @@ -300,9 +298,10 @@ class L2QBXL(E2EBase): lp.GlobalArg("target_boxes", None, shape=None, offset=lp.auto), lp.GlobalArg("centers", None, shape="dim, naligned_boxes"), - lp.ValueArg("src_rscale,tgt_rscale", None), + lp.ValueArg("src_rscale", None), lp.GlobalArg("qbx_centers", None, shape="dim, ncenters", dim_tags="sep,c"), + lp.GlobalArg("qbx_expansion_radii", None, shape="ncenters"), lp.ValueArg("naligned_boxes,target_base_ibox,nboxes", np.int32), lp.GlobalArg("expansions", None, shape=("nboxes", ncoeff_src), offset=lp.auto), @@ -335,11 +334,10 @@ class L2QBXL(E2EBase): # "1" may be passed for rscale, which won't have its type # meaningfully inferred. Make the type of rscale explicit. src_rscale = centers.dtype.type(kwargs.pop("src_rscale")) - tgt_rscale = centers.dtype.type(kwargs.pop("tgt_rscale")) return self.get_cached_optimized_kernel()(queue, centers=centers, - src_rscale=src_rscale, tgt_rscale=tgt_rscale, + src_rscale=src_rscale, **kwargs) # }}} @@ -373,11 +371,13 @@ class QBXL2P(E2PBase): <> icenter_tgt_start = center_to_targets_starts[src_icenter] <> icenter_tgt_end = center_to_targets_starts[src_icenter+1] + <> center[idim] = qbx_centers[idim, src_icenter] {dup=idim} + <> rscale = qbx_expansion_radii[src_icenter] + for icenter_tgt <> center_itgt = center_to_targets_lists[icenter_tgt] - <> center[idim] = qbx_centers[idim, src_icenter] {dup=idim} <> b[idim] = targets[idim, center_itgt] - center[idim] """] + [""" @@ -397,7 +397,6 @@ class QBXL2P(E2PBase): dim_tags="sep,C"), lp.GlobalArg("qbx_centers", None, shape="dim, ncenters", dim_tags="sep,c"), - lp.ValueArg("rscale", None), lp.GlobalArg("center_to_targets_starts,center_to_targets_lists", None, shape=None), lp.GlobalArg("qbx_expansions", None, @@ -428,15 +427,7 @@ class QBXL2P(E2PBase): return knl def __call__(self, queue, **kwargs): - qbx_centers = kwargs.pop("qbx_centers") - # "1" may be passed for rscale, which won't have its type - # meaningfully inferred. Make the type of rscale explicit. - rscale = qbx_centers[0].dtype.type(kwargs.pop("rscale")) - - return self.get_cached_optimized_kernel()(queue, - qbx_centers=qbx_centers, - rscale=rscale, - **kwargs) + return self.get_cached_optimized_kernel()(queue, **kwargs) # }}} -- GitLab From dc664cc985ba5a7593789f570fd6043496042e4b Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 28 Aug 2017 13:17:00 -0500 Subject: [PATCH 07/15] Generalize fmmlib backend to accept 2D Helmholtz kernel --- pytential/qbx/fmmlib.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index 68a270e9..881d849f 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -150,7 +150,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): else: source_deriv_name = None - result = isinstance(knl, HelmholtzKernel) and knl.dim == 3 + result = isinstance(knl, HelmholtzKernel) and knl.dim in [2, 3] if result: k_names.append(knl.helmholtz_k_name) source_deriv_names.append(source_deriv_name) @@ -481,8 +481,12 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): print("end par data prep") - # These get max'd/added onto: pass initialized versions. - ier = np.zeros(ngqbx_centers, dtype=np.int32) + if self.dim == 3: + # This gets max'd onto: pass initialized version. + ier = np.zeros(ngqbx_centers, dtype=np.int32) + kwargs["ier"] = ier + + # This gets added onto: pass initialized version. expn2 = np.zeros( (ngqbx_centers,) + self.expansion_shape(self.qbx_order), dtype=self.dtype) @@ -506,10 +510,13 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): # FIXME: center2 has wrong layout, will copy center2=qbx_centers[:, tgt_icenter_vec], expn2=expn2.T, - ier=ier, **kwargs).T + if self.dim == 3: + if ier.any(): + raise RuntimeError("m2qbxl failed") + local_exps[geo_data.global_qbx_centers()] += expn2 return local_exps -- GitLab From b5e44c2ff9c7aae6f88ac55ea67b7892e9e05478 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 28 Aug 2017 13:53:07 -0500 Subject: [PATCH 08/15] FMMlib QBX FMM: Use expansion radii for rscale --- pytential/qbx/fmmlib.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index 881d849f..59829cc4 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -31,9 +31,6 @@ from boxtree.pyfmmlib_integration import ( from sumpy.kernel import HelmholtzKernel -# FIXME: This should be replaced with the radius of the QBX expansions -_QBX_RSCALE = 1 - import logging logger = logging.getLogger(__name__) @@ -337,10 +334,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): isource += ns centers = qbx_centers[:, geo_data.global_qbx_centers()] - - rscale = _QBX_RSCALE # FIXME - rscale_vec = np.empty(len(center_source_counts) - 1, dtype=np.float64) - rscale_vec.fill(rscale) # FIXME + rscale_vec = geo_data.expansion_radii()[geo_data.global_qbx_centers()] nsources_vec = np.ones(self.tree.nsources, np.int32) @@ -444,8 +438,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): tgt_icenter_vec = geo_data.global_qbx_centers() icontaining_tgt_box_vec = qbx_center_to_target_box[tgt_icenter_vec] - # FIXME - rscale2 = np.ones(ngqbx_centers, np.float64) * _QBX_RSCALE + rscale2 = geo_data.expansion_radii()[geo_data.global_qbx_centers()] kwargs = {} if self.dim == 3: @@ -532,6 +525,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): trav = geo_data.traversal() qbx_center_to_target_box = geo_data.qbx_center_to_target_box() qbx_centers = geo_data.centers() + qbx_radii = geo_data.expansion_radii() locloc = self.get_translation_routine("%ddlocloc") @@ -573,7 +567,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): center1=src_center, expn1=local_exps[src_ibox].T, - rscale2=_QBX_RSCALE, + rscale2=qbx_radii[tgt_icenter], center2=tgt_center, nterms2=local_order, @@ -590,6 +584,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): ctt = geo_data.center_to_tree_targets() global_qbx_centers = geo_data.global_qbx_centers() qbx_centers = geo_data.centers() + qbx_radii = geo_data.expansion_radii() all_targets = geo_data.all_targets() @@ -605,7 +600,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): center = qbx_centers[:, src_icenter] pot, grad = taeval( - rscale=_QBX_RSCALE, + rscale=qbx_radii[src_icenter], center=center, expn=qbx_expansions[src_icenter].T, ztarg=all_targets[:, center_itgt], -- GitLab From 776983c49b2c79921dd125c5c3ea93cd2315092d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 28 Aug 2017 20:20:07 -0500 Subject: [PATCH 09/15] Fix unaccelerated QBX after rscale --- pytential/qbx/__init__.py | 5 ++++- pytential/qbx/direct.py | 2 ++ pytential/qbx/fmmlib.py | 39 ++++++++++++++++++++++----------- test/test_layer_pot_identity.py | 4 ++-- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index ee716f53..a59d0fb6 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -696,7 +696,9 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): queue, target_discr.nodes(), self.quad_stage2_density_discr.nodes(), utils.get_centers_on_side(self, o.qbx_forced_limit), - [strengths], **kernel_args) + [strengths], + expansion_radii=self._expansion_radii("nsources"), + **kernel_args) result.append((o.name, output_for_each_kernel[o.kernel_index])) else: # no on-disk kernel caching @@ -759,6 +761,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): targets=target_discr.nodes(), sources=self.quad_stage2_density_discr.nodes(), centers=geo_data.centers(), + expansion_radii=geo_data.expansion_radii(), strengths=[strengths], qbx_tgt_numbers=qbx_tgt_numbers, qbx_center_numbers=qbx_center_numbers, diff --git a/pytential/qbx/direct.py b/pytential/qbx/direct.py index b8ab7d24..6dc5cd9a 100644 --- a/pytential/qbx/direct.py +++ b/pytential/qbx/direct.py @@ -39,6 +39,7 @@ class LayerPotentialOnTargetAndCenterSubset(LayerPotentialBase): <> a[idim] = center[idim,icenter] - src[idim,isrc] {id=compute_a} <> b[idim] = tgt[idim,itgt_overall] - center[idim,icenter] \ {id=compute_b} + <> rscale = expansion_radii[icenter] end """ @@ -50,6 +51,7 @@ class LayerPotentialOnTargetAndCenterSubset(LayerPotentialBase): shape=(self.dim, "ntargets_total"), order="C"), lp.GlobalArg("center", None, shape=(self.dim, "ncenters_total"), order="C"), + lp.GlobalArg("expansion_radii", None, shape="ncenters_total"), lp.GlobalArg("qbx_tgt_numbers", None, shape="ntargets"), lp.GlobalArg("qbx_center_numbers", None, shape="ntargets"), lp.ValueArg("nsources", np.int32), diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index 59829cc4..9f1d3240 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -28,7 +28,7 @@ import pyopencl as cl # noqa import pyopencl.array # noqa: F401 from boxtree.pyfmmlib_integration import ( FMMLibExpansionWrangler, level_to_rscale) -from sumpy.kernel import HelmholtzKernel +from sumpy.kernel import LaplaceKernel, HelmholtzKernel import logging @@ -147,11 +147,16 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): else: source_deriv_name = None - result = isinstance(knl, HelmholtzKernel) and knl.dim in [2, 3] - if result: + if isinstance(knl, HelmholtzKernel) and knl.dim in [2, 3]: k_names.append(knl.helmholtz_k_name) source_deriv_names.append(source_deriv_name) - return result + return True + elif isinstance(knl, LaplaceKernel) and knl.dim in [2, 3]: + k_names.append(None) + source_deriv_names.append(source_deriv_name) + return True + + return False ifgrad = False outputs = [] @@ -164,8 +169,8 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): ifgrad = True else: raise NotImplementedError( - "only the 3D Helmholtz kernel and its target derivatives " - "are supported for now") + "only the 2/3D Laplace and Helmholtz kernel " + "and their derivatives are supported") from pytools import is_single_valued if not is_single_valued(source_deriv_names): @@ -179,7 +184,10 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): from pytools import single_valued k_name = single_valued(k_names) - helmholtz_k = kernel_extra_kwargs[k_name] + if k_name is None: + helmholtz_k = 0 + else: + helmholtz_k = kernel_extra_kwargs[k_name] self.level_orders = [ fmm_level_to_order(level) @@ -370,13 +378,18 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): kwargs["charge_starts"] = info.center_source_starts else: - kwargs["dipstr"] = src_weights kwargs["dipstr_offsets"] = info.center_source_offsets kwargs["dipstr_starts"] = info.center_source_starts - kwargs["dipvec"] = self.dipole_vec - kwargs["dipvec_offsets"] = info.center_source_offsets - kwargs["dipvec_starts"] = info.center_source_starts + if self.dim == 2 and self.eqn_letter == "l": + kwargs["dipstr"] = -src_weights * ( + self.dipole_vec[0] + 1j*self.dipole_vec[1]) + else: + kwargs["dipstr"] = src_weights + + kwargs["dipvec"] = self.dipole_vec + kwargs["dipvec_offsets"] = info.center_source_offsets + kwargs["dipvec_starts"] = info.center_source_starts # These get max'd/added onto: pass initialized versions. ier = np.zeros(info.ngqbx_centers, dtype=np.int32) @@ -441,7 +454,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): rscale2 = geo_data.expansion_radii()[geo_data.global_qbx_centers()] kwargs = {} - if self.dim == 3: + if self.dim == 3 and self.eqn_letter == "h": kwargs["radius"] = (0.5 * geo_data.expansion_radii()[geo_data.global_qbx_centers()]) @@ -542,7 +555,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): kwargs.update(self.kernel_kwargs) for tgt_icenter in range(geo_data.ncenters): - if self.dim == 3: + if self.dim == 3 and self.eqn_letter == "h": # Yuck: This keeps overwriting 'radius' in the dict. kwargs["radius"] = 0.5 * ( geo_data.expansion_radii()[tgt_icenter]) diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index 1e1c9213..477c6937 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -183,7 +183,7 @@ class StarfishGreenTest(StaticTestCase): _expansion_stick_out_factor = 0.5 - fmm_backend = "sumpy" + fmm_backend = "fmmlib" class WobblyCircleGreenTest(StaticTestCase): @@ -201,7 +201,7 @@ class WobblyCircleGreenTest(StaticTestCase): class SphereGreenTest(StaticTestCase): expr = GreenExpr() geometry = SphereGeometry() - k = 1.2 + k = 0 qbx_order = 3 fmm_order = 10 -- GitLab From fa6ad41d80a7a99fd59922267adc0acfe6e8d103 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 29 Aug 2017 00:06:20 -0500 Subject: [PATCH 10/15] Delete many-arm NaN reproducer --- examples/fmm-manyarm-starfish-nan.py | 109 --------------------------- 1 file changed, 109 deletions(-) delete mode 100644 examples/fmm-manyarm-starfish-nan.py diff --git a/examples/fmm-manyarm-starfish-nan.py b/examples/fmm-manyarm-starfish-nan.py deleted file mode 100644 index 7a8b1d3f..00000000 --- a/examples/fmm-manyarm-starfish-nan.py +++ /dev/null @@ -1,109 +0,0 @@ -import numpy as np -import numpy.linalg as la # noqa -import pyopencl as cl -import pyopencl.clmath # noqa - -from meshmode.mesh.generation import ( # noqa - ellipse, cloverleaf, NArmedStarfish, drop, n_gon, qbx_peanut, - make_curve_mesh, starfish) -from pytential import bind, sym, norm - -import logging -logger = logging.getLogger(__name__) - - -def get_starfish_mesh(nelements, target_order): - return make_curve_mesh( - NArmedStarfish(20, 0.8), - np.linspace(0, 1, nelements+1), - target_order) - - -WITH_EXTENTS = True -EXPANSION_STICKOUT_FACTOR = 0.5 - - -def get_green_error( - queue, mesh_getter, nelements, fmm_order, qbx_order, k=0): - - target_order = 12 - - mesh = mesh_getter(nelements, target_order) - - d = mesh.ambient_dim - - # u_sym = sym.var("u") - dn_u_sym = sym.var("dn_u") - - from sumpy.kernel import LaplaceKernel - k_sym = LaplaceKernel(d) - zero_op = ( - sym.S(k_sym, dn_u_sym, qbx_forced_limit=-1) - # - sym.D(k_sym, u_sym, qbx_forced_limit="avg") - # - 0.5*u_sym - ) - - from meshmode.discretization import Discretization - from meshmode.discretization.poly_element import ( - InterpolatoryQuadratureSimplexGroupFactory) - pre_density_discr = Discretization( - queue.context, mesh, - InterpolatoryQuadratureSimplexGroupFactory(target_order)) - - from pytential.qbx import QBXLayerPotentialSource - lpot_source = QBXLayerPotentialSource( - pre_density_discr, 4*target_order, - qbx_order, fmm_order=fmm_order, - _expansions_in_tree_have_extent=WITH_EXTENTS, - _expansion_stick_out_factor=EXPANSION_STICKOUT_FACTOR, - ) - - lpot_source, _ = lpot_source.with_refinement() - - density_discr = lpot_source.density_discr - - # {{{ compute values of a solution to the PDE - - nodes_host = density_discr.nodes().get(queue) - normal = bind(density_discr, sym.normal(d))(queue).as_vector(np.object) - normal_host = [normal[j].get() for j in range(d)] - - center = np.array([3, 1, 2])[:d] - diff = nodes_host - center[:, np.newaxis] - dist_squared = np.sum(diff**2, axis=0) - dist = np.sqrt(dist_squared) - if d == 2: - u = np.log(dist) - grad_u = diff/dist_squared - elif d == 3: - u = 1/dist - grad_u = -diff/dist**3 - else: - assert False - - dn_u = 0 - for i in range(d): - dn_u = dn_u + normal_host[i]*grad_u[i] - - # }}} - - u_dev = cl.array.to_device(queue, u) - dn_u_dev = cl.array.to_device(queue, dn_u) - - bound_op = bind(lpot_source, zero_op) - error = bound_op( - queue, u=u_dev, dn_u=dn_u_dev, k=k) - - print(len(error), np.where(np.isnan(error.get()))) - return norm(density_discr, queue, error) - - -def main(): - cl_ctx = cl.create_some_context() - queue = cl.CommandQueue(cl_ctx) - - get_green_error(queue, get_starfish_mesh, 500, 20, 2, k=0) - - -if __name__ == "__main__": - main() -- GitLab From adfbc843f452e8e6a2bd461922e5364686a6fc2e Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 29 Aug 2017 00:10:31 -0500 Subject: [PATCH 11/15] Add test for 2D/3D Laplace/Helmholtz fmmlib QBX FMM, remove 3D inteq fmmlib test to compensate --- test/test_layer_pot_identity.py | 6 +++++- test/test_scalar_int_eq.py | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index 477c6937..56f0d588 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -215,11 +215,12 @@ class SphereGreenTest(StaticTestCase): class DynamicTestCase(object): fmm_backend = "sumpy" - def __init__(self, geometry, expr, k): + def __init__(self, geometry, expr, k, fmm_backend="sumpy"): self.geometry = geometry self.expr = expr self.k = k self.qbx_order = 5 if geometry.dim == 2 else 3 + self.fmm_backend = fmm_backend if geometry.dim == 2: order_bump = 15 @@ -252,6 +253,9 @@ class DynamicTestCase(object): DynamicTestCase(geom, GradGreenExpr(), 0), DynamicTestCase(geom, GradGreenExpr(), 1.2), DynamicTestCase(geom, ZeroCalderonExpr(), 0), + + DynamicTestCase(geom, GreenExpr(), 0, fmm_backend="fmmlib"), + DynamicTestCase(geom, GreenExpr(), 1.2, fmm_backend="fmmlib"), ]]) def test_identity_convergence(ctx_getter, case, visualize=False): logging.basicConfig(level=logging.INFO) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 9443261a..ec8a4c94 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -683,8 +683,6 @@ class ManyEllipsoidIntEqTestCase(Helmholtz3DIntEqTestCase): for helmholtz_k in [0, 1.2] for bc_type in ["dirichlet", "neumann"] for loc_sign in [-1, +1] - ] + [ - EllipsoidIntEqTestCase(0.7, "neumann", +1) ]) # Sample test run: # 'test_integral_equation(cl._csc, EllipseIntEqTestCase(0, "dirichlet", +1), 5)' # noqa: E501 -- GitLab From 9f62b0ddc4c91b0cb6f5b1b2cb15dbaf98d3f68e Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 29 Aug 2017 12:56:55 -0500 Subject: [PATCH 12/15] Tweak 3D layer pot identity tests --- test/test_layer_pot_identity.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index 56f0d588..65e33ab4 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -108,7 +108,7 @@ class SphereGeometry(object): mesh_name = "sphere" dim = 3 - resolutions = [0, 1, 2] + resolutions = [0, 1] def get_mesh(self, resolution, tgt_order): return get_sphere_mesh(resolution, tgt_order) @@ -230,7 +230,9 @@ class DynamicTestCase(object): self.fmm_order = self.qbx_order + order_bump def check(self): - if self.geometry.mesh_name == "sphere" and self.k != 0: + if (self.geometry.mesh_name == "sphere" + and self.k != 0 + and self.fmm_backend == "sumpy"): pytest.skip("both direct eval and generating the FMM kernels " "are too slow") -- GitLab From c446b2871361fc2f8e17c38a934606cd472ce4bf Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 29 Aug 2017 12:57:05 -0500 Subject: [PATCH 13/15] Fix matrix generation for rscale --- pytential/symbolic/matrix.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 9f2f101a..84cd46ce 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -190,6 +190,7 @@ class MatrixBuilder(EvaluationMapperBase): target_discr.nodes(), source.quad_stage2_density_discr.nodes(), get_centers_on_side(source, expr.qbx_forced_limit), + expansion_radii=self.dep_source._expansion_radii("nsources"), **kernel_args) mat = mat.get() -- GitLab From dfbb821fb58af5e6a355931d424f8638cb9069b6 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 29 Aug 2017 17:57:48 -0500 Subject: [PATCH 14/15] Make fmmlib tests skippable if pyfmmlib is not available --- test/test_layer_pot_identity.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index 65e33ab4..e8044d39 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -240,6 +240,9 @@ class DynamicTestCase(object): and self.expr.zero_op_name == "green_grad"): pytest.skip("does not achieve sufficient precision") + if self.fmm_backend == "fmmlib": + pytest.importorskip("pyfmmlib") + # {{{ integral identity tester -- GitLab From ea36ff3de642c0c4e8b133c6f0ed768f64f30a91 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 31 Aug 2017 20:59:07 -0500 Subject: [PATCH 15/15] Allow failure of conda build because it takes forever --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7caff1a7..3d98c18b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,6 +31,7 @@ Python 3.5 Conda: - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh - ". ./build-and-test-py-project-within-miniconda.sh" + allow_failure: true # takes very long tags: - linux except: -- GitLab