diff --git a/hysop/backend/host/fortran/operator/poisson.py b/hysop/backend/host/fortran/operator/poisson.py new file mode 100644 index 0000000000000000000000000000000000000000..2c7a767efbf758bcca00492c77e0b01bb6ca6e8b --- /dev/null +++ b/hysop/backend/host/fortran/operator/poisson.py @@ -0,0 +1,100 @@ +from hysop.tools.types import check_instance, InstanceOf +from hysop.tools.decorators import debug +from hysop.tools.numpywrappers import npw +from hysop.fields.continuous_field import Field +from hysop.topology.cartesian_descriptor import CartesianTopologyDescriptors +from hysop.constants import FieldProjection +from hysop.backend.host.fortran.operator.fortran_fftw import fftw2py, FortranFFTWOperator +from hysop.core.graph.graph import op_apply +import numpy as np + + +class PoissonFFTW(FortranFFTWOperator): + + def __init__(self, Fin, Fout, variables, + extra_input_kwds=None, **kwds): + """Operator to solve Poisson equation using FFTW in Fortran. + + Solves: + Laplacian(Fout) = Fin + + Parameters + ---------- + Fout: Field + Input continuous field (rhs). + Fin: Field + Output continuous field (lhs), possibly inplace. + variables: dict + Dictionary of fields as keys and topology descriptors as values. + kwds: dict, optional + Base class arguments. + + """ + + check_instance(Fin, Field) + check_instance(Fout, Field) + check_instance(variables, dict, keys=Field, + values=CartesianTopologyDescriptors) + assert Fin.nb_components == Fout.nb_components + assert Fin.nb_components == 1, \ + "Poisson operator is implemented only for scalar fields" + + assert Fin.domain is Fout.domain, \ + 'only one domain is supported' + assert variables[Fin] is variables[Fout], \ + 'only one topology is supported' + + input_fields = {Fin: variables[Fin]} + output_fields = {Fout: variables[Fout]} + + super(PoissonFFTW, self).__init__(input_fields=input_fields, + output_fields=output_fields, + **kwds) + self.Fin = Fin + self.Fout = Fout + + def initialize(self, **kwds): + super(PoissonFFTW, self).initialize(**kwds) + dim = self.dim + if (dim == 2): + self._solve = fftw2py.solve_laplace_2d + elif (dim == 3): + self._solve = fftw2py.solve_laplace_3d + + @debug + def discretize(self): + if self.discretized: + return + super(PoissonFFTW, self).discretize() + self.dFin = self.Fin.discrete_fields[self.topology] + self.dFout = self.Fout.discrete_fields[self.topology] + assert (self.dFin.ghosts == self.dFout.ghosts).all(), \ + "Input and output fields must have the same ghosts." + + @op_apply + def apply(self, **kargs): + super(PoissonFFTW, self).apply(**kargs) + gh = self.input_fields[self.Fin].ghosts + + changeLayout, di, do = self._initialize_mem_layout(self.dFin, self.dFout) + # Vectors are given in ZYX layout to Fortran + do = self._solve(di[0], gh) + if changeLayout: + self.dFout.data[0][...] = -np.ascontiguousarray(do) + self.dFout.exchange_ghosts() + + def _initialize_mem_layout(self, fi, fo): + """Convert C-contiguous numpy arrays to Fortran-ordered data if needed.""" + changeLayout = False + if all([__.flags['C_CONTIGUOUS'] + for _ in (fi, fo) for __ in _.data ]): + changeLayout = True + print "CHange layout" + new_i = tuple(np.asfortranarray(_) for _ in fi.buffers) + new_o = tuple(np.asfortranarray(_) for _ in fo.buffers) + elif all([__.flags['F_CONTIGUOUS'] + for _ in (fi, fo) for __ in _.data ]): + new_i, new_o = fi, fo + else: + raise RuntimeError("Memory layout comatibility issue") + return changeLayout, new_i, new_o diff --git a/hysop/backend/host/fortran/operator/poisson_rotational.py b/hysop/backend/host/fortran/operator/poisson_rotational.py index 5620efbb0fade2da9b1ee2831a14fff9c44fe081..a718a487b5482ca9f8a611019dcad833b7cd7e9a 100644 --- a/hysop/backend/host/fortran/operator/poisson_rotational.py +++ b/hysop/backend/host/fortran/operator/poisson_rotational.py @@ -1,4 +1,3 @@ - from hysop.tools.types import check_instance, InstanceOf from hysop.tools.decorators import debug from hysop.tools.numpywrappers import npw @@ -7,13 +6,14 @@ from hysop.topology.cartesian_descriptor import CartesianTopologyDescriptors from hysop.constants import FieldProjection from hysop.backend.host.fortran.operator.fortran_fftw import fftw2py, FortranFFTWOperator from hysop.core.graph.graph import op_apply +import numpy as np class PoissonRotationalFFTW(FortranFFTWOperator): - - def __init__(self, velocity, vorticity, variables, - projection=FieldProjection.NONE, **kwds): + + def __init__(self, velocity, vorticity, variables, + projection=FieldProjection.NONE, **kwds): """PoissonRotational operator to solve incompressible flows using FFTW in Fortran. - + Parameters ---------- velocity : :class:`~hysop.fields.continuous_field.Field @@ -24,13 +24,13 @@ class PoissonRotationalFFTW(FortranFFTWOperator): dictionary of fields as keys and topologies as values. projection: hysop.constants.FieldProjection or positive integer, optional Project vorticity such that resolved velocity is divergence free (for 3D fields). - When active, projection is done prior to every solve, unless projection is + When active, projection is done prior to every solve, unless projection is an integer in which case it is done every n applies. This parameter is ignored for 2D fields and defaults to no projection. - kwds : + kwds : base class parameters. """ - + check_instance(velocity, Field) check_instance(vorticity, Field) check_instance(variables, dict, keys=Field, values=CartesianTopologyDescriptors) @@ -48,14 +48,14 @@ class PoissonRotationalFFTW(FortranFFTWOperator): else: assert (velocity.dim==3) and (vorticity.dim==3), 'Projection only available in 3D.' output_fields[vorticity] = wtopology - + super(PoissonRotationalFFTW,self).__init__(input_fields=input_fields, output_fields=output_fields, **kwds) - + self.velocity = velocity self.vorticity = vorticity self.projection = projection - + def initialize(self, **kwds): super(PoissonRotationalFFTW,self).initialize(**kwds) dim = self.dim @@ -63,22 +63,22 @@ class PoissonRotationalFFTW(FortranFFTWOperator): self._solve = self._solve_2d elif (dim==3): self._solve = self._solve_3d - + if (dim!=3) and (self.projection!=FieldProjection.NONE): raise ValueError('Velocity reprojection only available in 3D.') - + projection = self.projection if (projection == FieldProjection.NONE): self._do_project = lambda simu: False elif (projection == FieldProjection.EVERY_STEP): self._do_project = lambda simu: True - else: # projection is an integer representing frenquency + else: # projection is an integer representing frenquency freq = projection assert freq>=1 self._do_project = lambda simu: ((simu.current_iteration % freq)==0) self.projection = projection - + @debug def discretize(self): @@ -92,24 +92,51 @@ class PoissonRotationalFFTW(FortranFFTWOperator): def apply(self, simulation=None, **kargs): super(PoissonRotationalFFTW, self).apply(simulation=simulation, **kargs) if self._do_project(simulation): - self._project(simulation) - self._solve(simulation) - - def _solve_2d(self, simu=None): + self._project() + self._solve() + + def _initialize_mem_layout(self): + """Convert C-contiguous numpy arrays to Fortran-ordered data if needed.""" + changeLayout = False + if all([__.flags['C_CONTIGUOUS'] + for _ in (self.dvorticity,self.dvelocity) for __ in _.data ]): + changeLayout = True + print "CHange layout" + dw = tuple(np.asfortranarray(_) for _ in self.dvorticity.buffers) + dv = tuple(np.asfortranarray(_) for _ in self.dvelocity.buffers) + elif all([__.flags['F_CONTIGUOUS'] + for _ in (self.dvorticity,self.dvelocity) for __ in _.data ]): + dw, dv = self.dvorticity, self.dvelocity + else: + raise RuntimeError("Memory layout comatibility issue") + return changeLayout, dv, dw + + def _finalize_mem_layout(self, dv): + """Get back with C-contiguous data. + + We enforce a sign change for data because of the change + of direction ordering ZYX.""" + for dvi, dvi_f in zip(self.dvelocity.buffers, dv[::-1]): + dvi[...] = -np.ascontiguousarray(dvi_f) + + def _solve_2d(self): """ Solve 2D poisson problem, no projection, no correction. """ assert self.dim==2 ghosts_w = self.input_fields[self.vorticity].ghosts ghosts_v = self.output_fields[self.velocity].ghosts - self.dvelocity_data =\ - fftw2py.solve_poisson_2d(self.dvorticity.data[0], - self.dvelocity.data[0], - self.dvelocity.data[1], - ghosts_w, ghosts_v) + + changeLayout, dv, dw = self._initialize_mem_layout() + # Vectors are given in ZYX layout to Fortran + dv = fftw2py.solve_poisson_2d(dw[0], + dv[1], dv[0], + ghosts_w, ghosts_v) + if changeLayout: + self._finalize_mem_layout(dv) self.dvelocity.exchange_ghosts() - - def _solve_3d(self,simu=None): + + def _solve_3d(self): """ Solve 3D poisson problem, no projection, no correction """ @@ -117,26 +144,27 @@ class PoissonRotationalFFTW(FortranFFTWOperator): assert self.dim==3 ghosts_w = self.input_fields[self.vorticity].ghosts ghosts_v = self.output_fields[self.velocity].ghosts - self.dvelocity.data =\ - fftw2py.solve_poisson_3d(self.dvorticity.data[0], - self.dvorticity.data[1], - self.dvorticity.data[2], - self.dvelocity.data[0], - self.dvelocity.data[1], - self.dvelocity.data[2], - ghosts_w, ghosts_v) + + changeLayout, dv, dw = self._initialize_mem_layout() + # Vectors are given in ZYX layout to Fortran + dv = fftw2py.solve_poisson_3d(dw[2], dw[1], dw[0], + dv[2], dv[1], dv[0], + ghosts_w, ghosts_v) + if changeLayout: + self._finalize_mem_layout(dv) self.dvelocity.exchange_ghosts() - def _project(self, simulation): + def _project(self): """ Apply projection on vorticity such that the resolved velocity will have div(U)=0. """ assert self.dim==3 ghosts_w = self.output_fields[self.vorticity].ghosts + changeLayout, dv, dw = self._initialize_mem_layout() + # Vectors are given in ZYX layout to Fortran self.dvorticity.data =\ - fftw2py.projection_om_3d(self.dvorticity.data[0], - self.dvorticity.data[1], - self.dvorticity.data[2], ghosts_w) + fftw2py.projection_om_3d(dw[2], dw[1], dw[0], ghosts_w) + if changeLayout: + self._finalize_mem_layout(dv) self.dvorticity.exchange_ghosts() - diff --git a/hysop/core/graph/computational_graph.py b/hysop/core/graph/computational_graph.py index 8c9d2ab0d6c5c65e6920040b156e1c4b9ca2c7ec..417207d514135fe2ad98d62f57bb575a289dda76 100644 --- a/hysop/core/graph/computational_graph.py +++ b/hysop/core/graph/computational_graph.py @@ -19,29 +19,29 @@ class ComputationalGraph(ComputationalGraphNode): """ Interface of an abstract graph of continuous operators (ie. a computational graph). """ - + __metaclass__ = ABCMeta __FORCE_REPORTS__ = False - + @debug def __init__(self, **kwds): """ Parameters ---------- kwds: arguments for base classes. - + Notes ----- - The following base class variables cannot be specified during graph construction: + The following base class variables cannot be specified during graph construction: variables, input_variables, output_variables - Order of operation is: add_node, initialize, discretize, + Order of operation is: add_node, initialize, discretize, get_work_properties, setup, apply, finalize. Nodes can also be added during pre initialization step. - Graph building is done at the end of the initialization step, after all internal + Graph building is done at the end of the initialization step, after all internal nodes have been initialized. """ - + if ('input_fields' in kwds.keys()) or ('output_fields' in kwds.keys()): msg='input_fields or output_fields parameters should not be used in {}, they are \ deduced during graph construction (building step).'.format(cls) @@ -50,27 +50,27 @@ class ComputationalGraph(ComputationalGraphNode): msg='input_params or output_params parameters should not be used in {}, they are \ deduced during graph construction (building step).'.format(cls) raise ValueError(msg) - + super(ComputationalGraph,self).__init__(input_fields=None, output_fields=None, **kwds) - + self.nodes = [] self.graph = None self.graph_built = False self.graph_is_rendering = False - + @graph_built def _get_profiling_info(self): """Collect profiling informations of profiled attributes.""" for node in self.nodes: self._profiler += node._profiler - + def field_requirements_report(self, requirements): inputs, outputs = {}, {} sinputs, soutputs = {}, {} def sorted_reqs(reqs): return sorted(reqs, key=lambda x: \ - u'{}::{}'.format(x.operator.pretty_name.decode('utf-8'), + u'{}::{}'.format(x.operator.pretty_name.decode('utf-8'), x.field.pretty_name.decode('utf-8'))) for field, mreqs in requirements.input_field_requirements.iteritems(): for td, reqs in mreqs.requirements.iteritems(): @@ -87,7 +87,7 @@ class ComputationalGraph(ComputationalGraphNode): ghosts = u'{}<=ghosts<{}'.format(min_ghosts, max_ghosts) can_split=req.can_split.view(npw.int8) basis=u'{}'.format(u','.join(str(basis)[:3] for basis in req.basis)) \ - if req.basis else 'ANY' + if req.basis else 'ANY' tstates=u'{}'.format(u','.join(str(ts) for ts in req.tstates)) \ if req.tstates else 'ANY' sin.append( (opname, fname, ghosts, basis, tstates) ) @@ -106,11 +106,11 @@ class ComputationalGraph(ComputationalGraphNode): ghosts = u'{}<=ghosts<{}'.format(min_ghosts, max_ghosts) can_split=req.can_split.view(npw.int8) basis=u'{}'.format(u','.join(str(basis)[:3] for basis in req.basis)) \ - if req.basis else u'ANY' + if req.basis else u'ANY' tstates=u'{}'.format(u','.join(str(ts) for ts in req.tstates)) \ if req.tstates else u'ANY' sin.append( (opname, fname, ghosts, basis, tstates) ) - + titles = [[(u'OPERATOR', u'FIELD', u'GHOSTS', u'BASIS', u'TSTATES')]] name_size = max(len(s[0]) for ss in sinputs.values()+soutputs.values()+titles for s in ss) field_size = max(len(s[1]) for ss in sinputs.values()+soutputs.values()+titles for s in ss) @@ -119,7 +119,7 @@ class ComputationalGraph(ComputationalGraphNode): tstates_size = max(len(s[4]) for ss in sinputs.values()+soutputs.values()+titles for s in ss) template = u'\n {:<{name_size}} {:^{field_size}} {:^{ghosts_size}} {:^{basis_size}} {:^{tstates_size}}' - + ss= u'>INPUTS:' if sinputs: for (td, sreqs) in sinputs.iteritems(): @@ -128,12 +128,12 @@ class ComputationalGraph(ComputationalGraphNode): else: ss+=u'\n {}'.format(td) ss+= template.format(*titles[0][0], - name_size=name_size, field_size=field_size, ghosts_size=ghosts_size, + name_size=name_size, field_size=field_size, ghosts_size=ghosts_size, basis_size=basis_size, tstates_size=tstates_size) for (opname, fname, ghosts, basis, tstates) in sreqs: ss+=template.format( opname, fname, ghosts, basis, tstates, - name_size=name_size, field_size=field_size, ghosts_size=ghosts_size, + name_size=name_size, field_size=field_size, ghosts_size=ghosts_size, basis_size=basis_size, tstates_size=tstates_size) else: ss+=u' None' @@ -145,12 +145,12 @@ class ComputationalGraph(ComputationalGraphNode): else: ss+=u'\n {}'.format(td) ss+= template.format(*titles[0][0], - name_size=name_size, field_size=field_size, ghosts_size=ghosts_size, + name_size=name_size, field_size=field_size, ghosts_size=ghosts_size, basis_size=basis_size, tstates_size=tstates_size) for (opname, fname, ghosts, basis, tstates) in sreqs: ss+=template.format( opname, fname, ghosts, basis, tstates, - name_size=name_size, field_size=field_size, ghosts_size=ghosts_size, + name_size=name_size, field_size=field_size, ghosts_size=ghosts_size, basis_size=basis_size, tstates_size=tstates_size) else: ss+=u' None' @@ -173,10 +173,10 @@ class ComputationalGraph(ComputationalGraphNode): outfields = u'[{}]'.format(foutputs) if foutputs else u'' inparams = u'[{}]'.format(pinputs) if pinputs else u'' outparams = u'[{}]'.format(poutputs) if poutputs else u'' - + inputs = u'{}{}{}'.format(infields, u'x' if infields and inparams else u'', inparams) outputs = u'{}{}{}'.format(outfields, u'x' if outfields and outparams else u'', outparams) - + if inputs == u'': inputs=u'no inputs' if outputs == u'': @@ -198,7 +198,7 @@ class ComputationalGraph(ComputationalGraphNode): if outputs == '': outputs=u'no outputs' ops.setdefault(None, []).append( (op.pretty_name.decode('utf-8'), inputs, outputs, type(op).__name__) ) - + name_size = max(strlen(s[0]) for ss in ops.values() for s in ss) in_size = max(strlen(s[1]) for ss in ops.values() for s in ss) out_size = max(strlen(s[2]) for ss in ops.values() for s in ss) @@ -211,7 +211,7 @@ class ComputationalGraph(ComputationalGraphNode): ss += u'\n>{}'.format(domain.short_description()) ss += u'\n {:<{name_size}} {:<{in_size}} {:<{out_size}} {:<{type_size}}'.format( 'OPERATOR', 'INPUTS', 'OUTPUTS', 'OPERATOR TYPE', - name_size=name_size, in_size=in_size, + name_size=name_size, in_size=in_size, out_size=out_size, type_size=type_size) for (opname, inputs, outputs, optype) in dops: ss += u'\n {:<{name_size}} {:<{in_size}} -> {:<{out_size}} {:<{type_size}}'.format( @@ -222,17 +222,17 @@ class ComputationalGraph(ComputationalGraphNode): ss += u'\n>Domainless operators:' ss += u'\n {:<{name_size}} {:<{in_size}} {:<{out_size}} {:<{type_size}}'.format( 'OPERATOR', 'INPUTS', 'OUTPUTS', 'OPERATOR TYPE', - name_size=name_size, in_size=in_size, + name_size=name_size, in_size=in_size, out_size=out_size, type_size=type_size) for (opname, inputs, outputs, optype) in ops[None]: ss += u'\n {:<{name_size}} {:<{in_size}} -> {:<{out_size}} {:<{type_size}}'.format( opname, inputs, outputs, optype, name_size=name_size, in_size=in_size, out_size=out_size, type_size=type_size) - + title=u' ComputationalGraph {} domain and operator report '.format(self.pretty_name.decode('utf-8')) return u'\n{}\n'.format(framed_str(title=title, msg=ss[1:])).encode('utf-8') - + def topology_report(self): ss='' @@ -247,7 +247,7 @@ class ComputationalGraph(ComputationalGraphNode): reduced_graph = self.reduced_graph operators = reduced_graph.vertex_properties['operators'] fields = self.fields - + topologies = {} for field in self.fields: field_topologies = {} @@ -261,7 +261,7 @@ class ComputationalGraph(ComputationalGraphNode): topo = op.output_fields[field] field_topologies.setdefault(topo, []).append(op) for topo in field_topologies.keys(): - pnames = set(op.pretty_name.decode('utf-8') for op in field_topologies[topo]) + pnames = set(op.pretty_name.decode('utf-8') for op in field_topologies[topo]) sops=u', '.join(sorted(pnames)) entries = (str(topo.backend.kind).lower(), topo.tag, sops) topologies.setdefault(field, []).append(entries) @@ -272,7 +272,7 @@ class ComputationalGraph(ComputationalGraphNode): template = u'\n {:<{backend_size}} {:<{topo_size}} {}' sizes = {'backend_size': backend_size, 'topo_size': topo_size} - + ss = u'' for field in sorted(self.fields, key=lambda x: x.tag): ss += u'\n>FIELD {}::{}'.format(field.name, field.pretty_name.decode('utf-8')) @@ -284,7 +284,7 @@ class ComputationalGraph(ComputationalGraphNode): title = u' ComputationalGraph {} fields report '.format(self.pretty_name.decode('utf-8')) ss = u'\n{}\n'.format(framed_str(title=title, msg=ss[1:])) return ss.encode('utf-8') - + def operator_report(self): reduced_graph = self.reduced_graph operators = reduced_graph.vertex_properties['operators'] @@ -299,16 +299,16 @@ class ComputationalGraph(ComputationalGraphNode): foutputs = u','.join( sorted([u'{}.{}'.format(f.pretty_name.decode('utf-8'), t.pretty_tag.decode('utf-8')) for (f,t) in op.output_fields.iteritems()])) - pinputs = u','.join( sorted([p.pretty_name.decode('utf-8') + pinputs = u','.join( sorted([p.pretty_name.decode('utf-8') for p in op.input_params.values()])) - poutputs = u','.join( sorted([p.pretty_name.decode('utf-8') + poutputs = u','.join( sorted([p.pretty_name.decode('utf-8') for p in op.output_params.values()])) infields = u'[{}]'.format(finputs) if finputs else u'' outfields = u'[{}]'.format(foutputs) if foutputs else u'' inparams = u'[{}]'.format(pinputs) if pinputs else u'' outparams = u'[{}]'.format(poutputs) if poutputs else u'' - + inputs = u'{}{}{}'.format(infields, u'x' if infields and inparams else u'', inparams) outputs = u'{}{}{}'.format(outfields, u'x' if outfields and outparams else u'', outparams) if inputs == '': @@ -317,7 +317,7 @@ class ComputationalGraph(ComputationalGraphNode): outputs=u'no outputs' ops.append( (op.pretty_name.decode('utf-8'), inputs, outputs, type(op).__name__) ) - + isize = len(self.sorted_nodes) isize = int(npw.ceil(npw.log10(isize))) name_size = max(len(s[0]) for s in ops) @@ -328,7 +328,7 @@ class ComputationalGraph(ComputationalGraphNode): ss = u' {:<{isize}} {:<{name_size}} {:<{in_size}} {:<{out_size}} {:<{type_size}}'.format( 'ID', 'OPERATOR', 'INPUTS', 'OUTPUTS', 'OPERATOR TYPE', isize=isize, - name_size=name_size, in_size=in_size, + name_size=name_size, in_size=in_size, out_size=out_size, type_size=type_size) for i, (opname, inputs, outputs, optype) in enumerate(ops): ss += u'\n {:>{isize}} {:<{name_size}} {:<{in_size}} -> {:<{out_size}} {:<{type_size}}'.format( @@ -336,7 +336,7 @@ class ComputationalGraph(ComputationalGraphNode): isize=isize, name_size=name_size, in_size=in_size, out_size=out_size, type_size=type_size) - + title = u' ComputationalGraph {} discrete operator report '.format(self.pretty_name.decode('utf-8')) return u'\n{}\n'.format(framed_str(title=title, msg=ss)).encode('utf-8') @@ -346,7 +346,7 @@ class ComputationalGraph(ComputationalGraphNode): for (domain, ops) in node.get_domains().iteritems(): domains.setdefault(domain, set()).update(ops) return domains - + @graph_built def get_topologies(self): topologies = {} @@ -358,7 +358,7 @@ class ComputationalGraph(ComputationalGraphNode): topologies[backend] = set() topologies[backend].update(topos) return topologies - + @debug @not_initialized def push_nodes(self, *args): @@ -374,7 +374,7 @@ class ComputationalGraph(ComputationalGraphNode): else: msg = 'Given node is not an instance of ComputationalGraphNode (got a {}).' raise ValueError(msg.format(node.__class__)) - + def available_methods(self): avail_methods = {} if not self.nodes: @@ -388,33 +388,33 @@ class ComputationalGraph(ComputationalGraphNode): else: avail_methods[k] = v return avail_methods - + def default_method(self): return {} @debug - def initialize(self, + def initialize(self, is_root=True, - topgraph_method=None, + topgraph_method=None, outputs_are_inputs=False, **kwds): if self.initialized: return self.is_root = is_root - + if is_root: self.pre_initialize(**kwds) msg=u'ComputationalGraph {} is empty.' assert len(self.nodes) > 0, msg.format(self.pretty_name.decode('utf-8')).encode('utf-8') - + for node in self.nodes: node.pre_initialize(**kwds) - + method = self._setup_method(topgraph_method) self.handle_method(method) - + for node in self.nodes: node.initialize(is_root=False, outputs_are_inputs=False, @@ -430,7 +430,7 @@ class ComputationalGraph(ComputationalGraphNode): if is_root: field_requirements = self.get_and_set_field_requirements() field_requirements.build_topologies() - + self._build_graph(outputs_are_inputs=outputs_are_inputs, current_level=0) # fix for auto generated nodes @@ -439,15 +439,15 @@ class ComputationalGraph(ComputationalGraphNode): node.get_and_set_field_requirements() assert (node._field_requirements is not None) - # from now on, all nodes contained in self.nodes are ComputationalGraphOperator + # from now on, all nodes contained in self.nodes are ComputationalGraphOperator # (ordered for sequential execution) self.handle_topologies(self.input_topology_states, self.output_topology_states) self.check() - + self.initialized=True if is_root: self.post_initialize(**kwds) - + @debug def check(self): super(ComputationalGraph, self).check() @@ -467,7 +467,7 @@ class ComputationalGraph(ComputationalGraphNode): if (self.is_root and __VERBOSE__ or __DEBUG__ or self.__FORCE_REPORTS__): print self.field_requirements_report(requirements) return requirements - + @debug def handle_topologies(self, input_topology_states, output_topology_states): # do not call super method @@ -489,17 +489,17 @@ class ComputationalGraph(ComputationalGraphNode): from hysop.core.graph.graph_builder import GraphBuilder builder = GraphBuilder(node=self) - builder.configure(current_level=current_level, + builder.configure(current_level=current_level, outputs_are_inputs=outputs_are_inputs, **kwds) - + builder.build_graph() - + # keep variables - self._init_base(input_fields=builder.input_fields, + self._init_base(input_fields=builder.input_fields, output_fields=builder.output_fields, input_params=builder.input_params, output_params=builder.output_params) - + self.graph = builder.graph self.reduced_graph = builder.reduced_graph self.sorted_nodes = builder.sorted_nodes @@ -507,12 +507,12 @@ class ComputationalGraph(ComputationalGraphNode): self.input_topology_states = builder.op_input_topology_states self.output_topology_states = builder.op_output_topology_states - + self.initial_input_topology_states = builder.input_topology_states self.final_output_topology_states = builder.output_topology_states - + self.level = current_level - + self.initialized = True self.graph_built = True @@ -541,13 +541,13 @@ class ComputationalGraph(ComputationalGraphNode): else: command_queues = None active_ops = None - + def draw(): import time from gi.repository import Gtk, GObject from graph_tool.draw import GraphWindow, sfdp_layout - - pos_layout = sfdp_layout(graph) + + pos_layout = sfdp_layout(graph) win = GraphWindow(graph, pos_layout, geometry=(800,600), vertex_text = vertex_text, @@ -565,19 +565,19 @@ class ComputationalGraph(ComputationalGraphNode): win.graph.queue_draw() time.sleep(0.01) return True - + GObject.idle_add(update_window) win.connect("delete_event", Gtk.main_quit) win.show_all() Gtk.main() self.graph_is_rendering = False - + self.graph_is_rendering = True from threading import Thread display_thread = Thread(target=draw) display_thread.start() - + @debug @graph_built def discretize(self): @@ -588,7 +588,7 @@ class ComputationalGraph(ComputationalGraphNode): op = operators[vertex] if not op.discretized: op.discretize() - + if self.is_root: input_discrete_fields = {} for (field,topo) in self.input_fields.iteritems(): @@ -596,7 +596,7 @@ class ComputationalGraph(ComputationalGraphNode): istate = istate.copy(is_read_only=False) # problem inputs are writeable for initialization dfield = field.discretize(topo, istate) input_discrete_fields[field] = dfield - + output_discrete_fields = {} for field,topo in self.output_fields.iteritems(): ostate = self.final_output_topology_states[field][1] @@ -608,15 +608,15 @@ class ComputationalGraph(ComputationalGraphNode): self.input_discrete_fields = input_discrete_fields self.output_discrete_fields = output_discrete_fields - + self.discretized = True - - + + @debug @discretized def get_work_properties(self): requests = MultipleOperatorMemoryRequests() - + reduced_graph = self.reduced_graph operators = reduced_graph.vertex_properties['operators'] for vid in self.sorted_nodes: @@ -630,8 +630,8 @@ class ComputationalGraph(ComputationalGraphNode): ss = (srequests if (srequests != u'') else u' *no extra work requested*') title= u' ComputationalGraph {} work properties report '.format(self.pretty_name.decode('utf-8')) vprint(u'\n{}\n'.format(framed_str(title=title, msg=ss)).encode('utf-8')) - return requests - + return requests + @debug @discretized def setup(self, work=None): @@ -649,9 +649,9 @@ class ComputationalGraph(ComputationalGraphNode): op.setup(work=work) self.ready = True - def build(self, outputs_are_inputs=True, method=None, allow_subbuffers=False): + def build(self, outputs_are_inputs=True, method=None, allow_subbuffers=False): """ - Shortcut for initialize(), discretize(), get_work_properties(), setup() + Shortcut for initialize(), discretize(), get_work_properties(), setup() for quick graph initialization. """ self.initialize(outputs_are_inputs=outputs_are_inputs, @@ -669,7 +669,7 @@ class ComputationalGraph(ComputationalGraphNode): drawing = self.graph_is_rendering reduced_graph = self.reduced_graph operators = reduced_graph.vertex_properties['operators'] - + if drawing: active_ops = reduced_graph.vertex_properties['active_ops'] old_color = None @@ -690,14 +690,14 @@ class ComputationalGraph(ComputationalGraphNode): @debug @ready - def finalize(self): + def finalize(self, **kwds): reduced_graph = self.reduced_graph operators = reduced_graph.vertex_properties['operators'] for vid in self.sorted_nodes: vertex = reduced_graph.vertex(vid) op = operators[vertex] if op.ready: - op.finalize() + op.finalize(**kwds) self.ready = False @classmethod diff --git a/hysop/numerics/fftw_f/fft2d.f90 b/hysop/numerics/fftw_f/fft2d.f90 index 4a0a708d6c6989cbab7142aafebec699694ea541..98f79f55ee20467eef16980b1ea6848bbda1a9ce 100755 --- a/hysop/numerics/fftw_f/fft2d.f90 +++ b/hysop/numerics/fftw_f/fft2d.f90 @@ -29,7 +29,7 @@ module fft2d public :: init_c2c_2d,init_r2c_2d, r2c_scalar_2d, c2c_2d,c2r_2d,c2r_scalar_2d,& r2c_2d,cleanFFTW_2d, filter_poisson_2d, filter_curl_2d, getParamatersTopologyFFTW2d, & - filter_diffusion_2d, init_r2c_2dBIS + filter_diffusion_2d, init_r2c_2dBIS, filter_laplace_2d !> plan for fftw "c2c" forward or r2c transform @@ -289,7 +289,7 @@ contains do i = 1, fft_resolution(c_X) ig = i + ghosts(c_X) rdatain1(i, j) = input_x(ig,jg) - rdatain2(i, j) = input_y(ig,jg) + rdatain2(i, j) = input_y(ig,jg) end do end do @@ -484,6 +484,20 @@ contains end subroutine filter_poisson_2d + + subroutine filter_laplace_2d() + + integer(C_INTPTR_T) :: i, j + complex(C_DOUBLE_COMPLEX) :: coeff + do i = 1,local_resolution(c_X) + do j = 1, fft_resolution(c_Y) + coeff = ONE/(kx(i)**2+ky(j)**2) + dataout1(j,i) = coeff*dataout1(j,i) + end do + end do + dataout1(1,1) = 0 + end subroutine filter_laplace_2d + subroutine filter_diffusion_2d(nudt) real(C_DOUBLE), intent(in) :: nudt diff --git a/hysop/numerics/fftw_f/fft3d.f90 b/hysop/numerics/fftw_f/fft3d.f90 index c839f1752a9a2d510bc00faca0738283be900c83..c87435d7f02e4a842cdd3da3290d14a0f587bc23 100755 --- a/hysop/numerics/fftw_f/fft3d.f90 +++ b/hysop/numerics/fftw_f/fft3d.f90 @@ -29,7 +29,8 @@ module fft3d getParamatersTopologyFFTW3d,filter_poisson_3d,filter_curl_diffusion_3d, & init_r2c_3d_many, r2c_3d_many, c2r_3d_many, filter_diffusion_3d_many,& filter_poisson_3d_many, filter_diffusion_3d, filter_curl_3d, filter_projection_om_3d,& - filter_multires_om_3d, filter_pressure_3d, r2c_3d_scal, filter_spectrum_3d + filter_multires_om_3d, filter_pressure_3d, r2c_3d_scal, filter_spectrum_3d, & + filter_laplace_3d !> plan for fftw "c2c" forward or r2c transform type(C_PTR) :: plan_forward1, plan_forward2, plan_forward3 @@ -315,7 +316,7 @@ contains main_comm,ior(FFTW_MEASURE,FFTW_MPI_TRANSPOSED_OUT)) plan_backward1 = fftw_mpi_plan_dft_c2r_3d(fft_resolution(c_Z),fft_resolution(c_Y), fft_resolution(c_X), dataout1, rdatain1, & main_comm,ior(FFTW_MEASURE,FFTW_MPI_TRANSPOSED_IN)) - + call computeKx(lengths(c_X)) call computeKy(lengths(c_Y)) call computeKz(lengths(c_Z)) @@ -326,7 +327,7 @@ contains is3DUpToDate = .true. end subroutine init_r2c_scalar_3d - + !> forward transform - The result is saved in local buffers !! @param[in] omega_x 3d scalar field, x-component of the input vector field !! @param[in] omega_y 3d scalar field, y-component of the input vector field @@ -749,6 +750,21 @@ contains end subroutine filter_diffusion_3d + + subroutine filter_laplace_3d() + integer(C_INTPTR_T) :: i, j, k + complex(C_DOUBLE_COMPLEX) :: coeff + do j = 1,local_resolution(c_Y) + do k = 1, fft_resolution(c_Z) + do i = 1, local_resolution(c_X) + coeff = one/((kx(i)**2+ky(j)**2+kz(k)**2)) + dataout1(i,k,j) = coeff*dataout1(i,k,j) + end do + end do + end do + dataout1(1,1,1) = 0 + end subroutine filter_laplace_3d + !> Solve curl problem in the Fourier space : !! \f{eqnarray*} \omega &=& \nabla \times v subroutine filter_curl_3d() diff --git a/hysop/numerics/fftw_f/fftw2py.f90 b/hysop/numerics/fftw_f/fftw2py.f90 index 09517a4ca3034a9800a76e99c94ff9656963e2a9..230d6455b160e938f0ccbf0988bda6a1060a4929 100755 --- a/hysop/numerics/fftw_f/fftw2py.f90 +++ b/hysop/numerics/fftw_f/fftw2py.f90 @@ -15,7 +15,7 @@ module fftw2py implicit none contains - + !> Initialisation of fftw context : create plans and memory buffers !! @param[in] resolution global resolution of the discrete domain !! @param[in] lengths width of each side of the domain @@ -89,18 +89,19 @@ contains ! Duplicate comm into client_data::main_comm (used later in fft2d and fft3d) call mpi_comm_dup(comm, main_comm, ierr) - + !print*, "Init fftw/poisson solver for a 3d problem" call init_r2c_scalar_3d(resolution,lengths) - + call getParamatersTopologyFFTW3d(datashape,offset) - + end subroutine init_fftw_solver_scalar !> Free memory allocated for fftw-related objects (plans and buffers) subroutine clean_fftw_solver(dim) integer, intent(in) :: dim + print *, "Clean FFTW fortran" if(dim == 2) then call cleanFFTW_2d() else @@ -116,7 +117,7 @@ contains real(wp),dimension(size(omega,1),size(omega,2)),intent(out) :: velocity_x,velocity_y integer(kind=ip), dimension(2), intent(in) :: ghosts_vort, ghosts_velo !f2py intent(in,out) :: velocity_x,velocity_y - + call r2c_scalar_2d(omega, ghosts_vort) call filter_poisson_2d() @@ -126,6 +127,34 @@ contains end subroutine solve_poisson_2d + !> Solve + !! \f[ \nabla (u) = f \f] + !! u being a 2D scalar field and f a 2D scalar field. + subroutine solve_laplace_2d(f, u, ghosts) + real(wp),dimension(:,:),intent(in):: f + real(wp),dimension(size(f,1),size(f,2)),intent(out) :: u + integer(kind=ip), dimension(2), intent(in) :: ghosts + + call r2c_scalar_2d(f, ghosts) + call filter_laplace_2d() + call c2r_scalar_2d(u, ghosts) + + end subroutine solve_laplace_2d + + !> Solve + !! \f[ \nabla (u) = f \f] + !! u being a 3D scalar field and f a 3D scalar field. + subroutine solve_laplace_3d(f, u, ghosts) + real(wp),dimension(:,:,:),intent(in):: f + real(wp),dimension(size(f,1),size(f,2),size(f,3)),intent(out) :: u + integer(kind=ip), dimension(3), intent(in) :: ghosts + + call r2c_scalar_3d(f, ghosts) + call filter_laplace_3d() + call c2r_scalar_3d(u, ghosts) + + end subroutine solve_laplace_3d + !> Solve !! \f{eqnarray*} \frac{\partial \omega}{\partial t} &=& \nu \Delta \omega \f} !! omega being a 2D scalar field. diff --git a/hysop/numerics/fftw_f/fftw2py.pyf b/hysop/numerics/fftw_f/fftw2py.pyf index 74ac41ee1c12458b18ea26880d940ab6f0a52d9f..e7338d4c38ac4f67d61712dfc4d4b211e4fc59ad 100644 --- a/hysop/numerics/fftw_f/fftw2py.pyf +++ b/hysop/numerics/fftw_f/fftw2py.pyf @@ -35,6 +35,16 @@ module fftw2py ! in fftw2py.f90 integer(kind=ip) dimension(2),intent(in) :: ghosts_vort integer(kind=ip) dimension(2),intent(in) :: ghosts_velo end subroutine solve_poisson_2d + subroutine solve_laplace_2d(f, u, ghosts) + real(kind=wp),dimension(:,:),intent(in):: f + real(kind=wp),dimension(size(f,1),size(f,2)),intent(out) :: u + integer(kind=ip), dimension(2), intent(in) :: ghosts + end subroutine solve_laplace_2d + subroutine solve_laplace_3d(f, u, ghosts) + real(kind=wp),dimension(:,:,:),intent(in):: f + real(kind=wp),dimension(size(f,1),size(f,2),size(f,3)),intent(out) :: u + integer(kind=ip), dimension(3), intent(in) :: ghosts + end subroutine solve_laplace_3d subroutine solve_diffusion_2d(nudt,omega,ghosts_vort) ! in fftw2py.f90:fftw2py real(kind=wp) intent(in) :: nudt real(kind=wp) dimension(:,:),intent(in,out) :: omega diff --git a/hysop/operator/poisson.py b/hysop/operator/poisson.py index a07d8cfa58e2ad4a07989f8cba82e46a5a27f450..fe575f60d1e643a49f8f6445ec9a414df9b23115 100644 --- a/hysop/operator/poisson.py +++ b/hysop/operator/poisson.py @@ -1,4 +1,3 @@ - """ @file poisson.py Poisson solver frontend. @@ -13,37 +12,41 @@ from hysop.core.graph.computational_node_frontend import ComputationalGraphNodeF from hysop.backend.host.python.operator.poisson import PythonPoisson from hysop.backend.device.opencl.operator.poisson import OpenClPoisson +from hysop.backend.host.fortran.operator.poisson import PoissonFFTW class Poisson(ComputationalGraphNodeFrontend): """ Interface the poisson solver. - Available implementations are: + Available implementations are: *PYTHON (numpy local solver) + *OPENCL + *FORTRAN (FFTW solver) """ - + __implementations = { - Implementation.PYTHON: PythonPoisson, - Implementation.OPENCL: OpenClPoisson + Implementation.PYTHON: PythonPoisson, + Implementation.OPENCL: OpenClPoisson, + Implementation.FORTRAN: PoissonFFTW } @classmethod def implementations(cls): return cls.__implementations - + @classmethod def default_implementation(cls): return Implementation.PYTHON - + @debug - def __init__(self, Fin, Fout, variables, + def __init__(self, Fin, Fout, variables, implementation=None, base_kwds=None, **kwds): """ Initialize a n-dimensional Poisson operator frontend. Solves: Laplacian(Fout) = Fin - - + + Parameters ---------- Fout: Field @@ -59,7 +62,7 @@ class Poisson(ComputationalGraphNodeFrontend): Base class keywords arguments. If None, an empty dict will be passed. kwds: - Keywords arguments that will be passed towards implementation + Keywords arguments that will be passed towards implementation poisson operator __init__. """ base_kwds = base_kwds or dict() @@ -68,6 +71,6 @@ class Poisson(ComputationalGraphNodeFrontend): check_instance(Fin, Field) check_instance(variables, dict, keys=Field, values=CartesianTopologyDescriptors) check_instance(base_kwds, dict, keys=str) - - super(Poisson, self).__init__(Fin=Fin, Fout=Fout, + + super(Poisson, self).__init__(Fin=Fin, Fout=Fout, variables=variables, base_kwds=base_kwds, implementation=implementation, **kwds) diff --git a/hysop/operator/poisson_rotational.py b/hysop/operator/poisson_rotational.py index 4c30890232104945f8fd98b2ab08b15b0e5f0330..599c82d4cb4271dbbf6ebd8b4ad45f2c4ee4075a 100644 --- a/hysop/operator/poisson_rotational.py +++ b/hysop/operator/poisson_rotational.py @@ -1,4 +1,3 @@ - """ @file poisson.py PoissonRotational solver frontend. @@ -11,40 +10,42 @@ from hysop.fields.continuous_field import Field from hysop.topology.cartesian_descriptor import CartesianTopologyDescriptors from hysop.core.graph.computational_node_frontend import ComputationalGraphNodeFrontend -# from hysop.backend.host.fortran.operator.poisson_rotational import PoissonRotationalFFTW +from hysop.backend.host.fortran.operator.poisson_rotational import PoissonRotationalFFTW from hysop.backend.host.python.operator.poisson_rotational import PythonPoissonRotational from hysop.backend.device.opencl.operator.poisson_rotational import OpenClPoissonRotational class PoissonRotational(ComputationalGraphNodeFrontend): """ Interface the poisson solver. - Available implementations are: + Available implementations are: *FORTRAN (fftw based solver) + *PYTHON (gpyfft based solver) + *OPENCL (clfft based solver) """ - + __implementations = { - Implementation.PYTHON: PythonPoissonRotational, - Implementation.OPENCL: OpenClPoissonRotational, - #Implementation.FORTRAN: PoissonRotationalFFTW + Implementation.PYTHON: PythonPoissonRotational, + Implementation.OPENCL: OpenClPoissonRotational, + Implementation.FORTRAN: PoissonRotationalFFTW } @classmethod def implementations(cls): return cls.__implementations - + @classmethod def default_implementation(cls): return Implementation.PYTHON - + @debug - def __init__(self, velocity, vorticity, variables, + def __init__(self, velocity, vorticity, variables, implementation=None, base_kwds=None, **kwds): """ Initialize a PoissonRotational operator frontend for 2D or 3D streamfunction-vorticity formulations. - + in = W (vorticity) out = U (velocity) - + About dimensions: - if velocity is a 2D vector field, vorticity should have only one component Wx. - if velocity is a 3D vector field, vortcitiy should have three components (Wx,Wy,Wz). @@ -53,11 +54,11 @@ class PoissonRotational(ComputationalGraphNodeFrontend): laplacian(psi) = -W Ux = +dpsi/dy Uy = -dpsi/dx - + In 3D: laplacian(psi) = -W U = rot(psi) - + Parameters ---------- velocity: Field @@ -73,12 +74,12 @@ class PoissonRotational(ComputationalGraphNodeFrontend): Base class keywords arguments. If None, an empty dict will be passed. kwds: - Keywords arguments that will be passed towards implementation + Keywords arguments that will be passed towards implementation poisson operator __init__. Notes ----- - A PoissonRotational operator implementation should at least support + A PoissonRotational operator implementation should at least support the following __init__ attributes: velocity, vorticity, variables """ base_kwds = base_kwds or dict() @@ -87,11 +88,11 @@ class PoissonRotational(ComputationalGraphNodeFrontend): check_instance(vorticity, Field) check_instance(variables, dict, keys=Field, values=CartesianTopologyDescriptors) check_instance(base_kwds, dict, keys=str) - + dim = velocity.domain.dim vcomp = velocity.nb_components wcomp = vorticity.nb_components - + if (velocity.domain != vorticity.domain): msg = 'Velocity and vorticity do not share the same domain.' raise ValueError(msg) @@ -109,5 +110,5 @@ class PoissonRotational(ComputationalGraphNodeFrontend): msg='Vorticity component mistmach, got {} components but expected 3.'.format(wcomp) raise RuntimeError(msg) - super(PoissonRotational, self).__init__(velocity=velocity, vorticity=vorticity, + super(PoissonRotational, self).__init__(velocity=velocity, vorticity=vorticity, variables=variables, base_kwds=base_kwds, implementation=implementation, **kwds) diff --git a/hysop/operator/tests/test_poisson.py b/hysop/operator/tests/test_poisson.py index d5cf80c30852a67d46033ec6a42b98bfa48b210f..c345b8c4514507df6675dd65a11fe9777300c4f7 100644 --- a/hysop/operator/tests/test_poisson.py +++ b/hysop/operator/tests/test_poisson.py @@ -1,4 +1,3 @@ - import random, primefac from hysop.deps import it, sm, random from hysop.constants import HYSOP_REAL @@ -16,19 +15,19 @@ from hysop import Field, Box class TestPoissonOperator(object): @classmethod - def setup_class(cls, + def setup_class(cls, enable_extra_tests=__ENABLE_LONG_TESTS__, enable_debug_mode=False): IO.set_default_path('/tmp/hysop_tests/test_poisson') - + if enable_debug_mode: cls.size_min = 15 cls.size_max = 16 else: cls.size_min = 23 cls.size_max = 87 - + cls.enable_extra_tests = enable_extra_tests cls.enable_debug_mode = enable_debug_mode @@ -41,9 +40,9 @@ class TestPoissonOperator(object): from hysop.symbolic.frame import SymbolicFrame from hysop.symbolic.field import curl, laplacian enable_pretty_printing() - + #at this moment we just test a periodic solver - + analytic_solutions = {} analytic_functions = {} for dim in (1,2,3,4): @@ -51,7 +50,7 @@ class TestPoissonOperator(object): def gen_psi(): kis = tuple(random.randint(1,5) for _ in xrange(dim)) qis = tuple(npw.random.rand(dim).round(decimals=3).tolist()) - basis = tuple( (sm.cos(ki*xi+qi), sm.sin(ki*xi+qi)) + basis = tuple( (sm.cos(ki*xi+qi), sm.sin(ki*xi+qi)) for (ki,qi,xi) in zip(kis, qis, frame.coords) ) psi = sm.Integer(1) for i in xrange(dim): @@ -73,14 +72,14 @@ class TestPoissonOperator(object): def teardown_class(cls): pass - + def _test(self, dim, dtype, size_min=None, size_max=None): enable_extra_tests = self.enable_extra_tests size_min = first_not_None(size_min, self.size_min) size_max = first_not_None(size_max, self.size_max) - + valid_factors = {2,3,5,7,11,13} factors = {1} while (factors-valid_factors): @@ -88,14 +87,14 @@ class TestPoissonOperator(object): shape = tuple(npw.random.randint(low=size_min, high=size_max+1, size=dim).tolist()) for Si in shape: factors.update( set(primefac.primefac(int(Si-1))) ) - + domain = Box(length=(2*npw.pi,)*dim) - Psi = Field(domain=domain, name='Psi', dtype=dtype, + Psi = Field(domain=domain, name='Psi', dtype=dtype, nb_components=1, register_object=False) W = Field(domain=domain, name='W', dtype=dtype, nb_components=1, register_object=False) - - self._test_one(shape=shape, dim=dim, dtype=dtype, + + self._test_one(shape=shape, dim=dim, dtype=dtype, domain=domain, Psi=Psi, W=W) print @@ -114,8 +113,8 @@ class TestPoissonOperator(object): else: msg = 'Unknown dtype {}.'.format(dtype) raise NotImplementedError(msg) - - def _test_one(self, shape, dim, dtype, + + def _test_one(self, shape, dim, dtype, domain, Psi, W): print '\nTesting periodic {}D Poisson: dtype={} shape={}'.format( @@ -129,16 +128,20 @@ class TestPoissonOperator(object): print ' >Testing all implementations:' implementations = Poisson.implementations() - + # Compute reference solution - + variables = { Psi:shape, W:shape } def iter_impl(impl): base_kwds = dict(Fout=Psi, Fin=W, variables=variables, - implementation=impl, + implementation=impl, name='poisson_{}'.format(str(impl).lower())) - if impl is Implementation.PYTHON: + if impl is Implementation.FORTRAN: + msg=' *Fortran FFTW: ' + print msg, + yield Poisson(**base_kwds) + elif impl is Implementation.PYTHON: msg=' *Python FFTW: ' print msg, yield Poisson(**base_kwds) @@ -146,14 +149,14 @@ class TestPoissonOperator(object): msg=' *OpenCl CLFFT: ' print msg for cl_env in iter_clenv(): - msg=' |platform {}, device {}'.format(cl_env.platform.name.strip(), + msg=' |platform {}, device {}'.format(cl_env.platform.name.strip(), cl_env.device.name.strip()) print msg, yield Poisson(cl_env=cl_env, **base_kwds) else: msg='Unknown implementation to test {}.'.format(impl) raise NotImplementedError(msg) - + # Compare to analytic solution Psiref = None Wref = None @@ -161,27 +164,33 @@ class TestPoissonOperator(object): if (dim>3) and (impl is Implementation.OPENCL): print ' *OpenCl CLFFT: NO SUPPORT' continue + if (impl is Implementation.FORTRAN): + if ((dim<2) or (dim>3) or (not dtype is HYSOP_REAL)): + print ' *Fortran FFTW: NO SUPPORT' + continue for op in iter_impl(impl): op = op.build() dw = op.input_discrete_fields[W] dpsi = op.output_discrete_fields[Psi] - - dw.initialize(self.__analytic_init, dtype=dtype, + + dw.initialize(self.__analytic_init, dtype=dtype, fns=self.analytic_functions[dim]['Ws']) if (Psiref is None): - dpsi.initialize(self.__analytic_init, dtype=dtype, + dpsi.initialize(self.__analytic_init, dtype=dtype, fns=self.analytic_functions[dim]['Psis']) Wref = tuple( data.get().handle.copy() for data in dw.data ) Psiref = tuple( data.get().handle.copy() for data in dpsi.data ) dpsi.initialize(self.__random_init, dtype=dtype) - + op.apply() - + Wout = tuple( data.get().handle.copy() for data in dw.data ) Psiout = tuple( data.get().handle.copy() for data in dpsi.data ) self._check_output(impl, op, Wref, Psiref, Wout, Psiout) + if (impl is Implementation.FORTRAN): + op.finalize(clean_fftw_solver=True) print - + @classmethod def _check_output(cls, impl, op, Wref, Psiref, Wout, Psiout): check_instance(Wref, tuple, values=npw.ndarray) @@ -202,14 +211,14 @@ class TestPoissonOperator(object): print msg = msg0.format(iname) raise ValueError(msg) - - for (out_buffers, ref_buffers, name) in zip((Wout, Psiout), + + for (out_buffers, ref_buffers, name) in zip((Wout, Psiout), (Wref, Psiref), ('W', 'Psi')): for i, (fout,fref) in enumerate(zip(out_buffers, ref_buffers)): iname = '{}{}'.format(name,i) assert fout.dtype == fref.dtype, iname assert fout.shape == fref.shape, iname - + eps = npw.finfo(fout.dtype).eps dist = npw.abs(fout-fref) dinf = npw.max(dist) @@ -219,7 +228,7 @@ class TestPoissonOperator(object): continue has_nan = npw.any(npw.isnan(fout)) has_inf = npw.any(npw.isinf(fout)) - + print print print 'Test output comparisson for {} failed for component {}:'.format(name, i) @@ -253,10 +262,10 @@ class TestPoissonOperator(object): print w print print - + msg = 'Test failed for {} on component {} for implementation {}.' msg=msg.format(name, i, impl) - raise RuntimeError(msg) + raise RuntimeError(msg) def test_1d_float32(self): @@ -267,7 +276,7 @@ class TestPoissonOperator(object): self._test(dim=3, dtype=npw.float32) def test_4d_float32(self): self._test(dim=4, dtype=npw.float32) - + def test_1d_float64(self): self._test(dim=1, dtype=npw.float64) def test_2d_float64(self): @@ -288,13 +297,13 @@ class TestPoissonOperator(object): self.test_2d_float64() self.test_3d_float64() self.test_4d_float64() - + if __name__ == '__main__': - TestPoissonOperator.setup_class(enable_extra_tests=False, + TestPoissonOperator.setup_class(enable_extra_tests=False, enable_debug_mode=False) - + test = TestPoissonOperator() - + with test_context(): test.perform_tests() diff --git a/hysop/operator/tests/test_poisson_rotational.py b/hysop/operator/tests/test_poisson_rotational.py index 6223d9b5613f5d8b1b02945563010fb49696a9b3..971eaba7971c1a7e5a055ce322254bb1f753138a 100644 --- a/hysop/operator/tests/test_poisson_rotational.py +++ b/hysop/operator/tests/test_poisson_rotational.py @@ -1,3 +1,4 @@ +# coding: utf-8 import random, primefac from hysop.deps import it, sm, random @@ -16,19 +17,19 @@ from hysop import Field, Box class TestPoissonRotationalOperator(object): @classmethod - def setup_class(cls, + def setup_class(cls, enable_extra_tests=__ENABLE_LONG_TESTS__, enable_debug_mode=False): IO.set_default_path('/tmp/hysop_tests/test_poisson_rotational') - + if enable_debug_mode: cls.size_min = 5 cls.size_max = 5 else: cls.size_min = 53 cls.size_max = 87 - + cls.enable_extra_tests = enable_extra_tests cls.enable_debug_mode = enable_debug_mode @@ -41,7 +42,7 @@ class TestPoissonRotationalOperator(object): from hysop.symbolic.frame import SymbolicFrame from hysop.symbolic.field import curl, laplacian enable_pretty_printing() - + #at this moment we just test a periodic solver frame = SymbolicFrame(dim=3) def gen_psi(): @@ -53,11 +54,11 @@ class TestPoissonRotationalOperator(object): for i in xrange(dim): psi *= random.choice(basis[i]) return psi - + analytic_solutions = {} analytic_functions = {} for dim in (2,3): - psis = npw.asarray([gen_psi() for _ in xrange(3)], + psis = npw.asarray([gen_psi() for _ in xrange(3)], dtype=object).view(TensorBase) if (dim==2): psis[:2] = 0 @@ -84,7 +85,7 @@ class TestPoissonRotationalOperator(object): def teardown_class(cls): pass - + def _test(self, dim, dtype, size_min=None, size_max=None): enable_extra_tests = self.enable_extra_tests @@ -99,15 +100,15 @@ class TestPoissonRotationalOperator(object): shape = tuple(npw.random.randint(low=size_min, high=size_max+1, size=dim).tolist()) for Si in shape: factors.update( set(primefac.primefac(int(Si-1))) ) - + domain = Box(length=(2*npw.pi,)*dim) - U = Field(domain=domain, name='U', dtype=dtype, + U = Field(domain=domain, name='U', dtype=dtype, nb_components=dim, register_object=False) W = Field(domain=domain, name='W', dtype=dtype, nb_components=(3 if (dim==3) else 1), register_object=False) - - self._test_one(shape=shape, dim=dim, dtype=dtype, - domain=domain, U=U, W=W) + + self._test_one(shape=shape, dim=dim, dtype=dtype, + domain=domain, U=U, W=W) print @classmethod @@ -126,7 +127,7 @@ class TestPoissonRotationalOperator(object): msg = 'Unknown dtype {}.'.format(dtype) raise NotImplementedError(msg) - def _test_one(self, shape, dim, dtype, + def _test_one(self, shape, dim, dtype, domain, U, W): print '\nTesting periodic {}D PoissonRotational: dtype={} shape={}'.format( @@ -140,14 +141,14 @@ class TestPoissonRotationalOperator(object): print ' >Testing all implementations:' implementations = PoissonRotational.implementations() - + # Compute reference solution - + variables = { U:shape, W:shape } def iter_impl(impl): base_kwds = dict(velocity=U, vorticity=W, variables=variables, - implementation=impl, + implementation=impl, name='poisson_{}'.format(str(impl).lower())) if impl is Implementation.FORTRAN: msg=' *Fortran FFTW: ' @@ -161,18 +162,21 @@ class TestPoissonRotationalOperator(object): msg=' *OpenCl CLFFT: ' print msg for cl_env in iter_clenv(): - msg=' |platform {}, device {}'.format(cl_env.platform.name.strip(), + msg=' |platform {}, device {}'.format(cl_env.platform.name.strip(), cl_env.device.name.strip()) print msg, yield PoissonRotational(cl_env=cl_env, projection=0, **base_kwds) else: msg='Unknown implementation to test {}.'.format(impl) raise NotImplementedError(msg) - + # Compare to analytic solution Uref = None Wref = None for impl in implementations: + if (impl is Implementation.FORTRAN) and not dtype is HYSOP_REAL: + print ' *FORTRAN: NO SUPPORT for ' + str(dtype) + continue for (i,op) in enumerate(iter_impl(impl)): from hysop.tools.debug_dumper import DebugDumper name='{}_{}'.format(impl, i) @@ -181,24 +185,24 @@ class TestPoissonRotationalOperator(object): op = op.build() dw = op.input_discrete_fields[W] du = op.output_discrete_fields[U] - - dw.initialize(self.__analytic_init, dtype=dtype, + + dw.initialize(self.__analytic_init, dtype=dtype, fns=self.analytic_functions[dim]['Ws']) if (Uref is None): - du.initialize(self.__analytic_init, dtype=dtype, + du.initialize(self.__analytic_init, dtype=dtype, fns=self.analytic_functions[dim]['Us']) Wref = tuple( data.get().handle.copy() for data in dw.data ) Uref = tuple( data.get().handle.copy() for data in du.data ) du.initialize(self.__random_init, dtype=dtype) - + op.apply(simulation=None, debug_dumper=dbg) - + Wout = tuple( data.get().handle.copy() for data in dw.data ) Uout = tuple( data.get().handle.copy() for data in du.data ) # dbg(1, 1.0, 'U', Uout) self._check_output(impl, op, Wref, Uref, Wout, Uout) print - + @classmethod def _check_output(cls, impl, op, Wref, Uref, Wout, Uout): check_instance(Wref, tuple, values=npw.ndarray) @@ -219,13 +223,13 @@ class TestPoissonRotationalOperator(object): print msg = msg0.format(iname) raise ValueError(msg) - + for (out_buffers, ref_buffers, name) in zip((Wout, Uout), (Wref, Uref), ('W', 'U')): for i, (fout,fref) in enumerate(zip(out_buffers, ref_buffers)): iname = '{}{}'.format(name,i) assert fout.dtype == fref.dtype, iname assert fout.shape == fref.shape, iname - + eps = npw.finfo(fout.dtype).eps dist = npw.abs(fout-fref) dinf = npw.max(dist) @@ -235,7 +239,7 @@ class TestPoissonRotationalOperator(object): continue has_nan = npw.any(npw.isnan(fout)) has_inf = npw.any(npw.isinf(fout)) - + print print print 'Test output comparisson for {} failed for component {}:'.format(name, i) @@ -269,10 +273,10 @@ class TestPoissonRotationalOperator(object): print w print print - + msg = 'Test failed for {} on component {} for implementation {}.' msg = msg.format(name, i, impl) - raise RuntimeError(msg) + raise RuntimeError(msg) def test_2d_float32(self): @@ -291,15 +295,15 @@ class TestPoissonRotationalOperator(object): self.test_2d_float64() self.test_3d_float64() - + if __name__ == '__main__': - TestPoissonRotationalOperator.setup_class(enable_extra_tests=False, + TestPoissonRotationalOperator.setup_class(enable_extra_tests=False, enable_debug_mode=False) - + test = TestPoissonRotationalOperator() - with printoptions(threshold=10000, linewidth=240, - nanstr='nan', infstr='inf', + with printoptions(threshold=10000, linewidth=240, + nanstr='nan', infstr='inf', formatter={'float': lambda x: '{:>6.2f}'.format(x)}): test.perform_tests()