Source code for pyftle.ftle_solver

from queue import Queue
from typing import Optional

from pyftle.cauchy_green import (
    compute_flow_map_jacobian_2x2,
    compute_flow_map_jacobian_3x3,
)
from pyftle.data_source import BatchSource
from pyftle.file_writers import FTLEWriter
from pyftle.ftle import compute_ftle_2x2, compute_ftle_3x3
from pyftle.integrate import Integrator
from pyftle.my_types import ArrayN


[docs] class FTLESolver: """ Compute the Finite-Time Lyapunov Exponent (FTLE) field from a sequence of velocity data files. The solver integrates particle trajectories over a given flow map period, computes the flow map Jacobian, and evaluates the FTLE field either in 2D or 3D. Data are processed sequentially from a :class:`BatchSource`, using an :class:`Integrator` for time integration, and optionally writing results through a :class:`FTLEWriter`. Parameters ---------- source : BatchSource Source of velocity field data and particle initialization. Must provide velocity data, interpolation updates, and particle positions for each time step. integrator : Integrator Object responsible for advancing particle positions in time using the current velocity interpolator. progress_queue : Optional[Queue], default=None Optional multiprocessing queue for publishing progress updates during FTLE computation. Each iteration sends a tuple ``(id, i)`` and a final ``(id, "done")`` message when completed. output_writer : Optional[FTLEWriter], default=None Writer used to save the computed FTLE field to disk. If not provided, the computed field is returned instead of written. Notes ----- The `FTLESolver` assumes that the sequence of velocity field files in `source` is ordered correctly for either forward or backward FTLE mapping. It is the user's responsibility to ensure temporal consistency in the input data. Examples -------- >>> solver = FTLESolver(source, integrator, output_writer=writer) >>> solver.run() # Computes and writes FTLE field """ def __init__( self, source: BatchSource, integrator: Integrator, progress_queue: Optional[Queue] = None, output_writer: Optional[FTLEWriter] = None, ): self.source = source self.integrator = integrator self.output_writer = output_writer self.progress_queue = progress_queue
[docs] def run(self): # TODO: add return type """ Run the FTLE computation for the current flow map period. This method performs the full FTLE workflow: 1. Loads particle positions from the data source. 2. Sequentially integrates all time steps in the dataset. 3. Publishes progress updates to the queue (if provided). 4. Computes the FTLE field using the final particle configuration. 5. Writes or returns the FTLE field. Returns ------- Optional[ArrayN] The computed FTLE field if `output_writer` is not provided; otherwise, returns ``None`` after writing the results to disk. Notes ----- The time integration is performed in-place using the provided :class:`Integrator`, which updates particle positions over ``num_steps`` iterations. After all steps, the flow map Jacobian is evaluated to compute the FTLE. """ self.particles = self.source.get_particles() id = self.source.id timestep = self.source.timestep num_steps = self.source.num_steps for i in range(num_steps): self.source.update_interpolator(self.integrator.interpolator, i) self.integrator.integrate(timestep, self.particles) # publish progress: i goes from 1 ... num_steps if self.progress_queue: self.progress_queue.put((id, i)) if self.progress_queue: # signal task done self.progress_queue.put((id, "done")) ftle_field = self._compute_ftle() if self.output_writer is not None: filename = f"ftle_{id}" self.output_writer.write( filename, ftle_field, self.particles.initial_centroid ) else: return ftle_field
def _compute_ftle(self) -> ArrayN: r""" Compute the FTLE field from the final particle configuration. Depending on the number of neighboring particles, the method automatically selects the appropriate dimensional version (2x2 or 3x3) of the flow map Jacobian and FTLE computation. Returns ------- ftle_field : ArrayN The computed FTLE scalar field, one value per particle centroid. Notes ----- The flow map Jacobian is computed using: * :func:`compute_flow_map_jacobian_2x2` for 2D datasets. * :func:`compute_flow_map_jacobian_3x3` for 3D datasets. The FTLE is then obtained as: .. math:: \\text{FTLE} = \\frac{1}{|T|} \\ln \\sqrt{\\lambda_{max}(C)} where :math:`T` is the flow map period and :math:`\\lambda_{max}(C)` is the largest eigenvalue of the Cauchy-Green deformation tensor :math:`C = F^\\top F`. """ num_steps = self.source.num_steps timestep = self.source.timestep if self.particles.num_neighbors == 4: jacobian = compute_flow_map_jacobian_2x2(self.particles) map_period = (num_steps - 1) * abs(timestep) return compute_ftle_2x2(jacobian, map_period) jacobian = compute_flow_map_jacobian_3x3(self.particles) map_period = (num_steps - 1) * abs(timestep) return compute_ftle_3x3(jacobian, map_period)