Skip to content

Repository files navigation

Adaptive Mesh Refinement (AMR)

AMR on heat diffusion simulation

Overview

This project deals with the implementation of an Adaptive Mesh Refinement (AMR) method for the numerical solution of problems. AMR is a numerical technique that adjusts the mesh resolution based on local problem characteristics to optimize computational resources and obtain accurate solutions.

The main goal of the project is to develop such an algorithm, designed to generate Quadtree (2D) or Octree (3D) mesh based on a time-varying physical field and a specified indicator.

The following image illustrates the Octree and Quadtree data structures used to represent the mesh hierarchy:

Quadtree and Octree datastructures

A Quadtree or Octree mesh consists of cells that can be recursively subdivided into smaller cells. The level of a cell represents its depth in this recursive subdivision hierarchy - the root cell starts at level 0, and each subdivision increases the level by 1.

When a cell is refined, it is split into 4 equal child cells in a Quadtree and in 8 equal child cells in an Octree.

The level difference between adjacent cells is constrained to be at most 1 to ensure smooth transitions in mesh resolution. This means a cell can only interface with neighbors that are either at the same level, one level finer, or one level coarser.

Higher levels correspond to finer mesh resolution, allowing increased detail in regions of interest while maintaining efficiency by using coarser cells elsewhere. The algorithm will dynamically refine or coarsen the mesh by analyzing changes in the physical field.

The algorithm is then applied to solve a continuous heat transfer problem, in two dimensions, and observe the behavior of the mesh as the solution evolves over time. The results are validated by comparing them with a reference solution obtained using a uniform mesh.

The following sections provide a detailed overview of the project.

 

Please see the Running the project section below for running instructions.

Table of Contents

Author & Acknowledgments

Osmani Dion, Author, HES-SO Valais-Wallis Student, Informatique et systèmes de communication (3rd year)

Desmons Florian, Supervisor, HES-SO Valais-Wallis Lecturer, Informatique et systèmes de communication

Technical Documentation

The following sections provide a detailed technical overview of the project, including the algorithm description, code architecture and performance considerations.

Project Structure

Considering the project's structure, the codebase is organized as follows:

├── src/                    # Source code directory
├── tests/                  # Source code tests directory
├── output/                 # Output directory for simulation data VTK exports (not versioned)
├── export/                 # Export directory for simulation lineout data
├── README.md               # Project documentation
├── thermal_equation.py     # Main script for running the heat transfer simulation
├── compare_lineouts.py     # Script to compare simulation lineout data
├── pyproject.toml          # Project configuration file
└── uv.lock                 # Lock file generated by uv

Algorithm Description

The overall simulation algorithm can be broken down into the following steps:

  1. Initialization:

    • Create the initial uniform mesh based on the domain size and resolution.
    • Assign initial values to the mesh cells based on the initial temperature field.
  2. Time Integration:

    • Iterate over time steps until the final time is reached.
  3. Solve the Heat Equation:

    • Compute the heat diffusion equation using a finite differences numerical scheme.
    • Update the temperature field based on the computed values.
  4. Adaptive Mesh Refinement (only done every few iterations):

    • Evaluate the refinement criteria based on the temperature field.
    • Refine or coarsen the mesh based on the previous criteria.
  5. Data Export:

    • Save the simulation data for visualization.

The adaptive mesh refinement algorithm can be described as follows:

  1. Compute the refinement indicator for each cell.
  2. Compare the indicator with a threshold to decide whether or not to refine the cell.
  3. Refine the cell and its neighboring cells by splitting it into smaller cells or coarsen it by merging it with neighboring cells.
  4. Update the mesh structure and redistribute the temperature values accordingly.

The equation that governs the heat transfer problem is the heat diffusion equation:

$$\frac{\partial T}{\partial t} = \frac{\lambda}{\rho c_p} \left(\frac{\partial^2 T_n}{\partial x^2} + \frac{\partial^2 T_n}{\partial y^2}\right)$$

where $T$ is the temperature field, $\lambda$ is the thermal conductivity, $\rho$ is the density, $c_p$ is the specific heat capacity at constant pressure, and $T_n$ is the temperature at the current time step.

This description provides a high-level overview of the algorithm and its main components.

The more detailed implementation of the algorithm is described in the following sections.

