1212from vtk .util import numpy_support # type: ignore
1313
1414if "READTHEDOCS" not in os .environ :
15+ from gospl ._fortran import mfdreceivers
1516 from gospl ._fortran import donorslist
1617 from gospl ._fortran import donorsmax
1718 from gospl ._fortran import mfdrcvrs
@@ -150,26 +151,37 @@ def _matOcean(self):
150151
151152 # Define multiple flow directions for filled + eps elevations
152153 hl = self .hLocal .getArray ().copy ()
154+ if not self .flatModel :
155+ # Only consider filleps in the first kms offshore
156+ hsmth = self ._hillSlope (smooth = 2 )
157+ hsmth [self .coastDist > self .offshore ] = - 1.e6
158+ else :
159+ hsmth = hl .copy ()
160+
153161 fillz = np .zeros (self .mpoints , dtype = np .float64 ) - 1.0e8
154- fillz [self .locIDs ] = hl
162+ fillz [self .locIDs ] = hsmth
155163 MPI .COMM_WORLD .Allreduce (MPI .IN_PLACE , fillz , op = MPI .MAX )
156164 if MPIrank == 0 :
157165 minh = np .min (fillz ) + 0.1
158166 if not self .flatModel :
159- minh = min (minh , - 3.e3 )
167+ minh = min (minh , self . oFill )
160168 fillz = epsfill (minh , fillz )
161-
162169 # Send elevation + eps globally
163170 fillEPS = MPI .COMM_WORLD .bcast (fillz , root = 0 )
164- rcv , _ , wght = mfdrcvrs (12 , self .flowExp , fillEPS [self .locIDs ], - 1.0e6 )
171+ fillz = fillEPS [self .locIDs ]
172+ if not self .flatModel :
173+ fillz [self .coastDist > self .offshore ] = hl [self .coastDist > self .offshore ]
174+ rcv , _ , wght = mfdrcvrs (12 , self .flowExp , fillz , - 1.0e6 )
165175
166176 # Set borders nodes
167177 if self .flatModel :
168178 rcv [self .idBorders , :] = np .tile (self .idBorders , (12 , 1 )).T
169179 wght [self .idBorders , :] = 0.0
170180
171181 # Define downstream matrix based on filled + dir elevations
172- self .dMat = self .zMat .copy ()
182+ self .dMat1 = self .zMat .copy ()
183+ if not self .flatModel and self .Gmar > 0. :
184+ self .dMat2 = self .iMat .copy ()
173185 indptr = np .arange (0 , self .lpoints + 1 , dtype = petsc4py .PETSc .IntType )
174186 nodes = indptr [:- 1 ]
175187
@@ -186,7 +198,9 @@ def _matOcean(self):
186198 )
187199 tmpMat .assemblyEnd ()
188200 # Add the weights from each direction
189- self .dMat .axpy (1.0 , tmpMat )
201+ self .dMat1 .axpy (1.0 , tmpMat )
202+ if not self .flatModel and self .Gmar > 0. :
203+ self .dMat2 .axpy (- 1.0 , tmpMat )
190204 tmpMat .destroy ()
191205
192206 if self .memclear :
@@ -195,7 +209,9 @@ def _matOcean(self):
195209 gc .collect ()
196210
197211 # Store flow direction matrix
198- self .dMat .transpose ()
212+ self .dMat1 .transpose ()
213+ if not self .flatModel and self .Gmar > 0. :
214+ self .dMat2 .transpose ()
199215
200216 return
201217
@@ -333,7 +349,6 @@ def _diffuseOcean(self, dh):
333349 ts .setMaxTime (self .dt )
334350 ts .setMaxSteps (self .tsStep )
335351 ts .setExactFinalTime (petsc4py .PETSc .TS .ExactFinalTime .MATCHSTEP )
336- # ts.setExactFinalTime(petsc4py.PETSc.TS.ExactFinalTime.INTERPOLATE)
337352
338353 # Allow an unlimited number of failures
339354 ts .setMaxSNESFailures (- 1 ) # (step will be rejected and retried)
@@ -393,14 +408,145 @@ def _diffuseOcean(self, dh):
393408
394409 return
395410
411+ def _depMarineSystem (self , sedflux ):
412+ r"""
413+ Setup matrix for the marine sediment deposition.
414+
415+ The upstream incoming sediment flux is obtained from the total river sediment flux :math:`\mathrm{Q_{t_i}}` where:
416+
417+ .. math::
418+
419+ \mathrm{Q_{t_i}^{t+\Delta t} - \sum_{ups} w_{i,j} Q_{t_u}^{t+\Delta t}}= \mathrm{(\eta_i^{t} - \eta_i^{t+\Delta t}) \frac{\Delta t}{\Omega_i}}
420+
421+ which gives:
422+
423+ .. math::
424+
425+ \mathrm{Q_{s_i}} = \mathrm{Q_{t_i}} - \mathrm{(\eta_i^{t} - \eta_i^{t+\Delta t}) \frac{\Delta t}{\Omega_i}}
426+
427+ And the evolution of marine elevation is based on incoming sediment flux resulting.
428+
429+ .. math::
430+
431+ \mathrm{\frac{\eta_i^{t+\Delta t}-\eta_i^t}{\Delta t}} = \mathrm{G{_m} Q_{s_i} / \Omega_i}
432+
433+ This system of coupled equations is solved implicitly using PETSc by assembling the matrix and vectors using the nested submatrix and subvectors and by using the ``fieldsplit`` preconditioner combining two separate preconditioners for the collections of variables.
434+
435+ :arg sedflux: incoming marine sediment volumes
436+
437+ :return: volDep (the deposited volume of the distributed sediments)
438+ """
439+
440+ hl = self .hLocal .getArray ()
441+ fDepm = np .full (self .lpoints , self .Gmar )
442+ fDepm [fDepm > 0.99 ] = 0.99
443+ fDepm [hl > self .sealevel ] = 0.
444+
445+ # Define submatrices
446+ A00 = self ._matrix_build_diag (- fDepm )
447+ A00 .axpy (1.0 , self .iMat )
448+ A01 = self ._matrix_build_diag (- fDepm * self .dt / self .larea )
449+ A10 = self ._matrix_build_diag (self .larea / self .dt )
450+
451+ # Assemble the matrix for the coupled system
452+ mats = [[A00 , A01 ], [A10 , self .dMat2 ]]
453+ sysMat = petsc4py .PETSc .Mat ().createNest (mats = mats , comm = MPIcomm )
454+ sysMat .assemblyBegin ()
455+ sysMat .assemblyEnd ()
456+
457+ # Clean up
458+ A00 .destroy ()
459+ A01 .destroy ()
460+ A10 .destroy ()
461+ self .dMat2 .destroy ()
462+ mats [0 ][0 ].destroy ()
463+ mats [0 ][1 ].destroy ()
464+ mats [1 ][0 ].destroy ()
465+ mats [1 ][1 ].destroy ()
466+
467+ # Create nested vectors
468+ self .tmpL .setArray (1. - fDepm )
469+ self .dm .localToGlobal (self .tmpL , self .tmp )
470+ self .tmp .pointwiseMult (self .tmp , self .hGlobal )
471+
472+ self .tmpL .setArray (sedflux / self .dt )
473+ self .dm .localToGlobal (self .tmpL , self .tmp1 )
474+ self .h .pointwiseMult (self .hGlobal , self .areaGlobal )
475+ self .h .scale (1. / self .dt )
476+ self .tmp1 .axpy (1. , self .h )
477+
478+ rhs_vec = petsc4py .PETSc .Vec ().createNest ([self .tmp , self .tmp1 ], comm = MPIcomm )
479+ rhs_vec .setUp ()
480+ hq_vec = rhs_vec .duplicate ()
481+
482+ # Define solver and precondition conditions
483+ ksp = petsc4py .PETSc .KSP ().create (petsc4py .PETSc .COMM_WORLD )
484+ ksp .setType (petsc4py .PETSc .KSP .Type .TFQMR )
485+ ksp .setOperators (sysMat )
486+ ksp .setTolerances (rtol = self .rtol )
487+
488+ pc = ksp .getPC ()
489+ pc .setType ("fieldsplit" )
490+ nested_IS = sysMat .getNestISs ()
491+ pc .setFieldSplitIS (('h' , nested_IS [0 ][0 ]), ('q' , nested_IS [0 ][1 ]))
492+
493+ subksps = pc .getFieldSplitSubKSP ()
494+ subksps [0 ].setType ("preonly" )
495+ subksps [0 ].getPC ().setType ("asm" )
496+ subksps [1 ].setType ("preonly" )
497+ subksps [1 ].getPC ().setType ("bjacobi" )
498+
499+ ksp .solve (rhs_vec , hq_vec )
500+ r = ksp .getConvergedReason ()
501+ if r < 0 :
502+ KSPReasons = self ._make_reasons (petsc4py .PETSc .KSP .ConvergedReason ())
503+ if MPIrank == 0 :
504+ print (
505+ "Linear solver for marine deposition failed to converge after iterations" ,
506+ ksp .getIterationNumber (),
507+ flush = True ,
508+ )
509+ print ("with reason: " , KSPReasons [r ], flush = True )
510+ else :
511+ if MPIrank == 0 and self .verbose :
512+ print (
513+ "Linear solver for marine deposition converge after %d iterations"
514+ % ksp .getIterationNumber (),
515+ flush = True ,
516+ )
517+
518+ # Update the solution
519+ self .newH = hq_vec .getSubVector (nested_IS [0 ][0 ])
520+
521+ # Clean up
522+ subksps [0 ].destroy ()
523+ subksps [1 ].destroy ()
524+ nested_IS [0 ][0 ].destroy ()
525+ nested_IS [1 ][0 ].destroy ()
526+ nested_IS [0 ][1 ].destroy ()
527+ nested_IS [1 ][1 ].destroy ()
528+ pc .destroy ()
529+ ksp .destroy ()
530+ sysMat .destroy ()
531+ hq_vec .destroy ()
532+ rhs_vec .destroy ()
533+
534+ # Get the marine deposition volume
535+ self .tmp .waxpy (- 1.0 , self .hGlobal , self .newH )
536+ self .dm .globalToLocal (self .tmp , self .tmpL )
537+ volDep = self .tmpL .getArray ().copy () * self .larea
538+ volDep [volDep < 0 ] = 0.
539+
540+ return volDep
541+
396542 def _distOcean (self , sedflux ):
397543 """
398544 Based on the incoming marine volumes of sediment and maximum clinoforms slope we distribute
399545 locally sediments downslope.
400546
401547 :arg sedflux: incoming marine sediment volumes
402548
403- :return: seddep (the deposied volume of the distributed sediments)
549+ :return: vdep (the deposited volume of the distributed sediments)
404550 """
405551
406552 marVol = self .maxDepQs .copy ()
@@ -413,7 +559,7 @@ def _distOcean(self, sedflux):
413559 while self .tmp .sum () > 1.0 :
414560
415561 # Move to downstream nodes
416- self .dMat .mult (self .tmp , self .tmp1 )
562+ self .dMat1 .mult (self .tmp , self .tmp1 )
417563 self .dm .globalToLocal (self .tmp1 , self .tmpL )
418564
419565 # In case there is too much sediment coming in
@@ -445,7 +591,6 @@ def _distOcean(self, sedflux):
445591 )
446592
447593 step += 1
448- self .dMat .destroy ()
449594
450595 if self .memclear :
451596 del marVol , sinkVol
@@ -491,6 +636,12 @@ def seaChange(self):
491636 # Downstream direction matrix for ocean distribution
492637 self ._matOcean ()
493638 marDep = self ._distOcean (sedFlux )
639+ if not self .flatModel and self .Gmar > 0. :
640+ vdep = self ._depMarineSystem (marDep )
641+ marDep = self ._distOcean (vdep )
642+ self .dMat1 .destroy ()
643+
644+ # Diffuse downstream
494645 dh = np .divide (marDep , self .larea , out = np .zeros_like (self .larea ), where = self .larea != 0 )
495646 dh [dh < 1.e-3 ] = 0.
496647 self .tmpL .setArray (dh )
0 commit comments