Source code for model

import os

from mpi4py import MPI
from time import process_time

if "READTHEDOCS" not in os.environ:
    from .flow import FAMesh as _FAMesh
    from .flow import IceMesh as _IceMesh
    from .flow import PITFill as _PITFill
    from .eroder import SPL as _SPL
    from .eroder import nlSPL as _nlSPL
    from .eroder import soilSPL as _soilSPL
    from .sed import SEDMesh as _SEDMesh
    from .sed import hillSLP as _hillSLP
    from .sed import SEAMesh as _SEAMesh
    from .sed import STRAMesh as _STRAMesh
    from .tools import ReadYaml as _ReadYaml
    from .mesher import UnstMesh as _UnstMesh
    from .mesher import VoroBuild as _VoroBuild
    from .tools import GridProcess as _GridProcess
    from .mesher import Tectonics as _Tectonics
    from .tools import WriteMesh as _WriteMesh
    from .tools import Profiler as _Profiler

else:

    class _ReadYaml(object):
        def __init__(self, filename):
            print("Fake print statement for readthedocs", filename)

    class _VoroBuild(object):
        def __init__(self):
            pass

    class _UnstMesh(object):
        def __init__(self):
            pass

    class _Tectonics(object):
        def __init__(self):
            pass

    class _WriteMesh(object):
        def __init__(self):
            pass

    class _FAMesh(object):
        def __init__(self):
            pass

    class _IceMesh(object):
        def __init__(self):
            pass

    class _SPL(object):
        def __init__(self):
            pass

    class _nlSPL(object):
        def __init__(self):
            pass

    class _soilSPL(object):
        def __init__(self):
            pass

    class _PITFill(object):
        def __init__(self):
            pass

    class _SEDMesh(object):
        def __init__(self):
            pass

    class _hillSLP(object):
        def __init__(self):
            pass

    class _SEAMesh(object):
        def __init__(self):
            pass

    class _STRAMesh(object):
        def __init__(self):
            pass

    class _GridProcess(object):
        def __init__(self):
            pass

    class _Profiler(object):
        def __init__(self, *args, **kwargs):
            self.enabled = False

        def phase(self, name):
            from contextlib import nullcontext

            return nullcontext()

        def report(self, *args, **kwargs):
            return None

MPIrank = MPI.COMM_WORLD.Get_rank()
MPIsize = MPI.COMM_WORLD.Get_size()