Code Architecture

The src directory contains the main source code files:

├── mesh.py         # Mesh class used to represent the computational mesh
├── node.py         # Node class used to represent the mesh nodes
├── refinement.py   # Refinement class used to define mesh refinement criteria
├── scheme.py       # Scheme class used to implement the numerical scheme solver
└── benchmark.py    # Benchmark class used to compare simulation data

The overall architecture of the codebase is designed to be modular, extensible and efficient, allowing for easy integration of new features and optimization techniques. Each file is documented with detailed comments and docstrings explaining the purpose of the classes and methods. The codebase is structured around the following key components:

Mesh:

  • The Mesh class represents the computational mesh and contains methods for mesh initialization, refinement, data storage and much more.
  • A Mesh instance is defined by its root cell, which is recursively subdivided into smaller cells. This recursive structure defines the mesh hierarchy and implements the Octree/Quadtree data structure in a bidirectional-tree structure where each node has a reference to its parent and children nodes.
  • The Mesh class provides two main methods to initialize the mesh: uniform, a static method and create_root, a class method. The first one creates a uniform mesh based on the argument-defined resolution (defined throughout the number of cells per dimension, n) and domain size, while the latter creates the root cell of the mesh without creating its children.
  • The Mesh class provides a single mesh refinement endpoint method, refine, which is responsible for refining or coarsening the mesh based on the argument-given refinement criteria and depth constraints. The method ensures that modification of the structure does not yield any greater than one level difference between two adjacent cells. The method evaluates the refinement criteria against the cells of the mesh and, when marked for refinement, splits its neighboring cells before splitting the cell itself. This allows for a strip-like refinement pattern that ensures a greater resolution at the boundaries of the refined region. After the refinement process, the refinement criteria is again evaluated on the non-previously refined cells' parent cell to evaluate if said parent cell should be coarsened. If they shall be coarsened and their children are not marked for refinement then, the parent cell is coarsened and its children are removed from the mesh. The mesh integrity is ensured at any given operation and does not exceed the depth constraints.
  • The Mesh class provides different helper methods designed to offer an easy-of-use API and allow for a better user-experience. The solve method applies an argument-given numerical scheme (see Scheme to the mesh, the inject method applies a function to the mesh nodes, the leafs and root methods return a Generator of the leaf cells and the root cell of the mesh, respectively, and the save method exports the mesh structure and data to a VTK file.

These methods strongly rely on the Node class, which represents the mesh nodes.

Node:

  • The Node class represents the mesh nodes and contains methods for node initialization, data storage and much more.
  • A Node instance is defined by its value, its level in the mesh hierarchy, its parent node reference and its relative origin. The origin is set to the top-left corner of the cell and it is relative to the parent cell's referential. Hence, each individual node has its own referential system with children nodes of relative size 1 in any direction. This is better illustrated in the following image:
Mesh coordinates referential
  • The Node instance children are stored in a dictionary with keys corresponding to the relative origin of the child node in the parent referential. This allows for a quick access to the children nodes and ensures a constant time complexity for the access operation as it is based on the Python dictionary data structure.
  • An important, yet subtle implementation detail in the Node class is the __slots__ attribute. This attribute is used to define the class attributes and optimize the memory usage of the class instances. By defining the __slots__ attribute, the class instances do not store a dictionary of attributes, but rather a tuple of values. This reduces the memory overhead of the class instances and allows for a more efficient memory usage as the number of instances grows. The instances are said to be statically allocated and the memory usage is reduced to the minimum required by the class attributes. This optimization is critical in the project as the number of nodes grows exponentially with the mesh resolution and the memory usage must be optimized to handle large meshes efficiently.
  • The Node class provides a shall_refine method that evaluates an argument-given criteria against the node and ensures that a hypothetical refinement does not lead to any greater than one level difference with its surrounding neighboring cells. The shall_coarsen method does the same for the coarsening operation.
  • The Node class provides a refine method that is used to split the node into smaller nodes. It is used by the Mesh class to refine the mesh structure and assumes that the node is, indeed, marked for refinement, hence the method does not evaluate the refinement criteria. The method creates and stores the children nodes in the node's children dictionary. The method also redistributes the node's value to its children nodes using a linear interpolation using the neighboring nodes' centered values (of same level or greater). The node's value is then set to the average of its children nodes' values. The Node class also provides a coarsen method that deletes the children nodes and redistributes the node's value to its parent node. The method is used by the Mesh class to coarsen the mesh structure and assumes that the node is, indeed, marked for coarsening, hence the method does not evaluate the refinement criteria.
  • The Node class provides many geometrical and locality methods that are extensively used by the project implementation. First, the absolute_origin and absolute_centered_origin properties recursively compute and return the node's absolute origin and center origin in the global referential system. The adjacent method returns the local-referential (common parent) adjacent node given its relative origin in constant time. The neighbor method returns the neighboring Node instance (when it exists) in an argument-given cardinal direction (defined in the helper Direction enumeration class, in the node.py file). The neighbor that is, in order, assumed to be an adjacent neighbor, an adjacent neighbor of the parent and finally a child of the parent's adjacent neighbor (in the mirrored direction of that we are searching for). The buffer method returns a list of nodes that are within a buffer distance of the node. The buffer distance is defined as the argument-given distance and is assumed to be unit-less. Hence, the distance is assumed to be a number of nodes in any given direction and of whatever level. This method also makes use of the chain method that eases the access of neighboring nodes in diagonal directions by chaining the neighbor method calls. These different methods are all implemented to ensure a constant time complexity as they are critical in every step of the mesh refinement process. This time complexity is enabled by storing the parent reference and children references in the Node instances and by using the relative origin to access the children nodes. In reality, the Python dictionnary access operation is of constant average time complexity and may only be linear in worst case if the hash function leads to many collisions. However, the low number of keys (4 in a Quadtree structure and 8 in a Octree structure) in the children dictionary ensures a close to zero probability of collision and a constant time complexity in practice.
  • The Node class also provides different helper methods and properties which are used in the Mesh class.

The basic AMR logic is well implemented for both 2D and 3D meshes and hence supports the Quadtree and Octree data structures. The following image illustrates the mesh refinement process in an Octree structure:

Refinement applied on an Octree structure.

The, as described, geometrical and locality methods are not implemented for 3D meshes and the Octree data structure. This is due to the extra complexity of the 3D space and the Octree data structure. The implementation is, however, easily extensible to support 3D meshes and the Octree data structure by implementing the methods accordingly. This logic is further needed for numerical schemes that require a more complex neighborhood access and geometrical operations in the 3D space.

Refinement:

  • The RefinementCriterium class represents the refinement criteria. It contains an abstract method, eval, that must be implemented by subclasses to define the refinement criteria logic, and a helper static method handle_neighbor that is used in gradient computation due to the need for interpolation.
  • A concrete implementation of the RefinementCriterium class is provided with the CustomRefinementCriterium class. A custom function that is evaluated against a Node instance is provided to the class constructor and is used in the eval method to evaluate the refinement criteria. The CustomRefinementCriterium class is used in the project to quickly define a refinement criteria based on a custom function or bypass the refinement criterium, as done in the stripe-neighboring nodes refinement process.
  • The GradientRefinementCriterium implements a concrete implementation of the RefinementCriterium class. It implements a first-order two-dimensional gradient approximation using the interpolated neighboring nodes' values. Its scaled magnitude is then evaluated against a user-defined threshold to determine if said node shall be refined. A similar concrete implementation class LogScaleGradientRefinementCriterium is also provided to evaluate the log-scale gradient against a user-defined threshold. The log-scale allows for a greater sensitivity to small values and a better representation of the gradient's magnitude. It is used in the project to define a refinement criteria based on the temperature gradient of the heat diffusion problem.

This abstract class is used to define the refinement criteria and allows for a flexible and extensible implementation of different criteria based on the user's needs. This implementation, though, lacks support for 3D meshes and the Octree data structure due to the two-dimensional gradient calculation. It is, however, easily extensible to support 3D meshes and the Octree data structure by implementing the eval method and the helper methods accordingly.

Scheme:

  • The NumericalScheme class represents the numerical scheme solver and contains an apply method that must be implemented by subclasses to define the numerical scheme logic that is applied to an argument-given list of nodes.
  • A concrete implementation of the NumericalScheme class is provided with the SecondOrderCenteredFiniteDifferences class. It implements a second-order centered finite differences numerical scheme and Neumann boundaries as the domain is assumed to be conservative. The scheme is applied to the mesh nodes in the Mesh class to compute the temperature field at the next time step in the solve method.

This abstract class is used to define the numerical scheme and allows for a flexible and extensible implementation of different numerical schemes based on the user's needs. This implementation sadly lacks support for 3D meshes and the Octree data structure due to the two-dimensional gradient calculation (in the Laplacian). It is, however, easily extensible to support 3D meshes and the Octree data structure by implementing the apply method accordingly.

Benchmark:

  • The Benchmark class represents the benchmarking tool and contains methods that help measure the performance of the simulation, in time and memory usage.
  • The Benchmark class is implemented as a singleton class as it allows for a single instance to be used throughout the project and ensures consistent benchmarking results for individual functions or methods.
  • The Benchmark class provides different decorators for different needs. The repeat decorator repeats the decorated function a user-defined number of times. The time decorator measures the execution time of the decorated function and, finally, the memory decorator measures the memory usage of the decorated function.
  • The benchmarking can be reset at any given time using the corresponding method reset and the results of the benchmarking are stored in a dictionary and can be displayed in a human-readable format using the display method.

This class is used to measure the performance of the simulation and allows for a quick and easy way to compare different implementations or configurations.

Running the project.

The project has been developed in Python using uv and Python 3.12:

uv is an extremely fast Python package installer and resolver, written in Rust, and designed as a drop-in-replacement for pip and pip-tools workflows. - Charlie Marsh, founder of Astral and creator of uv

I recommend using uv to run the project as it allows for a fully reproducible environment and ensures that the project dependencies (as well as Python itself) are installed correctly.

To install uv, follow the instructions on the official website.

Please make sure that you correctly installed uv and that it is available in your PATH before running the project.

After everything is setup, you can simply run the heat transfer simulation using the following command, from the root directory of the project:

uv run thermal_equation.py

This is possible as the dependencies are defined in the pyproject.toml file and are automatically installed by uv when running the script. The Python version is also defined in the pyproject.toml file and is automatically installed by uv when running the script.

This will execute the main script of the project used to solve the heat transfer problem. The script will save the simulation data VTK files in the output/ directory and display the benchmarking results at the end of the simulation.

One can also provide an extra argument to the script to specify the number of iterations to run:

uv run thermal_equation.py 10 # Run for 10 iterations

It provides an easy way to run a simulation multiple times which can be useful for performance and benchmarking assessments.

One could also run the script using Python (version >= 3.12) after installing the dependencies directly with pip:

pip install psutil>=6.1.0 pytest>=8.3.4 # Install dependencies
python thermal_equation.py # Run the script

Modifying the simulation

If you are willing to modify the physical field, the physical properties or the refinement criterium, you can do so by editing the thermal_equation.py script. Here is an example of the physical properties and mesh/simulation parameters that can be modified:

# adaptive mesh refinement
MIN_RELATIVE_DEPTH: int = -3  # minimum depth of the tree (relative to the base cell)
MAX_RELATIVE_DEPTH: int = 2  # maximum depth of the tree (relative to the base cell)

# spatial
N: int = 16  # number of cells per dimension
LX: float = 10.0  # length of the domain in x [m]
LY: float = 10.0  # length of the domain in y [m]

# temporal
T: float = 100.0  # total simulation time [s]
DT: float = 0.01  # time step [s]

N_RECORDS: int = 200  # number of records to save

# material
RHO: float = 0.06  # density [kg/m^3]
CP: float = 404.0  # specific heat capacity [J/kg/K]
LAMBDA: float = 0.026  # thermal conductivity [W/m/K]

By default, the script does not check that the stability conditions are met. One can easily make sure that the stability conditions are met by uncommenting the following lines in the script:

# check stability condition
# if not DT < (RHO / (LAMBDA * CP * DX**2)) * 0.3:
#     print(f"DT: {DT}s ≮ {((RHO / (LAMBDA * CP * DX**2)) * 0.3):.4}s")
#     raise ValueError(
#         "Stability condition not met! Please provide a smaller time step."
#     )

# uncomment the lines above to check the stability condition

# check stability condition
if not DT < (RHO / (LAMBDA * CP * DX**2)) * 0.3:
    print(f"DT: {DT}s ≮ {((RHO / (LAMBDA * CP * DX**2)) * 0.3):.4}s")
    raise ValueError(
        "Stability condition not met! Please provide a smaller time step."
    )

The script injects a circular heat source of 2.0 meters radius at the center of the domain. One can easily modify the heat source by editing the heat_source method in the script as follows:

# inject values into tree
# to represent source of heat
def heat_source(node: Node) -> None:
    # some arbitrary condition
    ...

    # assign value to node
    node.value = ...

One could also easily modify the refinement criteria by making use of a CustomRefinementCriterium instance and providing a custom function to the class constructor as follows:

# create refinement criterium
# based on the gradient change
criterium = LogScaleGradientRefinementCriterium(threshold=0.1)

# replace this by a custom criteria

# create refinement criterium
# based on the gradient change
from src.refinement import CustomRefinementCriterium
criterium = CustomRefinementCriterium(lambda node: node.value > 0.5)

Many more modifications can be done to the script to adapt the simulation to your needs.

Running the validation

A script is provided to compare and validate the simulation data with a reference simulation data obtained using a uniform mesh.

A preset of lineout VisIt Curve2D exported folders are available in the export/ directory.

├── mesh-amr-r1/    # Adaptive Mesh Refinement, up-to 1 level of refinement
├── mesh-amr-r2/    # Adaptive Mesh Refinement, up-to 2 levels of refinement
└── mesh-no-amr/    # Uniform Mesh

You can run the comparison script using the following command:

uv run compare_lineouts.py <reference_folder/path> <comparison_folder/path>

The order in which the folders are specified matters as the first folder is assumed to be the reference simulation data against which we compare another simulation's data.

Hence, to compare the Adaptive Mesh Refinement simulation data with the Uniform Mesh simulation data, you can use the following command from the root directory:

uv run compare_lineouts.py export/mesh-no-amr export/mesh-amr-r1
# or
uv run compare_lineouts.py export/mesh-no-amr export/mesh-amr-r2

Running the tests

The project includes a set of tests to validate the implementation and ensure the correctness of the algorithm. The tests are implemented using the pytest framework and are located in the tests directory.

To run the tests, you can use the following command from the root directory :

uv run pytest

The tests are also automatically ran as the code is pushed to the repository using GitHub Actions.

Visualizing the simulation

The simulation outputs VTK files in the output/ directory that can be visualized using various tools. We recommend using either:

  • VisIt: An open-source visualization platform developed by Lawrence Livermore National Laboratory
  • ParaView: An open-source, multi-platform data analysis and visualization application

I personally made use of VisIt to visualize the simulation data and the mesh structure. One can easily visualize the simulation data by following these steps:

  1. Launch VisIt
  2. File > Open file > Navigate to the output/ directory
  3. Select the VTK files
  4. Choose "Pseudocolor" plot of the value field
  5. Click "Draw"

One can also visualize the gradient field by creating a new plot and selecting the gradient field, in "Pseudocolor".

The first image in this document shows the simulation at different time steps: $t_0$ (initial state), $t_1$ and $t_2$ (intermediate states which show clear refinement), and finally, the final state $t_\text{end}$.

During these time steps, the temperature field gradually diffuses outward from the central heat source as the mesh dynamically adapts to capture the evolving temperature gradients.

Performance and results

This section provides an overview of the performance considerations and optimizations used in the project.

As noted in the Code Architecture section, the implementation is designed to be efficient and optimized for performance. The codebase is structured around the Octree and Quadtree data structures, which allow for a more efficient representation of the mesh hierarchy and a more efficient use of computational resources.

The time complexity is optimized using a recursive structure that allows for a constant time complexity for the access operation. The memory usage is optimized using the __slots__ attribute to reduce the memory footprint of the class instances and ensure a more efficient memory usage.

The Python SQL toolkit SQLAlchemy, for example, uses the __slots__ attribute (as discussed here) to optimize the memory usage of the class instances and reduce the memory overhead of the ORM objects. This optimization is critical in the project as the number of nodes grows exponentially with the mesh resolution and the memory usage must be optimized to handle large meshes efficiently. Many more examples of the __slots__ attribute usage can be found in the Python standard library and in popular libraries.

Implementing the parallelization of the algorithm could be a potential optimization to further improve the performance of the simulation. By parallelizing the mesh refinement process, the algorithm could take advantage of multi-core processors and distribute the workload across multiple threads. This would allow for a more efficient use of computational resources and reduce the overall simulation time. As per data storage, the physical locality (in RAM) of the mesh structure would allow for a more efficient parallelization of the algorithm as the data would be stored in contiguous memory blocks and allow for a more efficient access pattern. This could be achieved by storing the mesh structure in a contiguous memory block and using a linear indexing scheme to access the nodes. This would allow for a more efficient access pattern and reduce the memory overhead of the mesh structure.

Both time and space complexity aren't optimized further as it is not the main focus of the project and the implementation is already efficient enough for the problem at hand.

The following table provides a comparison of time and space usage when running a heat transfer simulation of a continuous heat source with and without adaptive mesh refinement (10s simulation time, 0.01s delta, 20 refinement steps with a high conductivity material - more refinements):

Method Average Total Time (s) Average Memory Usage (MB)
Uniform Mesh 433.4 ± 7.382 50.578 ± 13.736
Adaptive Mesh Refinement 22.0 ± 0.31 4.251 ± 0.473

The simulation has been ran 10 times, on an Intel Core i9-9980HK CPU @ 2.40GHz with 32 GB of RAM (Ubuntu 24.04)

Please note that the resolution of the uniform mesh (256 cells per dimension) is the maximum number of cells per dimension the adaptive mesh refinement algorithm can reach (starts at 64 cells per dimension).

Moreover, the Uniform Mesh used for reference is implemented using the Quadtree data structure as implemented in the project.

The results show a significant improvement in both time (~19.7x) and memory usage (~11.9x) when using adaptive mesh refinement compared to a uniform mesh when solving the heat transfer problem with these parameters. The most time-consuming part of the simulation is the mesh numerical scheme solver, which is applied to each cell in the mesh and accounts for approximately 90% of the total time in both cases.

In the adaptive mesh refinement case, the refinement process accounts for ~4.7% of the total time and is responsible for the increased efficiency of the algorithm by reducing the number of cells that need to be solved. It is only done every 50 iterations, which allows for a more efficient use of computational resources and a faster simulation time.

The adaptive mesh refinement algorithm allows for a more efficient use of computational resources but comes with a tradeoff as the solution may not be as accurate as a uniform mesh solution.

When running the comparison script for both an AMR simulation with a maximum of 2 levels of refinement and an AMR simulation with a maximum of 1 level of refinement against a Uniform Mesh simulation (64 cells per dimension), the following results are obtained:

Method Average RMSE (Root Mean Square Error) [°C] Average Relative Error [%] Total Time [s]
+1 level AMR (32-64 cells per dimension) 0.222 ± 0.935 0.428 ± 0.302 131
+2 level AMR (16-64 cells per dimension) 0.397 ± 1.110 0.756 ± 0.425 133

At first glance, the results show that the AMR simulation with a maximum of 1 level of refinement provides a more accurate solution when compared to the Uniform Mesh simulation, with a lower RMSE and relative error. However, this greater similarity to the reference uniform mesh may be due to several factors.

Applying a second-order numerical scheme on a uniform mesh leads to sharper gradients getting averaged over a larger area. This averaging effect spreads out and diffuses the solution. Accumulating the error over time steps leads to a visually faster diffusion of the heat source.

Moreover, the discretization of the mesh introduces truncation errors that pronounce the artificial diffusion effect.

The adaptive mesh refinement algorithm, on the other hand, allows for a higher resolution at the boundaries of the heat source (throughout the gradient) and a lower resolution elsewhere. This leads to a more accurate representation of the heat source and a more accurate solution to the heat transfer problem.

The fact that, when initialized, the heat source is of lower resolution than the uniform mesh (4x smaller) also explains why the AMR simulation with a maximum of 1 level of refinement provides a more similar solution compared to the 2-level refinement one.

The two AMR simulations perform similarly in terms of computation time, demonstrating that the problem's scaling depends primarily on the maximum number of cells per dimension, as this resolution is applied near the heat source. However, this scaling behavior is also strongly influenced by the physical properties of the material, the rate of heat diffusion, and the characteristics of the heat source itself.

In conclusion, the adaptive mesh refinement simulation provides a more accurate solution to the heat transfer problem by focusing computational resources on regions of interest while reducing the overall computational cost. The tradeoff between performance - which is negligibly better in the +1 level AMR simulation due to reduced mesh solving time - and precision appears to be a key consideration when using AMR methods and should be carefully evaluated based on the specific requirements of the problem.

Conclusion

While I managed to understand and implement the main components of the project quite fast, I faced challenges in determining the self-directed evolution of the project. The core AMR algorithm and data structures came together smoothly, but identifying meaningful ways to extend and enhance the implementation, on my own, required more deliberate exploration. This highlighted the importance of having clear project goals and milestones beyond the initial implementation phase.

One key learning was recognizing earlier the critical role of comprehensive testing - while I did implement a test suite, setting up even more extensive tests from the start would have enabled faster and more confident iteration.

Nevertheless, working through this uncertainty helped me develop a more structured approach to problem-solving and a deeper appreciation for the iterative nature of software development. Testing has proven essential for maintaining reliability while making incremental improvements.

This project has been an enriching journey, providing deep insights into adaptive mesh refinement techniques and their practical implications. Through implementation, I gained a thorough understanding of both the advantages and limitations of AMR methods.

While AMR offers significant computational benefits, I learned how it also introduces complexities around mesh quality, refinement strategies, and solution accuracy. Exploring these tradeoffs between performance and precision has given me valuable perspective on numerical methods and scientific computing.

Future Work

The project lays a solid foundation for further exploration and development of adaptive mesh refinement techniques. Here are some potential areas for future work:

  • 3D Mesh Support: Extending the implementation to support 3D meshes and the Octree data structure would allow for more complex simulations and a more accurate representation of the physical field.
  • Parallelization: Implementing parallelization of the algorithm would allow for a more efficient use of computational resources and reduce the overall simulation time.
  • Advanced Refinement Criteria: Implementing more advanced refinement criteria based on physical properties or solution characteristics could improve the accuracy of the solution and reduce the computational cost.
  • Optimization Techniques: Exploring optimization techniques such as memory management, data storage, and access patterns could further improve the performance of the algorithm and reduce the memory usage.

License

This project is licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0). You are free to:

  • Share — copy and redistribute the material in any medium, format, or channel
  • Adapt — remix, transform, and build upon the material for any purpose, even commercially
  • Under the condition that you give appropriate credit to the author(s), provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the author(s) endorses you or your use.

