#import "@preview/diatypst:0.2.0": *

// #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",
  // ratio: 16/9,
)


#import "helpers.typ"

// KINDA COOL:
// _diatypst_ defines some default styling for elements, e.g Terms created with ```typc / Term: Definition``` will look like this

// / *Term*: Definition



// Setup of code location
#let t1 = json("../task1.ipynb")
#let t2 = json("../task2-particle-mesh.ipynb")


// Finally - The real 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. [#link(<task1:plot_particle_distribution>)[code]]



#columns(2)[
  #helpers.image_cell(t1, "plot_particle_distribution")
  Note: for visibility the outer particles are not shown.
  #colbreak()
  The system at hand is characterized by:
  - $N ~ 10^4$ stars
  - a _spherical_ distribution

  $==>$ treat the system as a *globular cluster*

]


== Density
We compare the computed density with the analytical model provided by the _Hernquist_ model:

#grid(
  columns: (1fr, 2fr),
  inset: 0.5em,
  block[
    $
    rho(r) = M/(2 pi)  a / (r dot (r + a)^3) 
    $
    where we infer $a$ from the half-mass radius:
    $
    r_"hm" = (1 + sqrt(2)) dot a
    $

    #text(size: 0.6em)[
      Density sampling [#link(<task1:function_density_distribution>)[code]];
    ]
  ],
  block[
    #helpers.image_cell(t1, "plot_density_distribution")
  ]
)


#block(
  height: 1fr,
)


== Force computation
// N Body and variations
#grid(
  columns: (2fr, 1fr),
  inset: 0.5em,
  block[
    #helpers.image_cell(t1, "plot_force_radial")
    // The radial force is computed as the sum of the forces of all particles in the system.
    #text(size: 0.6em)[
      Analytical force [#link(<task1:function_analytical_forces>)[code]];
      $N^2$ force [#link(<task1:function_n2_forces>)[code]];
      $epsilon$ computation [#link(<task1:function_interparticle_distance>)[code]];
    ]
  ],
  block[
    Discussion:
    - the analytical method replicates the behavior accurately
    - at small softenings the $N^2$ method has noisy artifacts 
    - a $1 dot epsilon$ softening is a good compromise between accuracy and stability
  ]
)


== Relaxation
Relaxation [#link(<task1:compute_relaxation_time>)[code]]:
// #helpers.code_cell(t1, "compute_relaxation_time")


Discussion!




= Particle Mesh

== Overview - the system
#page(
  columns: 2
)[
  #helpers.image_cell(t2, "plot_particle_distribution")
]



== Force computation
#helpers.code_reference_cell(t2, "function_mesh_force")

#helpers.image_cell(t2, "plot_force_radial")

#grid(
  columns: (2fr, 1fr),
  inset: 0.5em,
  block[
    #helpers.image_cell(t2, "plot_force_radial_single")
    // The radial force is computed as the sum of the forces of all particles in the system.
    #text(size: 0.6em)[
      $N^2$ force [#link(<task1:function_n2_forces>)[code]];
      $epsilon$ computation [#link(<task1:function_interparticle_distance>)[code]];
      Mesh force [#link(<task2:function_mesh_force>)[code]];
    ]
  ],
  block[
    Discussion:
    - using the (established) baseline of $N^2$ with $1 dot epsilon$ softening
    - small grids are stable but inaccurate at the center
    - very large grids have issues with overdiscretization

    $==> 75 times 75 times 75$ as a good compromise
  ]
)

== Time integration
=== Runge-Kutta
#helpers.code_reference_cell(t2, "function_runge_kutta")


#pagebreak()
=== Results
#align(center, block(
  height: 1fr,
)[
  #helpers.image_cell(t2, "plot_system_evolution")
])


== Particle mesh solver
sdlsd




= Appendix - Code <appendix>

== Code
#helpers.code_cell(t1, "plot_particle_distribution")
<task1:plot_particle_distribution>

#pagebreak(weak: true)

#helpers.code_reference_cell(t1, "function_density_distribution")
<task1:function_density_distribution>

#pagebreak(weak: true)

#helpers.code_reference_cell(t1, "function_analytical_forces")
<task1:function_analytical_forces>

#pagebreak(weak: true)

#helpers.code_reference_cell(t1, "function_n2_forces")
<task1:function_n2_forces>

#pagebreak(weak: true)

#helpers.code_reference_cell(t1, "function_interparticle_distance")
<task1:function_interparticle_distance>

#pagebreak(weak: true)

#helpers.code_cell(t1, "compute_relaxation_time")
<task1:compute_relaxation_time>

#pagebreak(weak: true)

#helpers.code_reference_cell(t2, "function_mesh_force")
<task2:function_mesh_force>





#context {
  counter(page).update(locate(<appendix>).page())
}
