diff --git a/.circleci/config.yml b/.circleci/config.yml index 2b6a32f7a..7582d0434 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -153,7 +153,8 @@ jobs: -DPHYLANX_WITH_ITERATIVE_SOLVERS=ON \ -DPHYLANX_WITH_DOCUMENTATION=ON \ -DPHYLANX_WITH_HIGHFIVE=OFF \ - -DPHYLANX_WITH_CXX17=ON + -DPHYLANX_WITH_CXX17=ON \ + -DPHYLANX_WITH_PYTHON_TESTS_EMIT_FULL_DIAGNOSTICS=On - persist_to_workspace: root: / paths: diff --git a/CMakeLists.txt b/CMakeLists.txt index ad712b2c6..f7bbb36ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,11 @@ phylanx_option( "Enable or disable the compilation of benchmark tests" ON ADVANCED CATEGORY "Build") +phylanx_option( + PHYLANX_WITH_PYTHON_TESTS_EMIT_FULL_DIAGNOSTICS BOOL + "Python tests emit the full HPX diagnostics if exceptions are thrown" + OFF ADVANCED CATEGORY "Build") + phylanx_option( PHYLANX_WITH_TESTS_REGRESSIONS BOOL "Enable or disable the compilation of regression tests" diff --git a/cmake/Phylanx_AddPythonTest.cmake b/cmake/Phylanx_AddPythonTest.cmake index a23128622..beefdd94c 100644 --- a/cmake/Phylanx_AddPythonTest.cmake +++ b/cmake/Phylanx_AddPythonTest.cmake @@ -30,11 +30,17 @@ macro(add_phylanx_python_test category name) phylanx_debug("add_phylanx_python_test (${name})" "WORKING_DIRECTORY: ${_working_directory}") set(_environment) + if(PHYLANX_WITH_PYTHON_TESTS_EMIT_FULL_DIAGNOSTICS) + set(_environment + PHYLANX_PYTHON_CONFIG="--hpx:ini=phylanx.full_error_diagnostics!=1" + ) + endif() if(${name}_ENVIRONMENT) + set(_environment ${${name}_ENVIRONMENT} ${_environment}) if(MSVC) - set(_environment ${CMAKE_COMMAND} -E env "${${name}_ENVIRONMENT}") + set(_environment ${CMAKE_COMMAND} -E env ${_environment}) else() - set(_environment ENVIRONMENT "${${name}_ENVIRONMENT}") + set(_environment ENVIRONMENT ${_environment}) endif() endif() phylanx_debug("add_phylanx_python_test (${name})" "ENVIRONMENT: ${_environment}") diff --git a/phylanx/execution_tree/compiler/actors.hpp b/phylanx/execution_tree/compiler/actors.hpp index 7fa028bc9..89ae4044e 100644 --- a/phylanx/execution_tree/compiler/actors.hpp +++ b/phylanx/execution_tree/compiler/actors.hpp @@ -173,6 +173,49 @@ namespace phylanx { namespace execution_tree { namespace compiler return extract_copy_value(arg_, name_); } + // evaluate object itself and use the returned value to + // asynchronously evaluate the arguments + hpx::shared_future async( + arguments_type const& args, eval_context ctx) const + { + if (is_primitive_operand(arg_)) + { + arguments_type params; + params.reserve(args.size()); + for (auto const& arg : args) + { + params.emplace_back(extract_ref_value(arg, name_)); + } + + return value_operand(arg_, std::move(params), name_).share(); + } + return hpx::make_ready_future(extract_copy_value(arg_, name_)) + .share(); + } + + hpx::shared_future async( + arguments_type&& args, eval_context ctx) const + { + if (is_primitive_operand(arg_)) + { + arguments_type keep_alive(std::move(args)); + + // construct argument-pack to use for actual call + arguments_type params; + params.reserve(keep_alive.size()); + for (auto const& arg : keep_alive) + { + params.emplace_back(extract_ref_value(arg, name_)); + } + + return value_operand( + arg_, std::move(params), name_, "", std::move(ctx)) + .share(); + } + return hpx::make_ready_future(extract_copy_value(arg_, name_)) + .share(); + } + using kwarguments_type = std::map; result_type operator()(arguments_type&& args, kwarguments_type&& kwargs, @@ -222,6 +265,53 @@ namespace phylanx { namespace execution_tree { namespace compiler return extract_copy_value(arg_, name_); } + hpx::shared_future async(arguments_type&& args, + kwarguments_type&& kwargs, eval_context ctx) const + { + if (is_primitive_operand(arg_)) + { + arguments_type keep_alive(std::move(args)); + kwarguments_type kw_keep_alive(std::move(kwargs)); + + // construct argument-pack to use for actual call + arguments_type params; + params.reserve(keep_alive.size() + num_named_args_); + for (auto const& arg : keep_alive) + { + params.emplace_back(extract_ref_value(arg, name_)); + } + + // fill in the given named arguments at their correct positions + for (auto const& kwarg : kw_keep_alive) + { + auto it = std::find(named_args_.get(), + named_args_.get() + num_named_args_, kwarg.first); + if (it == named_args_.get() + num_named_args_) + { + HPX_THROW_EXCEPTION(hpx::bad_parameter, + "function::operator()", + hpx::util::format("cannot locate requested " + "named argument '{}'", kwarg.first)); + } + + std::ptrdiff_t kwarg_pos = + std::distance(named_args_.get(), it); + if (kwarg_pos >= std::ptrdiff_t(params.size())) + { + params.resize(kwarg_pos + 1); + } + + params[kwarg_pos] = extract_ref_value(kwarg.second, name_); + } + + return value_operand( + arg_, std::move(params), name_, "", std::move(ctx)) + .share(); + } + return hpx::make_ready_future(extract_copy_value(arg_, name_)) + .share(); + } + template typename std::enable_if< !std::is_same::type>::value, diff --git a/phylanx/plugins/controls/controls.hpp b/phylanx/plugins/controls/controls.hpp index ec99234c4..01b20d188 100644 --- a/phylanx/plugins/controls/controls.hpp +++ b/phylanx/plugins/controls/controls.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #endif diff --git a/phylanx/plugins/controls/sleep_operation.hpp b/phylanx/plugins/controls/sleep_operation.hpp new file mode 100644 index 000000000..8fa01df75 --- /dev/null +++ b/phylanx/plugins/controls/sleep_operation.hpp @@ -0,0 +1,46 @@ +// Copyright (c) 2020 Hartmut Kaiser +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#if !defined(PHYLANX_SLEEP_OPERATION_APR_24_2020_0840AM) +#define PHYLANX_SLEEP_OPERATION_APR_24_2020_0840AM + +#include +#include +#include + +#include + +#include +#include + +namespace phylanx { namespace execution_tree { namespace primitives { + + class sleep_operation : public primitive_component_base + { + public: + static match_pattern_type const match_data; + + sleep_operation() = default; + + sleep_operation(primitive_arguments_type&& operands, + std::string const& name, std::string const& codename); + + protected: + hpx::future eval( + primitive_arguments_type const& operands, + primitive_arguments_type const& args, + eval_context ctx) const override; + }; + + inline primitive create_sleep_operation(hpx::id_type const& locality, + primitive_arguments_type&& operands, std::string const& name = "", + std::string const& codename = "") + { + return create_primitive_component( + locality, "sleep", std::move(operands), name, codename); + } +}}} // namespace phylanx::execution_tree::primitives + +#endif diff --git a/python/phylanx/ast/physl.py b/python/phylanx/ast/physl.py index b23ecbff7..e70e03712 100644 --- a/python/phylanx/ast/physl.py +++ b/python/phylanx/ast/physl.py @@ -338,32 +338,37 @@ class PhySLFunction: def __init__(self, physl): self.physl = physl - def compile_function(self): - self.physl._ensure_is_compiled() + def compile_function(self, loc): + self.physl._ensure_is_compiled(loc) @staticmethod - def compile(): + def compile(loc): + assert(loc is not None) + if PhySLFunction.functions: for func in PhySLFunction.functions: - func.compile_function() + func.compile_function(loc) PhySLFunction.functions = [] class PhySL: """Python AST to PhySL Transducer.""" - compiler_state = None + compiler_state = {} - def _ensure_compiler_state(self): + def _ensure_compiler_state(self, loc): """Ensure the compiler state object has been created""" - if PhySL.compiler_state is None: + assert(loc is not None) + + if (loc not in PhySL.compiler_state) or PhySL.compiler_state[loc] is None: if "compiler_state" in self.kwargs: - PhySL.compiler_state = self.kwargs['compiler_state'] + PhySL.compiler_state[loc] = self.kwargs['compiler_state'] else: # the static method compiler_state is constructed only once - PhySL.compiler_state = \ - phylanx.execution_tree.global_compiler_state(self.file_name) + PhySL.compiler_state[loc] = \ + phylanx.execution_tree.global_compiler_state( + self.file_name, loc) def _compile_or_load(self): """Compile or load this function from database""" @@ -421,49 +426,59 @@ def _compile_or_load(self): print_physl_src(self.__src__) print(end="", flush="") - def _ensure_global_state(self): + def _ensure_global_state(self, loc): """Ensure global PhySL session has been initialized""" + assert(loc is not None) + if not PhylanxSession.is_initialized: PhylanxSession.init(1) - if not self.is_compiled: + if (loc not in self.is_compiled) or not self.is_compiled[loc]: # compile all functions that have so far been collected without an # initialized session object - PhySLFunction.compile() + PhySLFunction.compile(loc) - def _ensure_is_compiled(self): + def _ensure_is_compiled(self, loc): """Ensure this function has been compiled, also compile all functions that have been collected so fart without being compiled""" - if self.is_compiled: + assert(loc is not None) + + if (loc in self.is_compiled) and self.is_compiled[loc]: return # create compiler state - self._ensure_compiler_state() + self._ensure_compiler_state(loc) # transduce the python code, generate AST self._compile_or_load() # compile this function phylanx.execution_tree.compile( - PhySL.compiler_state, self.file_name, + PhySL.compiler_state[loc], self.file_name, self.wrapped_function.__name__, self.__ast__) - self.is_compiled = True + self.is_compiled[loc] = True + + def _ensure_locality(self): + """Delay the initialization of the current locality""" + if self.locality is None: + self.locality = phylanx.execution_tree.find_here() def __init__(self, func, tree, kwargs): self.defined = set() self.numpy_aliases = {'numpy'} self.wrapped_function = func self.kwargs = kwargs - self.is_compiled = False + self.is_compiled = {} self.file_name = None self.__src__ = None self.__ast__ = None self.ir = None self.python_tree = tree + self.locality = None if 'doc_src' in kwargs and kwargs['doc_src']: self.doc_src = func.__doc__ else: @@ -492,8 +507,9 @@ def __init__(self, func, tree, kwargs): # compile this function if session was already initialized, otherwise # simply collect it for later compilation if PhylanxSession.is_initialized: - self._ensure_global_state() - self._ensure_is_compiled() + self._ensure_locality() + self._ensure_global_state(self.locality) + self._ensure_is_compiled(self.locality) else: func = PhySLFunction(self) @@ -527,20 +543,24 @@ def _apply_rule(self, node): return eval('self._%s' % node_name)(node) # Invocation support - def lazy(self, *args, **kwargs): + def lazy(self, *args, locality=None, **kwargs): """Compile a given function, return a variable binding the function to arguments""" - self._ensure_global_state() - self._ensure_is_compiled() + if locality is None: + self._ensure_locality() + locality = self.locality + + self._ensure_global_state(locality) + self._ensure_is_compiled(locality) if len(args) == 0 and len(kwargs) == 0: return phylanx.execution_tree.variable( phylanx.execution_tree.code_for( - PhySL.compiler_state, self.file_name, + PhySL.compiler_state[locality], self.file_name, self.wrapped_function.__name__)) - def map_wrapped(val): + def _map_wrapped(val): """If a variable is passed as an argument to an invocation of a Phylanx function we need to extract the compiled execution tree and pass that along instead""" @@ -550,20 +570,40 @@ def map_wrapped(val): return val - mapped_args = tuple(map(map_wrapped, args)) + mapped_args = tuple(map(_map_wrapped, args)) kwitems = kwargs.items() - mapped_kwargs = {k: map_wrapped(v) for k, v in kwitems} + mapped_kwargs = {k: _map_wrapped(v) for k, v in kwitems} return phylanx.execution_tree.variable( phylanx.execution_tree.bound_code_for( - PhySL.compiler_state, self.file_name, + PhySL.compiler_state[locality], self.file_name, self.wrapped_function.__name__, *mapped_args, **mapped_kwargs)) - def call(self, *args, **kwargs): + # Invocation support + def launch(self, *args, locality=None, **kwargs): + """Invoke this Phylanx function on the given locality, pass along the + given arguments""" + + if locality is None: + self._ensure_locality() + locality = self.locality + + self._ensure_global_state(locality) + self._ensure_is_compiled(locality) + + return phylanx.execution_tree.async_eval( + PhySL.compiler_state[locality], self.file_name, + self.wrapped_function.__name__, *args, **kwargs) + + def call(self, *args, locality=None, **kwargs): """Invoke this Phylanx function, pass along the given arguments""" - self._ensure_global_state() - self._ensure_is_compiled() + if locality is None: + self._ensure_locality() + locality = self.locality + + self._ensure_global_state(locality) + self._ensure_is_compiled(locality) self.__perfdata__ = (None, None, None) self.performance_primitives = None @@ -571,10 +611,10 @@ def call(self, *args, **kwargs): if self.performance: self.performance_primitives = \ phylanx.execution_tree.enable_measurements( - PhySL.compiler_state, True) + PhySL.compiler_state[locality], True) result = phylanx.execution_tree.eval( - PhySL.compiler_state, self.file_name, + PhySL.compiler_state[locality], self.file_name, self.wrapped_function.__name__, *args, **kwargs) if self.performance: @@ -582,25 +622,33 @@ def call(self, *args, **kwargs): self.__perfdata__ = ( phylanx.execution_tree.retrieve_counter_data( - PhySL.compiler_state), treedata[0], treedata[1]) + PhySL.compiler_state[locality]), treedata[0], treedata[1]) return result - def tree(self): + def tree(self, locality=None): """Return the tree data for this object""" - self._ensure_global_state() - self._ensure_is_compiled() + if locality is None: + self._ensure_locality() + locality = self.locality + + self._ensure_global_state(locality) + self._ensure_is_compiled(locality) return phylanx.execution_tree.retrieve_tree_topology( - PhySL.compiler_state, self.file_name, + PhySL.compiler_state[locality], self.file_name, self.wrapped_function.__name__) - def get_physl_source(self): + def get_physl_source(self, locality=None): """Return generated PhySL source string""" - self._ensure_global_state() - self._ensure_is_compiled() + if locality is None: + self._ensure_locality() + locality = self.locality + + self._ensure_global_state(locality) + self._ensure_is_compiled(locality) return self.__src__ diff --git a/python/phylanx/ast/transducer.py b/python/phylanx/ast/transducer.py index 264455ac9..0dbaf7833 100644 --- a/python/phylanx/ast/transducer.py +++ b/python/phylanx/ast/transducer.py @@ -94,10 +94,10 @@ def get_python_ast(self, src, f): assert len(tree.body) == 1 return tree - def get_physl_source(self): + def get_physl_source(self, loc=None): """Return generated PhySL source string""" - return self.backend.get_physl_source() + return self.backend.get_physl_source(loc) def compile_function(self, func): fn_src = inspect.getsource(func).strip() @@ -115,7 +115,7 @@ def compile_function(self, func): fn_ast = ast.Module(body=[fn_body]) return PhySL(func, fn_ast, self.kwargs) - def map_decorated(self, val): + def _map_decorated(self, val): """If a PhylanxDecorator is passed as an argument to an invocation of a Phylanx function we need to extract the compiled execution tree and pass along that instead. @@ -137,7 +137,7 @@ def map_decorated(self, val): return val - def lazy(self, *args, **kwargs): + def lazy(self, *args, locality=None, **kwargs): """Compile this decorator, return wrapper binding the function to arguments""" @@ -145,22 +145,38 @@ def lazy(self, *args, **kwargs): raise NotImplementedError( "OpenSCoP kernels are not yet callable.") - mapped_args = tuple(map(self.map_decorated, args)) + mapped_args = tuple(map(self._map_decorated, args)) kwitems = kwargs.items() - mapped_kwargs = {k: self.map_decorated(v) for k, v in kwitems} - return self.backend.lazy(*mapped_args, **mapped_kwargs) + mapped_kwargs = {k: self._map_decorated(v) for k, v in kwitems} + return self.backend.lazy( + *mapped_args, locality=locality, **mapped_kwargs) - def __call__(self, *args, **kwargs): + def launch(self, *args, locality=None, **kwargs): + """Compile this decorator, return wrapper binding the function to + arguments""" + + if self.backend == 'OpenSCoP': + raise NotImplementedError( + "OpenSCoP kernels are not yet callable.") + + mapped_args = tuple(map(self._map_decorated, args)) + kwitems = kwargs.items() + mapped_kwargs = {k: self._map_decorated(v) for k, v in kwitems} + return self.backend.launch( + *mapped_args, locality=locality, **mapped_kwargs) + + def __call__(self, *args, locality=None, **kwargs): """Invoke this decorator using the given arguments""" if self.backend == 'OpenSCoP': raise NotImplementedError( "OpenSCoP kernels are not yet callable.") - mapped_args = tuple(map(self.map_decorated, args)) + mapped_args = tuple(map(self._map_decorated, args)) kwitems = kwargs.items() - mapped_kwargs = {k: self.map_decorated(v) for k, v in kwitems} - result = self.backend.call(*mapped_args, **mapped_kwargs) + mapped_kwargs = {k: self._map_decorated(v) for k, v in kwitems} + result = self.backend.call( + *mapped_args, locality=locality, **mapped_kwargs) self.__perfdata__ = self.backend.__perfdata__ @@ -169,9 +185,10 @@ def __call__(self, *args, **kwargs): def generate_ast(self): return generate_phylanx_ast(self.__src__) - def tree(self): - return self.backend.tree() + def tree(self, locality=None): + return self.backend.tree(locality) + # expose lazy version of the decorator class _PhylanxLazyDecorator: def __init__(self, decorator): """ @@ -179,15 +196,14 @@ def __init__(self, decorator): """ self.decorator = decorator - def __call__(self, *args, **kwargs): + def __call__(self, *args, locality=None, **kwargs): """Invoke this decorator""" - return self.decorator.lazy(*args, **kwargs) + return self.decorator.lazy(*args, locality=locality, **kwargs) - def tree(self): + def tree(self, locality=None): """Return the tree data for this decorator""" - return self.decorator.tree() + return self.decorator.tree(locality) - # expose lazy version of the decorator def lazy(decorator): if type(decorator).__name__ != "_PhylanxDecorator": raise InvalidDecoratorArgumentError @@ -195,6 +211,30 @@ def lazy(decorator): setattr(Phylanx, 'lazy', lazy) + # expose launch version of the decorator + class _PhylanxAsyncDecorator: + def __init__(self, decorator): + """ + :function:decorator the decorated funtion. + """ + self.decorator = decorator + + def __call__(self, *args, locality=None, **kwargs): + """Invoke this decorator""" + return self.decorator.launch(*args, locality=locality, **kwargs) + + def tree(self, locality=None): + """Return the tree data for this decorator""" + return self.decorator.tree(locality) + + def launch(decorator): + if type(decorator).__name__ != "_PhylanxDecorator": + raise InvalidDecoratorArgumentError + return _PhylanxAsyncDecorator(decorator) + + setattr(Phylanx, 'launch', launch) + + # use this directly as a decorator if callable(__phylanx_arg): return _PhylanxDecorator(__phylanx_arg) elif __phylanx_arg is not None: diff --git a/python/phylanx/core/config.py b/python/phylanx/core/config.py index 740c35624..81fc64c7b 100644 --- a/python/phylanx/core/config.py +++ b/python/phylanx/core/config.py @@ -6,6 +6,9 @@ from phylanx.core import init_hpx_runtime from phylanx.exceptions import RuntimeAlreadyInitializedError +import os +import platform + class PhylanxSession: cfg = [ @@ -30,6 +33,17 @@ def init(num_threads=1): if not PhylanxSession.is_initialized: hpx_thread = "hpx.os_threads!=%s" % num_threads PhylanxSession.cfg[3] = hpx_thread + + # append additional config settings taken from the environment + if 'PHYLANX_PYTHON_CONFIG' in os.environ: + if platform.system() == "Windows": + sep = ';' + else: + sep = ':' + + for e in os.environ['PHYLANX_PYTHON_CONFIG'].split(sep): + PhylanxSession.cfg.append(e) + init_hpx_runtime(PhylanxSession.cfg) PhylanxSession.is_initialized = True else: diff --git a/python/phylanx/execution_tree/__init__.py b/python/phylanx/execution_tree/__init__.py index aafebc604..befcf2c69 100644 --- a/python/phylanx/execution_tree/__init__.py +++ b/python/phylanx/execution_tree/__init__.py @@ -29,13 +29,13 @@ def is_parallel_block(): # global compiler state is initialized only once -_compiler_state = None +_compiler_state = {} -def global_compiler_state(file_name=None): +def global_compiler_state(file_name=None, loc=None): global _compiler_state - if _compiler_state is None: + if (loc not in _compiler_state) or _compiler_state[loc] is None: from phylanx import PhylanxSession if not PhylanxSession.is_initialized: PhylanxSession.init(1) @@ -43,9 +43,9 @@ def global_compiler_state(file_name=None): if file_name is None: file_name = _name_of_importing_file - _compiler_state = compiler_state(file_name) + _compiler_state[loc] = compiler_state(file_name, loc) - return _compiler_state + return _compiler_state[loc] # wrap variable such that user does not need to supply compiler_state diff --git a/python/src/bindings/binding_helpers.cpp b/python/src/bindings/binding_helpers.cpp index a1152dd0d..2eb7e9e52 100644 --- a/python/src/bindings/binding_helpers.cpp +++ b/python/src/bindings/binding_helpers.cpp @@ -34,29 +34,31 @@ namespace phylanx { namespace bindings std::string const& file_name, std::string const& func_name, std::string const& xexpr_str) { + auto locality = state.get_locality(); + pybind11::gil_scoped_release release; // release GIL return hpx::threads::run_as_hpx_thread( [&]() -> std::string { auto const& code = phylanx::execution_tree::compile( - file_name, func_name, xexpr_str, state.eval_snippets, - state.eval_env); + file_name, func_name, xexpr_str, state.eval_snippets_, + state.eval_env_, locality); auto const& funcs = code.functions(); - if (state.enable_measurements) + if (state.enable_measurements_) { if (!funcs.empty()) { - state.primitive_instances.push_back( + state.primitive_instances_.push_back( phylanx::util::enable_measurements( funcs.front().name_)); } } // add all definitions to the global execution environment - code.run(state.eval_ctx); + code.run(state.eval_ctx_); return !funcs.empty() ? funcs.front().name_ : ""; }); @@ -67,29 +69,31 @@ namespace phylanx { namespace bindings std::string const& file_name, std::string const& func_name, std::vector const& xexpr) { + auto locality = state.get_locality(); + pybind11::gil_scoped_release release; // release GIL return hpx::threads::run_as_hpx_thread( [&]() -> std::string { auto const& code = phylanx::execution_tree::compile( - file_name, func_name, xexpr, state.eval_snippets, - state.eval_env); + file_name, func_name, xexpr, state.eval_snippets_, + state.eval_env_, locality); auto const& funcs = code.functions(); - if (state.enable_measurements) + if (state.enable_measurements_) { if (!funcs.empty()) { - state.primitive_instances.push_back( + state.primitive_instances_.push_back( phylanx::util::enable_measurements( funcs.front().name_)); } } // add all definitions to the global execution environment - code.run(state.eval_ctx); + code.run(state.eval_ctx_); return !funcs.empty() ? funcs.front().name_ : ""; }); @@ -100,6 +104,8 @@ namespace phylanx { namespace bindings std::string const& xexpr_str, pybind11::args args, pybind11::kwargs kwargs) { + auto locality = state.get_locality(); + pybind11::gil_scoped_release release; // release GIL using phylanx::execution_tree::primitive_argument_type; @@ -111,22 +117,22 @@ namespace phylanx { namespace bindings phylanx::util::none_wrapper wrap_cout(hpx::cout); phylanx::util::none_wrapper wrap_debug(hpx::consolestream); - auto const& code_x = - phylanx::execution_tree::compile(file_name, xexpr_str, - xexpr_str, state.eval_snippets, state.eval_env); + auto const& code_x = phylanx::execution_tree::compile(file_name, + xexpr_str, xexpr_str, state.eval_snippets_, state.eval_env_, + locality); - if (state.enable_measurements) + if (state.enable_measurements_) { auto const& funcs = code_x.functions(); if (!funcs.empty()) { - state.primitive_instances.push_back( + state.primitive_instances_.push_back( phylanx::util::enable_measurements( funcs.front().name_)); } } - auto x = code_x.run(state.eval_ctx); + auto x = code_x.run(state.eval_ctx_); phylanx::execution_tree::primitive_arguments_type fargs; fargs.reserve(args.size() + kwargs.size()); @@ -151,7 +157,7 @@ namespace phylanx { namespace bindings if (kwargs.size() == 0) { primitive_argument_type&& result = - x(std::move(fargs), state.eval_ctx); + x(std::move(fargs), state.eval_ctx_); pybind11::gil_scoped_acquire acquire; return pybind11::reinterpret_steal( @@ -162,7 +168,7 @@ namespace phylanx { namespace bindings } primitive_argument_type&& result = - x(std::move(fargs), std::move(fkwargs), state.eval_ctx); + x(std::move(fargs), std::move(fkwargs), state.eval_ctx_); pybind11::gil_scoped_acquire acquire; return pybind11::reinterpret_steal( @@ -173,6 +179,71 @@ namespace phylanx { namespace bindings }); } + hpx::shared_future + async_expression_evaluator(compiler_state& state, + std::string const& file_name, std::string const& xexpr_str, + pybind11::args args, pybind11::kwargs kwargs) + { + auto locality = state.get_locality(); + + pybind11::gil_scoped_release release; // release GIL + + using phylanx::execution_tree::primitive_argument_type; + + return hpx::threads::run_as_hpx_thread( + [&]() -> hpx::shared_future + { + // Make sure None is printed as "None" + phylanx::util::none_wrapper wrap_cout(hpx::cout); + phylanx::util::none_wrapper wrap_debug(hpx::consolestream); + + auto const& code_x = phylanx::execution_tree::compile(file_name, + xexpr_str, xexpr_str, state.eval_snippets_, state.eval_env_, + locality); + + if (state.enable_measurements_) + { + auto const& funcs = code_x.functions(); + if (!funcs.empty()) + { + state.primitive_instances_.push_back( + phylanx::util::enable_measurements( + funcs.front().name_)); + } + } + + auto x = code_x.run(state.eval_ctx_); + + phylanx::execution_tree::primitive_arguments_type fargs; + fargs.reserve(args.size() + kwargs.size()); + + std::map fkwargs; + + { + pybind11::gil_scoped_acquire acquire; + for (auto const& item : args) + { + fargs.emplace_back(item.cast()); + } + + if (kwargs) + { + fkwargs = kwargs.cast< + std::map>(); + } + } + + // potentially handle keyword arguments + if (kwargs.size() == 0) + { + return x.async(std::move(fargs), state.eval_ctx_); + } + + return x.async( + std::move(fargs), std::move(fkwargs), state.eval_ctx_); + }); + } + /////////////////////////////////////////////////////////////////////////// phylanx::execution_tree::primitive code_for( phylanx::bindings::compiler_state& state, std::string const& file_name, @@ -184,7 +255,7 @@ namespace phylanx { namespace bindings using namespace phylanx::execution_tree; // if the requested name is defined in the environment, use it - primitive_argument_type* var = state.eval_ctx.get_var(func_name); + primitive_argument_type* var = state.eval_ctx_.get_var(func_name); if (var != nullptr) { if (is_primitive_operand(*var)) @@ -199,7 +270,7 @@ namespace phylanx { namespace bindings // alternatively, locate requested function entry point for (auto const& entry_point : - state.eval_snippets.program_.entry_points()) + state.eval_snippets_.program_.entry_points()) { if (func_name == entry_point.func_name_) { @@ -323,7 +394,7 @@ namespace phylanx { namespace bindings using namespace phylanx::execution_tree; // if the requested name is defined in the environment, use it - primitive_argument_type* var = state.eval_ctx.get_var(func_name); + primitive_argument_type* var = state.eval_ctx_.get_var(func_name); if (var != nullptr && kwargs.size() == 0) { execution_tree::compiler::function f(*var, func_name); @@ -335,8 +406,8 @@ namespace phylanx { namespace bindings // locate requested function entry point execution_tree::compiler::entry_point ep(func_name, file_name); auto it = - state.eval_snippets.program_.entry_points().find(ep); - if (it != state.eval_snippets.program_.entry_points().end()) + state.eval_snippets_.program_.entry_points().find(ep); + if (it != state.eval_snippets_.program_.entry_points().end()) { auto && funcs = it->functions(); if (funcs.empty()) @@ -359,6 +430,17 @@ namespace phylanx { namespace bindings }); } + // return locality-id of current locality + std::uint32_t find_here() + { + pybind11::gil_scoped_release release; // release GIL + return hpx::threads::run_as_hpx_thread( + []() + { + return hpx::get_locality_id(); + }); + } + /////////////////////////////////////////////////////////////////////////// // initialize measurements for tree evaluations std::vector enable_measurements(compiler_state& c, @@ -370,28 +452,30 @@ namespace phylanx { namespace bindings [&]() -> std::vector { // measurements have been enabled - c.enable_measurements = true; + c.enable_measurements_ = true; - c.primitive_instances = - phylanx::util::enable_measurements(c.primitive_instances); + c.primitive_instances_ = + phylanx::util::enable_measurements(c.primitive_instances_); if (reset_counters) { hpx::reset_active_counters(); } - return c.primitive_instances; + return c.primitive_instances_; }); } // retrieve performance data from all active performance counters std::string retrieve_counter_data(compiler_state& c) { - if (!c.enable_measurements) + if (!c.enable_measurements_) { return std::string{}; } + auto locality = c.get_locality(); + pybind11::gil_scoped_release release; // release GIL return hpx::threads::run_as_hpx_thread( @@ -406,8 +490,8 @@ namespace phylanx { namespace bindings os << "primitive_instance,display_name,count,time,eval_direct\n"; // Print performance data - for (auto const& entry : - phylanx::util::retrieve_counter_data(c.primitive_instances)) + for (auto const& entry : phylanx::util::retrieve_counter_data( + c.primitive_instances_, locality)) { os << "\"" << entry.first << "\",\"" << phylanx::execution_tree::compiler:: @@ -429,26 +513,28 @@ namespace phylanx { namespace bindings std::string retrieve_dot_tree_topology(compiler_state& state, std::string const& file_name, std::string const& xexpr_str) { + auto locality = state.get_locality(); + pybind11::gil_scoped_release release; // release GIL return hpx::threads::run_as_hpx_thread( [&]() -> std::string { - auto const& code = phylanx::execution_tree::compile( - file_name, xexpr_str, state.eval_snippets, state.eval_env); + auto const& code = phylanx::execution_tree::compile(file_name, + xexpr_str, state.eval_snippets_, state.eval_env_, locality); - if (state.enable_measurements) + if (state.enable_measurements_) { auto const& funcs = code.functions(); if (!funcs.empty()) { - state.primitive_instances.push_back( + state.primitive_instances_.push_back( phylanx::util::enable_measurements( funcs.front().name_)); } } - auto const& program = state.eval_snippets.program_; + auto const& program = state.eval_snippets_.program_; std::set resolve_children; for (auto const& ep : program.entry_points()) @@ -476,26 +562,28 @@ namespace phylanx { namespace bindings std::string retrieve_newick_tree_topology(compiler_state& state, std::string const& file_name, std::string const& xexpr_str) { + auto locality = state.get_locality(); + pybind11::gil_scoped_release release; // release GIL return hpx::threads::run_as_hpx_thread( [&]() -> std::string { - auto const& code = phylanx::execution_tree::compile( - file_name, xexpr_str, state.eval_snippets, state.eval_env); + auto const& code = phylanx::execution_tree::compile(file_name, + xexpr_str, state.eval_snippets_, state.eval_env_, locality); - if (state.enable_measurements) + if (state.enable_measurements_) { auto const& funcs = code.functions(); if (!funcs.empty()) { - state.primitive_instances.push_back( + state.primitive_instances_.push_back( phylanx::util::enable_measurements( funcs.front().name_)); } } - auto const& program = state.eval_snippets.program_; + auto const& program = state.eval_snippets_.program_; std::set resolve_children; for (auto const& ep : program.entry_points()) @@ -523,26 +611,28 @@ namespace phylanx { namespace bindings std::list retrieve_tree_topology(compiler_state& state, std::string const& file_name, std::string const& xexpr_str) { + auto locality = state.get_locality(); + pybind11::gil_scoped_release release; // release GIL return hpx::threads::run_as_hpx_thread( [&]() -> std::list { - auto const& code = phylanx::execution_tree::compile( - file_name, xexpr_str, state.eval_snippets, state.eval_env); + auto const& code = phylanx::execution_tree::compile(file_name, + xexpr_str, state.eval_snippets_, state.eval_env_, locality); - if (state.enable_measurements) + if (state.enable_measurements_) { auto const& funcs = code.functions(); if (!funcs.empty()) { - state.primitive_instances.push_back( + state.primitive_instances_.push_back( phylanx::util::enable_measurements( funcs.front().name_)); } } - auto const& program = state.eval_snippets.program_; + auto const& program = state.eval_snippets_.program_; std::set resolve_children; for (auto const& ep : program.entry_points()) diff --git a/python/src/bindings/binding_helpers.hpp b/python/src/bindings/binding_helpers.hpp index 8bfa87812..bad61eb96 100644 --- a/python/src/bindings/binding_helpers.hpp +++ b/python/src/bindings/binding_helpers.hpp @@ -36,18 +36,32 @@ namespace phylanx { namespace bindings struct compiler_state { // keep module alive until this has been free'd - pybind11::weakref m; + pybind11::weakref m_; - phylanx::execution_tree::compiler::environment eval_env; - phylanx::execution_tree::compiler::function_list eval_snippets; - phylanx::execution_tree::eval_context eval_ctx; + phylanx::execution_tree::compiler::environment eval_env_; + phylanx::execution_tree::compiler::function_list eval_snippets_; + phylanx::execution_tree::eval_context eval_ctx_; // name of the main source compiled file std::string codename_; // data related to measurement status - bool enable_measurements; - std::vector primitive_instances; + bool enable_measurements_; + std::vector primitive_instances_; + + // The locality this compiler instance refers to (None if local) + pybind11::object locality_; + + // retrieve target locality_id + hpx::id_type get_locality() const + { + if (locality_.is_none()) + { + return hpx::find_here(); + } + return hpx::naming::get_id_from_locality_id( + locality_.cast()); + } static pybind11::object import_phylanx() { @@ -71,12 +85,13 @@ namespace phylanx { namespace bindings }); } - compiler_state(std::string codename) - : m(import_phylanx()) - , eval_env(construct_default_environment()) - , eval_snippets() + compiler_state(std::string codename, pybind11::object locality) + : m_(import_phylanx()) + , eval_env_(construct_default_environment()) + , eval_snippets_() , codename_(std::move(codename)) - , enable_measurements(false) + , enable_measurements_(false) + , locality_(locality) { } }; @@ -210,6 +225,12 @@ namespace phylanx { namespace bindings std::string const& xexpr_str, pybind11::args args, pybind11::kwargs kwargs); + // asynchronously evaluate compiled expression + hpx::shared_future + async_expression_evaluator(compiler_state& state, + std::string const& file_name, std::string const& xexpr_str, + pybind11::args args, pybind11::kwargs kwargs); + // extract pre-compiled code for given function name phylanx::execution_tree::primitive code_for( phylanx::bindings::compiler_state& state, @@ -222,6 +243,9 @@ namespace phylanx { namespace bindings std::string const& file_name, std::string const& func_name, pybind11::args args, pybind11::kwargs kwargs); + // return locality-id of current locality + std::uint32_t find_here(); + /////////////////////////////////////////////////////////////////////////// // initialize measurements for tree evaluations std::vector enable_measurements( diff --git a/python/src/bindings/execution_tree.cpp b/python/src/bindings/execution_tree.cpp index eb7775a6e..e258c9418 100644 --- a/python/src/bindings/execution_tree.cpp +++ b/python/src/bindings/execution_tree.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -31,7 +32,9 @@ void phylanx::bindings::bind_execution_tree(pybind11::module m) // Compiler State pybind11::class_( execution_tree, "compiler_state") - .def(pybind11::init()); + .def(pybind11::init(), + pybind11::arg("filename"), + pybind11::arg("locality") = pybind11::none()); /////////////////////////////////////////////////////////////////////////// execution_tree.def("compile", phylanx::bindings::expression_compiler, @@ -43,6 +46,13 @@ void phylanx::bindings::bind_execution_tree(pybind11::module m) execution_tree.def("eval", phylanx::bindings::expression_evaluator, "compile and evaluate a numerical expression in PhySL"); + execution_tree.def("async_eval", + phylanx::bindings::async_expression_evaluator, + "compile and asynchronously evaluate a numerical expression in PhySL"); + + execution_tree.def("find_here", phylanx::bindings::find_here, + "return locality-id of current locality"); + execution_tree.def( "eval", [](phylanx::bindings::compiler_state& state, std::string const& xexpr, @@ -284,5 +294,42 @@ void phylanx::bindings::bind_execution_tree(pybind11::module m) return f.get(); }); }, + "wait for future to become ready") + .def( + "__call__", + [](hpx::shared_future< + phylanx::execution_tree::primitive_argument_type> const& f) + { + pybind11::gil_scoped_release release; // release GIL + return hpx::threads::run_as_hpx_thread( + [&]() -> phylanx::execution_tree::primitive_argument_type + { + return f.get(); + }); + }, "wait for future to become ready"); + + // translate HPX exceptions and provide more error information, if desired + static pybind11::exception exc( + m, "HPXError", PyExc_RuntimeError); + + pybind11::register_exception_translator([](std::exception_ptr p) { + try + { + if (p) + std::rethrow_exception(p); + } + catch (hpx::exception& e) + { + if (hpx::get_config_entry("phylanx.full_error_diagnostics", "0") == + "1") + { + exc(hpx::diagnostic_information(e).c_str()); + } + else + { + exc(e.what()); + } + } + }); } diff --git a/python/src/bindings/variable.cpp b/python/src/bindings/variable.cpp index 34f9c664a..c99515b0f 100644 --- a/python/src/bindings/variable.cpp +++ b/python/src/bindings/variable.cpp @@ -589,16 +589,16 @@ namespace phylanx { namespace execution_tree "define(_get_variable_size, x, shape(x, 0))"); auto& state = var.state(); - auto* p = state.eval_ctx.get_var(funcname.key()); + auto* p = state.eval_ctx_.get_var(funcname.key()); if (p == nullptr) { phylanx::execution_tree::compile(state.codename_, - funcname.key(), expr, state.eval_snippets, - state.eval_env) - .run(state.eval_ctx); + funcname.key(), expr, state.eval_snippets_, + state.eval_env_) + .run(state.eval_ctx_); // now, the variable should have been defined - p = state.eval_ctx.get_var(funcname.key()); + p = state.eval_ctx_.get_var(funcname.key()); HPX_ASSERT(p != nullptr); } @@ -608,7 +608,7 @@ namespace phylanx { namespace execution_tree args.emplace_back(var.value()); auto result = phylanx::execution_tree::bind_arguments( - state.codename_, funcname.key(), state.eval_snippets, *p, + state.codename_, funcname.key(), state.eval_snippets_, *p, std::move(args)); pybind11::gil_scoped_acquire acquire; // acquire GIL @@ -647,16 +647,16 @@ namespace phylanx { namespace execution_tree "define(_get_variable_item, x, i, slice(x, i))"); auto& state = var.state(); - auto* p = state.eval_ctx.get_var(funcname.key()); + auto* p = state.eval_ctx_.get_var(funcname.key()); if (p == nullptr) { phylanx::execution_tree::compile(state.codename_, - funcname.key(), expr, state.eval_snippets, - state.eval_env) - .run(state.eval_ctx); + funcname.key(), expr, state.eval_snippets_, + state.eval_env_) + .run(state.eval_ctx_); // now, the variable should have been defined - p = state.eval_ctx.get_var(funcname.key()); + p = state.eval_ctx_.get_var(funcname.key()); HPX_ASSERT(p != nullptr); } @@ -667,7 +667,7 @@ namespace phylanx { namespace execution_tree args.emplace_back(std::int64_t(i)); auto result = phylanx::execution_tree::bind_arguments( - state.codename_, funcname.key(), state.eval_snippets, *p, + state.codename_, funcname.key(), state.eval_snippets_, *p, std::move(args)); pybind11::gil_scoped_acquire acquire; // acquire GIL @@ -709,16 +709,16 @@ namespace phylanx { namespace execution_tree "store(slice(x, i), val))"); auto& state = var.state(); - auto* p = state.eval_ctx.get_var(funcname.key()); + auto* p = state.eval_ctx_.get_var(funcname.key()); if (p == nullptr) { phylanx::execution_tree::compile(state.codename_, - funcname.key(), expr, state.eval_snippets, - state.eval_env) - .run(state.eval_ctx); + funcname.key(), expr, state.eval_snippets_, + state.eval_env_) + .run(state.eval_ctx_); // now, the variable should have been defined - p = state.eval_ctx.get_var(funcname.key()); + p = state.eval_ctx_.get_var(funcname.key()); HPX_ASSERT(p != nullptr); } @@ -730,7 +730,7 @@ namespace phylanx { namespace execution_tree args.emplace_back(std::move(value)); auto result = phylanx::execution_tree::bind_arguments( - state.codename_, funcname.key(), state.eval_snippets, *p, + state.codename_, funcname.key(), state.eval_snippets_, *p, std::move(args)); pybind11::gil_scoped_acquire acquire; // acquire GIL diff --git a/src/plugins/controls/async_operation.cpp b/src/plugins/controls/async_operation.cpp index 5297e61a1..82c8b220e 100644 --- a/src/plugins/controls/async_operation.cpp +++ b/src/plugins/controls/async_operation.cpp @@ -33,7 +33,7 @@ namespace phylanx { namespace execution_tree { namespace primitives { expr : an arbitrary expression that will be evaluated asynchronously - Returns:\n" + Returns: Returns a future representing the result of the evaluation of the given expression)") diff --git a/src/plugins/controls/controls.cpp b/src/plugins/controls/controls.cpp index 8b49cc116..8ffa602a2 100644 --- a/src/plugins/controls/controls.cpp +++ b/src/plugins/controls/controls.cpp @@ -35,6 +35,8 @@ PHYLANX_REGISTER_PLUGIN_FACTORY(parallel_map_operation_plugin, phylanx::execution_tree::primitives::parallel_map_operation::match_data); PHYLANX_REGISTER_PLUGIN_FACTORY(range_operation_plugin, phylanx::execution_tree::primitives::range_operation::match_data); +PHYLANX_REGISTER_PLUGIN_FACTORY(sleep_operation_plugin, + phylanx::execution_tree::primitives::sleep_operation::match_data); PHYLANX_REGISTER_PLUGIN_FACTORY(while_operation_plugin, phylanx::execution_tree::primitives::while_operation::match_data); diff --git a/src/plugins/controls/sleep_operation.cpp b/src/plugins/controls/sleep_operation.cpp new file mode 100644 index 000000000..1b0d9fc5c --- /dev/null +++ b/src/plugins/controls/sleep_operation.cpp @@ -0,0 +1,67 @@ +// Copyright (c) 2020 Hartmut Kaiser +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +namespace phylanx { namespace execution_tree { namespace primitives { + + /////////////////////////////////////////////////////////////////////////// + match_pattern_type const sleep_operation::match_data = + { + hpx::util::make_tuple("sleep", + std::vector{"sleep(_1)"}, + &create_sleep_operation, &create_primitive, R"( + time + + Args: + + time : an arbitrary expression that will be interpreted as an + integer denoting the time to sleep (in milliseconds) + + Returns: + + None)") + }; + + /////////////////////////////////////////////////////////////////////////// + sleep_operation::sleep_operation(primitive_arguments_type&& operands, + std::string const& name, std::string const& codename) + : primitive_component_base(std::move(operands), name, codename) + { + } + + hpx::future sleep_operation::eval( + primitive_arguments_type const& operands, + primitive_arguments_type const& args, eval_context ctx) const + { + if (operands.size() != 1) + { + HPX_THROW_EXCEPTION(hpx::bad_parameter, "sleep_operation::eval", + generate_error_message( + "the sleep_operation primitive requires exactly " + "one operand")); + } + + hpx::this_thread::sleep_for( + std::chrono::milliseconds(extract_scalar_integer_value_strict( + operands[0], name_, codename_))); + + return hpx::make_ready_future(primitive_argument_type{}); + } +}}} // namespace phylanx::execution_tree::primitives diff --git a/src/plugins/matrixops/gauss_inverse.cpp b/src/plugins/matrixops/gauss_inverse.cpp index 5db2fad8a..d343c9bfd 100644 --- a/src/plugins/matrixops/gauss_inverse.cpp +++ b/src/plugins/matrixops/gauss_inverse.cpp @@ -93,7 +93,7 @@ namespace phylanx { namespace execution_tree { namespace primitives { // Do gaussian elimination to get upper triangular // matrix with 1's across diagonal - for (std::size_t current_row = 0; current_row < n; current_row++) + for (std::size_t current_row = 0; current_row != n; current_row++) { // Swaps current row with nearest subsequent row such that // after swapping A[i][i] != 0. @@ -131,7 +131,7 @@ namespace phylanx { namespace execution_tree { namespace primitives { else // the inversion has not already failed { double scale = myMatrix(current_row, current_row); - for (std::size_t col = 0; col < n; col++) + for (std::size_t col = 0; col != n; col++) { myMatrix(current_row, col) = myMatrix(current_row, col) / scale; @@ -144,7 +144,7 @@ namespace phylanx { namespace execution_tree { namespace primitives { nextRow++) { double factor = myMatrix(nextRow, current_row); - for (std::size_t nextCol = 0; nextCol < n; nextCol++) + for (std::size_t nextCol = 0; nextCol != n; nextCol++) { myMatrix(nextRow, nextCol) = myMatrix(nextRow, nextCol) - diff --git a/tests/unit/python/CMakeLists.txt b/tests/unit/python/CMakeLists.txt index bdf4de573..06d007d07 100644 --- a/tests/unit/python/CMakeLists.txt +++ b/tests/unit/python/CMakeLists.txt @@ -27,6 +27,7 @@ endforeach() set(subdirs ast + distributed execution_tree plugins util diff --git a/tests/unit/python/distributed/CMakeLists.txt b/tests/unit/python/distributed/CMakeLists.txt new file mode 100644 index 000000000..666db458e --- /dev/null +++ b/tests/unit/python/distributed/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (c) 2020 Hartmut Kaiser +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +set(tests + simple_remote_eval + ) + +foreach(test ${tests}) + set(script ${test}.py) + + add_phylanx_python_unit_test("distributed" ${test} + SCRIPT ${script} + FOLDER "Tests/Python/Unit/Distributed" + DEPENDS phylanx_py python_setup + WORKING_DIRECTORY ${PHYLANX_PYTHON_EXTENSION_LOCATION} + ENVIRONMENT "PYTHONPATH=${PHYLANX_PYTHON_EXTENSION_LOCATION}:$ENV{PYTHONPATH}") + + add_phylanx_pseudo_target(tests.unit.python_distributed.${test}_py) + add_phylanx_pseudo_dependencies(tests.unit.python_distributed + tests.unit.python_distributed.${test}_py) + add_phylanx_pseudo_dependencies(tests.unit.python_distributed.${test}_py + ${test}_test_py) + +endforeach() + diff --git a/tests/unit/python/distributed/simple_remote_eval.py b/tests/unit/python/distributed/simple_remote_eval.py new file mode 100644 index 000000000..8506607d6 --- /dev/null +++ b/tests/unit/python/distributed/simple_remote_eval.py @@ -0,0 +1,42 @@ +# Copyright (c) 2020 Hartmut Kaiser +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +from phylanx import Phylanx, PhylanxSession +import time + +############################################################################### +# initialize Phylanx +PhylanxSession.init(1) + + +# silence flake +def sleep(x): + pass + + +@Phylanx +def call_sleep(): + sleep(1000) # sleep for 1 second + return + + +def call_sleep_async(): + # note: launch() requires for locality to be a named argument + # if locality=... is not specified it will execute the operation + # locally + return call_sleep.launch(locality=0) # run on locality 0 + + +start = time.time() +f1 = call_sleep_async() # asynchronously start call_sleep +f2 = call_sleep_async() # asynchronously start call_sleep again + +f1() # wait for f1 +f2() # wait for f2 +end = time.time() + +# the overall execution time should be smaller than 2 seconds +print(end - start, "seconds") +assert((end - start) < 2.0)