You can view the full license details at: Creative Commons Attribution 4.0 International License

References

I wish to thank my supervisor, Florian Desmons, for his guidance and support throughout the project. His expertise and insights have been invaluable in the development of the algorithm and the implementation of the simulation. Moreover, I made use of the following resources to deepen my understanding of the subject and develop the project:

Berger, M. J., & Colella, P. (1989). Local adaptive mesh refinement for shock hydrodynamics. Journal of Computational Physics, 82(1), 64–84. DOI: 10.1016/0021-9991(89)90035-1

Neumann boundary condition. (2022). In Wikipedia. Link

Popinet, S. (2000). Stabilité et formation de jets dans les bulles cavitantes [PhD thesis, Université Pierre et Marie Curie]. Link

Popinet, S. (2003). Gerris: a tree-based adaptive solver for the incompressible Euler equations in complex geometries. Journal of Computational Physics, 190(2), 572–600. DOI: 10.1016/S0021-9991(03)00298-5

Truncation error (numerical integration). (2022). In Wikipedia. Link

Claude 3.5 Sonnet. (2024). Code review, documentation suggestions, and technical writing assistance for AMR implementation. Anthropic.

I made use of an AI assistant, Claude 3.5 Sonnet, to assist in the development of the project. Claude provided code review and feedback on the implementation, helping to identify and resolve issues in the algorithm.

The project is an original work developed as part of the HES-SO Valais-Wallis curriculum. Please note that it is intended for educational purposes only and should not be used for commercial or production purposes without proper validation and testing. If you have any questions or need further information, please feel free to contact me at dion(dot)osmani(at)students(dot)hevs(dot)ch.

About

An Adaptive Mesh Refinement (AMR) implementation in Python featuring Quadtree/Octree mesh structures with gradient-based refinement on a heat diffusion simulation

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages