starting on the presentation

This commit is contained in:
Remy Moll 2025-01-31 18:36:11 +01:00
parent 9d54e9743e
commit 7db6480e51
5 changed files with 285 additions and 21 deletions

BIN
nbody/presentation/main.pdf Normal file

Binary file not shown.

148
nbody/presentation/main.typ Normal file
View File

@ -0,0 +1,148 @@
#import "@preview/diatypst:0.2.0": *
#import "@preview/based:0.2.0": base64
#set text(font: "Cantarell")
// #set heading(numbering: (..nums)=>"")
#show: slides.with(
title: "N-Body project ",
subtitle: "Computational Astrophysics, HS24",
date: "04.02.2024",
authors: ("Rémy Moll"),
toc: false,
// layout: "large",
)
// Helpers for code block displaying
#let cell_matcher(cell, cell_tag) = {
if cell.cell_type != "code" {
return false
}
let metadata = cell.metadata
if metadata.keys().contains("tags") == false {
return false
}
return cell.metadata.tags.contains(cell_tag)
}
#let code_cell(fcontent, cell_tag) = {
let cells = fcontent.cells
let matching_cell = cells.find(x => cell_matcher(x, cell_tag))
let cell_content = matching_cell.source
// format the cell content
let single_line = cell_content.fold("", (acc, x) => acc + x)
text(
raw(
single_line,
lang: "python",
block: true
),
size: 0.8em
)
}
#let image_cell(fcontent, cell_tag) = {
let cells = fcontent.cells
let matching_cell = cells.find(x => cell_matcher(x, cell_tag))
let outputs = matching_cell.outputs
for output in outputs {
let image_data = output.at("data", default: (:)).at("image/png", default: none)
if image_data != none {
align(
center,
image.decode(
base64.decode(image_data),
// format: "png",
height: 70%
)
)
}
}
}
// Where is the code?
#let t1 = json("../task1.ipynb")
#let t2 = json("../task2-particle-mesh.ipynb")
// Content
= N-body forces and analytical solutions
== Objective
Implement naive N-body force computation and get an intuition of the challenges:
- accuracy
- computation time
- stability
$=>$ still useful to compute basic quantities of the system, but too limited for large systems or the dynamical evolution of the system
== Overview - the system
Get a feel for the particles and their distribution. [Code at @task1:plot_particle_distribution[]]
#image_cell(t1, "plot_particle_distribution")
#code_cell(t1, "plotting")
== Inspecting the data
#code_cell(t2, "plotting")
#image_cell(t2, "plotting")
== Density
Some images about the density
== N Body and variations
sdsd
== Relaxation
sd
= Default Styling in diatypst
== Terms, Code, Lists
_diatypst_ defines some default styling for elements, e.g Terms created with ```typc / Term: Definition``` will look like this
/ *Term*: Definition
A code block like this
```python
// Example Code
print("Hello World!")
```
Lists have their marker respect the `title-color`
#columns(2)[
- A
- AAA
- B
#colbreak()
1. AAA
2. BBB
3. CCC
]
= Appendix - Code
== Code
<task1:plot_particle_distribution>
#code_cell(t1, "plot_particle_distribution")

File diff suppressed because one or more lines are too long

View File

@ -258,7 +258,11 @@
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"metadata": {
"tags": [
"plotting"
]
},
"outputs": [
{
"data": {
@ -385,7 +389,11 @@
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"metadata": {
"tags": [
"kjhfsdf"
]
},
"outputs": [],
"source": [
"def integrate(method: str, force_function: callable, p0: np.ndarray, t_range: np.ndarray) -> np.ndarray:\n",

View File

@ -0,0 +1,89 @@
## Implementation of a mesh based full solver with boundary conditions etc.
import numpy as np
from . import mesh_forces
import logging
logger = logging.getLogger(__name__)
def mesh_solver(
particles: np.ndarray,
G: float,
mapping: callable,
n_grid: int,
bounds: tuple = (-1, 1),
boundary: str = "vanishing",
) -> np.ndarray:
"""
Computes the gravitational force acting on a set of particles using a mesh-based approach. The mesh is of fixed size: n_grid x n_grid x n_grid spanning the given bounds. Particles reaching the boundary are treated according to the boundary condition.
Args:
- particles: np.ndarray, shape=(n, 4). Assumes that the particles array has the following columns: x, y, z, m.
- G: float, the gravitational constant.
- mapping: callable, the mapping function to use.
- n_grid: int, the number of grid points in each direction.
- bounds: tuple, the bounds of the mesh.
- boundary: str, the boundary condition to apply.
"""
if particles.shape[1] != 4:
raise ValueError("Particles array must have 4 columns: x, y, z, m")
logger.debug(f"Computing forces for {particles.shape[0]} particles using mesh [mapping={mapping.__name__}, {n_grid=}]")
# the function is fine, let's abuse it somewhat
axis = np.linspace(bounds[0], bounds[1], n_grid)
mesh = np.zeros((n_grid, n_grid, n_grid))
spacing = np.abs(axis[1] - axis[0])
logger.debug(f"Using mesh spacing: {spacing}")
# Check that the boundary condition is fullfilled
if boundary == "periodic":
raise NotImplementedError("Periodic boundary conditions are not implemented yet")
elif boundary == "vanishing":
# remove the particles that are outside the mesh
outlier_mask = particles[:, :3] < bounds[0] | particles[:, :3] > bounds[1]
if np.any(outlier_mask):
logger.warning(f"Removing {np.sum(outlier_mask)} particles that are outside the mesh")
particles = particles[~outlier_mask]
logger.debug(f"New particles shape: {particles.shape}")
else:
raise ValueError(f"Unknown boundary condition: {boundary}")
if logger.isEnabledFor(logging.DEBUG):
mesh_forces.show_mesh_information(mesh, "Density mesh")
# compute the potential and its gradient
phi_grad = mesh_forces.mesh_poisson(rho, G, spacing)
if logger.isEnabledFor(logging.DEBUG):
logger.debug(f"Got phi_grad with: {phi_grad.shape}, {np.max(phi_grad)}")
mesh_forces.show_mesh_information(phi_grad[0], "Potential gradient (x-direction)")
# compute the particle forces from the mesh potential
forces = np.zeros_like(particles[:, :3])
for i, p in enumerate(particles):
ijk = np.digitize(p, axis) - 1
logger.debug(f"Particle {p} maps to cell {ijk}")
# this gives 4 entries since p[3] the mass is digitized as well -> this is meaningless and we discard it
# logger.debug(f"Particle {p} maps to cell {ijk}")
forces[i] = - p[3] * phi_grad[..., ijk[0], ijk[1], ijk[2]]
return forces
def particles_to_mesh(particles: np.ndarray, mesh: np.ndarray, axis: np.ndarray, mapping: callable) -> None:
"""
Maps a list of particles to an existing mesh, filling it inplace
"""
if particles.shape[1] < 4:
raise ValueError("Particles array must have at least 4 columns: x, y, z, m")
# axis provide an easy way to map the particles to the mesh
for p in particles:
m = p[-1]
# spread the star onto cells through the shape function, taking into account the mass
ijks, weights = mapping(p, axis)
for ijk, weight in zip(ijks, weights):
mesh[ijk[0], ijk[1], ijk[2]] += weight * m