Skip to content

Commit 6b149fe

Browse files
committed
Updated PINN-based solver & fixed grid issues. ODE - tested, PDE - to be done
1 parent 2759078 commit 6b149fe

9 files changed

Lines changed: 843 additions & 19 deletions

File tree

epde/integrate/interface.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,32 +30,32 @@ def _term_solver_form(term, grids, default_domain, variables: List[str] = ['u',]
3030

3131
try:
3232
coeff_tensor = torch.ones_like(grids[0]).to(device)
33-
3433
except KeyError:
3534
raise NotImplementedError('No cache implemented')
35+
3636
for factor in term.structure:
3737
if factor.is_deriv:
3838
for param_idx, param_descr in factor.params_description.items():
3939
if param_descr['name'] == 'power':
4040
power_param_idx = param_idx
4141
deriv_orders.append(factor.deriv_code)
42+
4243
if factor.evaluator._evaluator != simple_function_evaluator:
4344
if factor.evaluator._evaluator._single_function_token:
4445
eval_func = factor.evaluator._evaluator._evaluation_functions_torch
4546
else:
4647
eval_func = factor.evaluator._evaluator._evaluation_functions_torch[factor.label]
4748
if not isinstance(eval_func, torch.nn.Sequential):
48-
# print(f'for term {factor.name} eval func {eval_func} is non')
4949
eval_func_kwargs = dict()
5050
for key in factor.evaluator._evaluator.eval_fun_params_labels:
5151
for param_idx, param_descr in factor.params_description.items():
5252
if param_descr['name'] == key:
5353
eval_func_kwargs[key] = factor.params[param_idx]
54-
# print(f'eval_func_kwargs for {factor.name} are {eval_func_kwargs}')
5554
lbd_eval_func = make_eval_func(eval_func, eval_func_kwargs)
5655
deriv_powers.append(lbd_eval_func)
5756
else:
5857
deriv_powers.append(factor.params[power_param_idx])
58+
5959
try:
6060
if isinstance(factor.variable, str):
6161
cur_deriv_var = variables.index(factor.variable)
@@ -83,8 +83,6 @@ def _term_solver_form(term, grids, default_domain, variables: List[str] = ['u',]
8383
if deriv_vars == []:
8484
if isinstance(deriv_powers, int) and deriv_powers != 0:
8585
raise Exception('Something went wrong with parsing an equation for solver')
86-
# elif isinstance(deriv_powers, list) and all([spec_power != 0 for spec_power in deriv_powers]):
87-
# raise Exception('Something went wrong with parsing an equation for solver')
8886
else:
8987
deriv_vars = [0,]
9088

@@ -96,7 +94,6 @@ def _term_solver_form(term, grids, default_domain, variables: List[str] = ['u',]
9694
'pow': deriv_powers,
9795
'var': deriv_vars}
9896

99-
# print(f'Translated {term.name} to "term" {deriv_orders}, "pow" {deriv_powers}, "var" {deriv_vars} ')
10097
return res
10198

10299
@singledispatchmethod
@@ -155,18 +152,24 @@ def adjust_shape(tensor, mode = 'NN'):
155152
return _solver_form
156153

157154
def use_grids(self, grids=None): #
155+
print('=' * 10 + ' USE GRIDS ' + '=' * 10)
156+
print('before: ', grids)
157+
158158
if grids is None and self.grids is None:
159159
_, self.grids = global_var.grid_cache.get_all(mode = 'torch')
160+
self.grids = [grid[global_var.grid_cache.g_func != 0] for grid in self.grids]
160161
elif grids is not None:
161162
if len(grids) != len(global_var.grid_cache.get_all(mode = 'torch')[1]):
162163
raise ValueError(
163164
'Number of passed grids does not match the problem')
164165
if isinstance(grids[0], np.ndarray):
165166
grids = [torch.from_numpy(subgrid).to(self._device) for subgrid in grids]
166167
self.grids = grids
168+
print(self.grids[0].shape)
169+
167170

168171

169-
def form(self, grids=None, mode = 'NN'):
172+
def form(self, grids=None, mode = 'NN'): # -> List[str, ]:
170173
self.use_grids(grids=grids)
171174
equation_forms = []
172175

epde/integrate/pinn_integration.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import numpy as np
1010
import torch
1111

12-
from typing import Callable, Union, Dict, List
12+
from typing import Callable, Union, Dict, List, Tuple
1313
from functools import singledispatchmethod, singledispatch
1414

1515
from torch.nn import Sequential
@@ -327,24 +327,24 @@ def create_domain(variables: List[str], grids : List[Union[np.ndarray, torch.Ten
327327
def solve_epde_system(self, system: Union[SoEq, dict], grids: list=None, boundary_conditions=None,
328328
mode='NN', data=None, use_cache: bool = False, use_fourier: bool = False,
329329
fourier_params: dict = None, use_adaptive_lambdas: bool = False,
330-
to_numpy: bool = False, grid_var_keys = None, *args, **kwargs):
330+
to_numpy: bool = False, grid_var_keys = None,
331+
*args, **kwargs) -> Tuple[float, Union[torch.Tensor, np.ndarray]]:
331332
solver_device(device = self._device)
332333

333334
if isinstance(system, SoEq):
334335
system_interface = SystemSolverInterface(system_to_adapt=system)
335336
system_solver_forms = system_interface.form(grids = grids, mode = mode)
336337
elif isinstance(system, dict):
337-
system_solver_forms = list(system.values())
338+
system_solver_forms = list(system.values()) # TODO: refactor instead of quickfixes
338339
elif isinstance(system, list):
339340
system_solver_forms = system
340341
else:
341342
raise TypeError(f'Incorrect type of the equations passed into solver. Expected dict or SoEq, got {type(system)}.')
342-
343+
343344
if boundary_conditions is None:
344-
raise NotImplementedError('TBD')
345345
op_gen = PregenBOperator(system=system,
346346
system_of_equation_solver_form=[sf_labeled[1] for sf_labeled
347-
in system.values()])
347+
in system_solver_forms]) # system.values .vals()
348348
op_gen.generate_default_bc(vals = data, grids = grids)
349349
boundary_conditions = op_gen.conditions
350350

@@ -355,6 +355,7 @@ def solve_epde_system(self, system: Union[SoEq, dict], grids: list=None, boundar
355355

356356
if grids is None:
357357
grid_var_keys, grids = global_var.grid_cache.get_all(mode = 'torch')
358+
grids = [grid[global_var.grid_cache.g_func != 0] for grid in grids]
358359
elif grid_var_keys is None:
359360
grid_var_keys, _ = global_var.grid_cache.get_all(mode = 'torch')
360361

@@ -367,15 +368,23 @@ def solve_epde_system(self, system: Union[SoEq, dict], grids: list=None, boundar
367368

368369
def solve(self, equations: Union[List, SoEq, SolverEquation], domain: Domain,
369370
boundary_conditions = None, mode = 'NN', use_cache: bool = False,
370-
use_fourier: bool = False, fourier_params: dict = None, # epochs = 1e3,
371-
use_adaptive_lambdas: bool = False, to_numpy = False, *args, **kwargs):
371+
use_fourier: bool = False, fourier_params: dict = None,
372+
use_adaptive_lambdas: bool = False, to_numpy = False,
373+
*args, **kwargs) -> Tuple[float, Union[torch.Tensor, np.ndarray]]:
372374

373375
if isinstance(equations, SolverEquation):
374376
equations_prepared = equations
375377
else:
376378
equations_prepared = SolverEquation()
377379
for form in equations:
378-
equations_prepared.add(form)
380+
print(f'form is solve has a type of {type(form)}: {form}')
381+
if isinstance(form, dict):
382+
equations_prepared.add(form)
383+
elif (isinstance(form, list) or isinstance(form, tuple)) and len(form) == 2:
384+
equations_prepared.add(form[1])
385+
else:
386+
raise ValueError()
387+
379388
if self.net is None:
380389
self.net = self.get_net(equations_prepared, mode, domain, use_fourier,
381390
fourier_params, device=self._device)

epde/interface/interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ def create_pool(self, data: Union[np.ndarray, list, tuple], variable_names=['u',
699699
global_var.reset_data_repr_nn(data = data, derivs = base_derivs, train = False,
700700
grids = grid, predefined_ann = data_nn, device = self._device)
701701
else:
702-
epochs_max = 1e4
702+
epochs_max = 1e4 # 1e4
703703
global_var.reset_data_repr_nn(data = data, derivs = base_derivs, epochs_max=epochs_max,
704704
grids = grid, predefined_ann = None, device = self._device,
705705
use_fourier = fourier_layers, fourier_params = fourier_params)

epde/operators/common/fitness.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,6 @@ def apply(self, objective : SoEq, arguments : dict, force_out_of_place: bool = F
240240
else:
241241
referential_data = global_var.tensor_cache.get((eq.main_var_to_explain, (1.0,)))
242242

243-
print(f'solution shape {solution.shape}')
244-
print(f'solution[..., eq_idx] {solution[..., eq_idx].shape}, eq_idx {eq_idx}')
245243
discr = (solution[..., eq_idx] - referential_data.reshape(solution[..., eq_idx].shape))
246244
discr = np.multiply(discr, self.g_fun_vals.reshape(discr.shape))
247245
rl_error = np.linalg.norm(discr, ord = 2)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import torch
2+
from epde.solver.callbacks.callback import Callback
3+
import os
4+
5+
6+
7+
class SaveModel(Callback):
8+
"""Class for saving model during train
9+
"""
10+
def __init__(self,
11+
path_to_folder: str,
12+
every_step : int = 1):
13+
"""
14+
Args:
15+
path (str): path_to_folder to save model
16+
every_step (int): save model every n steps. Defaults 1.
17+
"""
18+
super().__init__()
19+
self.path_to_folder = path_to_folder
20+
self.every_step = every_step
21+
22+
def save_model(self):
23+
model_name = "model-{}.pt".format(self.model.t-1)
24+
save_path = os.path.join(self.path_to_folder, model_name)
25+
torch.save(self.model.net, save_path)
26+
27+
def on_epoch_end(self, logs=None):
28+
if (self.model.t-1) % self.every_step == 0:
29+
self.save_model()

epde/solver/data_CSG.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
""" Module for construct a domain with complex or irregular geometry"""
2+
import torch
3+
from abc import ABC, abstractmethod
4+
5+
class Shape(ABC):
6+
@abstractmethod
7+
def contains(self, pts: torch.Tensor) -> torch.BoolTensor:
8+
"""
9+
Returns a mask (N,) indicating which pts are inside the shape.
10+
"""
11+
...
12+
13+
@abstractmethod
14+
def boundary(self, pts: torch.Tensor, rtol: float = 1e-4, atol: float = 0.0) -> torch.BoolTensor:
15+
"""
16+
Returns a mask (N,) indicating which pts lie on the boundary of the shape.
17+
"""
18+
...
19+
20+
class Rectangle(Shape):
21+
def __init__(self, lower, upper, dims=None):
22+
"""
23+
lower: sequence of lower bounds for each spatial dimension
24+
upper: sequence of upper bounds for each spatial dimension
25+
dims: indices of dimensions in the grid to apply the rectangle (default first len(lower))
26+
"""
27+
self.lower = torch.as_tensor(lower, dtype=torch.float32)
28+
self.upper = torch.as_tensor(upper, dtype=torch.float32)
29+
self.dims = dims if dims is not None else list(range(self.lower.numel()))
30+
31+
def _select(self, pts: torch.Tensor) -> torch.Tensor:
32+
return pts[:, self.dims]
33+
34+
def contains(self, pts: torch.Tensor) -> torch.BoolTensor:
35+
sub = self._select(pts)
36+
return ((sub >= self.lower) & (sub <= self.upper)).all(dim=1)
37+
38+
def boundary(self, pts: torch.Tensor, rtol: float = 1e-4, atol: float = 0.0) -> torch.BoolTensor:
39+
sub = self._select(pts)
40+
inside_or_bound = self.contains(pts)
41+
on_lower = torch.isclose(sub, self.lower.unsqueeze(0), rtol=rtol, atol=atol).any(dim=1)
42+
on_upper = torch.isclose(sub, self.upper.unsqueeze(0), rtol=rtol, atol=atol).any(dim=1)
43+
return inside_or_bound & (on_lower | on_upper)
44+
45+
class Circle(Shape):
46+
def __init__(self, center, radius, dims=None):
47+
"""
48+
center: sequence of center coordinates for each spatial dimension
49+
radius: scalar radius
50+
dims: indices of dimensions in the grid to apply the circle (default first len(center))
51+
"""
52+
self.center = torch.as_tensor(center, dtype=torch.float32)
53+
self.radius_sq = float(radius) ** 2
54+
self.dims = dims if dims is not None else list(range(self.center.numel()))
55+
56+
def _select(self, pts: torch.Tensor) -> torch.Tensor:
57+
return pts[:, self.dims]
58+
59+
def contains(self, pts: torch.Tensor) -> torch.BoolTensor:
60+
sub = self._select(pts)
61+
sqd = ((sub - self.center) ** 2).sum(dim=1)
62+
return sqd <= self.radius_sq
63+
64+
def boundary(self, pts: torch.Tensor, rtol: float = 1e-4, atol: float = 0.0) -> torch.BoolTensor:
65+
sub = self._select(pts)
66+
sqd = ((sub - self.center) ** 2).sum(dim=1)
67+
return torch.isclose(sqd, torch.tensor(self.radius_sq, dtype=pts.dtype), rtol=rtol, atol=atol)
68+
69+
# CSG operations
70+
71+
def csg_difference(grid: torch.Tensor, shape: Shape) -> torch.Tensor:
72+
"""
73+
Returns points of `grid` outside the given `shape`.
74+
75+
Args:
76+
grid: (N, D) tensor of coordinates.
77+
shape: a Shape instance.
78+
"""
79+
mask_outside = ~shape.contains(grid)
80+
return grid[mask_outside]
81+
82+
83+
def csg_boundary(grid: torch.Tensor, shape: Shape, rtol: float = 1e-4, atol: float = 0.0) -> torch.Tensor:
84+
"""
85+
Returns points of `grid` that lie on the boundary of the given `shape`.
86+
87+
Args:
88+
grid: (N, D) tensor of coordinates.
89+
shape: a Shape instance.
90+
rtol: relative tolerance for boundary detection.
91+
atol: absolute tolerance for boundary detection.
92+
"""
93+
mask_bnd = shape.boundary(grid, rtol=rtol, atol=atol)
94+
return grid[mask_bnd]
95+
96+
97+
98+

epde/solver/derivative.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ def take_derivative(self, term: dict, grid_points: torch.Tensor) -> torch.Tenso
133133
Returns:
134134
der_term (torch.Tensor): resulting field, computed on a grid.
135135
"""
136+
# print(f'In take_derivative {grid_points.shape}')
136137
dif_dir = list(term.keys())[1]
137138
# it is may be int, function of grid or torch.Tensor
138139
if callable(term['coeff']):
@@ -156,16 +157,20 @@ def take_derivative(self, term: dict, grid_points: torch.Tensor) -> torch.Tenso
156157
factor_val = term['pow'][j](der_args)
157158
else:
158159
factor_val = term['pow'][j](*der_args)
160+
# print(f'factor_val.shape is {factor_val.shape}')
159161
der_term = der_term * factor_val
160162
else:
161163
if derivative == [None] or derivative is None:
162164
der = self.model(grid_points)[:, term['var'][j]].reshape(-1, 1)
163165
else:
164166
der = self._nn_autograd(self.model, grid_points, term['var'][j], axis=derivative)
167+
# print(f'der.shape is {der.shape} from grid_points of shape {grid_points.shape}')
168+
165169
if isinstance(term['pow'][j],(int,float)):
166170
der_term = der_term * der ** term['pow'][j]
167171
elif isinstance(term['pow'][j], Callable):
168172
der_term = der_term * term['pow'][j](der)
173+
# print(f'coeff.shape {coeff.shape} & der_term.shape {der_term.shape}')
169174
der_term = coeff * der_term
170175
return der_term
171176

0 commit comments

Comments
 (0)