Source code for pina._src.callback.refinement.r3_refinement

"""Module for the R3Refinement callback."""

import torch
from pina._src.core.utils import check_consistency
from pina._src.core.label_tensor import LabelTensor
from pina._src.loss.dual_loss_interface import DualLossInterface
from pina._src.callback.refinement.base_refinement import BaseRefinement


[docs] class R3Refinement(BaseRefinement): """ Refinement strategy based on the R3 (Retain-Resample-Release) algorithm. This method adaptively updates collocation points by retaining points with high residuals, resampling new points in the domain, releasing points with low residuals. The objective is to concentrate sampling in regions where the PDE residual is large, improving training efficiency and solution accuracy. .. seealso:: **Original Reference**: Daw, Arka, et al. (2023). *Mitigating Propagation Failures in Physics-informed Neural Networks using Retain-Resample-Release (R3) Sampling*. DOI: `10.48550/arXiv.2207.02338 <https://doi.org/10.48550/arXiv.2207.02338>`_ :Example: >>> r3 = R3Refinement(sample_every=5) """ def __init__( self, sample_every, residual_loss=torch.nn.L1Loss, condition_to_update=None, ): """ Initialization of the :class:`R3Refinement` class. :param int sample_every: The number of epochs between successive refinement steps. :param residual_loss: The loss used to evaluate residual magnitude. Must be a subclass of :class:`torch.nn.Module` or :class:`pina.loss.DualLossInterface`. Default is :class:`torch.nn.L1Loss`. :type residual_loss: DualLossInterface | torch.nn.modules.loss._Loss :param condition_to_update: The condition(s) to be updated during refinement. If ``None``, all conditions associated with a domain are updated. Default is ``None``. :type condition_to_update: str | list[str] | tuple[str] :raises ValueError: If the condition_to_update is neither a string nor an iterable of strings. :raises ValueError: If the residual_loss is not a valid loss class. """ super().__init__(sample_every, condition_to_update) # Check consistency check_consistency( residual_loss, (DualLossInterface, torch.nn.modules.loss._Loss), subclass=True, ) # Store the loss function for computing residuals during sampling self.loss_fn = residual_loss(reduction="none")
[docs] def sample(self, current_points, condition_name, solver): """ Generate new sample points for a given condition. :param LabelTensor current_points: The existing points in the domain. :param str condition_name: The identifier of the condition to refine. :param BaseSolver solver: The solver used for sampling decisions. :return: Newly sampled points. :rtype: LabelTensor """ # Retrieve condition and current points device = solver.trainer.strategy.root_device condition = solver.problem.conditions[condition_name] current_points = current_points.to(device).requires_grad_(True) # Compute residuals for the given condition target = condition.evaluate({"input": current_points}, solver) residuals = self.loss_fn(target, torch.zeros_like(target)).mean( dim=tuple(range(1, target.ndim)) ) # Retrieve domain and initial population size domain_name = solver.problem.conditions[condition_name].domain domain = solver.problem.domains[domain_name] num_old_points = self.initial_population_size[condition_name] # Select points with residual above the mean mask = (residuals >= residuals.mean()).flatten() high_residual_pts = current_points[mask] high_residual_pts.labels = current_points.labels # Sample new points to maintain the initial population size num_new_pts = max(num_old_points - len(high_residual_pts), 0) samples = domain.sample(num_new_pts, "random").to(device) return LabelTensor.cat([high_residual_pts, samples])