Source code for fol.controls.voronoi_control2D

"""
 Authors: Kianoosh Taghikhani, https://github.com/kianoosh1989
 Date: October, 2024
 License: FOL/LICENSE
"""
from  .control import Control
import jax.numpy as jnp
from jax import jit,vmap
import numpy as np
from functools import partial
from fol.mesh_input_output.mesh import Mesh
from fol.tools.decoration_functions import *

[docs]class VoronoiControl2D(Control): """ Voronoi-based parametric control in two dimensions. This control maps a vector of control variables to a nodal scalar field using a Voronoi tessellation defined by a set of seed points in the two-dimensional domain. Each mesh node is assigned the value associated with the nearest seed point, measured by Euclidean distance in the (x, y) plane. The control variables consist of: - x-coordinates of the Voronoi seed points, - y-coordinates of the Voronoi seed points, - scalar values associated with each seed. For each mesh node, the controlled value is selected from the seed whose coordinates are closest to the node coordinates. This produces a piecewise-constant field over the mesh, with discontinuities along Voronoi cell boundaries. The number of control variables is three times the number of seeds, and the number of controlled variables equals the number of mesh nodes. Args: control_name (str): Name identifier for the control instance. control_settings (dict): Dictionary defining the Voronoi parameterization. Required entries include ``"number_of_seeds"`` and ``"E_values"``. The length of ``"E_values"`` must match ``number_of_seeds``. fe_mesh (Mesh): Finite element mesh providing nodal coordinates. """ def __init__(self,control_name: str,control_settings, fe_mesh: Mesh): super().__init__(control_name) self.settings = control_settings self.fe_mesh = fe_mesh
[docs] @print_with_timestamp_and_execution_time def Initialize(self,reinitialize=False) -> None: """ Initialize Voronoi seed configuration and control dimensions. This method reads the number of Voronoi seeds and their associated values from ``control_settings`` and sets the number of control and controlled variables. Initialization is performed once unless ``reinitialize`` is set to ``True``. Args: reinitialize (bool, optional): If ``True``, forces reinitialization even if already initialized. Default is ``False``. Returns: None Raises: ValueError: If ``E_values`` is not provided as a list or tuple, or if its length is incompatible with ``number_of_seeds``. """ if self.initialized and not reinitialize: return self.number_of_seeds = self.settings["number_of_seeds"] if not isinstance(self.settings["E_values"],tuple) and not isinstance(self.settings["E_values"],list): raise(ValueError("'E values' should be either tuple or list")) self.E_values = self.settings["E_values"] # number 3 stands for the following: x coordinates array, y coordinates array, and K values self.num_control_vars = self.number_of_seeds * 3 self.num_controlled_vars = self.fe_mesh.GetNumberOfNodes() self.initialized = True
[docs] @partial(jit, static_argnums=(0,)) def ComputeControlledVariables(self, variable_vector: jnp.array): """ Compute the nodal field induced by the Voronoi control. This method interprets the input control vector as concatenated arrays of seed x-coordinates, seed y-coordinates, and seed values. For each mesh node, the nearest seed point is identified using Euclidean distance, and the corresponding seed value is assigned to the node. Args: variable_vector (jax.numpy.ndarray): One-dimensional control vector of length ``3 * number_of_seeds``. The first third contains x-coordinates of the seeds, the second third contains y-coordinates, and the final third contains the scalar values associated with each seed. Returns: jax.numpy.ndarray: One-dimensional array of controlled values evaluated at mesh nodes, with length equal to the number of mesh nodes. """ x_coord = variable_vector[:self.number_of_seeds] y_coord = variable_vector[self.number_of_seeds:2 * self.number_of_seeds] k_values = variable_vector[2 * self.number_of_seeds:] X = self.fe_mesh.GetNodesX() Y = self.fe_mesh.GetNodesY() K = jnp.zeros((self.num_controlled_vars)) seed_points = jnp.vstack((x_coord, y_coord)).T grid_points = jnp.vstack([X.ravel(), Y.ravel()]).T # Calculate Euclidean distance between each grid point and each seed point def euclidean_distance(grid_point, seed_points): return jnp.sqrt(jnp.sum((grid_point - seed_points) ** 2, axis=1)) # Iterate over grid points and assign the value from the nearest seed point def assign_value_to_grid(grid_point): distances = euclidean_distance(grid_point, seed_points) nearest_seed_idx = jnp.argmin(distances) return k_values[nearest_seed_idx] assign_value_to_grid_vmap_compatible = vmap(assign_value_to_grid,in_axes= 0)(grid_points) K = assign_value_to_grid_vmap_compatible return K
[docs] @print_with_timestamp_and_execution_time def Finalize(self) -> None: pass