///|
pub fn map_particles(
  particles : ArrayView[Particle],
  transform : (Particle) -> Particle,
) -> Array[Particle] {
  particles.map(transform)
}

///|
pub fn map_field(field : Field1D, transform : (Double) -> Double) -> Field1D {
  {
    ..field,
    electric: field.electric.map(transform),
    potential: field.potential.map(transform),
  }
}

///|
pub fn scale_field(field : Field1D, factor : Double) -> Field1D {
  map_field(field, fn(value) { value * factor })
}

///|
pub fn shift_field(grid : Grid1D, field : Field1D, shift : Double) -> Field1D {
  {
    ..field,
    electric: shift_profile(grid, field.electric, shift),
    potential: shift_profile(grid, field.potential, shift),
  }
}

///|
pub fn mix_fields(
  first : Field1D,
  second : Field1D,
  fraction : Double,
) -> Field1D {
  {
    ..first,
    electric: field_add(
      field_scale(first.electric, 1.0 - fraction),
      field_scale(second.electric, fraction),
    ),
    potential: field_add(
      field_scale(first.potential, 1.0 - fraction),
      field_scale(second.potential, fraction),
    ),
  }
}

///|
pub fn normalize_field(field : Field1D) -> Field1D {
  let scale = field_maximum(field.electric)
  if scale == 0.0 {
    field
  } else {
    scale_field(field, 1.0 / scale)
  }
}

///|
pub fn project_field(field : Field1D) -> Field1D {
  {
    ..field,
    electric: project_zero_mean(field.electric),
    potential: poisson_mean_free(field.potential),
  }
}

///|
pub fn charge_neutralize(
  _grid : Grid1D,
  density : ArrayView[Double],
) -> Array[Double] {
  density.map(fn(value) { value - mean(density) })
}

///|
pub fn particle_mean_shift(particles : ArrayView[Particle]) -> Array[Particle] {
  let average = center_of_mass(Grid1D::new(1, 1.0), particles)
  translate_particles(particles, -average)
}

///|
pub fn particle_velocity_center(
  particles : ArrayView[Particle],
) -> Array[Particle] {
  let average = velocity_mean(particles)
  particles.map(fn(particle) {
    Particle::new(
      x=particle.x,
      v=particle.v - average,
      weight=particle.weight,
      charge=particle.charge,
      mass=particle.mass,
    )
  })
}

///|
pub fn particle_thermalize(
  particles : ArrayView[Particle],
  temperature : Double,
) -> Array[Particle] {
  let scale = temperature.max(0.0).sqrt()
  particles.map(fn(particle) {
    Particle::new(
      x=particle.x,
      v=particle.v * scale,
      weight=particle.weight,
      charge=particle.charge,
      mass=particle.mass,
    )
  })
}

///|
pub fn particle_merge(
  first : ArrayView[Particle],
  second : ArrayView[Particle],
) -> Array[Particle] {
  let output = sample_clone(first)
  for particle in second {
    output.push(particle)
  }
  output
}