# %%
# Particle tracking from baroclinic instability testcase
# ======================================================
# This example performs offline Lagrangian particle tracking using the 3D
# hydrodynamic output of the baroclinic instability testcase. Particles are
# continuously seeded along a vertical column near the center of the domain
# and are advected by the stored velocity field.
#
# The script reads the vertical coordinates and 3D velocity from the hydro
# output, advances the particles in time, exports active and deleted particles,
# and reseeds a new column of particles after each export interval.

import sys
import numpy as np
from slim4 import slim, slim3d, particles3d as p3d
from slim4.slim import si_format
from time import perf_counter as clock

# %%
# We first load the 3D hydrodynamic output used to drive the particle tracking.
# The free-surface coordinates and the velocity field are read from the eddies
# testcase output.

print("Loading 3D tracker")
mesh = slim3d.Mesh.from_output("run3d/", "mesh3dSlim.msh")
z = slim.DataOutput("run3d", "z", mesh)
velocity = slim.DataOutput("run3d", "u", mesh)


# %%
# The particle tracker uses prescribed horizontal and vertical diffusivities in
# addition to the resolved velocity field.

horizontal_diffusivity = slim.DataStatic.create_constant(mesh, 3.0)
vertical_diffusivity = slim.DataStatic.create_constant(mesh, 1e-5)
tracker = p3d.Tracker3d(mesh, z, velocity, horizontal_diffusivity, vertical_diffusivity)
tracker.set_boundary("Wall", "settle")

# %%
# We define the particle array. Custom attributes can be attached to particles;
# here we use "spawn_time", but any number (or none) can be used. A random seed
# may be set for reproducibility.

particles = p3d.ParticleArrays3d(mesh, ["spawn_time"], random_seed=1)


# %%
# We set the time step and the export frequency. The export interval is adjusted
# so that it is an integer multiple of the tracking time step.

dt = 300
export_dt = 3600

t0 = z.time[0]
tf = z.time[-1]
nsub = int(np.ceil(export_dt / dt))
dt = export_dt / nsub
nsteps = int(np.ceil(tf / dt))


# %%
# Particles are seeded along a vertical column in the middle of the domain. The
# number of particles can be provided on the command line; otherwise a default
# value is used.

nparticles = int(sys.argv[1]) if len(sys.argv) > 1 and sys.argv[1].isdigit() else 100
seed_position = np.zeros((3, nparticles))
seed_position[0, :] = 80e3
seed_position[1, :] = 250e3
seed_position[2, :] = np.linspace(0, -1000, nparticles)


def seed_particles(particle_arrays, time):
    """Insert a new column of particles and record their seeding time."""
    attributes = {"spawn_time": np.full(nparticles, time)}
    tracker.add_particles(particle_arrays, seed_position, time, attributes)


seed_particles(particles, t0)
print(f"number of particles: {particles.size:g}")


# %%
# We prepare the particle outputs. Active particles and deleted particles are
# written to separate XDMF files.

particles_output = "particles"
deleted_output = "deleted"
p3d.ParticleArrays3d.create_xdmf(particles_output)
p3d.ParticleArrays3d.create_xdmf(deleted_output)
particles.append_to_xdmf(particles_output, t0)


# %%
# We can now run the particle tracking. After each export interval, inactive
# particles are removed (and exported separately), active particles are written
# to disk, and a new column of particles is seeded. Particle sorting is important
# for performance, but it is also expensive and should not be done every time
# step. Here, remove_inactive() already performs sorting, so calling sort()
# separately would be redundant.

t = t0
iexport = 0

print("starting computation")
tic = clock()
for istep in range(nsteps):
    t = tracker.move(particles, t, dt)

    if (istep + 1) % nsub == 0:
        # particles.sort()  # useful for performance if not already handled
        deleted_particles = particles.remove_inactive()
        deleted_particles.append_to_xdmf(deleted_output, t, ["deletion_time"])
        particles.append_to_xdmf(particles_output, t)

        periter = (clock() - tic) / nsub
        tic = clock()
        iexport += 1
        print(f"Export {iexport}, Time {t}, periter {si_format(periter, 3)}s")

        seed_particles(particles, t)
