From c9e2ba7985702181e3644b014757b0f2f499807b Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Keck <Jean-Baptiste.Keck@imag.fr>
Date: Tue, 26 Jun 2018 18:49:34 +0200
Subject: [PATCH] First try for periodic sediment ladden fresh water above salt
 water

---
 .../particles_above_salt.py                   | 166 ----------
 .../particles_above_salt_periodic.py          | 310 ++++++++++++++++++
 hysop/domain/box.py                           |  19 +-
 3 files changed, 324 insertions(+), 171 deletions(-)
 delete mode 100644 examples/particles_above_salt/particles_above_salt.py
 create mode 100644 examples/particles_above_salt/particles_above_salt_periodic.py

diff --git a/examples/particles_above_salt/particles_above_salt.py b/examples/particles_above_salt/particles_above_salt.py
deleted file mode 100644
index 5011e13c7..000000000
--- a/examples/particles_above_salt/particles_above_salt.py
+++ /dev/null
@@ -1,166 +0,0 @@
-
-from hysop import IO, Domain, Field, Box, Discretization, \
-                  Simulation, Problem, IOParams
-from hysop.deps import np, sys
-from hysop.tools.contexts import printoptions
-from hysop.tools.sympy_utils import greak
-from hysop.parameters.default_parameters import EnstrophyParameter, KineticEnergyParameter
-
-from hysop.constants import Backend, AutotunerFlags, Implementation, \
-                            StretchingFormulation, FieldProjection,  \
-                            AdvectionCriteria, StretchingCriteria
-
-from hysop.operators import DirectionalAdvection, DirectionalStretching, DirectionalDiffusion, \
-                            PoissonRotational, Diffusion, AdaptiveTimeStep,                    \
-                            Enstrophy, KineticEnergy,                                          \
-                            MinMaxFieldStatistics, MinMaxGradientStatistics
-
-from hysop.methods import StrangOrder, SpaceDiscretization, Remesh, \
-                          TimeIntegrator, ComputeGranularity,       \
-                          OpenClKernelConfig, OpenClKernelAutotunerConfig
-
-from hysop.numerics.splitting.strang import StrangSplitting
-from hysop.numerics.odesolvers.runge_kutta import Euler, RK2, RK3, RK4
-
-IO.set_default_path('/tmp/hysop_examples/particles_above_salt')
-
-pi  = np.pi
-cos = np.cos
-sin = np.sin
-
-## See Meiburg 2012 & 2014
-## Sediment-laden fresh water above salt water.
-
-# Constants
-l0 = 1.5 #initial thickness of the profile
-(Sc, tau, Vp, Rs, Xo, Xn, N) = (10.0,  10, 0.00, 2.0, (-28.13, 0.0, 0.0), (+28.13, 28.13, 28.13), (385,128,128)) # large Sc (linear validation)
-(Sc, tau, Vp, Rs, Xo, Xn, N) = (1.00, 100, 0.00, 2.0, (-20.00, 0.0, 0.0), (+20.00, 20.00, 20.00), (385,128,128)) # small Sc (linear validation)
-(Sc, tau, Vp, Rs, Xo, Xn, N) = (0.70,  25, 0.04, 2.0, (-300,0),   (300,750),    (1537, 512))      # 2d configuration (DNS)
-(Sc, tau, Vp, Rs, Xo, Xn, N) = (7.00,  25, 0.04, 2.0, (-110,0,0), (65,100,100), (1537, 512, 512)) # 3d configuration (DNS)
-L = np.subtract(Xn, Xo)
-print L
-
-nu_S = 1.0/Sc
-nu_C = 1.0/(tau*Sc)
-
-
-## Function to compute Vreference vorticity
-def init_vorticity(data, coords, dim):
-    # the flow is initially quiescent
-    for d in data:
-        d[...] = 0.0
-
-## Function to compute initial sediment concentration profile
-def delta(*coords):
-    dim = len(coords)
-    if (dim==1):
-        (x,) = coords
-        d_hat = np.zeros_like(x)
-        
-    else:
-        raise NotImplementedError()
-    
-def init_concentration(data, coords):
-    data[0][...] = 0.5*(1+np.erf((coords[-1] - delta(*coords[:-1]))/l0))
-
-def init_salinity(data, coords, C):
-    data[0][...] = 1.0 - C[0]
-
-def run(dim=3, npts=64+1, viscosity=1./1600., lcfl=0.125, cfl=0.5):
-    d3d=(npts,)*dim
-    impl = Implementation.OPENCL
-
-    method = { ComputeGranularity:  0, 
-               SpaceDiscretization: 4,
-               TimeIntegrator:      RK4 }
-
-    if (impl is Implementation.OPENCL):
-        autotuner_config = OpenClKernelAutotunerConfig(
-           max_candidates=1,
-           autotuner_flag=AutotunerFlags.PATIENT, 
-           prune_threshold=1.2, override_cache=False, verbose=0)
-        kernel_config = OpenClKernelConfig(autotuner_config=autotuner_config)
-        method.update({OpenClKernelConfig : kernel_config})
-
-    box = Box(length=(2*pi,)*dim)
-
-    velo  = Field(domain=box, name='U', is_vector=True, dtype=np.float32)
-    vorti = Field(domain=box, name='W', pretty_name=greak[24], 
-            nb_components=(1 if dim==2 else dim), dtype=np.float32)
-    
-    kinetic_energy = KineticEnergyParameter(dtype=velo.dtype) 
-    enstrophy      = EnstrophyParameter(dtype=vorti.dtype)
-    
-    simu = Simulation(start=0.0, end=10.0, dt0=1e-4)
-    t  = simu.t
-    dt = simu.dt
-
-    diffusion = DirectionalDiffusion(implementation=impl,
-            name='diffusion',
-            fields=vorti, coeffs=viscosity, 
-            variables={vorti: d3d}, dt=dt)
-
-    advec = DirectionalAdvection(implementation=impl,
-            name='advec',
-            velocity = velo,       
-            advected_fields = (vorti,),
-            velocity_cfl = cfl,
-            variables = {velo: d3d, vorti: d3d},
-            method = {Remesh: Remesh.L4_2},
-            dt=dt)
-
-    if dim==3:
-        stretch = DirectionalStretching(implementation=impl,
-                 name='stretch',
-                 formulation = StretchingFormulation.CONSERVATIVE,
-                 velocity  = velo,       
-                 vorticity = vorti,
-                 variables = {velo: d3d, vorti: d3d},
-                 dt=dt)
-    else:
-        stretch = None
-    
-    splitting = StrangSplitting(splitting_dim=dim, 
-                    order=StrangOrder.STRANG_SECOND_ORDER)
-    splitting.push_operators(advec, stretch, diffusion)
-    
-    poisson = PoissonRotational(name='poisson', velocity=velo, vorticity=vorti, 
-                            variables={velo:d3d, vorti: d3d}, projection=10)
-    poisson.dump_outputs(fields=(vorti,), frequency=10)
-    poisson.dump_outputs(fields=(velo,), frequency=10)
-    
-    enstrophy_op = Enstrophy(name='enstrophy', vorticity=vorti, enstrophy=enstrophy,
-            variables={vorti:d3d}, implementation=impl)
-    min_max_U = MinMaxFieldStatistics(name='min_max_U', field=velo,
-            Finf=True, implementation=impl, variables={velo:d3d})
-    min_max_gradU = MinMaxGradientStatistics(F=velo,
-            Finf=True, implementation=Implementation.PYTHON, variables={velo: d3d})
-
-    adapt_dt = AdaptiveTimeStep(dt)
-    adapt_dt.push_cfl_criteria(cfl, Finf=min_max_U.Finf)
-    adapt_dt.push_advection_criteria(lcfl=lcfl, gradFinf=min_max_gradU.Finf, 
-            criteria=AdvectionCriteria.DEFORMATION)
-    adapt_dt.push_stretching_criteria(gradFinf=min_max_gradU.Finf, 
-            criteria=StretchingCriteria.GRAD_U)
-
-    problem = Problem(method=method)
-    problem.insert(poisson, min_max_U, min_max_gradU, enstrophy_op, splitting, adapt_dt)
-    problem.build()
-    problem.display()
-    
-    dfields = problem.input_discrete_fields
-
-    dfields[vorti].initialize(formula=init_vorticity, dim=dim)
-
-    simu.write_parameters(t, dt, enstrophy, 
-            filename='parameters.txt', precision=4)
-    
-    with printoptions(threshold=10000, linewidth=240, 
-                      nanstr='nan', infstr='inf', 
-                      formatter={'float': lambda x: '{:>6.2f}'.format(x)}):
-        problem.solve(simu)
-        problem.finalize()
-
-if __name__=='__main__':
-    run()
-
diff --git a/examples/particles_above_salt/particles_above_salt_periodic.py b/examples/particles_above_salt/particles_above_salt_periodic.py
new file mode 100644
index 000000000..6364e61d5
--- /dev/null
+++ b/examples/particles_above_salt/particles_above_salt_periodic.py
@@ -0,0 +1,310 @@
+## See Meiburg 2012 & 2014
+## Sediment-laden fresh water above salt water.
+    
+import numpy as np
+import scipy as sp
+import sympy as sm
+
+# initialize vorticity
+def init_vorticity(data, coords):
+    # the flow is initially quiescent
+    for d in data:
+        d[...] = 0.0
+
+# initialize velocity
+def init_velocity(data, coords):
+    # the flow is initially quiescent
+    for d in data:
+        d[...] = 0.0
+
+# initialize sediment concentration and salinity
+def delta(*coords):
+    d = np.prod(*coords)
+    return np.zeros_like(d)
+    
+def init_concentration(data, coords, l0):
+    X = coords[-1]
+    C = coords[:-1]
+    data[0][...] = 0.5*(1.0 +
+            sp.special.erf(2*np.abs(4*X-2) - 2))
+
+def init_salinity(data, coords, l0):
+    init_concentration(data, coords, l0)
+    data[0][...] = 1.0 - data[0][...]
+
+
+def compute(args):
+    from hysop import Field, Box, Simulation, Problem, MPIParams, IOParams
+    from hysop.defaults import VelocityField, VorticityField, \
+                               DensityField, ViscosityField, \
+                               LevelSetField, \
+                               EnstrophyParameter, TimeParameters, \
+                               VolumicIntegrationParameter
+    from hysop.constants import Implementation, AdvectionCriteria
+
+    from hysop.operators import DirectionalAdvection, DirectionalDiffusion,        \
+                                DirectionalStretchingDiffusion,                    \
+                                PoissonRotational, AdaptiveTimeStep,               \
+                                Enstrophy, MinMaxFieldStatistics, StrangSplitting, \
+                                ParameterPlotter, Integrate, HDF_Writer,           \
+                                DirectionalSymbolic
+
+    from hysop.methods import SpaceDiscretization, Remesh, TimeIntegrator, \
+                              ComputeGranularity, Interpolation
+    
+    from hysop.symbolic import sm, space_symbols
+    from hysop.symbolic.base import SymbolicTensor
+    from hysop.symbolic.field import curl
+    from hysop.symbolic.relational import Assignment, LogicalGT, LogicalLT
+    from hysop.symbolic.misc import Select
+    from hysop.symbolic.tmp import TmpScalar
+
+    # Constants
+    l0 = 1.5 #initial thickness of the profile
+    #(Sc, tau, Vp, Rs, Xo, Xn, N) = (10.0,  10, 0.00, 2.0, (-28.13, 0.0, 0.0), (+28.13, 28.13, 28.13), (385,128,128)) # large Sc (linear validation)
+    #(Sc, tau, Vp, Rs, Xo, Xn, N) = (1.00, 100, 0.00, 2.0, (-20.00, 0.0, 0.0), (+20.00, 20.00, 20.00), (385,128,128)) # small Sc (linear validation)
+    (Sc, tau, Vp, Rs, Xo, Xn, N) = (0.70,  25, 0.04, 2.0, (-300,0),   (300,750),    (1537, 512))      # 2d configuration (DNS)
+    #(Sc, tau, Vp, Rs, Xo, Xn, N) = (7.00,  25, 0.04, 2.0, (-110,0,0), (65,100,100), (1537, 512, 512)) # 3d configuration (DNS)
+
+    nu_S = 1.0/Sc
+    nu_C = 1.0/(tau*Sc)
+
+    # Define the domain
+    dim = args.ndim
+    L = np.subtract(Xn, Xo)
+    npts=args.npts
+    box = Box(dim=dim)
+    
+    # Get default MPI Parameters from domain (even for serial jobs)
+    mpi_params = MPIParams(comm=box.task_comm,
+                           task_id=box.current_task())
+
+    # Setup usual implementation specific variables
+    impl = args.impl
+    extra_op_kwds = {'mpi_params': mpi_params}
+    if (impl is Implementation.PYTHON):
+        method = {}
+    elif (impl is Implementation.OPENCL):
+        # For the OpenCL implementation we need to setup the compute device
+        # and configure how the code is generated and compiled at runtime.
+                
+        # Create an explicit OpenCL context from user parameters
+        from hysop.backend.device.opencl.opencl_tools import get_or_create_opencl_env
+        cl_env = get_or_create_opencl_env(mpi_params=mpi_params, 
+                                          platform_id=args.cl_platform_id, 
+                                          device_id=args.cl_device_id)
+        
+        # Configure OpenCL kernel generation and tuning (already done by HysopArgParser)
+        from hysop.methods import OpenClKernelConfig
+        method = { OpenClKernelConfig: args.opencl_kernel_config }
+        
+        # Setup opencl specific extra operator keyword arguments
+        extra_op_kwds['cl_env'] = cl_env
+    else:
+        msg='Unknown implementation \'{}\'.'.format(impl)
+        raise ValueError(msg)
+
+    # Define parameters and field (time, timestep, velocity, vorticity, enstrophy)
+    t, dt = TimeParameters(dtype=args.dtype)
+    velo  = VelocityField(domain=box, dtype=args.dtype)
+    vorti = VorticityField(domain=box, dtype=args.dtype)
+    C     = Field(domain=box, name='C', dtype=args.dtype)
+    S     = Field(domain=box, name='S', dtype=args.dtype)
+    
+    # Symbolic fields
+    frame = velo.domain.frame
+    Ws    = vorti.s(*frame.vars)
+    Cs    = C.s(*frame.vars)
+    Ss    = S.s(*frame.vars)
+
+    ### Build the directional operators
+    #> Directional advection 
+    advec = DirectionalAdvection(implementation=impl,
+            name='advec',
+            velocity = velo,       
+            advected_fields = (vorti,S,C),
+            velocity_cfl = args.cfl,
+            variables = {velo: npts, vorti: npts, S: npts, C: npts},
+            method = {Remesh: Remesh.L4_2},
+            dt=dt)
+    
+    #> Stretch and diffuse vorticity
+    if (dim==3):
+        stretch_diffuse = DirectionalStretchingDiffusion(implementation=impl,
+                 name='stretch_diffuse',
+                 pretty_name='sdiff',
+                 formulation = args.stretching_formulation,
+                 viscosity = 1.0,
+                 velocity  = velo,       
+                 vorticity = vorti,
+                 variables = {velo: npts, vorti: npts},
+                 dt=dt, **extra_op_kwds)
+    elif (dim==2):
+        stretch_diffuse = DirectionalDiffusion(implementation=impl,
+                 name='diffuse_{}'.format(vorti.name),
+                 pretty_name=u'diff{}'.format(vorti.pretty_name.decode('utf-8')),
+                 coeffs = 1.0,
+                 fields  = vorti,
+                 variables = {vorti: npts},
+                 dt=dt, **extra_op_kwds)
+    else:
+        msg='Unsupported dimension {}.'.format(dim)
+        raise RuntimeError(msg)
+    
+    #> Diffusion of S and C
+    diffuse_S = DirectionalDiffusion(implementation=impl,
+             name='diffuse_S',
+             pretty_name='diffS',
+             coeffs = nu_S,
+             fields = S,
+             variables = {S: npts},
+             dt=dt, **extra_op_kwds)
+    diffuse_C = DirectionalDiffusion(implementation=impl,
+             name='diffuse_C',
+             pretty_name='diffC',
+             coeffs = nu_C,
+             fields = C,
+             variables = {C: npts},
+             dt=dt, **extra_op_kwds)
+
+    #> External force rot(-rho*g) = rot(Rs*S + C)
+    Fext = np.zeros(shape=(dim,), dtype=object).view(SymbolicTensor)
+    Fext[0] = (Rs*Ss + Cs)
+    lhs = Ws.diff(frame.time)
+    rhs = curl(Fext, frame)
+    exprs = Assignment.assign(lhs, rhs)
+    external_force = DirectionalSymbolic(name='Fext', 
+                                    implementation=impl,
+                                    exprs=exprs, dt=dt,
+                                    variables={vorti: npts,
+                                               S: npts,
+                                               C: npts},
+                                    **extra_op_kwds)
+
+    splitting = StrangSplitting(splitting_dim=dim, 
+                    order=args.strang_order)
+    splitting.push_operators(advec, stretch_diffuse, diffuse_S, diffuse_C, external_force)
+    
+    ### Build standard operators
+    #> Poisson operator to recover the velocity from the vorticity
+    poisson = PoissonRotational(name='poisson', velocity=velo, vorticity=vorti, 
+                            variables={velo:npts, vorti: npts})
+    
+    #> Operator to compute the infinite norm of the velocity
+    min_max_U = MinMaxFieldStatistics(name='min_max_U', field=velo,
+            Finf=True, implementation=impl, variables={velo:npts},
+            **extra_op_kwds)
+    #> Operator to compute the infinite norm of the vorticity
+    min_max_W = MinMaxFieldStatistics(field=vorti,
+            Finf=True, implementation=impl, variables={vorti:npts},
+            **extra_op_kwds)
+    
+    #> Operators to dump all fields
+    io_params = IOParams(filename='fields', frequency=args.dump_freq)
+    dump_fields = HDF_Writer(name='dump',
+                             io_params=io_params,
+                             variables={velo: npts, 
+                                        vorti: npts,    
+                                        C: npts, 
+                                        S: npts})
+
+    ### Adaptive timestep operator
+    adapt_dt = AdaptiveTimeStep(dt, equivalent_CFL=True, max_dt=1e-3,
+                                    name='merge_dt', pretty_name='dt', )
+    dt_cfl   = adapt_dt.push_cfl_criteria(cfl=args.cfl, Finf=min_max_U.Finf,
+                                            equivalent_CFL=True, 
+                                            name='dt_cfl', pretty_name='CFL')
+    dt_advec = adapt_dt.push_advection_criteria(lcfl=args.lcfl, Finf=min_max_W.Finf,
+                                                 criteria=AdvectionCriteria.W_INF,
+                                                 name='dt_lcfl', pretty_name='LCFL')
+
+    
+    ## Create the problem we want to solve and insert our 
+    # directional splitting subgraph and the standard operators.
+    # The method dictionnary passed to this graph will be dispatched
+    # accross all operators contained in the graph.
+    method.update(
+            { 
+               ComputeGranularity:    args.compute_granularity,
+               SpaceDiscretization:   args.fd_order,
+               TimeIntegrator:        args.time_integrator,
+               Remesh:                args.remesh_kernel,
+               Interpolation:         args.interpolation
+            }
+        )
+
+    problem = Problem(method=method)
+    problem.insert(poisson, 
+                   splitting, 
+                   dump_fields,
+                   min_max_U, min_max_W, 
+                   adapt_dt)
+    problem.build()
+    
+    # If a visu_rank was provided, and show_graph was set,
+    # display the graph on the given process rank.
+    if args.display_graph:
+        problem.display()
+    
+    # Create a simulation
+    # (do not forget to specify the t and dt parameters here)
+    simu = Simulation(start=args.tstart, end=args.tend, 
+                      nb_iter=args.nb_iter,
+                      max_iter=args.max_iter,
+                      dt0=args.dt, times_of_interest=args.dump_times,
+                      t=t, dt=dt)
+    simu.write_parameters(t, dt_cfl, dt_advec, dt, 
+            min_max_U.Finf, min_max_W.Finf, adapt_dt.equivalent_CFL,
+            filename='parameters.txt', precision=8)
+    
+    # Initialize vorticity, velocity, S and C on all topologies
+    problem.initialize_field(field=velo,  formula=init_velocity)
+    problem.initialize_field(field=vorti, formula=init_vorticity)
+    problem.initialize_field(field=C,     formula=init_concentration, l0=l0)
+    problem.initialize_field(field=S,     formula=init_salinity, l0=l0)
+    
+    # Finally solve the problem 
+    problem.solve(simu)
+    
+    # Finalize
+    problem.finalize()
+
+
+if __name__=='__main__':
+    from examples.example_utils import HysopArgParser, colors
+
+    class ParticleAboveSaltArgParser(HysopArgParser):
+        def __init__(self):
+            prog_name = 'particle_above_salt_periodic'
+            default_dump_dir = '{}/hysop_examples/periodic_{}'.format(HysopArgParser.tmp_dir(), 
+                    prog_name)
+
+            description=colors.color('HySoP Particles Above Salt Example: ', fg='blue', style='bold')
+            description+=colors.color('[Meiburg 2014]', fg='yellow', style='bold')
+            description+=colors.color('\nSediment-laden fresh water above salt water.', fg='yellow')
+            description+='\n'
+            description+='\nThis example focuses on a validation study for the '
+            description+='hybrid particle-mesh vortex method in the Boussinesq approximation.'
+    
+            super(ParticleAboveSaltArgParser, self).__init__(
+                 prog_name=prog_name,
+                 description=description,
+                 default_dump_dir=default_dump_dir)
+
+        def _setup_parameters(self, args):
+            dim = args.ndim
+            if (dim not in (2,3)):
+                msg='Domain should be 2D or 3D.'
+                self.error(msg)
+
+
+    parser = ParticleAboveSaltArgParser()
+
+    parser.set_defaults(impl='cl', ndim=2, npts=(257,),
+                        box_origin=(0.0,), box_length=(1.0,), 
+                        tstart=0.0, tend=0.51, 
+                        dt=1e-6, cfl=0.5, lcfl=0.125,
+                        dump_freq=10)
+
+    parser.run(compute)
+
diff --git a/hysop/domain/box.py b/hysop/domain/box.py
index d6b97d937..4435eb793 100644
--- a/hysop/domain/box.py
+++ b/hysop/domain/box.py
@@ -6,7 +6,7 @@ from hysop.constants import BoundaryCondition, HYSOP_REAL
 from hysop.domain.domain import Domain, DomainView
 from hysop.tools.decorators import debug
 from hysop.tools.numpywrappers import npw
