///|
pub fn push_particle(
  grid : Grid1D,
  particle : Particle,
  electric~ : Double,
  dt~ : Double,
) -> Particle {
  let acceleration = particle.charge * electric / particle.mass
  let next_velocity = particle.v + acceleration * dt
  Particle::new(
    x=grid.wrap(particle.x + next_velocity * dt),
    v=next_velocity,
    weight=particle.weight,
    charge=particle.charge,
    mass=particle.mass,
  )
}

///|
pub fn stable_pic_dt(
  grid : Grid1D,
  maximum_speed : Double,
  safety_factor : Double,
) -> Double {
  if maximum_speed <= 0.0 || safety_factor <= 0.0 {
    0.0
  } else {
    safety_factor * grid.dx / maximum_speed
  }
}

///|
pub fn kinetic_temperature(particles : ArrayView[Particle]) -> Double {
  let variance = velocity_variance(particles)
  if particles.length() == 0 {
    0.0
  } else {
    let mass = particles[0].mass
    mass * variance / boltzmann_constant
  }
}

///|
pub fn advance_particles(
  grid : Grid1D,
  particles : ArrayView[Particle],
  electric : ArrayView[Double],
  dt : Double,
) -> Array[Particle] {
  particles.map(fn(particle) {
    let field = sample_linear(grid, electric, particle.x)
    push_particle(grid, particle, electric=field, dt~)
  })
}

///|
pub fn particle_speed_range(
  particles : ArrayView[Particle],
) -> (Double, Double) {
  if particles.length() == 0 {
    (0.0, 0.0)
  } else {
    let mut minimum = particles[0].v
    let mut maximum = particles[0].v
    for particle in particles {
      if particle.v < minimum {
        minimum = particle.v
      }
      if particle.v > maximum {
        maximum = particle.v
      }
    }
    (minimum, maximum)
  }
}