[docs] class Model( _ReadYaml, _WriteMesh, _UnstMesh, _VoroBuild, _GridProcess, _Tectonics, _FAMesh, _IceMesh, _SPL, _nlSPL, _soilSPL, _PITFill, _SEDMesh, _hillSLP, _SEAMesh, _STRAMesh, ): """ Instantiates model's objects and initialise classes. This object contains methods for the following operations: - initialisation of goSPL mesh based on input file options - computation of surface processes over time - cleaning/destruction of PETSC objects :arg filename: YAML input file :arg verbose: output flag for model main functions :arg showlog: output flag for PETSC logging file """ def __init__( self, filename, verbose=True, showlog=False, profile=False, *args, **kwargs ): self.showlog = showlog self.modelRunTime = process_time() self.verbose = verbose # Read input dataset _ReadYaml.__init__(self, filename) # Wall-clock phase profiler. Enabled by the `profile` kwarg OR the # YAML `output: profile: true` flag (parsed in _readOut). When off it # is a zero-overhead no-op. See gospl/tools/profiler.py. prof_on = profile or bool(getattr(self, "profileFlag", False)) self.profiler = _Profiler(enabled=prof_on) # Stratigraphy initialisation _STRAMesh.__init__(self) # Define voronoi mesh _VoroBuild.__init__(self) # Define unstructured mesh _UnstMesh.__init__(self) # Initialise output mesh _WriteMesh.__init__(self) # River flow initialisation _FAMesh.__init__(self, *args, **kwargs) # Ice flow initialisation _IceMesh.__init__(self, *args, **kwargs) # SPL initialisation _SPL.__init__(self, *args, **kwargs) # Non-linear SPL initialisation _nlSPL.__init__(self, *args, **kwargs) # Non-linear SPL with soil generation initialisation _soilSPL.__init__(self, *args, **kwargs) # Pit filling initialisation _PITFill.__init__(self, *args, **kwargs) # Continental sediment transfer initialisation _SEDMesh.__init__(self, *args, **kwargs) # Hillslope processes (linear and non-linear) initialisation _hillSLP.__init__(self, *args, **kwargs) # Marine sediment transport and deposition initialisation _SEAMesh.__init__(self, *args, **kwargs) # Define additional grid processes (flexure, orographic rain) _GridProcess.__init__(self) # Get external forces driving landscape dynamics over time (tectonic, rainfall...) _UnstMesh.applyForces(self) # Initialise horizontal tectonics forcings (multiple advection techniques) _Tectonics.__init__(self) # Check if simulation just restarted if self.rStep > 0: _WriteMesh.readData(self) if not self.fast: # Compute flow accumulation _FAMesh.flowAccumulation(self) if MPIrank == 0: print( "--- Initialisation Phase (%0.02f seconds)" % (process_time() - self.modelRunTime), flush=True, ) return
[docs] def runProcesses(self): """ Runs simulation over time. This function contains methods for the following operations: - computes flow accumulation based on imposed precipitation field - performs land surface evolution from river erosion, transport and deposition - executes creep processes and marine depostion (linear and non-linear diffusion) - records stratigraphic layers evolution and associated porosity variations - applies user-defined tectonics forcing (horizontal and vertical displacements) """ runStart = MPI.Wtime() while self.tNow <= self.tEnd: tstep = process_time() # Flexure interval counter (see GridProcess + flex_interval): the # load reference is snapshotted at interval starts (in the eroder) # and flexure applied at interval ends below. if self.flexOn: self.flexCount += 1 # Output time step with self.profiler.phase("tectonics"): _Tectonics.updatePaleoZ(self) with self.profiler.phase("output"): _WriteMesh.visModel(self) if self.tNow == self.tEnd: break # Create new stratal layer newLayer = self.tNow >= self.saveStrat if newLayer: self.stratStep += 1 self.saveStrat += self.strat # Perform advection and tectonics with self.profiler.phase("tectonics"): _Tectonics.getTectonics(self) if not self.fast: if self.iceOn: # Compute ice accumulation with self.profiler.phase("ice"): _IceMesh.iceAccumulation(self) # Compute flow accumulation with self.profiler.phase("flow"): _FAMesh.flowAccumulation(self) # Perform River Incision/Deposition based on Stream Power Law (different flavors) with self.profiler.phase("erosion"): if self.cptSoil: # Non-linear coupled to soil production _soilSPL.erodepSPLsoil(self) elif self.spl_n == 1.0: # Linear slope dependencies _SPL.erodepSPL(self) else: # Non-linear slope dependencies _nlSPL.erodepSPLnl(self) # Glacial till: abrasion-produced till transported by ice and # deposited (melt-out) as moraine in the ablation zone (glacial # till only; no-op otherwise). Done before fluvial deposition # so the moraine is reworked by meltwater/rivers this step. if self.iceOn: with self.profiler.phase("till"): _IceMesh.glacialTill(self) if not self.nodep: # Downstream sediment deposition over the continents with self.profiler.phase("flow"): _FAMesh.flowAccumulation(self) with self.profiler.phase("sed"): _SEDMesh.sedChange(self) if self.seaDepo: # Downstream sediment deposition in marine environments with self.profiler.phase("sea"): _SEAMesh.seaChange(self) # Hillslope diffusion (linear and non-linear) with self.profiler.phase("hillslope"): _hillSLP.getHillslope(self) if newLayer: # Stratigraphic layer porosity and thicknesses under compaction with self.profiler.phase("strat"): _STRAMesh.getCompaction(self) # Apply flexural isostasy (local and global). With flex_interval > 1 # the load accumulates and flexure is solved only at interval ends # (interval = 1 → every step, the default/unchanged behaviour). if self.flexOn and ( self.flexCount % self.flex_interval == self.flex_interval - 1 ): with self.profiler.phase("flexure"): _GridProcess.applyFlexure(self) # Update tectonic, sea-level & climatic conditions if self.tNow < self.tEnd: with self.profiler.phase("forcing"): _UnstMesh.applyForces(self) # Advance time self.tNow += self.dt if MPIrank == 0: print( "--- Computational Step (%0.02f seconds) | Time Step: %d years" % (process_time() - tstep, self.tNow), flush=True, ) # Cross-rank wall-clock profile + machine-readable profile.json # (collective; a no-op when profiling is disabled). self.profiler.report(self.outputDir, total_wall=MPI.Wtime() - runStart) return
[docs] def destroy(self): """ Destroy PETSc DMPlex objects and associated Petsc local/global Vectors and Matrices. Safely quit model. """ # When `showlog` is set, dump the PETSc Log summary (KSP/SNES/TS solver # timings, flop counts, MPI reductions) so solver-level performance is # captured alongside the wall-clock phase profile. Best-effort: a # missing viewer/log API must not break a clean shutdown. if getattr(self, "showlog", False) and getattr(self, "log", None) is not None: try: import petsc4py viewer = petsc4py.PETSc.Viewer().createASCII( os.path.join(self.outputDir, "petsc_log.txt"), comm=petsc4py.PETSc.COMM_WORLD, ) petsc4py.PETSc.Log.view(viewer) viewer.destroy() except Exception: pass _UnstMesh.destroy_DMPlex(self) return