-from hysop.tools.types import check_instance
+from hysop.tools.types import check_instance, first_not_None, to_tuple
 
 class BoxView(DomainView):
     
@@ -145,14 +145,23 @@ class Box(BoxView, Domain):
                 allow_none=True)
         check_instance(rboundaries, (np.ndarray,list,tuple), values=BoundaryCondition, 
                 allow_none=True)
-        
+
         if (length is None) and (origin is None) and (dim is None):
             msg='At least one of the following parameters should be given: length, origin, dim.'
             raise ValueError(msg)
         
-        dim    = int(dim or len(length) or len(origin))
-        length = npw.asrealarray(length or (1.0,)*dim)
-        origin = npw.asrealarray(origin or (0.0,)*dim)
+        length = to_tuple(first_not_None(length, 1.0))
+        origin = to_tuple(first_not_None(origin, 0.0))
+        dim = max(dim, len(length), len(origin))
+        if len(length)==1:
+            length *= dim
+        if len(origin)==1:
+            origin *= dim
+
+        length = npw.asrealarray(length)
+        origin = npw.asrealarray(origin)
+        check_instance(length, np.ndarray, size=dim)
+        check_instance(origin, np.ndarray, size=dim)
         assert (length>=0.0).all(), 'length < 0'
 
         lboundaries = npw.asarray( lboundaries or (BoundaryCondition.PERIODIC,)*dim )
-- 
GitLab