"""Plotting functions (generic in geometric dimension) for approximation spaces."""
# import warnings
from collections.abc import Sequence
from typing import Any, Callable
import numpy as np
from scimba_common.plots.plots_nd import (
_plot_abstract_approx_space,
_plot_abstract_approx_spaces,
)
from scimba_torch.approximation_space.abstract_space import AbstractApproxSpace
from scimba_torch.domain.mesh_based_domain.cuboid import Cuboid
from scimba_torch.domain.meshless_domain.base import SurfacicDomain, VolumetricDomain
from scimba_torch.plots._utils.eval_utilities import (
eval_on_np_tensors,
eval_scalar_func_on_np_tensors,
)
[docs]
def plot_abstract_approx_space(
space: AbstractApproxSpace,
spatial_domain: VolumetricDomain | Cuboid,
parameters_domain: Sequence[Sequence[float]] = [],
time_domain: Sequence[float] = [],
velocity_domain: SurfacicDomain | VolumetricDomain | None = None,
**kwargs,
):
"""Plot an AbstractApproxSpace on its domain.
Args:
space: the space to be plot
spatial_domain: the geometric domain on which space is defined
parameters_domain: the domain of parameters of space, [] meaning no parameters,
time_domain: the time domain of space, [] meaning space is time-independent,
velocity_domain: the velocity domain of space,
None meaning space has no velocity arguments,
**kwargs: arbitrary keyword arguments
Keyword Args:
parameters_values: a (list of) point(s) in the parameters domain,
or "mean" or "random", defaults to "mean",
time_values: a (list of) time(s) in the time domain,
or "initial" or "final", defaults to "final",
velocity_values: a (list of) point(s) in the velocity domain,
components: the list of components of the space to be plot,
defaults to the list of all the components,
loss: a GenericLosses object to be plot,
residual: an AbstractPDE object with a residual attribute,
derivatives: a list of strings representing the derivatives to be plot,
for instance "uxx"; defaults to [],
solution: a callable depending on the same args as space to be plot,
error: plot the absolute error with respect to the given solution,
cuts: for 2D geometric dim, a list of affine spaces of dimension 1,
each given as a tuple of 1 point and a basis
title: a str
...: see examples
Implemented only for 1 and 2 dimensional spaces.
Raises:
ValueError: some input arguments are not correctly formated
Examples:
>>> import matplotlib.pyplot as plt
>>> from scimba_torch.plots.plots_nd import plot_AbstractApproxSpace
>>> ...
>>> def exact_sol(x: LabelTensor, mu: LabelTensor):
x1, x2 = x.get_components()
mu1 = mu.get_components()
return mu1 * torch.sin(2.0 * torch.pi * x1) *
torch.sin(2.0 * torch.pi * x2)
>>> plot_AbstractApproxSpace(
pinns.space, #the approximation space
domain_x, #the geometric domain
[[1.0, 2.0]], #the parameters domain
loss=pinns.losses, #the loss
residual=pde, #the residual
solution=exact_sol, #the reference solution
error=exact_sol, #the ref. sol. to plot absolute error
derivatives=["ux", "uy"], #a list of string for derivatives
cuts=[ #a list of 2 1D cuts
([0.0, 0.0], [-0.5, 0.5]),
([0.0, 0.2], [0.0, 1.0]),
],
draw_contours=True, #whether to draw level lines
n_drawn_contours=20, #number of level lines
title="Learned solution to 2D Laplacian in strong
form with weak boundary conditions",
)
>>> plt.show()
"""
if not isinstance(space, AbstractApproxSpace):
raise ValueError("first argument (space) must be an AbstractApproxSpace")
def eval_approx_space_function(
t: np.ndarray,
x: np.ndarray,
v: np.ndarray,
mu: np.ndarray,
symb_dict: dict[str, list[str]],
component: int | dict[int, int] = 0,
**kwargs,
) -> dict[str, np.ndarray]:
return eval_on_np_tensors(space, t, x, v, mu, symb_dict, component, **kwargs)
def eval_scalar_func_of_space_function(
func: Callable,
t: np.ndarray,
x: np.ndarray,
v: np.ndarray,
mu: np.ndarray,
) -> np.ndarray:
return eval_scalar_func_on_np_tensors(func, space, t, x, v, mu)
_plot_abstract_approx_space(
eval_approx_space_function,
eval_scalar_func_of_space_function,
space.nb_unknowns,
spatial_domain,
parameters_domain,
time_domain,
velocity_domain,
**kwargs,
)
def _is_sequence_abstract_approx_space(l_spaces: Any) -> bool:
return isinstance(l_spaces, Sequence) and all(
isinstance(L, AbstractApproxSpace) for L in l_spaces
)
[docs]
def plot_abstract_approx_spaces(
spaces: AbstractApproxSpace | Sequence[AbstractApproxSpace],
spatial_domains: VolumetricDomain | Cuboid | Sequence[VolumetricDomain | Cuboid],
parameters_domains: Sequence[Sequence[float]]
| Sequence[Sequence[Sequence[float]]] = ([],),
time_domains: Sequence[float] | Sequence[Sequence[float]] = ([],),
velocity_domains: SurfacicDomain | None | Sequence[SurfacicDomain | None] = None,
**kwargs,
) -> None:
"""Plot a sequence of AbstractApproxSpaces on their domains.
Args:
spaces: the (sequence of) space(s) to be plot
spatial_domains: the (sequence of) geometric domain(s)
on which spaces are defined
parameters_domains: the (sequence of) domain(s) of parameters of space(s),
([],) meaning no parameters,
time_domains: the (sequence of) time domain(s) of space(s),
([],) meaning spaces is time-independent,
velocity_domains: the the (sequence of) velocity domain(s) of space(s),
None meaning space has no velocity arguments,
**kwargs: arbitrary keyword arguments
Keyword Args:
title: the main title of the figure
titles: a sequence of titles (1 for each approximation space)
...: same keyword arguments as in plot_AbstractApproxSpace,
are to be given as sequences of n values, where n is the number of spaces;
sequences of n same values can be shortcut by the value
Implemented only for 1 and 2 dimensional spaces
Raises:
ValueError: some input arguments are not correctly formated
Examples:
>>> import matplotlib.pyplot as plt
>>> from scimba_torch.plots.plots_nd import plot_AbstractApproxSpaces
>>> ...
>>> def exact_sol(x: LabelTensor, mu: LabelTensor):
x1, x2 = x.get_components()
mu1 = mu.get_components()
return mu1 * torch.sin(2.0 * torch.pi * x1) *
torch.sin(2.0 * torch.pi * x2)
>>> plot_AbstractApproxSpaces(
(pinns.space, pinns2.space, pinns3.space,),# a sequence of AbstractSpace
domain_x, # shortcut for (domain_x,domain_x,domain_x,)
((1.0, 1.0 + 1e-5),), # the same parameters domain for the 3 spaces
loss=( pinns.losses, pinns2.losses, pinns3.losses, ),
residual=( pinns.pde, pinns2.pde, pinns3.pde, ),
error=exact_sol,
draw_contours=True,
n_drawn_contours=20,
parameters_values="random",
)
>>> plt.show()
"""
nspaces = (spaces,) if isinstance(spaces, AbstractApproxSpace) else spaces
if not _is_sequence_abstract_approx_space(nspaces):
raise ValueError(
"first argument must be a AbstractApproxSpace or a Sequence of "
"AbstractApproxSpaces"
)
# nbspaces = len(nspaces)
eval_approx_space_functions = [
lambda *args, space=space, **kwargs: eval_on_np_tensors(space, *args, **kwargs)
for space in nspaces
]
eval_scalar_func_of_space_functions = [
lambda func, *args, space=space: eval_scalar_func_on_np_tensors(
func, space, *args
)
for space in nspaces
]
nnb_components = [space.nb_unknowns for space in nspaces]
_plot_abstract_approx_spaces(
eval_approx_space_functions,
eval_scalar_func_of_space_functions,
nnb_components,
spatial_domains,
parameters_domains,
time_domains,
velocity_domains,
**kwargs,
)