diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ce1d1fa6e6..1fc8abbf0fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,6 +278,28 @@ if(ASPECT_WITH_FASTSCAPE) endif() +# Fastscapelib (C++) +set(ASPECT_WITH_FASTSCAPELIB OFF CACHE BOOL "Whether the user wants to compile ASPECT with the landscape evolution C++ code Fastscape(lib), or not.") +if(ASPECT_WITH_FASTSCAPELIB) + find_package(fastscapelib CONFIG) + if(${fastscapelib_FOUND}) + set(FS_WITH_HEALPIX OFF CACHE BOOL "Turn off Fastscapelib support of Healpix grid" FORCE) + message(STATUS "Using ASPECT_WITH_FASTSCAPELIB = '${ASPECT_WITH_FASTSCAPELIB}'") + message(STATUS " fastscapelib_INCLUDE_DIR: ${fastscapelib_INCLUDE_DIRS}") + message(STATUS " fastscapelib_VERSION: ${fastscapelib_VERSION}") + # fastscapelib dependencies + find_package(xtensor REQUIRED) + message(STATUS " xtensor_INCLUDE_DIR: ${xtensor_INCLUDE_DIRS}") + message(STATUS " xtensor_VERSION: ${xtensor_VERSION}") + else() + message(STATUS "Fastscapelib not found. Disabling ASPECT_WITH_FASTSCAPELIB. You can specify a hint to your installation directory with fastscapelib_DIR.") + set(ASPECT_WITH_FASTSCAPELIB OFF CACHE BOOL "" FORCE) + endif() +else() + message(STATUS "Using ASPECT_WITH_FASTSCAPELIB = 'OFF'") +endif() + + # NetCDF (c including parallel) set(ASPECT_WITH_NETCDF ON CACHE BOOL "Check if the user wants to compile ASPECT with the NetCDF libraries.") @@ -408,6 +430,7 @@ if(ASPECT_WITH_WORLD_BUILDER) endif() message(STATUS "") +#include "datatypes.h" # # Other stuff about external tools and how we interact with the system: @@ -789,6 +812,16 @@ if (FASTSCAPE_LIB_NAME) endif() endif() +# Fastscapelib +if (${fastscapelib_FOUND}) + message(STATUS "Linking ASPECT against Fastscapelib (header-only)") + foreach(_T ${TARGET_EXECUTABLES}) + target_include_directories(${_T} PRIVATE ${fastscapelib_INCLUDE_DIRS}) + target_include_directories(${_T} PRIVATE ${xtensor_INCLUDE_DIRS}) + target_include_directories(${_T} PRIVATE ${xtl_INCLUDE_DIRS}) + endforeach() +endif() + # NetCDF if(${NETCDF_FOUND}) message(STATUS "Linking ASPECT against NetCDF") diff --git a/WITH_FASTSCAPELIB.md b/WITH_FASTSCAPELIB.md new file mode 100644 index 00000000000..5d15216f0bf --- /dev/null +++ b/WITH_FASTSCAPELIB.md @@ -0,0 +1,117 @@ +# Using Aspect with Fastscapelib (C++) + +## Install Fastscapelib using conda + +You can use conda to install fastscapelib and all of its dependencies (we assume +that you are familiar with managing conda environments): + +``` +conda install fastscapelib -c conda-forge +``` + +## Install Fastscapelib from source + +You can follow the steps below to install Fastscapelib and its dependencies from +source. + +### Set the installation prefix path for Fastscapelib and its dependencies + +Choose a common location where you want to download the source files of +Fastscapelib and its dependencies: + +``` +$ export FS_SOURCE_PATH=/path/to/fastscapelib/source +``` + +Choose a common location where you want to install Fastscapelib and its +dependencies: + +``` +export CMAKE_INSTALL_PREFIX=/path/to/fastscapelib/install/dir +``` + +### Install xtl and xtensor + +Download the `xtl` git repository and checkout the last stable version: + +``` +$ cd $FS_SOURCE_PATH +$ git clone https://github.com/xtensor-stack/xtl +$ cd xtl +$ git checkout 0.7.7 +``` + +Build and install `xtl`: + +``` +$ cmake -S. -Bbuild +$ cmake --install build +``` + +Download the `xtensor` git repository and checkout the last stable version: + +``` +$ cd $FS_SOURCE_PATH +$ git clone https://github.com/xtensor-stack/xtensor +$ cd xtensor +$ git checkout 0.25.0 +``` + +Build and install `xtensor`: + +``` +$ cmake -S. -Bbuild +$ cmake --install build +``` + +### Install Fastscapelib + +Download the `fastscapelib` git repository and checkout the last stable version: + +``` +$ cd $FS_SOURCE_PATH +$ git clone https://github.com/fastscape-lem/fastscapelib +$ cd fastscapelib +$ git checkout v0.2.2 +``` + +IMPORTANT: edit the `cmake/fastscapelibConfig.cmake` file with the editor of +your choice and add the following line just below `@PACKAGE_INIT@` (this will be +required every time you checkout another version or branch and re-install +Fastscapelib, but this won't be required for Fastscapelib version >0.2.2): + +``` +include(CMakeFindDependencyMacro) +``` + +Build and install `fastscapelib`: + +``` +$ cmake -S. -Bbuild +$ cmake --install build +``` + +## Compile Aspect with Fastscapelib enabled + +If you have installed Fastscapelib using conda, don't forget to activate the +environment where you have installed Fastscapelib. Once activated, configuring +`aspect` with CMake should then find Fastscapelib automatically. + +If you have installed Fastscapelib from source like described above, you can +help CMake find the Fastscapelib installation path using: + +``` +export CMAKE_PREFIX_PATH=/path/to/fastscapelib/install/dir +``` + +where the given path corresponds to the path that you set during installation +via the `CMAKE_INSTALL_PREFIX` environment variable (see instructions above). + +Configuring `aspect` should then find Fastscapelib. Check the output of the +following command run from aspect's source root directory (note: you might need +to set additional arguments or run extra commands in order to find the other +dependencies of `aspect`, we skipped it here): + +``` +cmake -S. -Bbuild -DASPECT_WITH_FASTSCAPELIB=ON +``` diff --git a/contrib/fastscape/fastscape_rewrite_VTK_with_time.py b/contrib/fastscape/fastscape_rewrite_VTK_with_time.py index ffde75c5bb6..f39d656de85 100644 --- a/contrib/fastscape/fastscape_rewrite_VTK_with_time.py +++ b/contrib/fastscape/fastscape_rewrite_VTK_with_time.py @@ -33,7 +33,7 @@ def get_times_pvd(filename): times[i] = float(temp_str) - return times + return times #%% Get file paths (absolute, not relative paths!) @@ -54,73 +54,73 @@ def absoluteFilePaths(directory): # trace generated using paraview version 5.8.0 # -# To ensure correct image size when batch processing, please search +# To ensure correct image size when batch processing, please search # for and uncomment the line `# renderView*.ViewSize = [*,*]` for File in FileNames: #### disable automatic camera reset on 'Show' paraview.simple._DisableFirstRenderCameraReset() - + # create a new 'Legacy VTK Reader' topography0000 = LegacyVTKReader(FileNames=File) - + # get animation scene animationScene1 = GetAnimationScene() - + # get the time-keeper timeKeeper1 = GetTimeKeeper() - + # update animation scene based on data timesteps animationScene1.UpdateAnimationUsingDataTimeSteps() - + # get active view renderView1 = GetActiveViewOrCreate('RenderView') # uncomment following to set a specific view size # renderView1.ViewSize = [882, 554] - + # get layout layout1 = GetLayout() - + # show data in view topography0000Display = Show(topography0000, renderView1, 'StructuredGridRepresentation') - + # trace defaults for the display properties. topography0000Display.Representation = 'Surface' - + # reset view to fit data renderView1.ResetCamera() - + # get the material library materialLibrary1 = GetMaterialLibrary() - + # show color bar/color legend topography0000Display.SetScalarBarVisibility(renderView1, True) - + # update the view to ensure updated data information renderView1.Update() - + # get color transfer function/color map for 'H' hLUT = GetColorTransferFunction('H') - + # get opacity transfer function/opacity map for 'H' hPWF = GetOpacityTransferFunction('H') - + # save data SaveData(File[:-4] + '.vts', proxy=topography0000, ChooseArraysToWrite=1, PointDataArrays=['HHHHH','basement','catchment','drainage_area','erosion_rate','topography','total_erosion']) - + #### saving camera placements for all active views - + # current camera placement for renderView1 renderView1.CameraPosition = [225625.0, 25625.0, 878497.0779980461] renderView1.CameraFocalPoint = [225625.0, 25625.0, 1135.7277145385742] renderView1.CameraParallelScale = 227077.82689023562 - + #### uncomment the following to render all views # RenderAllViews() # alternatively, if you want to write images, you can use SaveScreenshot(...). - + Delete(topography0000) del topography0000 @@ -140,17 +140,9 @@ def absoluteFilePaths(directory): for n, File in enumerate(FileNames): dataset_attributes = {"timestep": str(times[n]), "group": "", "group": "", "file": os.path.basename(File)[:-4]+'.vts'} - + ET.SubElement(doc, "DataSet", attrib=dataset_attributes).text="\n" tree = ET.ElementTree(root) -tree.write('./VTK/topography.pvd', xml_declaration=True, encoding='utf-8', method="xml") - - - - - - - - +tree.write('./VTK/topography.pvd', xml_declaration=True, encoding='utf-8', method="xml") diff --git a/include/aspect/compat.h b/include/aspect/compat.h index 00ae9ef9bb5..4a157671b32 100644 --- a/include/aspect/compat.h +++ b/include/aspect/compat.h @@ -213,4 +213,220 @@ namespace aspect #endif + +// deal.II version 9.7 introduces a new class VectorFunctionFromTensorFunctionObject +// that we would like to use also for earlier versions +#if !DEAL_II_VERSION_GTE(9,7,0) + +namespace aspect +{ + using namespace dealii; + + /** + * This class is built as a means of translating the Tensor<1,dim, + * RangeNumberType> values produced by function objects that + * for a given point return a tensor, + * and returning them as a multiple component version of the same thing as a + * Vector for use in, for example, the VectorTools::interpolate or the many + * other functions taking Function objects. It allows the user to place the + * desired components into an n_components long vector starting at + * the selected_component location in that vector and have all other + * components be 0. + * + * For example: Say you created a function object that returns the gravity + * (here, a radially inward pointing vector of magnitude 9.81): + * @code + * const auto gravity + * = [](const Point &p) -> Tensor<1,dim> { return -9.81 * (p / + * p.norm()); } + * @endcode + * and you want to interpolate this onto your mesh using the + * VectorTools::interpolate function, with a finite element for the + * DoFHandler object has 3 copies of a finite element with dim + * components, for a total of 3*dim components. To interpolate onto that + * DoFHandler, you need an object of type Function that has 3*dim vector + * components. Creating such an object from the existing gravity + * object is done using this piece of code: + * @code + * VectorFunctionFromTensorFunctionObject + * gravity_vector_function(gravity, 0, 3*dim); + * @endcode + * where the dim components of the `gravity` function are placed + * into the first dim components of the function object. + * + * @ingroup functions + */ + template + class VectorFunctionFromTensorFunctionObject + : public Function + { + public: + /** + * Given a function object that takes a Point and returns a + * Tensor<1,dim, RangeNumberType> value, convert this into an object + * that matches the Function@ interface. + * + * By default, create a Vector object of the same size as + * tensor_function returns, i.e., with dim components. + * + * @param tensor_function_object The TensorFunction that will form `dim` components of + * the resulting Vector Function object. + * @param n_components The total number of vector components of the + * resulting TensorFunction object. This clearly has to be at least `dim`. + * @param selected_component The first component that should be filled by + * the first argument. This should be such that the entire tensor_function + * fits inside the n_component length return vector. + */ + explicit VectorFunctionFromTensorFunctionObject( + const std::function(const Point &)> + &tensor_function_object, + const unsigned int selected_component = 0, + const unsigned int n_components = dim); + + /** + * This destructor is defined as virtual so as to coincide with all other + * aspects of class. + */ + virtual ~VectorFunctionFromTensorFunctionObject() override = default; + + /** + * Return a single component of a vector-valued function at a given point. + */ + virtual RangeNumberType + value(const Point &p, const unsigned int component = 0) const override; + + /** + * Return all components of a vector-valued function at a given point. + * + * values shall have the right size beforehand, i.e. #n_components. + */ + virtual void + vector_value(const Point &p, + Vector &values) const override; + + /** + * Return all components of a vector-valued function at a list of points. + * + * value_list shall be the same size as points and each + * element of the vector will be passed to vector_value() to evaluate the + * function + */ + virtual void + vector_value_list( + const std::vector> &points, + std::vector> &value_list) const override; + + private: + /** + * The TensorFunction object which we call when this class's vector_value() + * or vector_value_list() functions are called. + */ + const std::function(const Point &)> + tensor_function_object; + + /** + * The first vector component whose value is to be filled by the given + * TensorFunction. The values will be placed in components + * selected_component to selected_component+dim-1 for a + * TensorFunction<1,dim, RangeNumberType> object. + */ + const unsigned int selected_component; + }; + + + template + VectorFunctionFromTensorFunctionObject:: + VectorFunctionFromTensorFunctionObject( + const std::function(const Point &)> + &tensor_function_object, + const unsigned int selected_component, + const unsigned int n_components) + : Function(n_components) + , tensor_function_object(tensor_function_object) + , selected_component(selected_component) + { + // Verify that the Tensor<1,dim,RangeNumberType> will fit in the given length + // selected_components and not hang over the end of the vector. + AssertIndexRange(selected_component + dim - 1, this->n_components); + } + + + + template + inline RangeNumberType + VectorFunctionFromTensorFunctionObject::value( + const Point &p, + const unsigned int component) const + { + AssertIndexRange(component, this->n_components); + + // if the requested component is out of the range selected, then we can + // return early + if ((component < selected_component) || + (component >= selected_component + dim)) + return 0; + + // otherwise retrieve the values from the tensor_function to be + // placed at the selected_component to + // selected_component + dim - 1 elements of the Vector + // values and pick the correct one + const Tensor<1, dim, RangeNumberType> tensor_value = + tensor_function_object(p); + + return tensor_value[component - selected_component]; + } + + + template + inline void + VectorFunctionFromTensorFunctionObject::vector_value( + const Point &p, + Vector &values) const + { + Assert(values.size() == this->n_components, + ExcDimensionMismatch(values.size(), this->n_components)); + + // Retrieve the values from the tensor_function to be placed at + // the selected_component to + // selected_component + dim - 1 elements of the Vector + // values. + const Tensor<1, dim, RangeNumberType> tensor_value = + tensor_function_object(p); + + // First we make all elements of values = 0 + values = 0; + + // Second we adjust the desired components to take on the values in + // tensor_value. + for (unsigned int i = 0; i < dim; ++i) + values(i + selected_component) = tensor_value[i]; + } + + + /** + * Member function vector_value_list is the interface for giving a + * list of points (vector>) of which to evaluate + * using the vector_value member function. Again, this function is + * written so as to not replicate the function definition but passes each + * point on to vector_value to be evaluated. + */ + template + void + VectorFunctionFromTensorFunctionObject::vector_value_list( + const std::vector> &points, + std::vector> &value_list) const + { + Assert(value_list.size() == points.size(), + ExcDimensionMismatch(value_list.size(), points.size())); + + const unsigned int n_points = points.size(); + + for (unsigned int p = 0; p < n_points; ++p) + VectorFunctionFromTensorFunctionObject::vector_value( + points[p], value_list[p]); + } +} + +#endif + #endif diff --git a/include/aspect/config.h.in b/include/aspect/config.h.in index 19f699d4c41..16231850be8 100644 --- a/include/aspect/config.h.in +++ b/include/aspect/config.h.in @@ -32,6 +32,7 @@ #cmakedefine ASPECT_WITH_WORLD_BUILDER #cmakedefine ASPECT_USE_FP_EXCEPTIONS #cmakedefine ASPECT_WITH_FASTSCAPE +#cmakedefine ASPECT_WITH_FASTSCAPELIB #cmakedefine ASPECT_HAVE_FASTSCAPE_NAMED_VTK #define ASPECT_MAX_NUM_PARTICLE_SYSTEMS @ASPECT_MAX_NUM_PARTICLE_SYSTEMS@ diff --git a/include/aspect/mesh_deformation/fastscapecc.h b/include/aspect/mesh_deformation/fastscapecc.h new file mode 100644 index 00000000000..9d96bf9981c --- /dev/null +++ b/include/aspect/mesh_deformation/fastscapecc.h @@ -0,0 +1,350 @@ +/* + Copyright (C) 2023 - 2025 by the authors of the ASPECT code. + + This file is part of ASPECT. + + ASPECT is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + ASPECT is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ASPECT; see the file LICENSE. If not see + . +*/ + +#ifndef _aspect_mesh_deformation_fastscapecc_h +#define _aspect_mesh_deformation_fastscapecc_h + +#include + +#ifdef ASPECT_WITH_FASTSCAPELIB + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + + +#include + +#include +#include + +namespace aspect +{ + using namespace dealii; + + namespace MeshDeformation + { + /** + * A comparator for dealii::Point to use in std::map + * ------------------------------------------------------ + * std::map requires a way to compare keys (i.e., define a "less than" relation) + * dealii::Point does not provide operator< by default, + * so we must define one ourselves. + * + * This comparator compares two Point objects lexicographically: + * First compares x, then y (and z if dim == 3). + * + * This is required to allow using std::map, T> + * to uniquely index surface vertices on unstructured spherical meshes. + */ + template + struct PointComparator + { + bool operator()(const dealii::Point &a, const dealii::Point &b) const + { + for (unsigned int d = 0; d < dim; ++d) + { + if (a[d] < b[d]) return true; + if (a[d] > b[d]) return false; + } + return false; + } + }; + + template + void output_fastscape_visualization( + const unsigned int timestep_index, + const dealii::DoFHandler &dof_handler, + const xt::xarray &elevation, + const xt::xarray &uplift_rate, + const xt::xarray &erosion, + const xt::xarray &drainage_area, + const xt::xarray &sediment_flux, + const std::string &output_directory, + const MPI_Comm mpi_communicator, + const dealii::ConditionalOStream &pcout); + + + /** + * A plugin that utilizes the landscape evolution code Fastscapelib (C++) + * to deform the ASPECT boundary through erosion. + */ + template + class FastScapecc : public Interface, public SimulatorAccess + { + public: + /** + * Constructor. + */ + FastScapecc(); + + /** + * Initialize variables for FastScape. + */ + void initialize () override; + + /** + * A function that creates constraints for the velocity of certain mesh + * vertices (namely, the surface vertices) for a specific boundary. + * The calling class will respect + * these constraints when computing the new vertex positions. + */ + void + compute_velocity_constraints_on_boundary(const DoFHandler &mesh_deformation_dof_handler, + AffineConstraints &mesh_velocity_constraints, + const std::set &boundary_id) const override; + + + double interpolate_surface_velocity(const Point &p, + const std::vector &V) const; + /** + * Returns whether or not the plugin requires surface stabilization. + */ + bool needs_surface_stabilization () const override; + + /** + * Declare parameters for the FastScape plugin. + */ + static + void declare_parameters (ParameterHandler &prm); + + /** + * Parse parameters for the FastScape plugin. + */ + void parse_parameters (ParameterHandler &prm) override; + + + private: + + // friend class boost::serialization::access; + + /** + * For a given location, typically the location of a vertex of ASPECT's + * volume mesh, return the DoF index that corresponds to this vertex + * location within the surface_dof_handler. This index can then also + * be used to index into vectors defined on surface_dof_handler. + */ + unsigned int vertex_index(const Point &p) const; + + /* + * A map, used only for the spherical geometry, that maps the location + * of a vertex to an index between zero and the number of vertices + * in the surface mesh. + * + * TODO: This map is only used for the spherical shell geometry. We + * should prevent this object from being used altogether in all + * other cases, and this could be facilitated by wrapping it in + * std_cxx17::optional<>. Only when we encounter the spherical + * geometry do we actually set anything in the optional. + */ + std::map, unsigned int, PointComparator> spherical_vertex_index_map; + + /** + * The surface mesh is a distributed Triangulation to support large-scale + * parallel simulations (e.g., global models). + */ + using SurfaceMeshType = parallel::distributed::Triangulation; + + SurfaceMeshType surface_mesh; + DoFHandler surface_mesh_dof_handler; + mutable AffineConstraints surface_constraints; + + FE_Q surface_fe; + + /** + * Initialize the surface mesh based on the given geometry model. + */ + void init_surface_mesh(const GeometryModel::Interface &); + + /** + * Pointers to Fastscapelib objects + */ + using GridAdapterType = typename fastscapelib::dealii_grid; + using FlowGraphType = typename fastscapelib::flow_graph; + std::unique_ptr grid; + std::unique_ptr flow_graph; + std::unique_ptr> spl_eroder; + // mutable std::shared_ptr> surface_cache; + + + void project_surface_solution(const std::set &boundary_ids, + dealii::LinearAlgebra::distributed::Vector &surface_vertical_velocity, + dealii::LinearAlgebra::distributed::Vector &surface_elevation) const; + + + /** + * Project the Stokes velocity solution onto the + * free surface. Called by make_constraints() + */ + // void project_velocity_onto_boundary (const DoFHandler &free_surface_dof_handler, + // const IndexSet &mesh_locally_owned, + // const IndexSet &mesh_locally_relevant, + // LinearAlgebra::Vector &output) const; + + + /** + * Fill velocity data table to be interpolated back onto the ASPECT mesh. + */ + // Table fill_data_table(std::vector &values, + // TableIndices &size_idx, + // const int &array_size) const; + + /** + * Run-time parameters + * @{ + */ + /** + * Suggestion for the number of FastScape steps to run for every ASPECT timestep, + * where the FastScape time step is determined by ASPECT's time step length divided by + * this parameter. + */ + unsigned int fastscape_steps_per_aspect_step; + + /** + * Maximum time step allowed for FastScape. If the suggested time step exceeds this + * limit it is repeatedly divided by 2 until the final time step is smaller than this parameter. + */ + double maximum_fastscape_timestep; + + /** + * Total number of Fastscapelib grid nodes. + */ + unsigned int n_grid_nodes; + + /** + * How many levels FastScape should be refined above the maximum ASPECT surface resolution. + */ + unsigned int additional_refinement_levels; + + /** + * Maximum expected refinement level at ASPECT's surface. + * This and resolution_difference are required to properly transfer node data from + * ASPECT to FastScape. + */ + unsigned int surface_refinement_level; + + /** + * Difference in refinement levels expected at the ASPECT surface, + * where this would be set to 2 if 3 refinement levels are set at the surface. + * This and surface_resolution are required to properly transfer node data from + * ASPECT to FastScape. + * + * TODO: Should this be kept this way, or make it so the input is the expected levels + * of refinement at the surface, and we can subtract one within the code? Also, + * it would be good to find a way to check these are correct, because they are a + * common source of errors. + */ + int surface_refinement_difference; + + /** + * Magnitude (m) of the initial noise applied to FastScape. + * Applied as either a + or - value to the topography + * such that the total difference can be up to 2*noise_h. + */ + double noise_h; + + // FastScape erosional parameters // + /** + * Drainage area exponent for the stream power law (m variable in FastScape surface equation). + */ + double m; + + /** + * Slope exponent for the steam power law (n variable in FastScape surface equation). + */ + double n; + + /** + * Bedrock river incision rate for the stream power law + * (meters^(1-2m)/yr, kf variable in FastScape surface equation). + */ + double kff; + + /** + * Sediment river incision rate for the stream power law (meters^(1-2m)/yr). + * When set to -1 this is identical to the bedrock value. + * (kf variable in FastScape surface equation applied to sediment). + */ + double kfsed; + + /** + * Bedrock transport coefficient for hillslope diffusion (m^2/yr, kd in FastScape surface equation). + */ + double kdd; + + /** + * Bedrock transport coefficient for hillslope diffusion (m^2/yr). When set to -1 this is + * identical to the bedrock value. + * (kd in FastScape surface equation applied to sediment). + */ + double kdsed; + /** + * @} + */ + + + // Grid extent in each direction [min, max] + // std::array, 2> grid_extent_surface; + + std::array,dim> box_grid_extent; + + + // Grid resolution (spacing between points) + double dx; + double dy; + + // Number of grid cells in x and y directions + unsigned int nx; + unsigned int ny; + + mutable bool elevation_initialized = false; + mutable xt::xarray elevation; + + /** + * The number of cells in each coordinate direction. + */ + std::array repetitions; + std::array, dim - 1> grid_extent_surface; + + // A way to ouput internal fastscape iteration for visualisation + bool output_internal_fastscape_steps; + + + }; + } +} + +#endif // #ifdef ASPECT_WITH_FASTSCAPELIB + +#endif diff --git a/include/aspect/mesh_deformation/fastscapecc_adapter.h b/include/aspect/mesh_deformation/fastscapecc_adapter.h new file mode 100644 index 00000000000..cf18b867db7 --- /dev/null +++ b/include/aspect/mesh_deformation/fastscapecc_adapter.h @@ -0,0 +1,394 @@ +/* + Copyright (C) 2011 - 2024 by the authors of the ASPECT code. + + This file is part of ASPECT. + + ASPECT is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + ASPECT is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ASPECT; see the file LICENSE. If not see + . + */ + +#ifndef _aspect_mesh_deformation_fastscapecc_adapter_h +#define _aspect_mesh_deformation_fastscapecc_adapter_h + +#include + +#ifdef ASPECT_WITH_FASTSCAPELIB + +#include +#include + +// Include the Xtensor configuration file so that we can query the +// Xtensor version number below to address the fact that between +// 0.25.0 and 0.26.0, all Xtensor include files were moved around, +// see https://github.com/xtensor-stack/xtensor/pull/2829 and the +// complaints therein. +#if __has_include() // 0.26.0 and later +# include +#else // 0.25.0 and earlier +# include +#endif + +#if XTENSOR_VERSION_MAJOR ==0 && XTENSOR_VERSION_MINOR <= 25 +# include +# include +# include +# include +#else +# include +# include +# include +# include +#endif + +#include + +// Up until FastScape++ 0.2.2, there was a file xtensor_utils.hpp that declared +// alias types that are used elsewhere. After that, the file went away, see +// https://github.com/fastscape-lem/fastscapelib/pull/155 +// but we still need the contents and so have to declare them ourselves. +#include +#if FASTSCAPELIB_VERSION_MAJOR*10000 + FASTSCAPELIB_VERSION_MINOR*100 + FASTSCAPELIB_VERSION_PATCH <= 202 +# include +#else + +namespace fastscapelib +{ + /** + * Used to get the actual xtensor container type from a given selector. + * + * @tparam S The xtensor selector type. + * @tparam T The container value type. + * @tparam N The number of dimensions (only for static dimension containers) + */ + template + struct xt_container + { + }; + + template + struct xt_container + { + using tensor_type = xt::xtensor; + using array_type = xt::xarray; + }; + + /** + * Alias for the selected (static dimension) xtensor container type. + * + * @tparam S The xtensor selector type. + * @tparam T The container value type. + * @tparam N The fixed number of dimensions. + */ + template + using xt_tensor_t = typename xt_container::tensor_type; + + /** + * Alias for the selected (dynamic dimension) xtensor container type. + * + * @tparam S The xtensor selector type. + * @tparam T The container value type. + */ + template + using xt_array_t = typename xt_container::array_type; + +} // namespace fastscapelib +#endif + + +namespace fastscapelib +{ + template + class dealii_grid; + + /** + * Dealii grid specialized types + */ + template + struct grid_inner_types> + { + static constexpr bool is_structured = false; + static constexpr bool is_uniform = false; + + using grid_data_type = double; + + using xt_selector = S; + static constexpr std::size_t xt_ndims = 1; + + static constexpr uint8_t n_neighbors_max = 8; + using neighbors_cache_type = neighbors_no_cache; + }; + + /** + * @brief Grid adapter class for using a DEAL.II surface or boundary mesh with + * Fastscapelib + * + * This adapter should work with various kinds of DEAL.II meshes (e.g., box, + * sphere, etc.). The only requirement is that the mesh must be uniform (i.e., + * all cells have an equal area) at a given refinement level. + * + * Note: a ``dealii_grid`` node cooresponds to a DEAL.II mesh cell. + * + * @tparam T The DEAL.II mesh type. + * @tparam S The container selector for data array members. + */ + template + class dealii_grid : public grid> + { + public: + using self_type = dealii_grid; + using base_type = grid; + using inner_types = grid_inner_types; + + using grid_data_type = typename base_type::grid_data_type; + + using xt_selector = typename base_type::xt_selector; + using container_type = xt_tensor_t; + using neighbors_type = typename base_type::neighbors_type; + using neighbors_indices_type = typename base_type::neighbors_indices_type; + using neighbors_distances_type = typename base_type::neighbors_distances_type; + + using nodes_status_type = typename base_type::nodes_status_type; + using nodes_status_array_type = xt_tensor_t; + + using size_type = typename base_type::size_type; + using shape_type = typename base_type::shape_type; + + dealii_grid(T &triangulation, double cell_area); + + void set_nodes_status(const nodes_status_array_type &nodes_status); + + protected: + T &m_triangulation; + + shape_type m_shape; + size_type m_size; + double m_node_area; + + nodes_status_type m_nodes_status; + + using neighbors_distances_impl_type = typename base_type::neighbors_distances_impl_type; + using neighbors_indices_impl_type = typename base_type::neighbors_indices_impl_type; + + std::vector m_neighbors_count; + std::vector m_neighbors_indices; + std::vector m_neighbors_distances; + + void compute_connectivity(); + + inline container_type nodes_areas_impl() const; + inline grid_data_type nodes_areas_impl(const size_type &idx) const noexcept; + + inline size_type neighbors_count_impl(const size_type &idx) const; + + void neighbors_indices_impl(neighbors_indices_impl_type &neighbors, + const size_type &idx) const; + + inline const neighbors_distances_impl_type &neighbors_distances_impl( + const size_type &idx) const; + + static constexpr std::size_t dimension_impl() noexcept; + + friend class grid; + }; + + + /** + * Creates a new adapter from an existing DEAL.II surface mesh. + * + * @param triangulation The DEAL.II triangulation object. + * @param cell_area the uniform area of the mesh cells. + */ + template + dealii_grid::dealii_grid(T &triangulation, double cell_area) + : base_type(0) + , m_triangulation(triangulation) + , m_node_area(cell_area) + { + + m_size = triangulation.n_global_active_cells(); + m_shape = {{ static_cast(m_size) }}; + + // pre-compute explicit grid connectivity + compute_connectivity(); + + // Set all grid nodes as "active" + // - Fastscapelib doesn't support yet labelling grid nodes as ghost nodes + // - we assume that all the erosion processes that we apply here depend on flow routing + // (i.e., they rely on a `fastscapelib::flow_graph` object built on top of this grid, + // such as `fastscapelib::spl_eroder`) + // - It is better (more explicit) to set base level nodes via the flow graph + // object (i.e., `fastscapelib::flow_graph::set_base_levels`) + // - Masks (e.g., oceans) can be defined via the flow graph too + // (i.e., `fastscapelib::flow_graph::set_mask`) + // - TODO: periodic boundary conditions (if any) should be handled in `compute_connectivity` + // (there is no need to label nodes as periodic boundaries since the grid connectivity + // is computed explicitly here) + // m_nodes_status = node_status::core; + m_nodes_status = xt::xarray::from_shape({m_size}); + std::fill(m_nodes_status.begin(), m_nodes_status.end(), node_status::core); + + + } + + template + void dealii_grid::set_nodes_status(const nodes_status_array_type &nodes_status) + { + if (!xt::same_shape(nodes_status.shape(), m_shape)) + { + throw std::invalid_argument( + "invalid shape for nodes_status array (expects shape [N] where N is the total number of nodes)"); + } + m_nodes_status = nodes_status; + } + +// template +// void dealii_grid::compute_connectivity() +// { +// m_neighbors_count.resize(m_size); +// m_neighbors_indices.resize(m_size); +// m_neighbors_distances.resize(m_size); +// unsigned int counter = 0; +// std::unordered_map global_to_local_index; +// unsigned int local_index = 0; +// // for (const auto &cell : m_triangulation.get_dof_handler().active_cell_iterators()) +// for (const auto &cell : m_triangulation.active_cell_iterators()) +// { +// m_neighbors_count[counter] = cell->n_faces(); +// auto center = cell.center(); +// for (auto face_i = 0; face_i < m_neighbors_count[counter]; ++face_i) +// { +// unsigned int global_neighbor_index = 0; +// for (const auto &neighbor_cell : cell.neighbor(face_i)) +// { +// global_neighbor_index = neighbor_cell.global_active_cell_index(); +// m_neighbors_distances[counter][face_i] = center.distance(neighbor_cell.center()); +// // TODO: I assume we only have one neighbor per face, since we start with a uniform grid, we will only have one, but cwe could generatlize this +// } +// // TODO: add assert to check if neighbor_index is correclty set +// global_to_local_index.emplace(global_neighbor_index, counter); +// } + +// counter++; +// } +// counter = 0; +// // for (const auto &cell : this->get_dof_handler().active_cell_iterators()) +// for (const auto &cell : m_triangulation.active_cell_iterators()) +// { +// for (auto k = 0; k <= m_neighbors_count[counter]; k++) +// { +// auto global_nb_index = m_neighbors_indices[counter][k]; +// m_neighbors_indices[counter][k] = global_to_local_index.at(global_nb_index); +// } +// counter++; +// } +// } + + + template + void fastscapelib::dealii_grid::compute_connectivity() + { + // Resize internal storage for neighbor counts, indices, and distances + m_neighbors_count.resize(m_size); + m_neighbors_indices.resize(m_size); + m_neighbors_distances.resize(m_size); + + // Step 1: Map global active cell indices to local indices + // This is necessary to translate global indices into local array indices + std::unordered_map global_to_local_index; + unsigned int local_index = 0; + for (const auto &cell : m_triangulation.active_cell_iterators()) + { + global_to_local_index[cell->global_active_cell_index()] = local_index++; + } + + // Step 2: Loop through each active cell and determine valid neighbors + local_index = 0; + for (const auto &cell : m_triangulation.active_cell_iterators()) + { + const auto ¢er = cell->center(); // Get cell center for distance computation + unsigned int valid_neighbors = 0; + + for (unsigned int face_i = 0; face_i < cell->n_faces(); ++face_i) + { + // Only consider internal faces with valid neighbor + // if (!cell->at_boundary(face_i) && cell->neighbor_index_is_valid(face_i)) + if (!cell->at_boundary(face_i) && cell->neighbor(face_i).state() == dealii::IteratorState::valid) + { + const auto &neighbor = cell->neighbor(face_i); + const unsigned int global_nb_index = neighbor->global_active_cell_index(); + + auto it = global_to_local_index.find(global_nb_index); + AssertThrow(it != global_to_local_index.end(), + aspect::ExcMessage("Neighbor index not found in global_to_local_index map.")); + + + // Fill neighbor index and distance arrays + m_neighbors_indices[local_index][valid_neighbors] = it->second; + m_neighbors_distances[local_index][valid_neighbors] = center.distance(neighbor->center()); + + ++valid_neighbors; + } + } + + // Save how many neighbors this node has + m_neighbors_count[local_index] = valid_neighbors; + + ++local_index; + } + } + + + template + inline auto dealii_grid::nodes_areas_impl() const -> container_type + { + return xt::broadcast(m_node_area, m_shape); + } + + template + inline auto dealii_grid::nodes_areas_impl(const size_type & /*idx*/) const noexcept + -> grid_data_type + { + return m_node_area; + } + + template + inline auto dealii_grid::neighbors_count_impl(const size_type &idx) const -> size_type + { + return m_neighbors_count[idx]; + } + + template + void dealii_grid::neighbors_indices_impl(neighbors_indices_impl_type &neighbors, + const size_type &idx) const + { + const auto &size = m_neighbors_count[idx]; + + for (size_type i = 0; i < size; i++) + { + neighbors[i] = m_neighbors_indices[idx][i]; + } + } + + template + auto dealii_grid::neighbors_distances_impl(const size_type &idx) const + -> const neighbors_distances_impl_type & + { + return m_neighbors_distances[idx]; + } +} + +#endif // #ifdef ASPECT_WITH_FASTSCAPELIB + + +#endif // _aspect_mesh_deformation_fastscapecc_adapter_h diff --git a/source/mesh_deformation/fastscapecc.cc b/source/mesh_deformation/fastscapecc.cc new file mode 100644 index 00000000000..b0cd79e4e80 --- /dev/null +++ b/source/mesh_deformation/fastscapecc.cc @@ -0,0 +1,1146 @@ +/* + Copyright (C) 2011 - 2022 by the authors of the ASPECT code. + + This file is part of ASPECT. + + ASPECT is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + ASPECT is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ASPECT; see the file LICENSE. If not see + . + */ + +#include + +#ifdef ASPECT_WITH_FASTSCAPELIB + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // For Utilities::MPI::this_mpi_process +#include // For Utilities::create_directory +#include // C++17 or later + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + + + +namespace aspect +{ + namespace MeshDeformation + { + template + FastScapecc::FastScapecc() + : + surface_mesh(MPI_COMM_WORLD), + surface_fe(1) // Use a Q1 element for the surface mesh + // , surface_mesh_dof_handler(surface_mesh) // Link DoFHandler to surface_mesh + {} + + struct ValuesAtSurfaceVertex + { + double topography; + double surface_uplift_rate; + + template + void serialize(Archive &ar, const unsigned int /*version*/) + { + ar & topography; + ar & surface_uplift_rate; + } + }; + + template + void FastScapecc::initialize() + { + const auto &geom_model = this->get_geometry_model(); + init_surface_mesh(geom_model); + + //Fastscapelib operates on grid nodes, each of which conceptually represents a cell center + //and has associated area and neighbor connections. + // n_grid_nodes = surface_mesh.n_active_cells(); + n_grid_nodes = surface_mesh.n_used_vertices(); + } + + template + void FastScapecc::init_surface_mesh(const GeometryModel::Interface &geom_model) + { + // First create the surface triangulation objects. This is necessarily different + // between geometries. + if (const auto *box = dynamic_cast *>(&geom_model)) + { + this->get_pcout() << "Box geometry detected. Initializing FastScape for Box geometry..." << std::endl; + + // Get origin and extent of the ASPECT domain + const auto origin = box->get_origin(); + const auto extent = box->get_extents(); + + for (unsigned int i = 0; i < dim; ++i) + { + box_grid_extent[i].first = origin[i]; + box_grid_extent[i].second = origin[i] + extent[i]; + } + + // Build surface mesh corners from grid extent + Point p1, p2; + for (unsigned int i = 0; i < dim - 1; ++i) + { + p1[i] = box_grid_extent[i].first; + p2[i] = box_grid_extent[i].second; + } + + // Extract surface repetitions from full domain repetitions + std::vector surface_repetitions(repetitions.begin(), repetitions.begin() + (dim - 1)); + + // Apply surface refinement factor + const unsigned int total_refinement = surface_refinement_level + additional_refinement_levels; + const unsigned int refinement_factor = static_cast(std::pow(2.0, total_refinement)); + + for (unsigned int i = 0; i < dim - 1; ++i) + surface_repetitions[i] *= refinement_factor; + + // Store grid dimensions for indexing and spacing + nx = surface_repetitions[0]+1; + ny = surface_repetitions[1]+1; + dx = (box_grid_extent[0].second - box_grid_extent[0].first) / static_cast(nx-1); + dy = (box_grid_extent[1].second - box_grid_extent[1].first) / static_cast(ny-1); + + // Create refined surface mesh + GridGenerator::subdivided_hyper_rectangle(surface_mesh, + surface_repetitions, + p1, + p2, + /* colorize */ true); + Assert (surface_mesh.n_used_vertices() == nx*ny, ExcInternalError()); + } + else if (const auto *spherical_shell = dynamic_cast *>(&geom_model)) + { + this->get_pcout() << "Spherical Shell geometry detected. Initializing FastScape for Spherical Shell geometry..." << std::endl; + + const Point center; + GridGenerator::hyper_sphere(surface_mesh, center, spherical_shell->outer_radius()); + surface_mesh.refine_global(3); // Adjust as needed + + // Create a unique index for each surface vertex + unsigned int counter = 0; + for (const auto &cell : surface_mesh.active_cell_iterators()) + for (unsigned int v = 0; v < GeometryInfo::vertices_per_cell; ++v) + { + const Point &vertex = cell->vertex(v); + if (spherical_vertex_index_map.find(vertex) == spherical_vertex_index_map.end()) + { + spherical_vertex_index_map[vertex] = counter++; + } + } + + n_grid_nodes = spherical_vertex_index_map.size(); + } + else + AssertThrow(false, ExcMessage("FastScapecc plugin only supports Box or Spherical Shell geometries.")); + + // Having create the mesh, now set up the DoFHandlers and constraints objects + surface_mesh_dof_handler.reinit(surface_mesh); + surface_mesh_dof_handler.distribute_dofs(surface_fe); + + surface_constraints.clear(); + DoFTools::make_hanging_node_constraints(surface_mesh_dof_handler, surface_constraints); + surface_constraints.close(); + } + + + + template + void FastScapecc::project_surface_solution(const std::set & /*boundary_ids*/, + dealii::LinearAlgebra::distributed::Vector &surface_vertical_velocity, + dealii::LinearAlgebra::distributed::Vector &surface_elevation ) const + { + TimerOutput::Scope timer_section(this->get_computing_timer(), "Project surface solution"); + + const auto *spherical_model = dynamic_cast *>(&this->get_geometry_model()); + const auto *box_model = dynamic_cast *>(&this->get_geometry_model()); + + AssertThrow(spherical_model || box_model, + ExcMessage("FastScapecc only supports Box or SphericalShell geometries.")); + + // Initialize the surface solution vector on the FastScape surface mesh + const IndexSet locally_relevant_dofs_surface + = DoFTools::extract_locally_relevant_dofs(surface_mesh_dof_handler); + + surface_vertical_velocity.reinit(surface_mesh_dof_handler.locally_owned_dofs(), + locally_relevant_dofs_surface, + this->get_mpi_communicator()); + + surface_elevation.reinit(surface_mesh_dof_handler.locally_owned_dofs(), + locally_relevant_dofs_surface, + this->get_mpi_communicator()); + const types::boundary_id top_boundary = + this->get_geometry_model().translate_symbolic_boundary_name_to_id("top"); + // std::cout<<"here it works 0a"<get_dof_handler().active_cell_iterators()) + if (volume_cell->is_locally_owned()) + { + for (unsigned int face = 0; face < volume_cell->n_faces(); ++face) + { + if (!volume_cell->face(face)->at_boundary() || + volume_cell->face(face)->boundary_id() != top_boundary) + continue; + + for (unsigned int v = 0; v < volume_cell->face(face)->n_vertices(); ++v) + { + // std::cout<<"here it works 0a"< pos = volume_cell->face(face)->vertex(v); + // std::cout<<"here it works 0b"< velocity; + for (unsigned int d = 0; d < dim; ++d) + { + // Query velocity Dofs using introspection + const unsigned int component_index = this->introspection().component_indices.velocities[d]; + velocity[d] = this->get_solution()[volume_cell->face(face)->vertex_dof_index(v, component_index)]; + } + // std::cout<<"here it works 0c"< gravity = this->get_gravity_model().gravity_vector(pos); + Tensor<1, dim> gravity_dir = gravity; + + const double gravity_norm = gravity.norm(); + if (gravity_norm > 0.0) + gravity_dir /= gravity_norm; + // std::cout<<"here it works 0d"<get_geometry_model().height_above_reference_surface(pos); + const unsigned int index = this->vertex_index(pos); + // std::cout<<"here it works 0e"<get_pcout() << "Projected surface solution onto FastScape mesh." << std::endl; + + } + + template + unsigned int FastScapecc::vertex_index(const Point &p) const + { + if (const auto *box_model = dynamic_cast *>(&this->get_geometry_model())) + { + if constexpr (dim == 3) + { + const unsigned int i =std::min(static_cast((p[0] - box_grid_extent[0].first) / dx + 0.5), nx - 1); + const unsigned int j =std::min(static_cast((p[1] - box_grid_extent[1].first) / dy + 0.5), ny - 1); + const unsigned int index = j * nx + i; + + // std::cout << "[DEBUG vertex_index] p: (" << x << ", " << y << "), " + // << "box_grid_extent x0: " << x0 << ", y0: " << y0 << ", " + // << "dx: " << dx << ", dy: " << dy << ", " + // << "i: " << i << ", j: " << j << ", nx: " << nx << ", " + // << "index: " << index << std::endl; + + return index; + } + else if constexpr (dim == 2) + { + const double x = p[0]; + const double x0 = box_grid_extent[0].first; + const unsigned int i = static_cast((x - x0) / dx + 0.5); + + std::cout << "[DEBUG vertex_index 2D] p: " << x + << ", box_grid_extent x0: " << x0 + << ", dx: " << dx << ", i: " << i << std::endl; + + return i; + } + } + else if (const auto *spherical_model = dynamic_cast *>(&this->get_geometry_model())) + { + const auto it = spherical_vertex_index_map.find(p); + AssertThrow(it != spherical_vertex_index_map.end(), + ExcMessage("Point not found in spherical_vertex_index_map.")); + return it->second; + } + else + { + AssertThrow(false, ExcMessage("Unsupported geometry in vertex_index().")); + return numbers::invalid_unsigned_int; + } + } + + + // Make visualisation its own function for later + // template + // void output_fastscape_visualization( + // const unsigned int timestep_index, + // const dealii::DoFHandler &dof_handler, + // const xt::xarray &elevation, + // const xt::xarray &uplift_rate, + // const xt::xarray &erosion, + // const xt::xarray &drainage_area, + // const xt::xarray &sediment_flux, + // const std::string &output_directory, + // const MPI_Comm mpi_communicator, + // const dealii::ConditionalOStream &pcout) + // { + // if (!std::filesystem::exists(output_directory)) + // std::filesystem::create_directories(output_directory); + + // // Flatten in case they're 2D arrays + // const auto elev_flat = xt::flatten(elevation); + // const auto uplift_flat = xt::flatten(uplift_rate); + // const auto erosion_flat = xt::flatten(erosion); + // const auto drainage_flat = xt::flatten(drainage_area); + // const auto sediment_flat = xt::flatten(sediment_flux); + + // AssertThrow(elev_flat.size() == uplift_flat.size() && + // elev_flat.size() == erosion_flat.size() && + // elev_flat.size() == drainage_flat.size() && + // elev_flat.size() == sediment_flat.size(), + // dealii::ExcMessage("All FastScape input arrays must have same number of elements.")); + + // const unsigned int n_grid_nodes = elev_flat.size(); + + // // === Prepare deal.II vectors === + // dealii::Vector elevation_output(n_grid_nodes); + // dealii::Vector uplift_rate_output(n_grid_nodes); + // dealii::Vector erosion_output(n_grid_nodes); + // dealii::Vector drainage_area_output(n_grid_nodes); + // dealii::Vector sediment_flux_output(n_grid_nodes); + + // for (unsigned int j = 0; j < n_grid_nodes; ++j) + // { + // elevation_output[j] = elev_flat(j); + // uplift_rate_output[j] = uplift_flat(j); + // erosion_output[j] = erosion_flat(j); + // drainage_area_output[j] = drainage_flat(j); + // sediment_flux_output[j] = sediment_flat(j); + // } + + // // === Build VTU file === + // dealii::DataOut data_out; + // data_out.attach_dof_handler(dof_handler); + // data_out.add_data_vector(elevation_output, "Elevation"); + // data_out.add_data_vector(uplift_rate_output, "UpliftRate"); + // data_out.add_data_vector(erosion_output, "Erosion"); + // data_out.add_data_vector(drainage_area_output, "DrainageArea"); + // data_out.add_data_vector(sediment_flux_output, "SedimentFlux"); + // data_out.build_patches(); + + // const unsigned int mpi_rank = dealii::Utilities::MPI::this_mpi_process(mpi_communicator); + // const unsigned int mpi_size = dealii::Utilities::MPI::n_mpi_processes(mpi_communicator); + + // const std::string basename = "fastscape_timestep_" + dealii::Utilities::int_to_string(timestep_index, 4); + // const std::string filename_base = output_directory + "/" + basename; + // const std::string vtu_filename = filename_base + "." + dealii::Utilities::int_to_string(mpi_rank, 4) + ".vtu"; + + // { + // std::ofstream output(vtu_filename); + // data_out.write_vtu(output); + // } + + // if (mpi_rank == 0) + // { + // // Write .pvtu file + // std::vector vtu_filenames; + // for (unsigned int r = 0; r < mpi_size; ++r) + // vtu_filenames.push_back(basename + "." + dealii::Utilities::int_to_string(r, 4) + ".vtu"); + + // const std::string pvtu_filename = filename_base + ".pvtu"; + // { + // std::ofstream pvtu(pvtu_filename); + // data_out.write_pvtu_record(pvtu, vtu_filenames); + // } + + // // Append or create .pvd file + // const std::string pvd_filename = output_directory + "/fastscape_timeseries.pvd"; + // std::vector existing_entries; + + // if (std::filesystem::exists(pvd_filename)) + // { + // std::ifstream in(pvd_filename); + // std::string line; + // while (std::getline(in, line)) + // if (line.find(""; + // existing_entries.push_back(new_entry.str()); + + // std::ofstream pvd_file(pvd_filename); + // pvd_file << "\n" + // << "\n" + // << " \n"; + // for (const auto &entry : existing_entries) + // pvd_file << entry << "\n"; + // pvd_file << " \n\n"; + // } + + // pcout << "✅ Wrote FastScape output for timestep " << timestep_index << ".\n"; + // } + + + + template + void FastScapecc::compute_velocity_constraints_on_boundary( + const DoFHandler &mesh_deformation_dof_handler, + AffineConstraints &mesh_velocity_constraints, + const std::set &boundary_ids) const + { + if (this->get_timestep_number() == 0) + return; + + TimerOutput::Scope timer_section(this->get_computing_timer(), "FastScape plugin"); + + // Step 1: Project current surface velocity from ASPECT solution + dealii::LinearAlgebra::distributed::Vector surface_vertical_velocity, surface_elevation; + // std::cout<<"here it works 0"<project_surface_solution(boundary_ids, surface_vertical_velocity, surface_elevation); + // std::cout<<"here it works 1"< *>(&this->get_geometry_model()); + const auto *box_model = dynamic_cast *>(&this->get_geometry_model()); + AssertThrow(spherical_model || box_model, ExcMessage("Unsupported geometry model in FastScapecc.")); + +// TODO: You are using these three arrays in the following way: +// temp[0][i] = topography value at some location +// temp[1][i] = DoF index within surface_dof_handler for this location +// temp[2][i] = surface_uplift_rate for this location +// I think this would be more transparent if you had the following: +// struct ValuesAtSurfaceVertex +// { +// double topography; +// double surface_uplift_rate; +// }; +// and then you store everything in a +// std::multimap surface_vertex_to_surface_values; +// Note that here I chose a multimap because you visit vertices more than once from all of +// the adjacent cells, at least in the current code you enter the same vertex into +//the temp vectors multiple times. I *think* that you should get the same values every +//time you visit a vertex, so perhaps a std::map is a better choice. + std::multimap surface_vertex_to_surface_values; + + for (const auto &surface_cell : surface_mesh_dof_handler.active_cell_iterators()) + { + for (unsigned int vertex_index = 0; vertex_index < surface_cell->n_vertices(); ++vertex_index) + { + const Point vertex = surface_cell->vertex(vertex_index); + const types::global_dof_index dof_index = surface_cell->vertex_dof_index(vertex_index, 0); + + const double surface_uplift_rate = surface_vertical_velocity[dof_index]; + const double topography = surface_elevation[dof_index]; + + // Store values in the map + surface_vertex_to_surface_values.emplace(dof_index, ValuesAtSurfaceVertex{ + .topography = topography, + .surface_uplift_rate = surface_uplift_rate * year_in_seconds + }); + } + } + + std::vector V(n_grid_nodes); + + // Send local data to rank 0 if not rank 0. Utilities::MPI::isend() is + // in reality a non-blocking send, but we have nothing to do and so + // may as well wait for the results to be send -- this happens + // implicitly by ignoring the returned Utilities::MPI::Future object. + if (Utilities::MPI::this_mpi_process(this->get_mpi_communicator()) != 0) + { + Utilities::MPI::isend(surface_vertex_to_surface_values, + this->get_mpi_communicator(), + /* target = */ 0, + /* mpi_tag = */ 42); + } + + // Rank 0 gathers and processes everything + if (Utilities::MPI::this_mpi_process(this->get_mpi_communicator()) == 0) + { + std::multimap all_surface_data = surface_vertex_to_surface_values; + + for (unsigned int p = 1; + p < Utilities::MPI::n_mpi_processes(this->get_mpi_communicator()); + ++p) + { + std::multimap received_map = + Utilities::MPI::irecv>( + this->get_mpi_communicator(), p, 42).get(); + + all_surface_data.insert(received_map.begin(), received_map.end()); + } + + + std::vector h(n_grid_nodes, 0.0); + std::vector vz(n_grid_nodes, 0.0); + std::vector count(n_grid_nodes, 0); + + for (const auto &[index, values] : all_surface_data) + { + if (index < n_grid_nodes) // protect against out-of-range indexing + { + h[index] += values.topography; + vz[index] += values.surface_uplift_rate; + count[index] += 1; + } + } + + for (unsigned int i = 0; i < n_grid_nodes; ++i) + { + if (count[i] > 0) + { + h[i] /= count[i]; + vz[i] /= count[i]; + } + else + { + h[i] = std::numeric_limits::quiet_NaN(); + vz[i] = 0.0; + } + } + + +// TODO: This will then simply be a loop over all elements of the map/multimap above. +// Note that right now you encounter the same vertex multiple times, and you simply +// overwrite early content with later content (which is ok). + + // Save the current elevation so that we can later take the difference to + // the one after calling FastScape: + const std::vector h_old = h; + + // std::cout<<"here it works 11"<get_timestep() / year_in_seconds; + + unsigned int fastscape_iterations = fastscape_steps_per_aspect_step; + double fastscape_timestep_in_years = aspect_timestep_in_years / fastscape_iterations; + while (fastscape_timestep_in_years > maximum_fastscape_timestep) + { + fastscape_iterations *= 2; + fastscape_timestep_in_years *= 0.5; + } + + std::cout << "[DEBUG] fastscape_steps_per_aspect_step = " << fastscape_steps_per_aspect_step << std::endl; + std::cout << "[DEBUG] aspect_timestep_in_years = " << aspect_timestep_in_years << std::endl; + std::cout << "[DEBUG] fastscape_iterations = " << fastscape_iterations << std::endl; + + +// TODO: This is a pretty arbitrary cell, with a pretty arbitrary cell diameter. Is that what you +// want? If so, perhaps say so in a comment and explain what it is used for. + const double surface_cell_area = surface_mesh.begin_active()->measure(); + + // 1. Create the FastScape grid adapter +// TODO: I haven't looked at the GridAdapter yet, but we should see whether we can't get rid of the const_cast. + auto grid = GridAdapterType(const_cast(surface_mesh), surface_cell_area); + auto shape = grid.shape(); // Should be {n_grid_nodes} + + // 2. Define the node status array with correct shape and type + xt::xtensor node_status_array(shape); + std::fill(node_status_array.begin(), node_status_array.end(), fastscapelib::node_status::core); + grid.set_nodes_status(node_status_array); + + // // 3. Create the flow graph + // auto flow_graph = FlowGraphType( + // grid, + // { fastscapelib::single_flow_router(), fastscapelib::MSTSinkResolver()} + // ); + + auto flow_graph = FlowGraphType( + grid, + { + std::make_shared(), + std::make_shared() + } +); + + // 4. Set base level nodes + std::vector base_level_nodes = {0}; + flow_graph.set_base_levels(base_level_nodes); + + // 5. Build the erosion model + fastscapelib::spl_eroder spl_eroder + = + // fastscapelib::make_spl_eroder(flow_graph, kff, m, n, 1e-5) + fastscapelib::make_spl_eroder(flow_graph, 5e-4, 0.4, 1, 1e-5); + std::cout << "KFF : " << kff<< std::endl; + + //Only for raster grid + //To propose if we add back the raster grid option later + // auto diffusion_eroder = fastscapelib::make_diffusion_adi_eroder(grid, kdd); + + // Create an xarray with correct shape_vec + xt::xarray uplift_rate_vec = xt::zeros({vz.size()}); + for (std::size_t i = 0; i < vz.size(); ++i) + uplift_rate_vec[i] = vz[i] / year_in_seconds; + + // Reshape uplift_rate to match grid shape (same as elevation) + auto uplift_rate = xt::adapt(uplift_rate_vec.data(), shape); + //To test with a uplift_rate of zero + // uplift_rate.fill(0.0); + + std::cout<<"Vsize: " << vz.size()<< ", Gridsize: "<< grid.size()< drainage_area = xt::zeros(shape); + xt::xarray sediment_flux = xt::zeros(shape); + xt::xarray spl_erosion = xt::zeros(shape); + xt::xarray uplifted_elevation= xt::zeros(shape); + + + std::cout << "\n[DEBUG INIT] Max elevation = " << xt::amax(elevation)() + << ", Min = " << xt::amin(elevation)() << std::endl; + std::cout << "[DEBUG INIT] Max uplift rate = " << xt::amax(uplift_rate)() + << ", Min = " << xt::amin(uplift_rate)() << std::endl; + + + // TODO: Is the comment wrong? We're not at the initial state any more, right? + + // TODO: In the long run (perhaps not right away), we should adopt a system like many of + // the other plugins where we don't output information in *every* time step, but only + // when requested. + + + // Call the visualization function + // const unsigned int timestep_number = this->get_timestep_number(); + + // if (timestep_number == 1) + // { + // std::cout << "[DEBUG] Reset elevation from ASPECT before first FastScape output.\n"; + // output_fastscape_visualization( + // timestep_number, + // surface_mesh_dof_handler, + // elevation, + // uplift_rate, + // spl_erosion, + // drainage_area, + // sediment_flux, + // this->get_output_directory() + "/fastscapeCC", + // this->get_mpi_communicator(), + // this->get_pcout()); + // } + + // // --- FASTSCAPE LOOP --- + // for (unsigned int i = 0; i < fastscape_iterations; ++i) + // { + // std::cout << "\nFastScape iteration " << (i+1) << "/" << fastscape_iterations << std::endl; + + // // Uplift + // uplifted_elevation = elevation + fastscape_timestep_in_years * uplift_rate; + + // std::cout << "[DEBUG] uplifted_elevation max = " << xt::amax(uplifted_elevation)() + // << ", min = " << xt::amin(uplifted_elevation)() << std::endl; + + // // Flow routing + // flow_graph.update_routes(uplifted_elevation); + // flow_graph.accumulate(drainage_area, 1.0); + + // std::cout << "[DEBUG] drainage_area max = " << xt::amax(drainage_area)() + // << ", min = " << xt::amin(drainage_area)() << std::endl; + + // // Stream Power Law erosion + // auto spl_erosion = spl_eroder.erode(uplifted_elevation, drainage_area, fastscape_timestep_in_years); + + // std::cout << "[DEBUG] spl_erosion max = " << xt::amax(spl_erosion)() + // << ", min = " << xt::amin(spl_erosion)() << std::endl; + + // // Apply erosion + // sediment_flux = flow_graph.accumulate(spl_erosion); + // elevation = uplifted_elevation - spl_erosion; + + // std::cout << "[DEBUG] updated elevation max = " << xt::amax(elevation)() + // << ", min = " << xt::amin(elevation)() << std::endl; + // } + + // Call the visualization function + // output_fastscape_visualization( + // this->get_timestep_number(), + // surface_mesh_dof_handler, + // elevation, + // uplift_rate, + // spl_erosion, + // drainage_area, + // sediment_flux, + // this->get_output_directory() + "/fastscapeCC", + // this->get_mpi_communicator(), + // this->get_pcout()); + + + // While figuring out the visualization function we continue to use this below + // --- FASTSCAPE LOOP --- + for (unsigned int i = 0; i < fastscape_iterations; ++i) + { + std::cout << "\nFastScape iteration " << (i+1) << "/" << fastscape_iterations << std::endl; + + // Uplift + uplifted_elevation = elevation + fastscape_timestep_in_years * uplift_rate; + + std::cout << "[DEBUG] uplifted_elevation max = " << xt::amax(uplifted_elevation)() + << ", min = " << xt::amin(uplifted_elevation)() << std::endl; + + // Flow routing + flow_graph.update_routes(uplifted_elevation); + flow_graph.accumulate(drainage_area, 1.0); + + std::cout << "[DEBUG] drainage_area max = " << xt::amax(drainage_area)() + << ", min = " << xt::amin(drainage_area)() << std::endl; + + // Stream Power Law erosion + auto spl_erosion = spl_eroder.erode(uplifted_elevation, drainage_area, fastscape_timestep_in_years); + + std::cout << "[DEBUG] spl_erosion max = " << xt::amax(spl_erosion)() + << ", min = " << xt::amin(spl_erosion)() << std::endl; + + // Apply erosion + sediment_flux = flow_graph.accumulate(spl_erosion); + elevation = uplifted_elevation - spl_erosion; + + std::cout << "[DEBUG] updated elevation max = " << xt::amax(elevation)() + << ", min = " << xt::amin(elevation)() << std::endl; + } + + + const unsigned int timestep_index = this->get_timestep_number(); + const std::string output_directory = this->get_output_directory() + "/fastscapeCC"; + + if (!std::filesystem::exists(output_directory)) + std::filesystem::create_directories(output_directory); + + // === Prepare data === + dealii::Vector elevation_output(n_grid_nodes); + dealii::Vector uplift_rate_output(n_grid_nodes); + dealii::Vector erosion_output(n_grid_nodes); + dealii::Vector drainage_area_output(n_grid_nodes); + + for (unsigned int j = 0; j < n_grid_nodes; ++j) + { + elevation_output[j] = elevation[j]; + uplift_rate_output[j] = uplift_rate[j]; + erosion_output[j] = spl_erosion[j]; + drainage_area_output[j] = drainage_area[j]; + } + + // === Write per-process VTU === + dealii::DataOut data_out; + data_out.attach_dof_handler(surface_mesh_dof_handler); + data_out.add_data_vector(elevation_output, "Elevation"); + data_out.add_data_vector(uplift_rate_output, "UpliftRate"); + data_out.add_data_vector(erosion_output, "Erosion"); + data_out.add_data_vector(drainage_area_output, "DrainageArea"); + data_out.build_patches(); + + const std::string basename = "fastscape_timestep_" + Utilities::int_to_string(timestep_index, 4); + const std::string filename_base = output_directory + "/" + basename; + + const auto mpi_rank = Utilities::MPI::this_mpi_process(this->get_mpi_communicator()); + const auto mpi_size = Utilities::MPI::n_mpi_processes(this->get_mpi_communicator()); + + const std::string vtu_filename = filename_base + "." + Utilities::int_to_string(mpi_rank, 4) + ".vtu"; + { + std::ofstream output(vtu_filename); + data_out.write_vtu(output); + } + + // === Rank 0: Write .pvtu and update .pvd === + if (mpi_rank == 0) + { + // .pvtu + std::vector vtu_filenames; + for (unsigned int r = 0; r < mpi_size; ++r) + vtu_filenames.push_back(basename + "." + Utilities::int_to_string(r, 4) + ".vtu"); + + const std::string pvtu_filename = filename_base + ".pvtu"; + { + std::ofstream pvtu(pvtu_filename); + data_out.write_pvtu_record(pvtu, vtu_filenames); + } + + // .pvd + const std::string pvd_filename = output_directory + "/fastscape_timeseries.pvd"; + std::vector existing_entries; + if (std::filesystem::exists(pvd_filename)) + { + std::ifstream in(pvd_filename); + std::string line; + while (std::getline(in, line)) + if (line.find(""; + existing_entries.push_back(new_entry.str()); + + std::ofstream pvd_file(pvd_filename); + pvd_file << "\n" + << "\n" + << " \n"; + for (const auto &entry : existing_entries) + pvd_file << entry << "\n"; + pvd_file << " \n\n"; + } + + + // Compute erosion velocities + for (unsigned int i = 0; i < n_grid_nodes; ++i) + { + V[i] = (elevation[i] - elevation_old[i]) / this->get_timestep(); // in m/s + // std::cout << "grid node: " << i << ", V: " << V[i] << std::endl; + } + + + std::cout<<"here it works 13"<get_mpi_communicator(), V, /* root= */ 0); + + // Maybe this is enough + // Step 1: Get V ordered + auto erosion_rate_and_direction = [&](const Point &p) -> Tensor<1,dim> + { + const unsigned int index = this->vertex_index(p); + + const double vertical_velocity = V[index]; // in m/s + + // We need to know which *direction* erosion acts in to prescribe + // boundary conditions for surface nodes. We use the (positive) gravity + // direction for this: + const Tensor<1,dim> gravity_vector = this->get_gravity_model().gravity_vector(p); + const Tensor<1,dim> gravity_direction = gravity_vector / gravity_vector.norm(); + + return vertical_velocity * gravity_direction; + }; + std::cout << "here it works 14" << std::endl; + + //Eventually we could interpolate for better accuracy + // auto erosion_function = [&](const Point &p) -> double + // { + // try + // { + // const auto cell_and_point = dealii::GridTools::find_active_cell_around_point(*surface_cache, p); + + // const auto &cell = cell_and_point.first; + + // if (cell->is_valid()) + // { + // // Interpolate using FEValues + // const MappingQ1 mapping; + // FEValues fe_values(mapping, + // surface_fe, + // Quadrature(cell_and_point.second), + // update_values); + + // fe_values.reinit(cell); + + // std::vector values(fe_values.dofs_per_cell); + // cell->get_dof_values(V, values); + + // return values[0]; + // } + // else + // { + // return 0.0; + // } + // } + // catch (...) + // { + // // Fallback for out-of-bound or failure + // return 0.0; + // } + // }; + + + const VectorFunctionFromTensorFunctionObject radial_velocity_field(erosion_rate_and_direction); + std::cout << "Rank " << Utilities::MPI::this_mpi_process(this->get_mpi_communicator()) + << ": here it works 15" << std::endl; + + + // Interpolate velocity computed by FastScape onto the boundary nodes of the + // volume mesh, and put that into the the output object of this function. + for (const auto boundary_id : boundary_ids) + VectorTools::interpolate_boundary_values( + this->get_mapping(), + mesh_deformation_dof_handler, + boundary_id, + radial_velocity_field, + mesh_velocity_constraints); + + this->get_pcout() << "Applying erosion velocities to mesh boundary." << std::endl; + } + + + template + bool + FastScapecc:: + needs_surface_stabilization () const + { + return true; + } + + + template + void FastScapecc::declare_parameters(ParameterHandler &prm) + { + prm.enter_subsection("Geometry model"); + { + prm.enter_subsection("Box"); + { + prm.declare_entry ("X repetitions", "1", + Patterns::Integer (1), + "Number of cells in X direction."); + prm.declare_entry ("Y repetitions", "1", + Patterns::Integer (1), + "Number of cells in Y direction."); + prm.declare_entry ("Z repetitions", "1", + Patterns::Integer (1), + "Number of cells in Z direction."); + } + prm.leave_subsection(); + } + prm.leave_subsection(); + + prm.enter_subsection ("Mesh deformation"); + { + prm.enter_subsection ("Fastscapecc"); + { + prm.declare_entry("Number of steps", "10", + Patterns::Integer(), + "Number of steps per ASPECT timestep"); + prm.declare_entry("Maximum timestep", "10e3", + Patterns::Double(0), + "Maximum timestep for FastScape. Units: $\\{yrs}$"); + prm.declare_entry("Additional fastscape refinement levels", "0", + Patterns::Integer(), + "How many levels above ASPECT FastScape should be refined."); + prm.declare_entry("Surface refinement level", "1", + Patterns::Integer(), + "This should be set to the highest ASPECT refinement level expected at the surface."); + prm.declare_entry("Surface refinement difference", "0", + Patterns::Integer(), + "The difference between the lowest and highest refinement level at the surface. E.g., if three resolution " + "levels are expected, this would be set to 2."); + prm.declare_entry ("Use velocities", "true", + Patterns::Bool (), + "Flag to use FastScape advection and uplift."); + prm.declare_entry("Precision", "0.001", + Patterns::Double(), + "Precision value for how close a ASPECT node must be to the FastScape node for the value to be transferred."); + prm.declare_entry("Initial noise magnitude", "5", + Patterns::Double(), + "Maximum topography change from the initial noise. Units: $\\{m}$"); + + prm.declare_entry("Output internal fastscape steps", "false", + Patterns::Bool(), + "Whether to output visualization files for internal FastScape steps."); + + + prm.enter_subsection ("Erosional parameters"); + { + prm.declare_entry("Drainage area exponent", "0.4", + Patterns::Double(), + "Exponent for drainage area."); + prm.declare_entry("Slope exponent", "1", + Patterns::Double(), + "The slope exponent for SPL (n). Generally m/n should equal approximately 0.4"); + prm.declare_entry("Bedrock river incision rate", "1e-5", + Patterns::Double(), + "River incision rate for bedrock in the Stream Power Law. Units: $\\{m^(1-2*drainage_area_exponent)/yr}$"); + prm.declare_entry("Sediment river incision rate", "-1", + Patterns::Double(), + "River incision rate for sediment in the Stream Power Law. -1 sets this to the bedrock river incision rate. Units: $\\{m^(1-2*drainage_area_exponent)/yr}$ "); + prm.declare_entry("Bedrock diffusivity", "1e-2", + Patterns::Double(), + "Transport coefficient (diffusivity) for bedrock. Units: $\\{m^2/yr}$ "); + prm.declare_entry("Sediment diffusivity", "-1", + Patterns::Double(), + "Transport coefficient (diffusivity) for sediment. -1 sets this to the bedrock diffusivity. Units: $\\{m^2/yr}$"); + prm.declare_entry("Elevation factor", "1", + Patterns::Double(), + "Amount to multiply kf and kd by past given orographic elevation control."); + } + prm.leave_subsection(); + } + prm.leave_subsection(); + } + prm.leave_subsection (); + } + + + template + void FastScapecc::parse_parameters(ParameterHandler &prm) + { + prm.enter_subsection("Geometry model"); + { + prm.enter_subsection("Box"); + { + repetitions[0] = prm.get_integer ("X repetitions"); + if (dim >= 2) + { + repetitions[1] = prm.get_integer ("Y repetitions"); + } + if (dim >= 3) + { + repetitions[dim-1] = prm.get_integer ("Z repetitions"); + } + } + prm.leave_subsection(); + } + prm.leave_subsection(); + + prm.enter_subsection ("Mesh deformation"); + { + prm.enter_subsection("Fastscapecc"); + { + fastscape_steps_per_aspect_step = prm.get_integer("Number of steps"); + maximum_fastscape_timestep = prm.get_double("Maximum timestep"); + additional_refinement_levels = prm.get_integer("Additional fastscape refinement levels"); + surface_refinement_level = prm.get_integer("Surface refinement level"); + surface_refinement_difference = prm.get_integer("Surface refinement difference"); + noise_h = prm.get_double("Initial noise magnitude"); + output_internal_fastscape_steps = prm.get_bool("Output internal fastscape steps"); + + + if (!this->convert_output_to_years()) + { + maximum_fastscape_timestep /= year_in_seconds; + } + + prm.enter_subsection("Erosional parameters"); + { + m = prm.get_double("Drainage area exponent"); + n = prm.get_double("Slope exponent"); + kfsed = prm.get_double("Sediment river incision rate"); + kff = prm.get_double("Bedrock river incision rate"); + kdsed = prm.get_double("Sediment diffusivity"); + kdd = prm.get_double("Bedrock diffusivity"); + + // if (!this->convert_output_to_years()) + // { + // kff *= year_in_seconds; + // kdd *= year_in_seconds; + // kfsed *= year_in_seconds; + // kdsed *= year_in_seconds; + // } + + } + prm.leave_subsection(); + + } + prm.leave_subsection(); + } + prm.leave_subsection (); + } + } +} + + +// explicit instantiation of the functions we implement in this file +namespace aspect +{ + namespace MeshDeformation + { + ASPECT_REGISTER_MESH_DEFORMATION_MODEL(FastScapecc, + "fastscapecc", +// TODO: This comment is no longer accurate :-) + "A plugin, which prescribes the surface mesh to " + "deform according to an analytically prescribed " + "function. Note that the function prescribes a " + "deformation velocity, i.e. the return value of " + "this plugin is later multiplied by the time step length " + "to compute the displacement increment in this time step. " + "The format of the " + "functions follows the syntax understood by the " + "muparser library, see Section~\\ref{sec:muparser-format}.") + } +} + +#endif // #ifdef ASPECT_WITH_FASTSCAPELIB diff --git a/tests/fastscape_eroding_box_cc.prm b/tests/fastscape_eroding_box_cc.prm new file mode 100644 index 00000000000..c42661ecf99 --- /dev/null +++ b/tests/fastscape_eroding_box_cc.prm @@ -0,0 +1,195 @@ +# At the top, we define the number of space dimensions we would like to +# work in: +set Dimension = 3 + +set Use years in output instead of seconds = true +set End time = 1e6 +set Maximum time step = 1000 +set Output directory = eroding_box + +set Pressure normalization = no +set Surface pressure = 0 + +# A box which has one half raised by 1 km. +subsection Geometry model + set Model name = box + + subsection Box + set X extent = 100e3 + set Y extent = 100e3 + set Z extent = 25e3 + set X repetitions = 5 + set Y repetitions = 5 + end +end + +# subsection Initial topography model +# set Model name = function +# subsection Function +# set Function expression = if(y>20e3 && y<80e3 && x>20e3 && x<80e3, 2500, 0) +# end +# end +# end + +# We do not consider temperature in this setup +subsection Initial temperature model + set Model name = function + + subsection Function + set Function expression = 0 + end +end + +subsection Boundary temperature model + set Fixed temperature boundary indicators = bottom, top + set List of model names = initial temperature +end + +# X Set all boundaries except the top to freeslip. +subsection Boundary velocity model + set Tangential velocity boundary indicators = bottom, front, back, right, left +# set Prescribed velocity boundary indicators = left x:function +# subsection Function +# set Variable names = x,y,t +# set Function constants = cm=0.01,year=1 +# set Function expression = if(x<50e3, 1*cm/year,0) ;0 +# end +end + + + + +# Here we setup the top boundary with fastscape. +subsection Mesh deformation + set Mesh deformation boundary indicators = top : fastscapecc + + subsection Fastscapecc + # As the highest resolution at the surface is 4 in the mesh refinement function, we set this the same. + set Maximum surface refinement level = 0 + + # Because we only have the same level across the entire surface we set this to zero. + # If we had multiple resolutions, e.g. 3 different cell sizes at the surface, + # this would be set to 2 (number of cell sizes at surface - 1). + set Surface refinement difference = 0 + + # We set FastScape as 1 level more resolved than ASPECT's initial global surface resolution. + set Additional fastscape refinement = 0 + + # Number of FastScape timesteps we want per ASPECT timestep. + set Number of steps = 5 + + # Set the maximum allowable FastScape timestep. If the (ASPECT timestep)/(Number of steps) + # is greater than this value, then the number of steps is automatically doubled. + # This is continued until the FastScape timestep is less than this value. + set Maximum timestep = 10000 + + # Seed number for initial topography noise + set Fastscape seed = 0 + + + # Fix all boundaries in FastScape. + subsection Boundary conditions + set Front = 1 + set Right = 1 + set Back = 1 + set Left = 1 + end + + # We use both the Steam Power Law (SPL) and hillslope diffusion. The bedrock deposition + # variable allows deposition of sediment, however, because + # the sediment variables are not set, they are equal to + # bedrock values. + subsection Erosional parameters + set Drainage area exponent = 0.4 + set Bedrock diffusivity = 1e-2 + set Bedrock river incision rate = 1e-4 + set Slope exponent = 1 + end + end + + set Additional tangential mesh velocity boundary indicators = left, right, front, back + +end + +subsection Gravity model + set Model name = vertical + + subsection Vertical + set Magnitude = 0 + end +end + +# Dimensionless +subsection Material model + set Model name = simple + + subsection Simple model + set Reference density = 1 + set Reference specific heat = 1 + set Reference temperature = 0 + set Thermal conductivity = 1 + set Thermal expansion coefficient = 1 + set Viscosity = 1 + end +end + + +# We also have to specify that we want to use the Boussinesq +# approximation (assuming the density in the temperature +# equation to be constant, and incompressibility). +subsection Formulation + set Formulation = Boussinesq approximation +end + + +# We set a maximum surface refinement level of 4 using the max/minimum refinement level. +# This will make it so that the ASPECT surface refinement is the same as what we set +# for our maximum surface refinement level for FastScape. +# subsection Mesh refinement +# set Initial global refinement = 4 +# set Initial adaptive refinement = 1 +# set Time steps between mesh refinement = 0 +# set Strategy = minimum refinement function, maximum refinement function + +# subsection Maximum refinement function +# set Coordinate system = cartesian +# set Variable names = x,y,z +# set Function expression = if(z>=21000, 4, 2) +# end + +# subsection Minimum refinement function +# set Coordinate system = cartesian +# set Variable names = x,y,z +# set Function expression = if(z>=21000, 4, 2) +# end +# end + +subsection Mesh refinement + set Initial global refinement = 0 + set Initial adaptive refinement = 0 + set Time steps between mesh refinement = 0 + # set Strategy = minimum refinement function, maximum refinement function + + # subsection Maximum refinement function + # set Coordinate system = cartesian + # set Variable names = x,y,z + # set Function expression = if(z>=21000, 4, 2) + # end + + # subsection Minimum refinement function + # set Coordinate system = cartesian + # set Variable names = x,y,z + # set Function expression = if(z>=21000, 4, 2) + # end +end + +subsection Postprocess + set List of postprocessors = visualization + + subsection Visualization + set Time between graphical output = 0 + set List of output variables = strain rate + set Interpolate output = false + set Write higher order output = false + end +end diff --git a/tests/fastscape_eroding_box_cc/output_screen b/tests/fastscape_eroding_box_cc/output_screen new file mode 100644 index 00000000000..22d10c18c6e --- /dev/null +++ b/tests/fastscape_eroding_box_cc/output_screen @@ -0,0 +1,277 @@ +----------------------------------------------------------------------------- +-- This is ASPECT, the Advanced Solver for Problems in Earth's ConvecTion. +-- . version 2.6.0-pre (coupling_fs, 19ad382da) +-- . using deal.II 9.4.0 +-- . with 32 bit indices and vectorization level 0 (64 bits) +-- . using Trilinos 13.2.0 +-- . using p4est 2.3.2 +-- . running in DEBUG mode +-- . running with 1 MPI process +----------------------------------------------------------------------------- + + +----------------------------------------------------------------------------- +The output directory provided in the input file appears not to exist. +ASPECT will create it for you. +----------------------------------------------------------------------------- + + +----------------------------------------------------------------------------- +-- For information on how to cite ASPECT, see: +-- https://aspect.geodynamics.org/citing.html?ver=2.6.0-pre&sha=19ad382da&src=code +----------------------------------------------------------------------------- +Number of active cells: 25 (on 1 levels) +Number of degrees of freedom: 1,524 (1,089+72+363) + +Number of mesh deformation degrees of freedom: 216 + Solving mesh displacement system... 0 iterations. +*** Timestep 0: t=0 years, dt=0 years + Solving mesh displacement system... 0 iterations. + Skipping temperature solve because RHS is zero. + Rebuilding Stokes preconditioner... + Solving Stokes system... 0+0 iterations. + + Postprocessing: + Writing graphical output: eroding_box/solution/solution-00000 + +*** Timestep 1: t=1000 years, dt=1000 years + Initializing FastScape... 1 levels, cell size: 20000 m. +here it works 2 +here it works 3 +here it works 4 +here it works 5 +here it works 6 +here it works 7 +here it works 8 +Fastscape ite 0 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 1 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 2 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 3 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 4 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 10 +here it works 11 +here it works 12 + Solving mesh displacement system... 0 iterations. + Skipping temperature solve because RHS is zero. + Rebuilding Stokes preconditioner... + Solving Stokes system... 0+0 iterations. + + Postprocessing: + Writing graphical output: eroding_box/solution/solution-00001 + +*** Timestep 2: t=2000 years, dt=1000 years + Initializing FastScape... 1 levels, cell size: 20000 m. +here it works 2 +here it works 3 +here it works 4 +here it works 5 +here it works 6 +here it works 7 +here it works 8 +Fastscape ite 0 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 1 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 2 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 3 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 4 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 10 +here it works 11 +here it works 12 + Solving mesh displacement system... 0 iterations. + Skipping temperature solve because RHS is zero. + Rebuilding Stokes preconditioner... + Solving Stokes system... 0+0 iterations. + + Postprocessing: + Writing graphical output: eroding_box/solution/solution-00002 + +*** Timestep 3: t=3000 years, dt=1000 years + Initializing FastScape... 1 levels, cell size: 20000 m. +here it works 2 +here it works 3 +here it works 4 +here it works 5 +here it works 6 +here it works 7 +here it works 8 +Fastscape ite 0 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 1 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 2 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 3 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 8 +Fastscape ite 4 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +here it works 9 +here it works 9b +here it works 10 +here it works 11 +here it works 12 + Solving mesh displacement system... 0 iterations. + Skipping temperature solve because RHS is zero. + Rebuilding Stokes preconditioner... + Solving Stokes system... 0+0 iterations. + + Postprocessing: + Writing graphical output: eroding_box/solution/solution-00003 + +*** Timestep 4: t=4000 years, dt=1000 years + Initializing FastScape... 1 levels, cell size: 20000 m. +here it works 2 +here it works 3 +here it works 4 +here it works 5 +here it works 6 +here it works 7 +here it works 8 +Fastscape ite 0 +here it works 8b +here it works 8c +here it works 8d +here it works 8e +[MacBook-Pro:08191] *** Process received signal *** +[MacBook-Pro:08191] Signal: Abort trap: 6 (6) +[MacBook-Pro:08191] Signal code: (0) +[MacBook-Pro:08191] [ 0] 0 libsystem_platform.dylib 0x000000019ea52a84 _sigtramp + 56 +[MacBook-Pro:08191] [ 1] 0 libsystem_pthread.dylib 0x000000019ea23c28 pthread_kill + +288 +[MacBook-Pro:08191] [ 2] 0 libsystem_c.dylib 0x000000019e931ae8 abort + 180 +[MacBook-Pro:08191] [ 3] 0 libsystem_malloc.dylib 0x000000019e852e28 malloc_vreport ++ 908 +[MacBook-Pro:08191] [ 4] 0 libsystem_malloc.dylib 0x000000019e8695d4 +malloc_zone_error + 104 +[MacBook-Pro:08191] [ 5] 0 libsystem_malloc.dylib 0x000000019e861148 +nanov2_guard_corruption_detected + 44 +[MacBook-Pro:08191] [ 6] 0 libsystem_malloc.dylib 0x000000019e860344 _nanov2_free + +0 +[MacBook-Pro:08191] [ 7] 0 libc++abi.dylib 0x000000019e9de924 _Znwm + 32 +[MacBook-Pro:08191] [ 8] 0 aspect 0x0000000105070738 +_ZN3xtl3mpl9static_ifIZN2xt18assign_xexpressionINS2_17xtensor_containerINS2_7uvectorIdNSt3__19allocatorIdEEEELm2ELNS2_11layout_typeE1ENS2_22xtensor_expression_tagEEENS2_13xstrided_viewIRSC_NS6_5arrayImLm2EEELSA_0ENS2_6detail20inner_storage_getterISE_EEEEEEvRNS2_11xexpressionIT_EERKNSL_IT0_EEEUlSM_E_ZNS3_ISC_SK_EEvSO_SS_EUlSM_E0_EEDcNS6_17integral_constantIbLb0EEERKSM_RKSP_ ++ 188 +[MacBook-Pro:08191] [ 9] 0 aspect 0x000000010506e1b8 +_ZN12fastscapelib20diffusion_adi_eroderINS_14raster_grid_xtINS_11xt_selectorELNS_14raster_connectE1ENS_15neighbors_cacheILh8EEEEES2_E13solve_adi_rowIN2xt13xstrided_viewIRNS9_17xtensor_containerINS9_7uvectorIdNSt3__19allocatorIdEEEELm2ELNS9_11layout_typeE1ENS9_22xtensor_expression_tagEEENSD_5arrayImLm2EEELSH_0ENS9_6detail20inner_storage_getterISK_EEEENSA_IRNSB_ISG_Lm3ELSH_1ESI_EENSL_ImLm3EEELSH_0ENSO_ISS_EEEESV_EEDaOT_OT0_OT1_mmd ++ 144 +[MacBook-Pro:08191] [10] 0 aspect 0x000000010501a780 +_ZN12fastscapelib20diffusion_adi_eroderINS_14raster_grid_xtINS_11xt_selectorELNS_14raster_connectE1ENS_15neighbors_cacheILh8EEEEES2_E5erodeERKN2xt16xarray_containerINS8_7uvectorIdNSt3__19allocatorIdEEEELNS8_11layout_typeE1ENS8_7svectorImLm4ENSC_ImEELb1EEENS8_22xtensor_expression_tagEEEd ++ 400 +[MacBook-Pro:08191] [11] 0 aspect 0x000000010501f064 +_ZNK6aspect15MeshDeformation11FastScapeccILi3EE40compute_velocity_constraints_on_boundaryERKN6dealii10DoFHandlerILi3ELi3EEERNS3_17AffineConstraintsIdEERKNSt3__13setIjNSB_4lessIjEENSB_9allocatorIjEEEE ++ 6844 +[MacBook-Pro:08191] [12] 0 aspect 0x0000000105039b38 +_ZN6aspect15MeshDeformation22MeshDeformationHandlerILi3EE16make_constraintsEv + 1036 +[MacBook-Pro:08191] [13] 0 aspect 0x0000000105039618 +_ZN6aspect15MeshDeformation22MeshDeformationHandlerILi3EE7executeEv + 152 +[MacBook-Pro:08191] [14] 0 aspect 0x00000001048a16f8 +_ZN6aspect9SimulatorILi3EE14solve_timestepEv + 44 +[MacBook-Pro:08191] [15] 0 aspect 0x00000001048a0084 +_ZN6aspect9SimulatorILi3EE3runEv + 892 +[MacBook-Pro:08191] [16] 0 aspect 0x00000001051b4f44 +_Z13run_simulatorILi3EEvRKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEES8_bbb + +868 +[MacBook-Pro:08191] [17] 0 aspect 0x00000001051b41a8 main + 2580 +[MacBook-Pro:08191] [18] 0 dyld 0x000000019e6cbf28 start + 2236 +[MacBook-Pro:08191] *** End of error message *** +-------------------------------------------------------------------------- +Primary job terminated normally, but 1 process returned +a non-zero exit code. Per user-direction, the job has been aborted. +-------------------------------------------------------------------------- +-------------------------------------------------------------------------- +mpirun noticed that process rank 0 with PID 0 on node MacBook-Pro exited on signal 6 (Abort trap: +6). +--------------------------------------------------------------------------