* Fix adaptive refinement (#571) --------- Co-authored-by: Dario Coscia <93731561+dario-coscia@users.noreply.github.com> * Remove collector * Fixes * Fixes * rm unnecessary comment * fix advection (#581) * Fix tutorial .html link (#580) * fix problem data collection for v0.1 (#584) * Message Passing Module (#516) * add deep tensor network block * add interaction network block * add radial field network block * add schnet block * add equivariant network block * fix + tests + doc files * fix egnn + equivariance/invariance tests Co-authored-by: Dario Coscia <dariocos99@gmail.com> --------- Co-authored-by: giovanni <giovanni.canali98@yahoo.it> Co-authored-by: AleDinve <giuseppealessio.d@student.unisi.it> * add type checker (#527) --------- Co-authored-by: Filippo Olivo <filippo@filippoolivo.com> Co-authored-by: Giovanni Canali <115086358+GiovanniCanali@users.noreply.github.com> Co-authored-by: giovanni <giovanni.canali98@yahoo.it> Co-authored-by: AleDinve <giuseppealessio.d@student.unisi.it>
479 lines
14 KiB
Python
479 lines
14 KiB
Python
import torch
|
|
import pytest
|
|
from pina import LabelTensor
|
|
from pina.operator import grad, div, laplacian, advection
|
|
|
|
|
|
class Function(object):
|
|
|
|
def __iter__(self):
|
|
functions = [
|
|
(
|
|
getattr(self, f"{name}_input"),
|
|
getattr(self, f"{name}"),
|
|
getattr(self, f"{name}_grad"),
|
|
getattr(self, f"{name}_div"),
|
|
getattr(self, f"{name}_lap"),
|
|
)
|
|
for name in [
|
|
"scalar_scalar",
|
|
"scalar_vector",
|
|
"vector_scalar",
|
|
"vector_vector",
|
|
]
|
|
]
|
|
return iter(functions)
|
|
|
|
# Scalar to scalar function
|
|
@staticmethod
|
|
def scalar_scalar(x):
|
|
return x**2
|
|
|
|
@staticmethod
|
|
def scalar_scalar_grad(x):
|
|
return 2 * x
|
|
|
|
@staticmethod
|
|
def scalar_scalar_div(x):
|
|
return 2 * x
|
|
|
|
@staticmethod
|
|
def scalar_scalar_lap(x):
|
|
return 2 * torch.ones_like(x)
|
|
|
|
@staticmethod
|
|
def scalar_scalar_input():
|
|
input_ = torch.rand((20, 1), requires_grad=True)
|
|
return LabelTensor(input_, ["x"])
|
|
|
|
# Scalar to vector function
|
|
@staticmethod
|
|
def scalar_vector(x):
|
|
u = x**2
|
|
v = x**3 + x
|
|
return torch.cat((u, v), dim=-1)
|
|
|
|
@staticmethod
|
|
def scalar_vector_grad(x):
|
|
u = 2 * x
|
|
v = 3 * x**2 + 1
|
|
return torch.cat((u, v), dim=-1)
|
|
|
|
@staticmethod
|
|
def scalar_vector_div(x):
|
|
return ValueError
|
|
|
|
@staticmethod
|
|
def scalar_vector_lap(x):
|
|
u = 2 * torch.ones_like(x)
|
|
v = 6 * x
|
|
return torch.cat((u, v), dim=-1)
|
|
|
|
@staticmethod
|
|
def scalar_vector_input():
|
|
input_ = torch.rand((20, 1), requires_grad=True)
|
|
return LabelTensor(input_, ["x"])
|
|
|
|
# Vector to scalar function
|
|
@staticmethod
|
|
def vector_scalar(x):
|
|
return torch.prod(x**2, dim=-1, keepdim=True)
|
|
|
|
@staticmethod
|
|
def vector_scalar_grad(x):
|
|
return 2 * torch.prod(x**2, dim=-1, keepdim=True) / x
|
|
|
|
@staticmethod
|
|
def vector_scalar_div(x):
|
|
return ValueError
|
|
|
|
@staticmethod
|
|
def vector_scalar_lap(x):
|
|
return 2 * torch.sum(
|
|
torch.prod(x**2, dim=-1, keepdim=True) / x**2,
|
|
dim=-1,
|
|
keepdim=True,
|
|
)
|
|
|
|
@staticmethod
|
|
def vector_scalar_input():
|
|
input_ = torch.rand((20, 2), requires_grad=True)
|
|
return LabelTensor(input_, ["x", "yy"])
|
|
|
|
# Vector to vector function
|
|
@staticmethod
|
|
def vector_vector(x):
|
|
u = torch.prod(x**2, dim=-1, keepdim=True)
|
|
v = torch.sum(x**2, dim=-1, keepdim=True)
|
|
return torch.cat((u, v), dim=-1)
|
|
|
|
@staticmethod
|
|
def vector_vector_grad(x):
|
|
u = 2 * torch.prod(x**2, dim=-1, keepdim=True) / x
|
|
v = 2 * x
|
|
return torch.cat((u, v), dim=-1)
|
|
|
|
@staticmethod
|
|
def vector_vector_div(x):
|
|
u = 2 * torch.prod(x**2, dim=-1, keepdim=True) / x[..., 0]
|
|
v = 2 * x[..., 1]
|
|
return u + v
|
|
|
|
@staticmethod
|
|
def vector_vector_lap(x):
|
|
u = torch.sum(
|
|
2 * torch.prod(x**2, dim=-1, keepdim=True) / x**2,
|
|
dim=-1,
|
|
keepdim=True,
|
|
)
|
|
v = 2 * x.shape[-1] * torch.ones_like(u)
|
|
return torch.cat((u, v), dim=-1)
|
|
|
|
@staticmethod
|
|
def vector_vector_input():
|
|
input_ = torch.rand((20, 2), requires_grad=True)
|
|
return LabelTensor(input_, ["x", "yy"])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"f",
|
|
Function(),
|
|
ids=["scalar_scalar", "scalar_vector", "vector_scalar", "vector_vector"],
|
|
)
|
|
def test_gradient(f):
|
|
|
|
# Unpack the function
|
|
func_input, func, func_grad, _, _ = f
|
|
|
|
# Define input and output
|
|
input_ = func_input()
|
|
output_ = func(input_)
|
|
labels = [f"u{i}" for i in range(output_.shape[-1])]
|
|
output_ = LabelTensor(output_, labels)
|
|
|
|
# Compute the true gradient and the pina gradient
|
|
pina_grad = grad(output_=output_, input_=input_)
|
|
true_grad = func_grad(input_)
|
|
|
|
# Check the shape and labels of the gradient
|
|
n_components = len(output_.labels) * len(input_.labels)
|
|
assert pina_grad.shape == (*output_.shape[:-1], n_components)
|
|
assert pina_grad.labels == [
|
|
f"d{c}d{i}" for c in output_.labels for i in input_.labels
|
|
]
|
|
|
|
# Compare the values
|
|
assert torch.allclose(pina_grad, true_grad)
|
|
|
|
# Test if labels are handled correctly
|
|
grad(output_=output_, input_=input_, components=output_.labels[0])
|
|
grad(output_=output_, input_=input_, d=input_.labels[0])
|
|
|
|
# Should fail if input not a LabelTensor
|
|
with pytest.raises(TypeError):
|
|
grad(output_=output_, input_=input_.tensor)
|
|
|
|
# Should fail if output not a LabelTensor
|
|
with pytest.raises(TypeError):
|
|
grad(output_=output_.tensor, input_=input_)
|
|
|
|
# Should fail for non-existent input labels
|
|
with pytest.raises(RuntimeError):
|
|
grad(output_=output_, input_=input_, d=["x", "y"])
|
|
|
|
# Should fail for non-existent output labels
|
|
with pytest.raises(RuntimeError):
|
|
grad(output_=output_, input_=input_, components=["a", "b", "c"])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"f",
|
|
Function(),
|
|
ids=["scalar_scalar", "scalar_vector", "vector_scalar", "vector_vector"],
|
|
)
|
|
def test_divergence(f):
|
|
|
|
# Unpack the function
|
|
func_input, func, _, func_div, _ = f
|
|
|
|
# Define input and output
|
|
input_ = func_input()
|
|
output_ = func(input_)
|
|
labels = [f"u{i}" for i in range(output_.shape[-1])]
|
|
output_ = LabelTensor(output_, labels)
|
|
|
|
# Scalar to vector or vector to scalar functions
|
|
if func_div(input_) == ValueError:
|
|
with pytest.raises(ValueError):
|
|
div(output_=output_, input_=input_)
|
|
|
|
# Scalar to scalar or vector to vector functions
|
|
else:
|
|
# Compute the true divergence and the pina divergence
|
|
pina_div = div(output_=output_, input_=input_)
|
|
true_div = func_div(input_)
|
|
|
|
# Check the shape and labels of the divergence
|
|
assert pina_div.shape == (*output_.shape[:-1], 1)
|
|
tmp_labels = [
|
|
f"d{c}d{d_}" for c, d_ in zip(output_.labels, input_.labels)
|
|
]
|
|
assert pina_div.labels == ["+".join(tmp_labels)]
|
|
|
|
# Compare the values
|
|
assert torch.allclose(pina_div, true_div)
|
|
|
|
# Test if labels are handled correctly. Performed in a single call to
|
|
# avoid components and d having different lengths.
|
|
div(
|
|
output_=output_,
|
|
input_=input_,
|
|
components=output_.labels[0],
|
|
d=input_.labels[0],
|
|
)
|
|
|
|
# Should fail if input not a LabelTensor
|
|
with pytest.raises(TypeError):
|
|
div(output_=output_, input_=input_.tensor)
|
|
|
|
# Should fail if output not a LabelTensor
|
|
with pytest.raises(TypeError):
|
|
div(output_=output_.tensor, input_=input_)
|
|
|
|
# Should fail for non-existent labels
|
|
with pytest.raises(RuntimeError):
|
|
div(output_=output_, input_=input_, d=["x", "y"])
|
|
|
|
with pytest.raises(RuntimeError):
|
|
div(output_=output_, input_=input_, components=["a", "b", "c"])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"f",
|
|
Function(),
|
|
ids=["scalar_scalar", "scalar_vector", "vector_scalar", "vector_vector"],
|
|
)
|
|
def test_laplacian(f):
|
|
|
|
# Unpack the function
|
|
func_input, func, _, _, func_lap = f
|
|
|
|
# Define input and output
|
|
input_ = func_input()
|
|
output_ = func(input_)
|
|
labels = [f"u{i}" for i in range(output_.shape[-1])]
|
|
output_ = LabelTensor(output_, labels)
|
|
|
|
# Compute the true laplacian and the pina laplacian
|
|
pina_lap = laplacian(output_=output_, input_=input_)
|
|
true_lap = func_lap(input_)
|
|
|
|
# Check the shape and labels of the laplacian
|
|
assert pina_lap.shape == output_.shape
|
|
assert pina_lap.labels == [f"dd{l}" for l in output_.labels]
|
|
|
|
# Compare the values
|
|
assert torch.allclose(pina_lap, true_lap)
|
|
|
|
# Test if labels are handled correctly
|
|
laplacian(output_=output_, input_=input_, components=output_.labels[0])
|
|
laplacian(output_=output_, input_=input_, d=input_.labels[0])
|
|
|
|
# Should fail if input not a LabelTensor
|
|
with pytest.raises(TypeError):
|
|
laplacian(output_=output_, input_=input_.tensor)
|
|
|
|
# Should fail if output not a LabelTensor
|
|
with pytest.raises(TypeError):
|
|
laplacian(output_=output_.tensor, input_=input_)
|
|
|
|
# Should fail for non-existent input labels
|
|
with pytest.raises(RuntimeError):
|
|
laplacian(output_=output_, input_=input_, d=["x", "y"])
|
|
|
|
# Should fail for non-existent output labels
|
|
with pytest.raises(RuntimeError):
|
|
laplacian(output_=output_, input_=input_, components=["a", "b", "c"])
|
|
|
|
|
|
def test_advection_scalar():
|
|
|
|
# Define 3-dimensional input
|
|
input_ = torch.rand((20, 3), requires_grad=True)
|
|
input_ = LabelTensor(input_, ["x", "y", "z"])
|
|
|
|
# Define 3-dimensional velocity field and quantity to be advected
|
|
velocity = torch.rand((20, 3), requires_grad=True)
|
|
field = torch.sum(input_**2, dim=-1, keepdim=True)
|
|
|
|
# Combine velocity and field into a LabelTensor
|
|
labels = ["ux", "uy", "uz", "c"]
|
|
output_ = LabelTensor(torch.cat((velocity, field), dim=1), labels)
|
|
|
|
# Compute the pina advection
|
|
components = ["c"]
|
|
pina_adv = advection(
|
|
output_=output_,
|
|
input_=input_,
|
|
velocity_field=["ux", "uy", "uz"],
|
|
components=components,
|
|
d=["x", "y", "z"],
|
|
)
|
|
|
|
# Compute the true advection
|
|
grads = 2 * input_
|
|
true_adv = torch.sum(grads * velocity, dim=grads.ndim - 1, keepdim=True)
|
|
|
|
# Check the shape, labels, and value of the advection
|
|
assert pina_adv.shape == (*output_.shape[:-1], len(components))
|
|
assert pina_adv.labels == ["adv_c"]
|
|
assert torch.allclose(pina_adv, true_adv)
|
|
|
|
# Should fail if input not a LabelTensor
|
|
with pytest.raises(TypeError):
|
|
advection(
|
|
output_=output_,
|
|
input_=input_.tensor,
|
|
velocity_field=["ux", "uy", "uz"],
|
|
)
|
|
|
|
# Should fail if output not a LabelTensor
|
|
with pytest.raises(TypeError):
|
|
advection(
|
|
output_=output_.tensor,
|
|
input_=input_,
|
|
velocity_field=["ux", "uy", "uz"],
|
|
)
|
|
|
|
# Should fail for non-existent input labels
|
|
with pytest.raises(RuntimeError):
|
|
advection(
|
|
output_=output_,
|
|
input_=input_,
|
|
d=["x", "a"],
|
|
velocity_field=["ux", "uy", "uz"],
|
|
)
|
|
|
|
# Should fail for non-existent output labels
|
|
with pytest.raises(RuntimeError):
|
|
advection(
|
|
output_=output_,
|
|
input_=input_,
|
|
components=["a", "b", "c"],
|
|
velocity_field=["ux", "uy", "uz"],
|
|
)
|
|
|
|
# Should fail if velocity_field labels are not present in the output labels
|
|
with pytest.raises(RuntimeError):
|
|
advection(
|
|
output_=output_,
|
|
input_=input_,
|
|
velocity_field=["ux", "uy", "nonexistent"],
|
|
components=["c"],
|
|
)
|
|
|
|
# Should fail if velocity_field dimensionality does not match input tensor
|
|
with pytest.raises(RuntimeError):
|
|
advection(
|
|
output_=output_,
|
|
input_=input_,
|
|
velocity_field=["ux", "uy"],
|
|
components=["c"],
|
|
)
|
|
|
|
|
|
def test_advection_vector():
|
|
|
|
# Define 3-dimensional input
|
|
input_ = torch.rand((20, 3), requires_grad=True)
|
|
input_ = LabelTensor(input_, ["x", "y", "z"])
|
|
|
|
# Define 3-dimensional velocity field
|
|
velocity = torch.rand((20, 3), requires_grad=True)
|
|
|
|
# Define 2-dimensional field to be advected
|
|
field_1 = torch.sum(input_**2, dim=-1, keepdim=True)
|
|
field_2 = torch.sum(input_**3, dim=-1, keepdim=True)
|
|
|
|
# Combine velocity and field into a LabelTensor
|
|
labels = ["ux", "uy", "uz", "c1", "c2"]
|
|
output_ = LabelTensor(
|
|
torch.cat((velocity, field_1, field_2), dim=1), labels
|
|
)
|
|
|
|
# Compute the pina advection
|
|
components = ["c1", "c2"]
|
|
pina_adv = advection(
|
|
output_=output_,
|
|
input_=input_,
|
|
velocity_field=["ux", "uy", "uz"],
|
|
components=components,
|
|
d=["x", "y", "z"],
|
|
)
|
|
|
|
# Compute the true gradients of the fields "c1", "c2"
|
|
grads1 = 2 * input_
|
|
grads2 = 3 * input_**2
|
|
|
|
# Compute the true advection for each field
|
|
true_adv1 = torch.sum(grads1 * velocity, dim=grads1.ndim - 1, keepdim=True)
|
|
true_adv2 = torch.sum(grads2 * velocity, dim=grads2.ndim - 1, keepdim=True)
|
|
true_adv = torch.cat((true_adv1, true_adv2), dim=-1)
|
|
|
|
# Check the shape, labels, and value of the advection
|
|
assert pina_adv.shape == (*output_.shape[:-1], len(components))
|
|
assert pina_adv.labels == ["adv_c1", "adv_c2"]
|
|
assert torch.allclose(pina_adv, true_adv)
|
|
|
|
# Should fail if input not a LabelTensor
|
|
with pytest.raises(TypeError):
|
|
advection(
|
|
output_=output_,
|
|
input_=input_.tensor,
|
|
velocity_field=["ux", "uy", "uz"],
|
|
)
|
|
|
|
# Should fail if output not a LabelTensor
|
|
with pytest.raises(TypeError):
|
|
advection(
|
|
output_=output_.tensor,
|
|
input_=input_,
|
|
velocity_field=["ux", "uy", "uz"],
|
|
)
|
|
|
|
# Should fail for non-existent input labels
|
|
with pytest.raises(RuntimeError):
|
|
advection(
|
|
output_=output_,
|
|
input_=input_,
|
|
d=["x", "a"],
|
|
velocity_field=["ux", "uy", "uz"],
|
|
)
|
|
|
|
# Should fail for non-existent output labels
|
|
with pytest.raises(RuntimeError):
|
|
advection(
|
|
output_=output_,
|
|
input_=input_,
|
|
components=["a", "b", "c"],
|
|
velocity_field=["ux", "uy", "uz"],
|
|
)
|
|
|
|
# Should fail if velocity_field labels are not present in the output labels
|
|
with pytest.raises(RuntimeError):
|
|
advection(
|
|
output_=output_,
|
|
input_=input_,
|
|
velocity_field=["ux", "uy", "nonexistent"],
|
|
components=["c"],
|
|
)
|
|
|
|
# Should fail if velocity_field dimensionality does not match input tensor
|
|
with pytest.raises(RuntimeError):
|
|
advection(
|
|
output_=output_,
|
|
input_=input_,
|
|
velocity_field=["ux", "uy"],
|
|
components=["c"],
|
|
)
|