diff --git a/README.md b/README.md index 2cc1f01d1..19ec5824b 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,11 @@ $ conda install -c conda-forge deepxde $ git clone https://github.com/lululxvi/deepxde.git ``` +- If you want to use the new [deepxde.experimental](https://github.com/lululxvi/deepxde/tree/main/deepxde/experimental) module, you can use: +``` sh +$ pip install deepxde[experimental] +``` + ## Explore more - [Install and Setup](https://deepxde.readthedocs.io/en/latest/user/installation.html) diff --git a/deepxde/experimental/__init__.py b/deepxde/experimental/__init__.py new file mode 100644 index 000000000..388086d03 --- /dev/null +++ b/deepxde/experimental/__init__.py @@ -0,0 +1,21 @@ +__all__ = [ + "callbacks", + "geometry", + "grad", + "icbc", + "metrics", + "nn", + "problem", + "utils", + "Trainer", +] + +from . import callbacks +from . import geometry +from . import grad +from . import icbc +from . import metrics +from . import nn +from . import problem +from . import utils +from ._trainer import Trainer diff --git a/deepxde/experimental/_trainer.py b/deepxde/experimental/_trainer.py new file mode 100644 index 000000000..69aaeb1ee --- /dev/null +++ b/deepxde/experimental/_trainer.py @@ -0,0 +1,534 @@ +import time +from typing import Union, Sequence, Callable, Optional + +import brainstate as bst +import brainunit as u +import jax.numpy as jnp +import jax.tree +import numpy as np + +from deepxde.model import LossHistory, TrainState as TrainStateBase +from deepxde.utils.internal import timing +from . import metrics as metrics_module +from .callbacks import CallbackList, Callback +from .problem.base import Problem +from .utils.display import training_display +from .utils.external import saveplot + +__all__ = [ + "Trainer", + "TrainState", + "LossHistory", +] + + +class Trainer: + """ + A ``Trainer`` trains a neural network on a ``Problem``. + + Args: + problem: ``experimental.problem.Problem`` instance. + external_trainable_variables: A trainable ``brainstate.ParamState`` object or a list + of trainable ``brainstate.ParamState`` objects. The unknown parameters in the + physics systems that need to be recovered. + """ + + __module__ = "deepxde.experimental" + optimizer: bst.optim.Optimizer # optimizer + problem: Problem # problem + params: bst.util.FlattedDict # trainable variables + + def __init__( + self, + problem: Problem, + external_trainable_variables: Union[ + bst.ParamState, Sequence[bst.ParamState] + ] = None, + batch_size: Optional[int] = None, + ): + """ + Initialize the Trainer. + + Args: + problem (Problem): The problem instance to be solved. + external_trainable_variables (Union[bst.ParamState, Sequence[bst.ParamState]], optional): + External trainable variables to be included in the optimization process. + Can be a single ParamState or a sequence of ParamStates. Defaults to None. + batch_size (Optional[int], optional): The batch size to be used during training. + If None, the entire dataset will be used. Defaults to None. + + Raises: + ValueError: If the problem does not define an approximator. + AssertionError: If the problem is not a Problem instance or if external_trainable_variables + are not ParamState instances. + + Returns: + None + """ + # the problem + self.problem = problem + assert isinstance(self.problem, Problem), "problem must be a Problem instance." + + # the approximator + if self.problem.approximator is None: + raise ValueError("Problem must define an approximator before training.") + + # parameters and external trainable variables + params = bst.graph.states(self.problem.approximator, bst.ParamState) + if external_trainable_variables is None: + external_trainable_variables = [] + else: + if not isinstance(external_trainable_variables, list): + external_trainable_variables = [external_trainable_variables] + for i, var in enumerate(external_trainable_variables): + assert isinstance(var, bst.ParamState), ( + "external_trainable_variables must be a " "list of ParamState instance." + ) + params[("external_trainable_variable", i)] = var + self.params = params + + # other useful parameters + self.metrics = None + self.batch_size = batch_size + + # training state + self.train_state = TrainState() + self.loss_history = LossHistory() + self.stop_training = False + + @timing + def compile( + self, + optimizer: bst.optim.Optimizer, + metrics: Union[str, Sequence[str]] = None, + measture_train_step_compile_time: bool = False, + ): + """ + Configures the trainer for training. + + Args: + optimizer: String name of an optimizer, or an optimizer class instance. + metrics: List of metrics to be evaluated by the trainer during training. + """ + print("Compiling trainer...") + + # optimizer + assert isinstance( + optimizer, bst.optim.Optimizer + ), "optimizer must be an Optimizer instance." + self.optimizer = optimizer + self.optimizer.register_trainable_weights(self.params) + + # metrics may use trainer variables such as self.net, + # and thus are instantiated after compile. + metrics = metrics or [] + self.metrics = [metrics_module.get(m) for m in metrics] + + def fn_outputs(training: bool, inputs): + with bst.environ.context(fit=training): + inputs = jax.tree.map( + lambda x: u.math.asarray(x), inputs, is_leaf=u.math.is_quantity + ) + return self.problem.approximator(inputs) + + def fn_outputs_losses(training, inputs, targets, **kwargs): + with bst.environ.context(fit=training): + # inputs + inputs = jax.tree.map( + lambda x: u.math.asarray(x), inputs, is_leaf=u.math.is_quantity + ) + + # outputs + outputs = self.problem.approximator(inputs) + + # targets + if targets is not None: + targets = jax.tree.map( + lambda x: u.math.asarray(x), targets, is_leaf=u.math.is_quantity + ) + + # compute losses + if training: + losses = self.problem.losses_train( + inputs, outputs, targets, **kwargs + ) + else: + losses = self.problem.losses_test( + inputs, outputs, targets, **kwargs + ) + return outputs, losses + + def fn_outputs_losses_train(inputs, targets, **aux): + return fn_outputs_losses(True, inputs, targets, **aux) + + def fn_outputs_losses_test(inputs, targets, **aux): + return fn_outputs_losses(False, inputs, targets, **aux) + + def fn_train_step(inputs, targets, **aux): + def _loss_fun(): + losses = fn_outputs_losses_train(inputs, targets, **aux)[1] + return u.math.sum( + u.math.asarray([loss.sum() for loss in jax.tree.leaves(losses)]) + ) + + grads = bst.augment.grad(_loss_fun, grad_states=self.params)() + self.optimizer.update(grads) + + # Callables + self.fn_outputs = bst.compile.jit(fn_outputs, static_argnums=0) + self.fn_outputs_losses_train = bst.compile.jit(fn_outputs_losses_train) + self.fn_outputs_losses_test = bst.compile.jit(fn_outputs_losses_test) + self.fn_train_step = bst.compile.jit(fn_train_step) + + if measture_train_step_compile_time: + t0 = time.time() + self._compile_training_step(self.batch_size) + t1 = time.time() + return self, t1 - t0 + + return self + + @timing + def train( + self, + iterations: int, + batch_size: int = None, + display_every: int = 1000, + disregard_previous_best: bool = False, + callbacks: Union[Callback, Sequence[Callback]] = None, + model_restore_path: str = None, + model_save_path: str = None, + measture_train_step_time: bool = False, + ): + """ + Trains the trainer. + + Args: + iterations (Integer): Number of iterations to train the trainer, i.e., number + of times the network weights are updated. + batch_size: Integer, tuple, or ``None``. + + - If you solve PDEs via ``experimental.problem.PDE`` or ``experimental.problem.TimePDE``, do not use `batch_size`, + and instead use `experimental.callbacks.PDEPointResampler + `_, + see an `example `_. + - For DeepONet in the format of Cartesian product, if `batch_size` is an Integer, + then it is the batch size for the branch input; + if you want to also use mini-batch for the trunk net input, + set `batch_size` as a tuple, where the fist number is the batch size for the branch net input + and the second number is the batch size for the trunk net input. + display_every (Integer): Print the loss and metrics every this steps. + disregard_previous_best: If ``True``, disregard the previous saved best + trainer. + callbacks: List of ``experimental.callbacks.Callback`` instances. List of callbacks + to apply during training. + model_restore_path (String): Path where parameters were previously saved. + model_save_path (String): Prefix of filenames created for the checkpoint. + """ + + if measture_train_step_time: + t0 = time.time() + + if self.metrics is None: + raise ValueError("Compile the trainer before training.") + + # callbacks + callbacks = CallbackList( + callbacks=[callbacks] if isinstance(callbacks, Callback) else callbacks + ) + callbacks.set_model(self) + + # disregard previous best + if disregard_previous_best: + self.train_state.disregard_best() + + # restore + if model_restore_path is not None: + self.restore(model_restore_path, verbose=1) + + print("Training trainer...\n") + self.stop_training = False + + # testing + self.train_state.set_data_train(*self.problem.train_next_batch(batch_size)) + self.train_state.set_data_test(*self.problem.test()) + self._test() + + # training + callbacks.on_train_begin() + self._train(iterations, display_every, batch_size, callbacks) + callbacks.on_train_end() + + # summary + print("") + training_display.summary(self.train_state) + if model_save_path is not None: + self.save(model_save_path, verbose=1) + + if measture_train_step_time: + t1 = time.time() + return self, t1 - t0 + return self + + def _compile_training_step(self, batch_size=None): + # get data + self.train_state.set_data_train(*self.problem.train_next_batch(batch_size)) + + # train one batch + self.fn_train_step.compile( + self.train_state.X_train, + self.train_state.y_train, + **self.train_state.Aux_train, + ) + + def _train(self, iterations, display_every, batch_size, callbacks): + for i in range(iterations): + callbacks.on_epoch_begin() + callbacks.on_batch_begin() + + # get data + self.train_state.set_data_train(*self.problem.train_next_batch(batch_size)) + + # train one batch + self.fn_train_step( + self.train_state.X_train, + self.train_state.y_train, + **self.train_state.Aux_train, + ) + + self.train_state.epoch += 1 + self.train_state.step += 1 + if self.train_state.step % display_every == 0 or i + 1 == iterations: + self._test() + + callbacks.on_batch_end() + callbacks.on_epoch_end() + + if self.stop_training: + break + + def _test(self): + # evaluate the training data + ( + self.train_state.y_pred_train, + self.train_state.loss_train, + ) = self.fn_outputs_losses_train( + self.train_state.X_train, + self.train_state.y_train, + **self.train_state.Aux_train, + ) + + # evaluate the test data + (self.train_state.y_pred_test, self.train_state.loss_test) = ( + self.fn_outputs_losses_test( + self.train_state.X_test, + self.train_state.y_test, + **self.train_state.Aux_test, + ) + ) + + # metrics + if isinstance(self.train_state.y_test, (list, tuple)): + self.train_state.metrics_test = [ + m(self.train_state.y_test[i], self.train_state.y_pred_test[i]) + for m in self.metrics + for i in range(len(self.train_state.y_test)) + ] + else: + self.train_state.metrics_test = [ + m(self.train_state.y_test, self.train_state.y_pred_test) + for m in self.metrics + ] + + # history + self.train_state.update_best() + self.loss_history.append( + self.train_state.step, + self.train_state.loss_train, + self.train_state.loss_test, + self.train_state.metrics_test, + ) + + # check NaN + if ( + jnp.isnan(jnp.asarray(jax.tree.leaves(self.train_state.loss_train))).any() + or jnp.isnan(jnp.asarray(jax.tree.leaves(self.train_state.loss_test))).any() + ): + self.stop_training = True + + # display + training_display(self.train_state) + + def predict( + self, + xs, + operator: Optional[Callable] = None, + callbacks: Union[Callback, Sequence[Callback]] = None, + ): + """Generates predictions for the input samples. If `operator` is ``None``, + returns the network output, otherwise returns the output of the `operator`. + + Args: + xs: The network inputs. A Numpy array or a tuple of Numpy arrays. + operator: A function takes arguments (`neural_net`, `inputs`) and outputs a tensor. `inputs` and + `outputs` are the network input and output tensors, respectively. `operator` is typically + chosen as the PDE (used to define `experimental.problem.PDE`) to predict the PDE residual. + callbacks: List of ``experimental.callbacks.Callback`` instances. List of callbacks + to apply during prediction. + """ + xs = jax.tree.map( + lambda x: u.math.asarray(x, dtype=bst.environ.dftype()), + xs, + is_leaf=u.math.is_quantity, + ) + callbacks = CallbackList( + callbacks=[callbacks] if isinstance(callbacks, Callback) else callbacks + ) + callbacks.set_model(self) + callbacks.on_predict_begin() + ys = self.fn_outputs(False, xs) + if operator is not None: + ys = operator(xs, ys) + callbacks.on_predict_end() + return ys + + def save(self, save_path, verbose: int = 0): + """Saves all variables to a disk file. + + Args: + save_path (string): Prefix of filenames to save the trainer file. + verbose (int): Verbosity mode, 0 or 1. + + Returns: + string: Path where trainer is saved. + """ + import braintools + + # save path + save_path = f"{save_path}-{self.train_state.epoch}.msgpack" + + # avoid the duplicate ParamState save + model = bst.graph.Dict(params=self.params, optimizer=self.optimizer) + + checkpoint = bst.graph.states(model).to_nest() + braintools.file.msgpack_save(save_path, checkpoint) + + if verbose > 0: + print( + "Epoch {}: saving trainer to {} ...\n".format( + self.train_state.epoch, save_path + ) + ) + return save_path + + def restore(self, save_path, verbose: int = 0): + """Restore all variables from a disk file. + + Args: + save_path (string): Path where trainer was previously saved. + verbose (int): Verbosity mode, 0 or 1. + """ + import braintools + + if verbose > 0: + print("Restoring trainer from {} ...\n".format(save_path)) + + data = bst.graph.Dict(params=self.params, optimizer=self.optimizer) + + checkpoint = bst.graph.states(data).to_nest() + braintools.file.msgpack_load(save_path, target=checkpoint) + + def saveplot( + self, + issave: bool = True, + isplot: bool = True, + loss_fname: str = "loss.dat", + train_fname: str = "train.dat", + test_fname: str = "test.dat", + output_dir: str = None, + ): + """ + Saves and plots the loss and metrics. + + Args: + issave: If ``True``, save the loss and metrics to files. + isplot: If ``True``, plot the loss and metrics. + loss_fname: Filename to save the loss. + train_fname: Filename to save the training metrics. + test_fname: Filename to save the test metrics. + output_dir: Directory to save the files. + """ + saveplot( + self.loss_history, + self.train_state, + issave=issave, + isplot=isplot, + loss_fname=loss_fname, + train_fname=train_fname, + test_fname=test_fname, + output_dir=output_dir, + ) + + +class TrainState(TrainStateBase): + __module__ = "deepxde.experimental" + + def __init__(self): + self.epoch = 0 + self.step = 0 + + # Current data + self.X_train = None + self.y_train = None + self.Aux_train = dict() + self.X_test = None + self.y_test = None + self.Aux_test = dict() + + # Results of current step + # Train results + self.loss_train = None + self.y_pred_train = None + # Test results + self.loss_test = None + self.y_pred_test = None + self.y_std_test = None + self.metrics_test = None + + # The best results correspond to the min train loss + self.best_step = 0 + self.best_loss_train = np.inf + self.best_loss_test = np.inf + self.best_y = None + self.best_ystd = None + self.best_metrics = None + + def set_data_train(self, X_train, y_train, *args): + self.X_train = X_train + self.y_train = y_train + if len(args) > 0: + assert len(args) == 1, "Auxiliary training data must be a single argument." + assert isinstance( + args[0], dict + ), "Auxiliary training data must be a dictionary." + self.Aux_train = args[0] + + def set_data_test(self, X_test, y_test, *args): + self.X_test = X_test + self.y_test = y_test + if len(args) > 0: + assert len(args) == 1, "Auxiliary test data must be a single argument." + assert isinstance( + args[0], dict + ), "Auxiliary test data must be a dictionary." + self.Aux_test = args[0] + + def update_best(self): + current_loss_train = jnp.sum(jnp.asarray(jax.tree.leaves(self.loss_train))) + if self.best_loss_train > current_loss_train: + self.best_step = self.step + self.best_loss_train = current_loss_train + self.best_loss_test = jnp.sum(jnp.asarray(jax.tree.leaves(self.loss_test))) + self.best_y = self.y_pred_test + self.best_ystd = self.y_std_test + self.best_metrics = self.metrics_test diff --git a/deepxde/experimental/callbacks.py b/deepxde/experimental/callbacks.py new file mode 100644 index 000000000..d380ee4d5 --- /dev/null +++ b/deepxde/experimental/callbacks.py @@ -0,0 +1,171 @@ +import sys + +import brainstate as bst +import brainunit as u +import jax.tree +import numpy as np + +from deepxde.callbacks import ( + Callback, + CallbackList, + ModelCheckpoint, + Timer, + MovieDumper, + PDEPointResampler, + EarlyStopping as EarlyStoppingCallback, + DropoutUncertainty as DropoutUncertaintyCallback, + OperatorPredictor as OperatorPredictorCallback, +) +from deepxde.utils.internal import list_to_str + +__all__ = [ + "Callback", + "CallbackList", + "ModelCheckpoint", + "EarlyStopping", + "Timer", + "DropoutUncertainty", + "VariableValue", + "OperatorPredictor", + "MovieDumper", + "PDEPointResampler", +] + + +class EarlyStopping(EarlyStoppingCallback): + """Stop training when a monitored quantity (training or testing loss) has stopped improving. + Only checked at validation step according to ``display_every`` in ``Trainer.train``. + + Args: + min_delta: Minimum change in the monitored quantity + to qualify as an improvement, i.e. an absolute + change of less than min_delta, will count as no + improvement. + patience: Number of epochs with no improvement + after which training will be stopped. + baseline: Baseline value for the monitored quantity to reach. + Training will stop if the trainer doesn't show improvement + over the baseline. + monitor: The loss function that is monitored. Either 'loss_train' or 'loss_test' + start_from_epoch: Number of epochs to wait before starting + to monitor improvement. This allows for a warm-up period in which + no improvement is expected and thus training will not be stopped. + """ + + def get_monitor_value(self): + if self.monitor == "loss_train": + result = np.sum(jax.tree.leaves(self.model.train_state.loss_train)) + elif self.monitor == "loss_test": + result = np.sum(jax.tree.leaves(self.model.train_state.loss_test)) + else: + raise ValueError("The specified monitor function is incorrect.") + + return result + + +class DropoutUncertainty(DropoutUncertaintyCallback): + """Uncertainty estimation via MC dropout. + + References: + `Y. Gal, & Z. Ghahramani. Dropout as a Bayesian approximation: Representing + trainer uncertainty in deep learning. International Conference on Machine + Learning, 2016 `_. + + Warning: + This cannot be used together with other techniques that have different behaviors + during training and testing, such as batch normalization. + """ + + def on_epoch_end(self): + self.epochs_since_last += 1 + if self.epochs_since_last >= self.period: + self.epochs_since_last = 0 + y_preds = [] + for _ in range(1000): + y_pred_test_one = self.model.fn_outputs( + True, self.model.train_state.X_test + ) + y_preds.append(y_pred_test_one) + y_preds = jax.tree.map( + lambda *x: u.math.stack(x, axis=0), *y_preds, is_leaf=u.math.is_quantity + ) + self.model.train_state.y_std_test = jax.tree.map( + lambda x: u.math.std(x, axis=0), y_preds, is_leaf=u.math.is_quantity + ) + + +class VariableValue(Callback): + """Get the variable values. + + Args: + var_list: A `TensorFlow Variable `_ + or a list of TensorFlow Variable. + period (int): Interval (number of epochs) between checking values. + filename (string): Output the values to the file `filename`. + The file is kept open to allow instances to be re-used. + If ``None``, output to the screen. + precision (int): The precision of variables to display. + """ + + def __init__(self, var_list, period=1, filename=None, precision=2): + super().__init__() + self.var_list = var_list if isinstance(var_list, (tuple, list)) else [var_list] + for v in self.var_list: + if not isinstance(v, bst.State): + raise ValueError("The variable must be a brainstate.State object.") + + self.period = period + self.precision = precision + + self.file = sys.stdout if filename is None else open(filename, "w", buffering=1) + self.value = None + self.epochs_since_last = 0 + + def on_train_begin(self): + self.value = [var.value for var in self.var_list] + + print( + self.model.train_state.epoch, + list_to_str(self.value, precision=self.precision), + file=self.file, + ) + self.file.flush() + + def on_epoch_end(self): + self.epochs_since_last += 1 + if self.epochs_since_last >= self.period: + self.epochs_since_last = 0 + self.on_train_begin() + + def on_train_end(self): + if not self.epochs_since_last == 0: + self.on_train_begin() + + def get_value(self): + """Return the variable values.""" + return self.value + + +class OperatorPredictor(OperatorPredictorCallback): + """ + Generates operator values for the input samples. + + Args: + x: The input data. + op: The operator with inputs (x, y). + period (int): Interval (number of epochs) between checking values. + filename (string): Output the values to the file `filename`. + The file is kept open to allow instances to be re-used. + If ``None``, output to the screen. + precision (int): The precision of variables to display. + """ + + def on_predict_end(self): + self.value = self._eval() + # self.value = jax.tree.map(np.asarray, self._eval()) + + @bst.compile.jit(static_argnums=0) + def _eval(self): + with bst.environ.context(fit=False): + outputs = self.model.problem.approximator(self.x) + return self.op(self.x, outputs) diff --git a/deepxde/experimental/geometry/__init__.py b/deepxde/experimental/geometry/__init__.py new file mode 100644 index 000000000..99b6ce842 --- /dev/null +++ b/deepxde/experimental/geometry/__init__.py @@ -0,0 +1,25 @@ +__all__ = [ + "DictPointGeometry", + "Cuboid", + "Disk", + "Ellipse", + "GeometryXTime", + "Hypercube", + "Hypersphere", + "Interval", + "PointCloud", + "Polygon", + "Rectangle", + "Sphere", + "StarShaped", + "TimeDomain", + "Triangle", +] + +from .base import DictPointGeometry +from .geometry_1d import Interval +from .geometry_2d import Disk, Ellipse, Polygon, Rectangle, StarShaped, Triangle +from .geometry_3d import Cuboid, Sphere +from .geometry_nd import Hypercube, Hypersphere +from .pointcloud import PointCloud +from .timedomain import TimeDomain, GeometryXTime diff --git a/deepxde/experimental/geometry/base.py b/deepxde/experimental/geometry/base.py new file mode 100644 index 000000000..a4773c5a6 --- /dev/null +++ b/deepxde/experimental/geometry/base.py @@ -0,0 +1,491 @@ +from typing import Dict, Union + +import brainstate as bst +import brainunit as u +import jax.numpy as jnp +import numpy as np + +from deepxde.geometry.geometry import Geometry +from deepxde.experimental import utils + +__all__ = [ + "GeometryExperimental", + "DictPointGeometry", +] + + +class GeometryExperimental(Geometry): + """ + A base class for geometries in the PINNx (Physics-Informed Neural Networks Extended) framework. + + This class extends the functionality of the base Geometry class to provide additional + features specific to the PINNx framework. It serves as a foundation for creating + more specialized geometry classes that can work with dictionary-based point representations + and unit-aware computations. + + Attributes: + Inherits all attributes from the Geometry base class. + + Methods: + to_dict_point(*names, **kw_names): + Converts the geometry to a dictionary-based point representation. + + Note: + This class is designed to be subclassed for specific geometry implementations + in the PINNx framework. It provides a bridge between the standard Geometry + representations and the more flexible, unit-aware representations used in PINNx. + + Example: + class CustomGeometry(GeometryExperimental): + def __init__(self, dim, bbox, diam): + super().__init__(dim, bbox, diam) + # Additional initialization specific to CustomGeometry + + # Implement other required methods + + # Usage + custom_geom = CustomGeometry(dim=2, bbox=[0, 1, 0, 1], diam=1.414) + dict_geom = custom_geom.to_dict_point('x', 'y', z=u.meter) + """ + + def to_dict_point(self, *names, **kw_names): + """ + Convert the geometry to a dictionary geometry. + + This method creates a DictPointGeometry object, which represents the geometry + using named coordinates and their associated units. + + Args: + *names (str): Variable length argument list of coordinate names. + These are assumed to be unitless. + **kw_names (dict): Arbitrary keyword arguments where keys are coordinate names + and values are their corresponding units. + + Returns: + DictPointGeometry: A new geometry object that represents the current geometry + using a dictionary-based structure with named coordinates + and units. + + Raises: + ValueError: If the number of provided names doesn't match the dimension of the geometry. + + Note: + If a coordinate is specified in both *names and **kw_names, the unit from **kw_names will be used. + """ + return DictPointGeometry(self, *names, **kw_names) + + +def quantity_to_array( + quantity: Union[np.ndarray, jnp.ndarray, u.Quantity], unit: u.Unit +): + """ + Convert a quantity to an array with specified units. + + This function takes a quantity (which can be a numpy array, JAX array, or a Quantity object) + and converts it to an array with the specified units. If the input is already a Quantity, + it is converted to the specified unit and its magnitude is returned. If the input is an array, + it is returned as-is, but only if the specified unit is unitless. + + Parameters: + ----------- + quantity : Union[np.ndarray, jnp.ndarray, u.Quantity] + The input quantity to be converted. Can be a numpy array, JAX array, or a Quantity object. + unit : u.Unit + The target unit for conversion. If the input is not a Quantity, this must be unitless. + + Returns: + -------- + Union[np.ndarray, jnp.ndarray] + The magnitude of the quantity in the specified units, returned as an array. + + Raises: + ------- + AssertionError + If the input is not a Quantity and the specified unit is not unitless. + """ + if isinstance(quantity, u.Quantity): + return quantity.to(unit).magnitude + else: + assert unit.is_unitless, "The unit should be unitless." + return quantity + + +def array_to_quantity(array: Union[np.ndarray, jnp.ndarray], unit: u.Unit): + """ + Convert an array to a Quantity object with specified units. + + This function takes an array (either numpy or JAX) and a unit, and returns + a Quantity object representing the array with the given unit. + + Parameters: + ----------- + array : Union[np.ndarray, jnp.ndarray] + The input array to be converted to a Quantity. Can be either a numpy array + or a JAX array. + unit : u.Unit + The unit to be associated with the array values. + + Returns: + -------- + u.Quantity + A Quantity object representing the input array with the specified unit. + The returned object may be a decimal representation if appropriate. + """ + return u.math.maybe_decimal(u.Quantity(array, unit=unit)) + + +class DictPointGeometry(GeometryExperimental): + """ + A class that converts a standard Geometry object to a dictionary-based geometry representation. + + This class extends GeometryExperimental to provide a more flexible, named coordinate system + with unit awareness. It wraps an existing Geometry object and allows access to its + methods while providing additional functionality for working with named coordinates. + + Attributes: + geom (Geometry): The original geometry object being wrapped. + name2unit (dict): A dictionary mapping coordinate names to their corresponding units. + + Methods: + arr_to_dict(x): Convert an array to a dictionary of named quantities. + dict_to_arr(x): Convert a dictionary of named quantities to an array. + inside(x): Check if points are inside the geometry. + on_initial(x): Check if points are on the initial boundary. + on_boundary(x): Check if points are on the boundary of the geometry. + distance2boundary(x, dirn): Calculate the distance to the boundary in a specific direction. + mindist2boundary(x): Calculate the minimum distance to the boundary. + boundary_constraint_factor(x, **kw): Calculate the boundary constraint factor. + boundary_normal(x): Calculate the boundary normal vectors. + uniform_points(n, boundary): Generate uniformly distributed points in the geometry. + random_points(n, random): Generate random points in the geometry. + uniform_boundary_points(n): Generate uniformly distributed points on the boundary. + random_boundary_points(n, random): Generate random points on the boundary. + periodic_point(x, component): Find the periodic point for a given point and component. + background_points(x, dirn, dist2npt, shift): Generate background points. + random_initial_points(n, random): Generate random initial points. + uniform_initial_points(n): Generate uniformly distributed initial points. + + The class provides a bridge between array-based and dictionary-based representations + of geometric points, allowing for more intuitive and unit-aware manipulations of + geometric data in the context of physics-informed neural networks. + + Example: + geom = Geometry(dim=2, bbox=[0, 1, 0, 1], diam=1.414) + dict_geom = DictPointGeometry(geom, 'x', y=u.meter) + points = dict_geom.uniform_points(100) + # points will be a dictionary with keys 'x' (unitless) and 'y' (in meters) + """ + + def __init__(self, geom: Geometry, *names, **kw_names): + """ + Initialize a DictPointGeometry object. + + Args: + geom (Geometry): The base geometry object to be converted. + *names (str): Variable length argument list of coordinate names (assumed to be unitless). + **kw_names (dict): Arbitrary keyword arguments where keys are coordinate names + and values are their corresponding units. + + Raises: + ValueError: If the number of provided names doesn't match the dimension of the geometry. + """ + super().__init__(geom.dim, geom.bbox, geom.diam) + + self.geom = geom + for name in names: + assert isinstance(name, str), "The name should be a string." + kw_names = { + key: u.UNITLESS if unit is None else unit for key, unit in kw_names.items() + } + for key, unit in kw_names.items(): + assert isinstance(key, str), "The name should be a string." + assert isinstance(unit, u.Unit), "The unit should be a brainunit.Unit." + self.name2unit = {name: u.UNITLESS for name in names} + self.name2unit.update(kw_names) + if len(self.name2unit) != geom.dim: + raise ValueError( + "The number of names should match the dimension of the geometry. " + "But got {} names and {} dimensions.".format( + len(self.name2unit), geom.dim + ) + ) + + def arr_to_dict(self, x: bst.typing.ArrayLike) -> Dict[str, bst.typing.ArrayLike]: + """ + Convert an array to a dictionary of named quantities. + + Args: + x (ArrayLike): The input array to be converted. + + Returns: + Dict[str, ArrayLike]: A dictionary where keys are coordinate names and values are quantities. + """ + return { + name: array_to_quantity(x[..., i], unit) + for i, (name, unit) in enumerate(self.name2unit.items()) + } + + def dict_to_arr(self, x: Dict[str, bst.typing.ArrayLike]) -> bst.typing.ArrayLike: + """ + Convert a dictionary of named quantities to an array. + + Args: + x (Dict[str, ArrayLike]): The input dictionary to be converted. + + Returns: + ArrayLike: The resulting array. + """ + arrs = [ + quantity_to_array(x[name], unit) for name, unit in self.name2unit.items() + ] + mod = utils.smart_numpy(arrs[0]) + return mod.stack(arrs, axis=-1) + + def inside( + self, x: Union[np.ndarray, jnp.ndarray, u.Quantity, Dict] + ) -> np.ndarray[bool]: + """ + Check if points are inside the geometry. + + Args: + x (Union[np.ndarray, jnp.ndarray, u.Quantity, Dict]): The points to check. + + Returns: + np.ndarray[bool]: Boolean array indicating whether each point is inside the geometry. + """ + if isinstance(x, dict): + x = self.dict_to_arr(x) + return self.geom.inside(x) + + def on_initial( + self, x: Union[np.ndarray, jnp.ndarray, u.Quantity, Dict] + ) -> np.ndarray: + """ + Check if points are on the initial boundary. + + Args: + x (Union[np.ndarray, jnp.ndarray, u.Quantity, Dict]): The points to check. + + Returns: + np.ndarray: Array indicating whether each point is on the initial boundary. + """ + if isinstance(x, dict): + x = self.dict_to_arr(x) + return self.geom.on_initial(x) + + def on_boundary( + self, x: Union[np.ndarray, jnp.ndarray, u.Quantity, Dict] + ) -> np.ndarray[bool]: + """ + Check if points are on the boundary of the geometry. + + Args: + x (Union[np.ndarray, jnp.ndarray, u.Quantity, Dict]): The points to check. + + Returns: + np.ndarray[bool]: Boolean array indicating whether each point is on the boundary. + """ + if isinstance(x, dict): + x = self.dict_to_arr(x) + return self.geom.on_boundary(x) + + def distance2boundary( + self, x: Union[np.ndarray, jnp.ndarray, u.Quantity, Dict], dirn: int + ) -> np.ndarray: + """ + Calculate the distance to the boundary in a specific direction. + + Args: + x (Union[np.ndarray, jnp.ndarray, u.Quantity, Dict]): The points to calculate from. + dirn (int): The direction to calculate the distance. + + Returns: + np.ndarray: Array of distances to the boundary. + """ + if isinstance(x, dict): + x = self.dict_to_arr(x) + return self.geom.distance2boundary(x, dirn) + + def mindist2boundary( + self, x: Union[np.ndarray, jnp.ndarray, u.Quantity, Dict] + ) -> np.ndarray: + """ + Calculate the minimum distance to the boundary. + + Args: + x (Union[np.ndarray, jnp.ndarray, u.Quantity, Dict]): The points to calculate from. + + Returns: + np.ndarray: Array of minimum distances to the boundary. + """ + if isinstance(x, dict): + x = self.dict_to_arr(x) + return self.geom.mindist2boundary(x) + + def boundary_constraint_factor( + self, x: Union[np.ndarray, jnp.ndarray, u.Quantity, Dict], **kw + ) -> np.ndarray: + """ + Calculate the boundary constraint factor. + + Args: + x (Union[np.ndarray, jnp.ndarray, u.Quantity, Dict]): The points to calculate for. + **kw: Additional keyword arguments. + + Returns: + np.ndarray: Array of boundary constraint factors. + """ + if isinstance(x, dict): + x = self.dict_to_arr(x) + return self.geom.boundary_constraint_factor(x, **kw) + + def boundary_normal( + self, x: Union[np.ndarray, jnp.ndarray, u.Quantity, Dict] + ) -> Dict[str, bst.typing.ArrayLike]: + """ + Calculate the boundary normal vectors. + + Args: + x (Union[np.ndarray, jnp.ndarray, u.Quantity, Dict]): The points to calculate for. + + Returns: + Dict[str, ArrayLike]: Dictionary of boundary normal vectors. + """ + if isinstance(x, dict): + x = self.dict_to_arr(x) + normal = self.geom.boundary_normal(x) + return self.arr_to_dict(normal) + + def uniform_points( + self, n, boundary: bool = True + ) -> Dict[str, bst.typing.ArrayLike]: + """ + Generate uniformly distributed points in the geometry. + + Args: + n (int): Number of points to generate. + boundary (bool, optional): Whether to include boundary points. Defaults to True. + + Returns: + Dict[str, ArrayLike]: Dictionary of generated points. + """ + points = self.geom.uniform_points(n, boundary=boundary) + return self.arr_to_dict(points) + + def random_points(self, n, random="pseudo") -> Dict[str, bst.typing.ArrayLike]: + """ + Generate random points in the geometry. + + Args: + n (int): Number of points to generate. + random (str, optional): Type of random number generation. Defaults to "pseudo". + + Returns: + Dict[str, ArrayLike]: Dictionary of generated points. + """ + points = self.geom.random_points(n, random=random) + return self.arr_to_dict(points) + + def uniform_boundary_points(self, n) -> Dict[str, bst.typing.ArrayLike]: + """ + Generate uniformly distributed points on the boundary. + + Args: + n (int): Number of points to generate. + + Returns: + Dict[str, ArrayLike]: Dictionary of generated boundary points. + """ + points = self.geom.uniform_boundary_points(n) + return self.arr_to_dict(points) + + def random_boundary_points( + self, n, random: str = "pseudo" + ) -> Dict[str, bst.typing.ArrayLike]: + """ + Generate random points on the boundary. + + Args: + n (int): Number of points to generate. + random (str, optional): Type of random number generation. Defaults to "pseudo". + + Returns: + Dict[str, ArrayLike]: Dictionary of generated boundary points. + """ + points = self.geom.random_boundary_points(n, random=random) + return self.arr_to_dict(points) + + def periodic_point( + self, x, component: Union[str, int] + ) -> Dict[str, bst.typing.ArrayLike]: + """ + Find the periodic point for a given point and component. + + Args: + x (Union[np.ndarray, jnp.ndarray, u.Quantity, Dict]): The point to find the periodic point for. + component (Union[str, int]): The component to consider for periodicity. + + Returns: + Dict[str, ArrayLike]: Dictionary of the periodic point. + + Raises: + AssertionError: If the component is not an integer or a string. + """ + if isinstance(x, dict): + x = self.dict_to_arr(x) + if isinstance(component, str): + component = list(self.name2unit.keys()).index(component) + assert isinstance( + component, int + ), f"The component should be an integer or a string. But got {component}." + x = self.geom.periodic_point(x, component) + return self.arr_to_dict(x) + + def background_points( + self, x, dirn, dist2npt, shift + ) -> Dict[str, bst.typing.ArrayLike]: + """ + Generate background points. + + Args: + x (Union[np.ndarray, jnp.ndarray, u.Quantity, Dict]): The reference points. + dirn: The direction for generating background points. + dist2npt: The distance to number of points mapping. + shift: The shift to apply. + + Returns: + Dict[str, ArrayLike]: Dictionary of generated background points. + """ + if isinstance(x, dict): + x = self.dict_to_arr(x) + points = self.geom.background_points(x, dirn, dist2npt, shift) + return self.arr_to_dict(points) + + def random_initial_points( + self, n: int, random: str = "pseudo" + ) -> Dict[str, bst.typing.ArrayLike]: + """ + Generate random initial points. + + Args: + n (int): Number of points to generate. + random (str, optional): Type of random number generation. Defaults to "pseudo". + + Returns: + Dict[str, ArrayLike]: Dictionary of generated initial points. + """ + points = self.geom.random_initial_points(n, random=random) + return self.arr_to_dict(points) + + def uniform_initial_points(self, n: int) -> Dict[str, bst.typing.ArrayLike]: + """ + Generate uniformly distributed initial points. + + Args: + n (int): Number of points to generate. + + Returns: + Dict[str, ArrayLike]: Dictionary of generated initial points. + """ + points = self.geom.uniform_initial_points(n) + return self.arr_to_dict(points) diff --git a/deepxde/experimental/geometry/geometry_1d.py b/deepxde/experimental/geometry/geometry_1d.py new file mode 100644 index 000000000..aeb1bbf8e --- /dev/null +++ b/deepxde/experimental/geometry/geometry_1d.py @@ -0,0 +1,330 @@ +from typing import Literal, Union + +import brainstate as bst +import jax.numpy as jnp + +from deepxde.geometry.sampler import sample +from deepxde.experimental import utils +from .base import GeometryExperimental as Geometry + + +class Interval(Geometry): + """ + Represents a 1D interval geometry. + + This class defines an interval [l, r] and provides various methods for + working with points within and on the boundary of this interval. + """ + + def __init__(self, l, r): + """ + Initialize the Interval object. + + Args: + l (float): The left endpoint of the interval. + r (float): The right endpoint of the interval. + """ + super().__init__( + 1, + ( + jnp.array([l], dtype=bst.environ.dftype()), + jnp.array([r], dtype=bst.environ.dftype()), + ), + r - l, + ) + self.l, self.r = l, r + + def inside(self, x): + """ + Check if points are inside the interval. + + Args: + x (array-like): The points to check. + + Returns: + array: Boolean array indicating whether each point is inside the interval. + """ + mod = utils.smart_numpy(x) + return mod.logical_and(self.l <= x, x <= self.r).flatten() + + def on_boundary(self, x): + """ + Check if points are on the boundary of the interval. + + Args: + x (array-like): The points to check. + + Returns: + array: Boolean array indicating whether each point is on the boundary. + """ + mod = utils.smart_numpy(x) + return mod.any( + mod.isclose(x, jnp.array([self.l, self.r], dtype=bst.environ.dftype())), + axis=-1, + ) + + def distance2boundary(self, x, dirn): + """ + Compute the distance from points to the boundary in a specified direction. + + Args: + x (array-like): The points to compute distance for. + dirn (int): Direction indicator (-1 for left, 1 for right). + + Returns: + array: Distances to the boundary. + """ + return x - self.l if dirn < 0 else self.r - x + + def mindist2boundary(self, x): + """ + Compute the minimum distance from points to the boundary. + + Args: + x (array-like): The points to compute distance for. + + Returns: + float: Minimum distance to the boundary. + """ + mod = utils.smart_numpy(x) + return min(mod.amin(x - self.l), mod.amin(self.r - x)) + + def boundary_constraint_factor( + self, + x, + smoothness: Literal["C0", "C0+", "Cinf"] = "C0+", + where: Union[None, Literal["left", "right"]] = None, + ): + """Compute the hard constraint factor at x for the boundary. + + This function is used for the hard-constraint methods in Physics-Informed Neural Networks (PINNs). + The hard constraint factor satisfies the following properties: + + - The function is zero on the boundary and positive elsewhere. + - The function is at least continuous. + + In the ansatz `boundary_constraint_factor(x) * NN(x) + boundary_condition(x)`, when `x` is on the boundary, + `boundary_constraint_factor(x)` will be zero, making the ansatz be the boundary condition, which in + turn makes the boundary condition a "hard constraint". + + Args: + x: A 2D array of shape (n, dim), where `n` is the number of points and + `dim` is the dimension of the geometry. Note that `x` should be a tensor type + of backend (e.g., `tf.Tensor` or `torch.Tensor`), not a numpy array. + smoothness (string, optional): A string to specify the smoothness of the distance function, + e.g., "C0", "C0+", "Cinf". "C0" is the least smooth, "Cinf" is the most smooth. + Default is "C0+". + + - C0 + The distance function is continuous but may not be non-differentiable. + But the set of non-differentiable points should have measure zero, + which makes the probability of the collocation point falling in this set be zero. + + - C0+ + The distance function is continuous and differentiable almost everywhere. The + non-differentiable points can only appear on boundaries. If the points in `x` are + all inside or outside the geometry, the distance function is smooth. + + - Cinf + The distance function is continuous and differentiable at any order on any + points. This option may result in a polynomial of HIGH order. + + where (string, optional): A string to specify which part of the boundary to compute the distance, + e.g., "left", "right". If `None`, compute the distance to the whole boundary. Default is `None`. + + Returns: + A tensor of a type determined by the backend, which will have a shape of (n, 1). + Each element in the tensor corresponds to the computed distance value for the respective point in `x`. + """ + + if where not in [None, "left"]: + raise ValueError("where must be None or left") + if smoothness not in ["C0", "C0+", "Cinf"]: + raise ValueError("smoothness must be one of C0, C0+, Cinf") + + # To convert self.l and self.r to tensor, + # and avoid repeated conversion in the loop + if not hasattr(self, "self.l_tensor"): + self.l_tensor = jnp.asarray(self.l) + self.r_tensor = jnp.asarray(self.r) + + dist_l = dist_r = None + if where != "right": + dist_l = jnp.abs((x - self.l_tensor) / (self.r_tensor - self.l_tensor) * 2) + if where != "left": + dist_r = jnp.abs((x - self.r_tensor) / (self.r_tensor - self.l_tensor) * 2) + + if where is None: + if smoothness == "C0": + return jnp.minimum(dist_l, dist_r) + if smoothness == "C0+": + return dist_l * dist_r + return jnp.square(dist_l * dist_r) + if where == "left": + if smoothness == "Cinf": + dist_l = jnp.square(dist_l) + return dist_l + if smoothness == "Cinf": + dist_r = jnp.square(dist_r) + return dist_r + + def boundary_normal(self, x): + """ + Compute the normal vector at boundary points. + + Args: + x (array-like): The points to compute normal vectors for. + + Returns: + array: Normal vectors at the given points. + """ + mod = utils.smart_numpy(x) + return -mod.isclose(x, self.l).astype(bst.environ.dftype()) + mod.isclose( + x, self.r + ) + + def uniform_points(self, n, boundary=True): + """ + Generate uniformly distributed points in the interval. + + Args: + n (int): Number of points to generate. + boundary (bool): Whether to include boundary points. + + Returns: + array: Uniformly distributed points. + """ + if boundary: + return jnp.linspace(self.l, self.r, num=n, dtype=bst.environ.dftype())[ + :, None + ] + return jnp.linspace( + self.l, self.r, num=n + 1, endpoint=False, dtype=bst.environ.dftype() + )[1:, None] + + def log_uniform_points(self, n, boundary=True): + """ + Generate logarithmically uniformly distributed points in the interval. + + Args: + n (int): Number of points to generate. + boundary (bool): Whether to include boundary points. + + Returns: + array: Logarithmically uniformly distributed points. + """ + eps = 0 if self.l > 0 else jnp.finfo(bst.environ.dftype()).eps + l = jnp.log(self.l + eps) + r = jnp.log(self.r + eps) + if boundary: + x = jnp.linspace(l, r, num=n, dtype=bst.environ.dftype())[:, None] + else: + x = jnp.linspace( + l, r, num=n + 1, endpoint=False, dtype=bst.environ.dftype() + )[1:, None] + return jnp.exp(x) - eps + + def random_points(self, n, random="pseudo"): + """ + Generate random points in the interval. + + Args: + n (int): Number of points to generate. + random (str): Type of random number generation ("pseudo" or other). + + Returns: + array: Randomly distributed points. + """ + x = sample(n, 1, random) + return (self.diam * x + self.l).astype(bst.environ.dftype()) + + def uniform_boundary_points(self, n): + """ + Generate uniformly distributed points on the boundary. + + Args: + n (int): Number of points to generate. + + Returns: + array: Uniformly distributed boundary points. + """ + if n == 1: + return jnp.array([[self.l]]).astype(bst.environ.dftype()) + xl = jnp.full((n // 2, 1), self.l).astype(bst.environ.dftype()) + xr = jnp.full((n - n // 2, 1), self.r).astype(bst.environ.dftype()) + return jnp.vstack((xl, xr)) + + def random_boundary_points(self, n, random="pseudo"): + """ + Generate random points on the boundary. + + Args: + n (int): Number of points to generate. + random (str): Type of random number generation ("pseudo" or other). + + Returns: + array: Randomly distributed boundary points. + """ + if n == 2: + return jnp.array([[self.l], [self.r]]).astype(bst.environ.dftype()) + return bst.random.choice([self.l, self.r], n)[:, None].astype( + bst.environ.dftype() + ) + + def periodic_point(self, x, component=0): + """ + Map points to their periodic equivalents within the interval. + + Args: + x (array-like): Points to map. + component (int): Component to apply periodicity to (unused in 1D). + + Returns: + array: Mapped periodic points. + """ + tmp = jnp.copy(x) + tmp[utils.isclose(x, self.l)] = self.r + tmp[utils.isclose(x, self.r)] = self.l + return tmp + + def background_points(self, x, dirn, dist2npt, shift): + """ + Generate background points based on a given point and direction. + + Args: + x (array-like): Reference point. + dirn (int): Direction (-1 for left, 1 for right, 0 for both). + dist2npt (callable): Function to convert distance to number of points. + shift (int): Number of points to shift. + + Returns: + array: Generated background points. + """ + + def background_points_left(): + dx = x[0] - self.l + n = max(dist2npt(dx), 1) + h = dx / n + pts = ( + x[0] - jnp.arange(-shift, n - shift + 1, dtype=bst.environ.dftype()) * h + ) + return pts[:, None] + + def background_points_right(): + dx = self.r - x[0] + n = max(dist2npt(dx), 1) + h = dx / n + pts = ( + x[0] + jnp.arange(-shift, n - shift + 1, dtype=bst.environ.dftype()) * h + ) + return pts[:, None] + + return ( + background_points_left() + if dirn < 0 + else ( + background_points_right() + if dirn > 0 + else jnp.vstack((background_points_left(), background_points_right())) + ) + ) diff --git a/deepxde/experimental/geometry/geometry_2d.py b/deepxde/experimental/geometry/geometry_2d.py new file mode 100644 index 000000000..cc15cd66f --- /dev/null +++ b/deepxde/experimental/geometry/geometry_2d.py @@ -0,0 +1,1123 @@ +__all__ = ["Disk", "Ellipse", "Polygon", "Rectangle", "StarShaped", "Triangle"] + +from typing import Union, Literal + +import brainstate as bst +import jax.numpy as jnp +from scipy import spatial + +from deepxde.geometry.sampler import sample +from deepxde.experimental import utils +from deepxde.utils.internal import vectorize +from .base import GeometryExperimental as Geometry +from .geometry_nd import Hypercube, Hypersphere +from ..utils import isclose + + +class Disk(Hypersphere): + """ + A class representing a disk in 2D space, inheriting from Hypersphere. + """ + + def inside(self, x): + """ + Check if points are inside the disk. + + Args: + x (array-like): The coordinates of points to check. + + Returns: + array-like: Boolean array indicating whether each point is inside the disk. + """ + mod = utils.smart_numpy(x) + return mod.linalg.norm(x - self.center, axis=-1) <= self.radius + + def on_boundary(self, x): + """ + Check if points are on the boundary of the disk. + + Args: + x (array-like): The coordinates of points to check. + + Returns: + array-like: Boolean array indicating whether each point is on the disk's boundary. + """ + mod = utils.smart_numpy(x) + return mod.isclose(mod.linalg.norm(x - self.center, axis=-1), self.radius) + + def distance2boundary_unitdirn(self, x, dirn): + """ + Calculate the distance from points to the disk boundary in a given unit direction. + + Args: + x (array-like): The coordinates of points. + dirn (array-like): The unit direction vector. + + Returns: + array-like: Distances from points to the disk boundary in the given direction. + """ + mod = utils.smart_numpy(x) + xc = x - self.center + ad = jnp.dot(xc, dirn) + return (-ad + (ad**2 - mod.sum(xc * xc, axis=-1) + self._r2) ** 0.5).astype( + bst.environ.dftype() + ) + + def distance2boundary(self, x, dirn): + """ + Calculate the distance from points to the disk boundary in a given direction. + + Args: + x (array-like): The coordinates of points. + dirn (array-like): The direction vector (not necessarily unit). + + Returns: + array-like: Distances from points to the disk boundary in the given direction. + """ + mod = utils.smart_numpy(x) + return self.distance2boundary_unitdirn(x, dirn / mod.linalg.norm(dirn)) + + def mindist2boundary(self, x): + """ + Calculate the minimum distance from points to the disk boundary. + + Args: + x (array-like): The coordinates of points. + + Returns: + array-like: Minimum distances from points to the disk boundary. + """ + mod = utils.smart_numpy(x) + return mod.amin(self.radius - mod.linalg.norm(x - self.center, axis=1)) + + def boundary_normal(self, x): + """ + Calculate the unit normal vector to the disk boundary at given points. + + Args: + x (array-like): The coordinates of points on the disk boundary. + + Returns: + array-like: Unit normal vectors to the disk boundary at the given points. + """ + mod = utils.smart_numpy(x) + _n = x - self.center + l = mod.linalg.norm(_n, axis=-1, keepdims=True) + _n = _n / l * mod.isclose(l, self.radius) + return _n + + def random_points(self, n, random="pseudo"): + """ + Generate random points inside the disk. + + Args: + n (int): Number of points to generate. + random (str, optional): Method for generating random numbers. Defaults to "pseudo". + + Returns: + array-like: Coordinates of randomly generated points inside the disk. + """ + rng = sample(n, 2, random) + r, theta = rng[:, 0], 2 * jnp.pi * rng[:, 1] + x, y = jnp.cos(theta), jnp.sin(theta) + return self.radius * (jnp.sqrt(r) * jnp.vstack((x, y))).T + self.center + + def uniform_boundary_points(self, n): + """ + Generate uniformly distributed points on the disk boundary. + + Args: + n (int): Number of points to generate. + + Returns: + array-like: Coordinates of uniformly distributed points on the disk boundary. + """ + theta = jnp.linspace(0, 2 * jnp.pi, num=n, endpoint=False) + X = jnp.vstack((jnp.cos(theta), jnp.sin(theta))).T + return self.radius * X + self.center + + def random_boundary_points(self, n, random="pseudo"): + """ + Generate random points on the disk boundary. + + Args: + n (int): Number of points to generate. + random (str, optional): Method for generating random numbers. Defaults to "pseudo". + + Returns: + array-like: Coordinates of randomly generated points on the disk boundary. + """ + u = sample(n, 1, random) + theta = 2 * jnp.pi * u + X = jnp.hstack((jnp.cos(theta), jnp.sin(theta))) + return self.radius * X + self.center + + def background_points(self, x, dirn, dist2npt, shift): + """ + Generate background points along a line passing through given points. + + Args: + x (array-like): The coordinates of points. + dirn (array-like): The direction vector. + dist2npt (callable): Function to determine number of points based on distance. + shift (float): Shift factor for point generation. + + Returns: + array-like: Coordinates of generated background points. + """ + dirn = dirn / jnp.linalg.norm(dirn) + dx = self.distance2boundary_unitdirn(x, -dirn) + n = max(dist2npt(dx), 1) + h = dx / n + pts = ( + x + - jnp.arange(-shift, n - shift + 1, dtype=bst.environ.dftype())[:, None] + * h + * dirn + ) + return pts + + +class Ellipse(Geometry): + """ + A class representing an ellipse in 2D space. + + This class inherits from the Geometry class and provides methods for working with ellipses, + including generating points on the boundary and inside the ellipse, and computing distances. + + Args: + center (array-like): The coordinates of the center of the ellipse. + semimajor (float): The length of the semimajor axis of the ellipse. + semiminor (float): The length of the semiminor axis of the ellipse. + angle (float, optional): The rotation angle of the ellipse in radians. Defaults to 0. + A positive angle rotates the ellipse clockwise about the center, + while a negative angle rotates it counterclockwise. + """ + + def __init__(self, center, semimajor, semiminor, angle=0): + self.center = jnp.array(center, dtype=bst.environ.dftype()) + self.semimajor = semimajor + self.semiminor = semiminor + self.angle = angle + self.c = (semimajor**2 - semiminor**2) ** 0.5 + + self.focus1 = jnp.array( + [ + center[0] - self.c * jnp.cos(angle), + center[1] + self.c * jnp.sin(angle), + ], + dtype=bst.environ.dftype(), + ) + self.focus2 = jnp.array( + [ + center[0] + self.c * jnp.cos(angle), + center[1] - self.c * jnp.sin(angle), + ], + dtype=bst.environ.dftype(), + ) + self.rotation_mat = jnp.array( + [[jnp.cos(-angle), -jnp.sin(-angle)], [jnp.sin(-angle), jnp.cos(-angle)]] + ) + ( + self.theta_from_arc_length, + self.total_arc, + ) = self._theta_from_arc_length_constructor() + super().__init__( + 2, (self.center - semimajor, self.center + semiminor), 2 * self.c + ) + + def on_boundary(self, x): + """ + Check if points are on the boundary of the ellipse. + + Args: + x (array-like): The coordinates of points to check. + + Returns: + array-like: Boolean array indicating whether each point is on the ellipse's boundary. + """ + d1 = jnp.linalg.norm(x - self.focus1, axis=-1) + d2 = jnp.linalg.norm(x - self.focus2, axis=-1) + return isclose(d1 + d2, 2 * self.semimajor) + + def inside(self, x): + """ + Check if points are inside the ellipse. + + Args: + x (array-like): The coordinates of points to check. + + Returns: + array-like: Boolean array indicating whether each point is inside the ellipse. + """ + d1 = jnp.linalg.norm(x - self.focus1, axis=-1) + d2 = jnp.linalg.norm(x - self.focus2, axis=-1) + return d1 + d2 <= 2 * self.semimajor + + def _ellipse_arc(self): + """ + Calculate the cumulative arc length of the ellipse. + + Returns: + tuple: A tuple containing: + - theta (array-like): Angle values. + - cumulative_distance (array-like): Cumulative distance at each theta. + - c (float): Total arc length of the ellipse. + """ + # Divide the interval [0 , theta] into n steps at regular angles + theta = jnp.linspace(0, 2 * jnp.pi, 10000) + coords = jnp.array( + [self.semimajor * jnp.cos(theta), self.semiminor * jnp.sin(theta)] + ) + # Compute vector distance between each successive point + coords_diffs = jnp.diff(coords) + # Compute the full arc + delta_r = jnp.linalg.norm(coords_diffs, axis=0) + cumulative_distance = jnp.concatenate(([0], jnp.cumsum(delta_r))) + c = jnp.sum(delta_r) + return theta, cumulative_distance, c + + def _theta_from_arc_length_constructor(self): + """ + Construct a function that returns the angle associated with a given cumulative arc length. + + Returns: + tuple: A tuple containing: + - f (callable): A function that takes an arc length and returns the corresponding angle. + - total_arc (float): The total arc length of the ellipse. + """ + theta, cumulative_distance, total_arc = self._ellipse_arc() + + # Construct the inverse arc length function + def f(s): + return jnp.interp(s, cumulative_distance, theta) + + return f, total_arc + + def random_points(self, n, random="pseudo"): + """ + Generate random points inside the ellipse. + + Args: + n (int): Number of points to generate. + random (str, optional): Method for generating random numbers. Defaults to "pseudo". + + Returns: + array-like: Coordinates of randomly generated points inside the ellipse. + """ + # http://mathworld.wolfram.com/DiskPointPicking.html + rng = sample(n, 2, random) + r, theta = rng[:, 0], 2 * jnp.pi * rng[:, 1] + x, y = self.semimajor * jnp.cos(theta), self.semiminor * jnp.sin(theta) + X = jnp.sqrt(r) * jnp.vstack((x, y)) + return jnp.matmul(self.rotation_mat, X).T + self.center + + def uniform_boundary_points(self, n): + """ + Generate uniformly distributed points on the ellipse boundary. + + Args: + n (int): Number of points to generate. + + Returns: + array-like: Coordinates of uniformly distributed points on the ellipse boundary. + """ + # https://codereview.stackexchange.com/questions/243590/generate-random-points-on-perimeter-of-ellipse + u = jnp.linspace(0, 1, num=n, endpoint=False).reshape((-1, 1)) + theta = self.theta_from_arc_length(u * self.total_arc) + X = jnp.hstack( + (self.semimajor * jnp.cos(theta), self.semiminor * jnp.sin(theta)) + ) + return jnp.matmul(self.rotation_mat, X.T).T + self.center + + def random_boundary_points(self, n, random="pseudo"): + """ + Generate random points on the ellipse boundary. + + Args: + n (int): Number of points to generate. + random (str, optional): Method for generating random numbers. Defaults to "pseudo". + + Returns: + array-like: Coordinates of randomly generated points on the ellipse boundary. + """ + u = sample(n, 1, random) + theta = self.theta_from_arc_length(u * self.total_arc) + X = jnp.hstack( + (self.semimajor * jnp.cos(theta), self.semiminor * jnp.sin(theta)) + ) + return jnp.matmul(self.rotation_mat, X.T).T + self.center + + def boundary_constraint_factor( + self, x, smoothness: Literal["C0", "C0+", "Cinf"] = "C0+" + ): + """ + Compute the boundary constraint factor for given points. + + This function calculates a factor that represents how close points are to the ellipse boundary. + The factor is zero on the boundary and positive elsewhere. + + Args: + x (array-like): The coordinates of points to evaluate. + smoothness (str, optional): The smoothness of the constraint factor. + Must be one of "C0", "C0+", or "Cinf". Defaults to "C0+". + + Returns: + array-like: The computed boundary constraint factors for the input points. + + Raises: + ValueError: If smoothness is not one of the allowed values. + """ + if smoothness not in ["C0", "C0+", "Cinf"]: + raise ValueError("`smoothness` must be one of C0, C0+, Cinf") + + if not hasattr(self, "self.focus1_tensor"): + self.focus1_tensor = jnp.asarray(self.focus1) + self.focus2_tensor = jnp.asarray(self.focus2) + + d1 = jnp.linalg.norm(x - self.focus1_tensor, axis=-1, keepdims=True) + d2 = jnp.linalg.norm(x - self.focus2_tensor, axis=-1, keepdims=True) + dist = d1 + d2 - 2 * self.semimajor + + if smoothness == "Cinf": + dist = jnp.square(dist) + else: + dist = jnp.abs(dist) + + return dist + + +class Rectangle(Hypercube): + """ + A class representing a rectangle in 2D space. + + This class inherits from the Hypercube class and provides methods for working with rectangles, + including generating points on the boundary and inside the rectangle, and computing distances. + + Args: + xmin: Coordinate of bottom left corner. + xmax: Coordinate of top right corner. + """ + + def __init__(self, xmin, xmax): + """ + Initialize a Rectangle object. + + Args: + xmin (array-like): Coordinate of the bottom left corner. + xmax (array-like): Coordinate of the top right corner. + """ + super().__init__(xmin, xmax) + self.perimeter = 2 * jnp.sum(self.xmax - self.xmin) + self.area = jnp.prod(self.xmax - self.xmin) + + def uniform_boundary_points(self, n): + """ + Generate uniformly distributed points on the rectangle boundary. + + Args: + n (int): Number of points to generate. + + Returns: + array-like: Coordinates of uniformly distributed points on the rectangle boundary. + """ + nx, ny = jnp.ceil(n / self.perimeter * (self.xmax - self.xmin)).astype(int) + xbot = jnp.hstack( + ( + jnp.linspace(self.xmin[0], self.xmax[0], num=nx, endpoint=False)[ + :, None + ], + jnp.full([nx, 1], self.xmin[1]), + ) + ) + yrig = jnp.hstack( + ( + jnp.full([ny, 1], self.xmax[0]), + jnp.linspace(self.xmin[1], self.xmax[1], num=ny, endpoint=False)[ + :, None + ], + ) + ) + xtop = jnp.hstack( + ( + jnp.linspace(self.xmin[0], self.xmax[0], num=nx + 1)[1:, None], + jnp.full([nx, 1], self.xmax[1]), + ) + ) + ylef = jnp.hstack( + ( + jnp.full([ny, 1], self.xmin[0]), + jnp.linspace(self.xmin[1], self.xmax[1], num=ny + 1)[1:, None], + ) + ) + x = jnp.vstack((xbot, yrig, xtop, ylef)) + if n != len(x): + print( + "Warning: {} points required, but {} points sampled.".format(n, len(x)) + ) + return x + + def random_boundary_points(self, n, random="pseudo"): + """ + Generate random points on the rectangle boundary. + + Args: + n (int): Number of points to generate. + random (str, optional): Method for generating random numbers. Defaults to "pseudo". + + Returns: + array-like: Coordinates of randomly generated points on the rectangle boundary. + """ + l1 = self.xmax[0] - self.xmin[0] + l2 = l1 + self.xmax[1] - self.xmin[1] + l3 = l2 + l1 + u = jnp.ravel(sample(n + 2, 1, random)) + # Remove the possible points very close to the corners + u = u[jnp.logical_not(isclose(u, l1 / self.perimeter))] + u = u[jnp.logical_not(isclose(u, l3 / self.perimeter))] + u = u[:n] + + u *= self.perimeter + x = [] + for l in u: + if l < l1: + x.append([self.xmin[0] + l, self.xmin[1]]) + elif l < l2: + x.append([self.xmax[0], self.xmin[1] + l - l1]) + elif l < l3: + x.append([self.xmax[0] - l + l2, self.xmax[1]]) + else: + x.append([self.xmin[0], self.xmax[1] - l + l3]) + return jnp.vstack(x) + + @staticmethod + def is_valid(vertices): + """ + Check if the geometry is a valid Rectangle. + + Args: + vertices (array-like): An array of 4 vertices defining the rectangle. + + Returns: + bool: True if the geometry is a valid rectangle, False otherwise. + """ + return ( + len(vertices) == 4 + and isclose(jnp.prod(vertices[1] - vertices[0]), 0) + and isclose(jnp.prod(vertices[2] - vertices[1]), 0) + and isclose(jnp.prod(vertices[3] - vertices[2]), 0) + and isclose(jnp.prod(vertices[0] - vertices[3]), 0) + ) + + +class StarShaped(Geometry): + """Star-shaped 2d domain, i.e., a geometry whose boundary is parametrized in polar coordinates as: + + $$ + r(theta) := r_0 + sum_{i = 1}^N [a_i cos( i theta) + b_i sin(i theta) ], theta in [0,2 pi]. + $$ + + For more details, refer to: + `Hiptmair et al. Large deformation shape uncertainty quantification in acoustic + scattering. Adv Comp Math, 2018. + `_ + + Args: + center: Center of the domain. + radius: 0th-order term of the parametrization (r_0). + coeffs_cos: i-th order coefficients for the i-th cos term (a_i). + coeffs_sin: i-th order coefficients for the i-th sin term (b_i). + """ + + def __init__(self, center, radius, coeffs_cos, coeffs_sin): + self.center = jnp.array(center, dtype=bst.environ.dftype()) + self.radius = radius + self.coeffs_cos = coeffs_cos + self.coeffs_sin = coeffs_sin + max_radius = radius + jnp.sum(coeffs_cos) + jnp.sum(coeffs_sin) + super().__init__( + 2, + (self.center - max_radius, self.center + max_radius), + 2 * max_radius, + ) + + def _r_theta(self, theta): + """Define the parametrization r(theta) at angles theta.""" + result = self.radius * jnp.ones(theta.shape) + for i, (coeff_cos, coeff_sin) in enumerate( + zip(self.coeffs_cos, self.coeffs_sin), start=1 + ): + result += coeff_cos * jnp.cos(i * theta) + coeff_sin * jnp.sin(i * theta) + return result + + def _dr_theta(self, theta): + """Evalutate the polar derivative r'(theta) at angles theta""" + result = jnp.zeros(theta.shape) + for i, (coeff_cos, coeff_sin) in enumerate( + zip(self.coeffs_cos, self.coeffs_sin), start=1 + ): + result += -coeff_cos * i * jnp.sin(i * theta) + coeff_sin * i * jnp.cos( + i * theta + ) + return result + + def inside(self, x): + r, theta = polar(x - self.center) + r_theta = self._r_theta(theta) + return r_theta >= r + + def on_boundary(self, x): + r, theta = polar(x - self.center) + r_theta = self._r_theta(theta) + return isclose(jnp.linalg.norm(r_theta - r), 0) + + def boundary_normal(self, x): + _, theta = polar(x - self.center) + dr_theta = self._dr_theta(theta) + r_theta = self._r_theta(theta) + + dxt = jnp.vstack( + ( + dr_theta * jnp.cos(theta) - r_theta * jnp.sin(theta), + dr_theta * jnp.sin(theta) + r_theta * jnp.cos(theta), + ) + ).T + norm = jnp.linalg.norm(dxt, axis=-1, keepdims=True) + dxt /= norm + return jnp.array([dxt[:, 1], -dxt[:, 0]]).T + + def random_points(self, n, random="pseudo"): + x = jnp.empty((0, 2), dtype=bst.environ.dftype()) + vbbox = self.bbox[1] - self.bbox[0] + while len(x) < n: + x_new = sample(n, 2, sampler="pseudo") * vbbox + self.bbox[0] + x = jnp.vstack((x, x_new[self.inside(x_new)])) + return x[:n] + + def uniform_boundary_points(self, n): + theta = jnp.linspace(0, 2 * jnp.pi, num=n, endpoint=False) + r_theta = self._r_theta(theta) + X = jnp.vstack((r_theta * jnp.cos(theta), r_theta * jnp.sin(theta))).T + return X + self.center + + def random_boundary_points(self, n, random="pseudo"): + u = sample(n, 1, random) + theta = 2 * jnp.pi * u + r_theta = self._r_theta(theta) + X = jnp.hstack((r_theta * jnp.cos(theta), r_theta * jnp.sin(theta))) + return X + self.center + + +class Triangle(Geometry): + """Triangle. + + The order of vertices can be in a clockwise or counterclockwise direction. The + vertices will be re-ordered in counterclockwise (right hand rule). + """ + + def __init__(self, x1, x2, x3): + self.area = polygon_signed_area([x1, x2, x3]) + # Clockwise + if self.area < 0: + self.area = -self.area + x2, x3 = x3, x2 + + self.x1 = jnp.array(x1, dtype=bst.environ.dftype()) + self.x2 = jnp.array(x2, dtype=bst.environ.dftype()) + self.x3 = jnp.array(x3, dtype=bst.environ.dftype()) + + self.v12 = self.x2 - self.x1 + self.v23 = self.x3 - self.x2 + self.v31 = self.x1 - self.x3 + self.l12 = jnp.linalg.norm(self.v12) + self.l23 = jnp.linalg.norm(self.v23) + self.l31 = jnp.linalg.norm(self.v31) + self.n12 = self.v12 / self.l12 + self.n23 = self.v23 / self.l23 + self.n31 = self.v31 / self.l31 + self.n12_normal = clockwise_rotation_90(self.n12) + self.n23_normal = clockwise_rotation_90(self.n23) + self.n31_normal = clockwise_rotation_90(self.n31) + self.perimeter = self.l12 + self.l23 + self.l31 + + super().__init__( + 2, + ( + jnp.minimum(x1, jnp.minimum(x2, x3)), + jnp.maximum(x1, jnp.maximum(x2, x3)), + ), + self.l12 + * self.l23 + * self.l31 + / ( + self.perimeter + * (self.l12 + self.l23 - self.l31) + * (self.l23 + self.l31 - self.l12) + * (self.l31 + self.l12 - self.l23) + ) + ** 0.5, + ) + + def inside(self, x): + # https://stackoverflow.com/a/2049593/12679294 + _sign = jnp.hstack( + [ + jnp.cross(self.v12, x - self.x1)[:, jnp.newaxis], + jnp.cross(self.v23, x - self.x2)[:, jnp.newaxis], + jnp.cross(self.v31, x - self.x3)[:, jnp.newaxis], + ] + ) + return ~jnp.logical_and( + jnp.any(_sign > 0, axis=-1), jnp.any(_sign < 0, axis=-1) + ) + + def on_boundary(self, x): + l1 = jnp.linalg.norm(x - self.x1, axis=-1) + l2 = jnp.linalg.norm(x - self.x2, axis=-1) + l3 = jnp.linalg.norm(x - self.x3, axis=-1) + return jnp.any( + isclose( + [l1 + l2 - self.l12, l2 + l3 - self.l23, l3 + l1 - self.l31], + 0, + ), + axis=0, + ) + + def boundary_normal(self, x): + l1 = jnp.linalg.norm(x - self.x1, axis=-1, keepdims=True) + l2 = jnp.linalg.norm(x - self.x2, axis=-1, keepdims=True) + l3 = jnp.linalg.norm(x - self.x3, axis=-1, keepdims=True) + on12 = isclose(l1 + l2, self.l12) + on23 = isclose(l2 + l3, self.l23) + on31 = isclose(l3 + l1, self.l31) + # Check points on the vertexes + if jnp.any(jnp.count_nonzero(jnp.hstack([on12, on23, on31]), axis=-1) > 1): + raise ValueError( + "{}.boundary_normal do not accept points on the vertexes.".format( + self.__class__.__name__ + ) + ) + return self.n12_normal * on12 + self.n23_normal * on23 + self.n31_normal * on31 + + def random_points(self, n, random="pseudo"): + # There are two methods for triangle point picking. + # Method 1 (used here): + # - https://math.stackexchange.com/questions/18686/uniform-random-point-in-triangle + # Method 2: + # - http://mathworld.wolfram.com/TrianglePointPicking.html + # - https://hbfs.wordpress.com/2010/10/05/random-points-in-a-triangle-generating-random-sequences-ii/ + # - https://stackoverflow.com/questions/19654251/random-point-inside-triangle-inside-java + sqrt_r1 = jnp.sqrt(bst.random.rand(n, 1)) + r2 = bst.random.rand(n, 1) + return ( + (1 - sqrt_r1) * self.x1 + + sqrt_r1 * (1 - r2) * self.x2 + + r2 * sqrt_r1 * self.x3 + ) + + def uniform_boundary_points(self, n): + density = n / self.perimeter + x12 = ( + jnp.linspace(0, 1, num=int(jnp.ceil(density * self.l12)), endpoint=False)[ + :, None + ] + * self.v12 + + self.x1 + ) + x23 = ( + jnp.linspace(0, 1, num=int(jnp.ceil(density * self.l23)), endpoint=False)[ + :, None + ] + * self.v23 + + self.x2 + ) + x31 = ( + jnp.linspace(0, 1, num=int(jnp.ceil(density * self.l31)), endpoint=False)[ + :, None + ] + * self.v31 + + self.x3 + ) + x = jnp.vstack((x12, x23, x31)) + if n != len(x): + print( + "Warning: {} points required, but {} points sampled.".format(n, len(x)) + ) + return x + + def random_boundary_points(self, n, random="pseudo"): + u = jnp.ravel(sample(n + 2, 1, random)) + # Remove the possible points very close to the corners + u = u[jnp.logical_not(isclose(u, self.l12 / self.perimeter))] + u = u[jnp.logical_not(isclose(u, (self.l12 + self.l23) / self.perimeter))] + u = u[:n] + + u *= self.perimeter + x = [] + for l in u: + if l < self.l12: + x.append(l * self.n12 + self.x1) + elif l < self.l12 + self.l23: + x.append((l - self.l12) * self.n23 + self.x2) + else: + x.append((l - self.l12 - self.l23) * self.n31 + self.x3) + return jnp.vstack(x) + + def boundary_constraint_factor( + self, + x, + smoothness: Literal["C0", "C0+", "Cinf"] = "C0+", + where: Union[None, Literal["x1-x2", "x1-x3", "x2-x3"]] = None, + ): + """Compute the hard constraint factor at x for the boundary. + + This function is used for the hard-constraint methods in Physics-Informed Neural Networks (PINNs). + The hard constraint factor satisfies the following properties: + + - The function is zero on the boundary and positive elsewhere. + - The function is at least continuous. + + In the ansatz `boundary_constraint_factor(x) * NN(x) + boundary_condition(x)`, when `x` is on the boundary, + `boundary_constraint_factor(x)` will be zero, making the ansatz be the boundary condition, which in + turn makes the boundary condition a "hard constraint". + + Args: + x: A 2D array of shape (n, dim), where `n` is the number of points and + `dim` is the dimension of the geometry. Note that `x` should be a tensor type + of backend (e.g., `tf.Tensor` or `torch.Tensor`), not a numpy array. + smoothness (string, optional): A string to specify the smoothness of the distance function, + e.g., "C0", "C0+", "Cinf". "C0" is the least smooth, "Cinf" is the most smooth. + Default is "C0+". + + - C0 + The distance function is continuous but may not be non-differentiable. + But the set of non-differentiable points should have measure zero, + which makes the probability of the collocation point falling in this set be zero. + + - C0+ + The distance function is continuous and differentiable almost everywhere. The + non-differentiable points can only appear on boundaries. If the points in `x` are + all inside or outside the geometry, the distance function is smooth. + + - Cinf + The distance function is continuous and differentiable at any order on any + points. This option may result in a polynomial of HIGH order. + + where (string, optional): A string to specify which part of the boundary to compute the distance. + If `None`, compute the distance to the whole boundary. + "x1-x2" indicates the line segment with vertices x1 and x2 (after reordered). Default is `None`. + + Returns: + A tensor of a type determined by the backend, which will have a shape of (n, 1). + Each element in the tensor corresponds to the computed distance value for the respective point in `x`. + """ + + if where not in [None, "x1-x2", "x1-x3", "x2-x3"]: + raise ValueError("where must be one of None, x1-x2, x1-x3, x2-x3") + if smoothness not in ["C0", "C0+", "Cinf"]: + raise ValueError("smoothness must be one of C0, C0+, Cinf") + + if not hasattr(self, "self.x1_tensor"): + self.x1_tensor = jnp.asarray(self.x1) + self.x2_tensor = jnp.asarray(self.x2) + self.x3_tensor = jnp.asarray(self.x3) + + diff_x1_x2 = diff_x1_x3 = diff_x2_x3 = None + if where not in ["x1-x3", "x2-x3"]: + diff_x1_x2 = ( + jnp.linalg.norm(x - self.x1_tensor, axis=-1, keepdims=True) + + jnp.linalg.norm(x - self.x2_tensor, axis=-1, keepdims=True) + - self.l12 + ) + if where not in ["x1-x2", "x2-x3"]: + diff_x1_x3 = ( + jnp.linalg.norm(x - self.x1_tensor, axis=-1, keepdims=True) + + jnp.linalg.norm(x - self.x3_tensor, axis=-1, keepdims=True) + - self.l31 + ) + if where not in ["x1-x2", "x1-x3"]: + diff_x2_x3 = ( + jnp.linalg.norm(x - self.x2_tensor, axis=-1, keepdims=True) + + jnp.linalg.norm(x - self.x3_tensor, axis=-1, keepdims=True) + - self.l23 + ) + + if where is None: + if smoothness == "C0": + return jnp.minimum(jnp.minimum(diff_x1_x2, diff_x1_x3), diff_x2_x3) + return diff_x1_x2 * diff_x1_x3 * diff_x2_x3 + if where == "x1-x2": + return diff_x1_x2 + if where == "x1-x3": + return diff_x1_x3 + return diff_x2_x3 + + +class Polygon(Geometry): + """ + Represents a simple polygon geometry. + + This class creates a polygon object from a set of vertices. The vertices can be provided + in either clockwise or counterclockwise order, and will be reordered to counterclockwise + (right-hand rule) if necessary. + + Args: + vertices (list or array-like): A sequence of (x, y) coordinates defining the vertices + of the polygon. The order can be clockwise or counterclockwise. + + Raises: + ValueError: If the polygon is a triangle (use Triangle class instead) or + if the polygon is a rectangle (use Rectangle class instead). + + Attributes: + vertices (jnp.ndarray): Array of vertex coordinates. + area (float): Signed area of the polygon. + diagonals (jnp.ndarray): Square matrix of distances between vertices. + nvertices (int): Number of vertices in the polygon. + perimeter (float): Perimeter of the polygon. + bbox (jnp.ndarray): Bounding box of the polygon. + segments (jnp.ndarray): Vectors representing the edges of the polygon. + normal (jnp.ndarray): Normal vectors for each edge of the polygon. + + Note: + This class inherits from the Geometry base class and implements several + methods for working with polygons, including checking if points are inside + or on the boundary, and generating random points within or on the boundary + of the polygon. + """ + + def __init__(self, vertices): + self.vertices = jnp.array(vertices, dtype=bst.environ.dftype()) + if len(vertices) == 3: + raise ValueError("The polygon is a triangle. Use Triangle instead.") + if Rectangle.is_valid(self.vertices): + raise ValueError("The polygon is a rectangle. Use Rectangle instead.") + + self.area = polygon_signed_area(self.vertices) + # Clockwise + if self.area < 0: + self.area = -self.area + self.vertices = jnp.flipud(self.vertices) + + self.diagonals = spatial.distance.squareform( + spatial.distance.pdist(self.vertices) + ) + super().__init__( + 2, + (jnp.amin(self.vertices, axis=0), jnp.amax(self.vertices, axis=0)), + jnp.max(self.diagonals), + ) + self.nvertices = len(self.vertices) + self.perimeter = jnp.sum( + jnp.asarray( + [self.diagonals[i, i + 1] for i in range(-1, self.nvertices - 1)] + ) + ) + self.bbox = jnp.array( + [jnp.min(self.vertices, axis=0), jnp.max(self.vertices, axis=0)] + ) + + self.segments = self.vertices[1:] - self.vertices[:-1] + self.segments = jnp.vstack( + (self.vertices[0] - self.vertices[-1], self.segments) + ) + self.normal = clockwise_rotation_90(self.segments.T).T + self.normal = self.normal / jnp.linalg.norm(self.normal, axis=1).reshape(-1, 1) + + def inside(self, x): + def wn_PnPoly(P, V): + """Winding number algorithm. + + https://en.wikipedia.org/wiki/Point_in_polygon + http://geomalgorithms.com/a03-_inclusion.html + + Args: + P: A point. + V: Vertex points of a polygon. + + Returns: + wn: Winding number (=0 only if P is outside polygon). + """ + wn = jnp.zeros(len(P)) # Winding number counter + + # Repeat the first vertex at end + # Loop through all edges of the polygon + for i in range(-1, self.nvertices - 1): # Edge from V[i] to V[i+1] + tmp = jnp.all( + jnp.hstack( + [ + V[i, 1] <= P[:, 1:2], # Start y <= P[1] + V[i + 1, 1] > P[:, 1:2], # An upward crossing + is_left(V[i], V[i + 1], P) > 0, # P left of edge + ] + ), + axis=-1, + ) + wn = wn.at[tmp].add(1) # Have a valid up intersect + tmp = jnp.all( + jnp.hstack( + [ + V[i, 1] > P[:, 1:2], # Start y > P[1] + V[i + 1, 1] <= P[:, 1:2], # A downward crossing + is_left(V[i], V[i + 1], P) < 0, # P right of edge + ] + ), + axis=-1, + ) + wn = wn.at[tmp].add(-1) # Have a valid down intersect + return wn + + return wn_PnPoly(x, self.vertices) != 0 + + def on_boundary(self, x): + _on = jnp.zeros(shape=len(x), dtype=int) + for i in range(-1, self.nvertices - 1): + l1 = jnp.linalg.norm(self.vertices[i] - x, axis=-1) + l2 = jnp.linalg.norm(self.vertices[i + 1] - x, axis=-1) + _on = _on.at[isclose(l1 + l2, self.diagonals[i, i + 1])].add(1) + return _on > 0 + + @vectorize(excluded=[0], signature="(n)->(n)") + def boundary_normal(self, x): + for i in range(self.nvertices): + if is_on_line_segment(self.vertices[i - 1], self.vertices[i], x): + return self.normal[i] + return jnp.array([0, 0]) + + def random_points(self, n, random="pseudo"): + x = jnp.empty((0, 2), dtype=bst.environ.dftype()) + vbbox = self.bbox[1] - self.bbox[0] + while len(x) < n: + x_new = sample(n, 2, sampler="pseudo") * vbbox + self.bbox[0] + x = jnp.vstack((x, x_new[self.inside(x_new)])) + return x[:n] + + def uniform_boundary_points(self, n): + density = n / self.perimeter + x = [] + for i in range(-1, self.nvertices - 1): + x.append( + jnp.linspace( + 0, + 1, + num=int(jnp.ceil(density * self.diagonals[i, i + 1])), + endpoint=False, + )[:, None] + * (self.vertices[i + 1] - self.vertices[i]) + + self.vertices[i] + ) + x = jnp.vstack(x) + if n != len(x): + print( + "Warning: {} points required, but {} points sampled.".format(n, len(x)) + ) + return x + + def random_boundary_points(self, n, random="pseudo"): + u = jnp.ravel(sample(n + self.nvertices, 1, random)) + # Remove the possible points very close to the corners + l = 0 + for i in range(0, self.nvertices - 1): + l += self.diagonals[i, i + 1] + u = u[jnp.logical_not(isclose(u, l / self.perimeter))] + u = u[:n] + u *= self.perimeter + u.sort() + + x = [] + i = -1 + l0 = 0 + l1 = l0 + self.diagonals[i, i + 1] + v = (self.vertices[i + 1] - self.vertices[i]) / self.diagonals[i, i + 1] + for l in u: + if l > l1: + i += 1 + l0, l1 = l1, l1 + self.diagonals[i, i + 1] + v = (self.vertices[i + 1] - self.vertices[i]) / self.diagonals[i, i + 1] + x.append((l - l0) * v + self.vertices[i]) + return jnp.vstack(x) + + +def polygon_signed_area(vertices): + """The (signed) area of a simple polygon. + + If the vertices are in the counterclockwise direction, then the area is positive; if + they are in the clockwise direction, the area is negative. + + Shoelace formula: https://en.wikipedia.org/wiki/Shoelace_formula + """ + x, y = zip(*vertices) + x = jnp.array(list(x) + [x[0]]) + y = jnp.array(list(y) + [y[0]]) + return 0.5 * (jnp.sum(x[:-1] * y[1:]) - jnp.sum(x[1:] * y[:-1])) + + +def clockwise_rotation_90(v): + """Rotate a vector of 90 degrees clockwise about the origin.""" + return jnp.array([v[1], -v[0]]) + + +def is_left(P0, P1, P2): + """Test if a point is Left|On|Right of an infinite line. + + See: the January 2001 Algorithm "Area of 2D and 3D Triangles and Polygons". + + Args: + P0: One point in the line. + P1: One point in the line. + P2: A array of point to be tested. + + Returns: + >0 if P2 left of the line through P0 and P1, =0 if P2 on the line, <0 if P2 + right of the line. + """ + return jnp.cross(P1 - P0, P2 - P0, axis=-1).reshape((-1, 1)) + + +def is_rectangle(vertices): + """Check if the geometry is a rectangle. + + https://stackoverflow.com/questions/2303278/find-if-4-points-on-a-plane-form-a-rectangle/2304031 + + 1. Find the center of mass of corner points: cx=(x1+x2+x3+x4)/4, cy=(y1+y2+y3+y4)/4 + 2. Test if square of distances from center of mass to all 4 corners are equal + """ + if len(vertices) != 4: + return False + + c = jnp.mean(vertices, axis=0) + d = jnp.sum((vertices - c) ** 2, axis=1) + return jnp.allclose(d, jnp.full(4, d[0])) + + +def is_on_line_segment(P0, P1, P2): + """Test if a point is between two other points on a line segment. + + Args: + P0: One point in the line. + P1: One point in the line. + P2: The point to be tested. + + References: + https://stackoverflow.com/questions/328107 + """ + v01 = P1 - P0 + v02 = P2 - P0 + v12 = P2 - P1 + return ( + # check that P2 is almost on the line P0 P1 + isclose(jnp.cross(v01, v02) / jnp.linalg.norm(v01), 0) + # check that projection of P2 to line is between P0 and P1 + and v01 @ v02 >= 0 + and v01 @ v12 <= 0 + ) + # Not between P0 and P1, but close to P0 or P1 + # or isclose(np.linalg.norm(v02), 0) # check whether P2 is close to P0 + # or isclose(np.linalg.norm(v12), 0) # check whether P2 is close to P1 + + +def polar(x): + """Get the polar coordinated for a 2d vector in cartesian coordinates.""" + r = jnp.sqrt(x[:, 0] ** 2 + x[:, 1] ** 2) + theta = jnp.arctan2(x[:, 1], x[:, 0]) + return r, theta diff --git a/deepxde/experimental/geometry/geometry_3d.py b/deepxde/experimental/geometry/geometry_3d.py new file mode 100644 index 000000000..6d7f33954 --- /dev/null +++ b/deepxde/experimental/geometry/geometry_3d.py @@ -0,0 +1,222 @@ +import itertools +from typing import Union, Literal + +import brainstate as bst +import jax.numpy as jnp + +from .geometry_2d import Rectangle +from .geometry_nd import Hypercube, Hypersphere + + +class Cuboid(Hypercube): + """ + A class representing a 3D cuboid, inheriting from Hypercube. + + Args: + xmin: Coordinate of bottom left corner. + xmax: Coordinate of top right corner. + """ + + def __init__(self, xmin, xmax): + """ + Initialize the Cuboid object. + + Args: + xmin: Coordinate of bottom left corner. + xmax: Coordinate of top right corner. + """ + super().__init__(xmin, xmax) + dx = self.xmax - self.xmin + self.area = 2 * jnp.sum(dx * jnp.roll(dx, 2)) + + def random_boundary_points(self, n, random="pseudo"): + """ + Generate random points on the boundary of the cuboid. + + Args: + n (int): The number of points to generate. + random (str, optional): The type of random number generation. Defaults to "pseudo". + + Returns: + jnp.ndarray: An array of shape (n, 3) containing the generated boundary points. + """ + pts = [] + density = n / self.area + rect = Rectangle(self.xmin[:-1], self.xmax[:-1]) + for z in [self.xmin[-1], self.xmax[-1]]: + u = rect.random_points(int(jnp.ceil(density * rect.area)), random=random) + pts.append(jnp.hstack((u, jnp.full((len(u), 1), z)))) + rect = Rectangle(self.xmin[::2], self.xmax[::2]) + for y in [self.xmin[1], self.xmax[1]]: + u = rect.random_points(int(jnp.ceil(density * rect.area)), random=random) + pts.append(jnp.hstack((u[:, 0:1], jnp.full((len(u), 1), y), u[:, 1:]))) + rect = Rectangle(self.xmin[1:], self.xmax[1:]) + for x in [self.xmin[0], self.xmax[0]]: + u = rect.random_points(int(jnp.ceil(density * rect.area)), random=random) + pts.append(jnp.hstack((jnp.full((len(u), 1), x), u))) + pts = jnp.vstack(pts) + if len(pts) > n: + return pts[bst.random.choice(len(pts), size=n, replace=False)] + return pts + + def uniform_boundary_points(self, n): + """ + Generate uniformly distributed points on the boundary of the cuboid. + + Args: + n (int): The target number of points to generate. + + Returns: + jnp.ndarray: An array of shape (m, 3) containing the generated boundary points, + where m may not exactly equal n. + """ + h = (self.area / n) ** 0.5 + nx, ny, nz = jnp.ceil((self.xmax - self.xmin) / h).astype(int) + 1 + x = jnp.linspace(self.xmin[0], self.xmax[0], num=nx) + y = jnp.linspace(self.xmin[1], self.xmax[1], num=ny) + z = jnp.linspace(self.xmin[2], self.xmax[2], num=nz) + + pts = [] + for v in [self.xmin[-1], self.xmax[-1]]: + u = list(itertools.product(x, y)) + pts.append(jnp.hstack((u, jnp.full((len(u), 1), v)))) + if nz > 2: + for v in [self.xmin[1], self.xmax[1]]: + u = jnp.array(list(itertools.product(x, z[1:-1]))) + pts.append(jnp.hstack((u[:, 0:1], jnp.full((len(u), 1), v), u[:, 1:]))) + if ny > 2 and nz > 2: + for v in [self.xmin[0], self.xmax[0]]: + u = list(itertools.product(y[1:-1], z[1:-1])) + pts.append(jnp.hstack((jnp.full((len(u), 1), v), u))) + pts = jnp.vstack(pts) + if n != len(pts): + print( + "Warning: {} points required, but {} points sampled.".format( + n, len(pts) + ) + ) + return pts + + def boundary_constraint_factor( + self, + x, + smoothness: Literal["C0", "C0+", "Cinf"] = "C0+", + where: Union[ + None, Literal["back", "front", "left", "right", "bottom", "top"] + ] = None, + inside: bool = True, + ): + """ + Compute the hard constraint factor at x for the boundary. + + This function is used for the hard-constraint methods in Physics-Informed Neural Networks (PINNs). + The hard constraint factor satisfies the following properties: + + - The function is zero on the boundary and positive elsewhere. + - The function is at least continuous. + + In the ansatz `boundary_constraint_factor(x) * NN(x) + boundary_condition(x)`, when `x` is on the boundary, + `boundary_constraint_factor(x)` will be zero, making the ansatz be the boundary condition, which in + turn makes the boundary condition a "hard constraint". + + Args: + x: A 2D array of shape (n, dim), where `n` is the number of points and + `dim` is the dimension of the geometry. Note that `x` should be a tensor type + of backend (e.g., `tf.Tensor` or `torch.Tensor`), not a numpy array. + smoothness (string, optional): A string to specify the smoothness of the distance function, + e.g., "C0", "C0+", "Cinf". "C0" is the least smooth, "Cinf" is the most smooth. + Default is "C0+". + + - C0 + The distance function is continuous but may not be non-differentiable. + But the set of non-differentiable points should have measure zero, + which makes the probability of the collocation point falling in this set be zero. + + - C0+ + The distance function is continuous and differentiable almost everywhere. The + non-differentiable points can only appear on boundaries. If the points in `x` are + all inside or outside the geometry, the distance function is smooth. + + - Cinf + The distance function is continuous and differentiable at any order on any + points. This option may result in a polynomial of HIGH order. + + where (string, optional): A string to specify which part of the boundary to compute the distance. + "back": x[0] = xmin[0], "front": x[0] = xmax[0], "left": x[1] = xmin[1], + "right": x[1] = xmax[1], "bottom": x[2] = xmin[2], "top": x[2] = xmax[2]. + If `None`, compute the distance to the whole boundary. Default is `None`. + inside (bool, optional): The `x` is either inside or outside the geometry. + The cases where there are both points inside and points + outside the geometry are NOT allowed. NOTE: currently only support `inside=True`. + + Returns: + A tensor of a type determined by the backend, which will have a shape of (n, 1). + Each element in the tensor corresponds to the computed distance value for the respective point in `x`. + """ + if where not in [None, "back", "front", "left", "right", "bottom", "top"]: + raise ValueError( + "where must be one of None, back, front, left, right, bottom, top" + ) + if smoothness not in ["C0", "C0+", "Cinf"]: + raise ValueError("smoothness must be one of C0, C0+, Cinf") + if self.dim != 3: + raise ValueError("self.dim must be 3") + if not inside: + raise ValueError("inside=False is not supported for Cuboid") + + if not hasattr(self, "self.xmin_tensor"): + self.xmin_tensor = jnp.asarray(self.xmin) + self.xmax_tensor = jnp.asarray(self.xmax) + + dist_l = dist_r = None + if where not in ["front", "right", "top"]: + dist_l = jnp.abs( + (x - self.xmin_tensor) / (self.xmax_tensor - self.xmin_tensor) * 2 + ) + if where not in ["back", "left", "bottom"]: + dist_r = jnp.abs( + (x - self.xmax_tensor) / (self.xmax_tensor - self.xmin_tensor) * 2 + ) + + if where == "back": + return dist_l[:, 0:1] + if where == "front": + return dist_r[:, 0:1] + if where == "left": + return dist_l[:, 1:2] + if where == "right": + return dist_r[:, 1:2] + if where == "bottom": + return dist_l[:, 2:] + if where == "top": + return dist_r[:, 2:] + + if smoothness == "C0": + dist_l = jnp.min(dist_l, axis=-1, keepdims=True) + dist_r = jnp.min(dist_r, axis=-1, keepdims=True) + return jnp.minimum(dist_l, dist_r) + dist_l = jnp.prod(dist_l, axis=-1, keepdims=True) + dist_r = jnp.prod(dist_r, axis=-1, keepdims=True) + return dist_l * dist_r + + +class Sphere(Hypersphere): + """ + A class representing a 3D sphere, inheriting from Hypersphere. + + This class provides functionality for creating and manipulating a 3D sphere + in geometric computations and simulations. + + Args: + center (array-like): The coordinates of the center of the sphere. + Should be a sequence of 3 numbers representing x, y, and z coordinates. + radius (float): The radius of the sphere. + Must be a positive number. + + Attributes: + center (array-like): The center coordinates of the sphere. + radius (float): The radius of the sphere. + + Note: + This class inherits additional methods and attributes from the Hypersphere class. + """ diff --git a/deepxde/experimental/geometry/geometry_nd.py b/deepxde/experimental/geometry/geometry_nd.py new file mode 100644 index 000000000..279efdfda --- /dev/null +++ b/deepxde/experimental/geometry/geometry_nd.py @@ -0,0 +1,518 @@ +import itertools +from typing import Literal + +import brainstate as bst +import jax +import jax.numpy as jnp +from scipy import stats +from sklearn import preprocessing + +from deepxde.geometry.sampler import sample +from deepxde.experimental import utils +from .base import GeometryExperimental as Geometry +from ..utils import isclose + + +class Hypercube(Geometry): + """ + Represents a hypercube geometry in N-dimensional space. + + This class defines a hypercube with specified minimum and maximum coordinates + for each dimension. + """ + + def __init__(self, xmin, xmax): + """ + Initialize a Hypercube object. + + Args: + xmin (array-like): Minimum coordinates for each dimension. + xmax (array-like): Maximum coordinates for each dimension. + + Raises: + ValueError: If dimensions of xmin and xmax do not match or if xmin >= xmax. + """ + if len(xmin) != len(xmax): + raise ValueError("Dimensions of xmin and xmax do not match.") + + self.xmin = jnp.array(xmin, dtype=bst.environ.dftype()) + self.xmax = jnp.array(xmax, dtype=bst.environ.dftype()) + if jnp.any(self.xmin >= self.xmax): + raise ValueError("xmin >= xmax") + + self.side_length = self.xmax - self.xmin + super().__init__( + len(xmin), (self.xmin, self.xmax), jnp.linalg.norm(self.side_length) + ) + self.volume = jnp.prod(self.side_length) + + def inside(self, x): + """ + Check if points are inside the hypercube. + + Args: + x (array-like): Points to check. + + Returns: + array-like: Boolean array indicating whether each point is inside the hypercube. + """ + mod = utils.smart_numpy(x) + return mod.logical_and( + mod.all(x >= self.xmin, axis=-1), mod.all(x <= self.xmax, axis=-1) + ) + + def on_boundary(self, x): + """ + Check if points are on the boundary of the hypercube. + + Args: + x (array-like): Points to check. + + Returns: + array-like: Boolean array indicating whether each point is on the boundary. + """ + mod = utils.smart_numpy(x) + if x.ndim == 0: + _on_boundary = mod.logical_or( + mod.isclose(x, self.xmin), mod.isclose(x, self.xmax) + ) + else: + _on_boundary = mod.logical_or( + mod.any(mod.isclose(x, self.xmin), axis=-1), + mod.any(mod.isclose(x, self.xmax), axis=-1), + ) + return mod.logical_and(self.inside(x), _on_boundary) + + def boundary_normal(self, x): + """ + Compute the normal vectors at boundary points. + + Args: + x (array-like): Points on the boundary. + + Returns: + array-like: Normal vectors at the given points. + """ + mod = utils.smart_numpy(x) + _n = -mod.isclose(x, self.xmin).astype(bst.environ.dftype()) + mod.isclose( + x, self.xmax + ) + # For vertices, the normal is averaged for all directions + idx = mod.count_nonzero(_n, axis=-1) > 1 + _n = jax.vmap( + lambda idx_, n_: jax.numpy.where( + idx_, n_ / mod.linalg.norm(n_, keepdims=True), n_ + ) + )(idx, _n) + return mod.asarray(_n) + + def uniform_points(self, n, boundary=True): + """ + Generate uniformly distributed points in the hypercube. + + Args: + n (int): Number of points to generate. + boundary (bool): Whether to include boundary points. + + Returns: + array-like: Uniformly distributed points. + """ + dx = (self.volume / n) ** (1 / self.dim) + xi = [] + for i in range(self.dim): + ni = int(jnp.ceil(self.side_length[i] / dx)) + if boundary: + xi.append( + jnp.linspace( + self.xmin[i], self.xmax[i], num=ni, dtype=bst.environ.dftype() + ) + ) + else: + xi.append( + jnp.linspace( + self.xmin[i], + self.xmax[i], + num=ni + 1, + endpoint=False, + dtype=bst.environ.dftype(), + )[1:] + ) + x = jnp.array(list(itertools.product(*xi))) + if n != len(x): + print( + "Warning: {} points required, but {} points sampled.".format(n, len(x)) + ) + return x + + def uniform_boundary_points(self, n): + """ + Generate uniformly distributed points on the boundary of the hypercube. + + Args: + n (int): Number of points to generate. + + Returns: + array-like: Uniformly distributed boundary points. + """ + points_per_face = max(1, n // (2 * self.dim)) + points = [] + for d in range(self.dim): + for boundary in [self.xmin[d], self.xmax[d]]: + xi = [] + for i in range(self.dim): + if i == d: + xi.append(jnp.array([boundary], dtype=bst.environ.dftype())) + else: + ni = int(jnp.ceil(points_per_face ** (1 / (self.dim - 1)))) + xi.append( + jnp.linspace( + self.xmin[i], + self.xmax[i], + num=ni + 1, + endpoint=False, + dtype=bst.environ.dftype(), + )[1:] + ) + face_points = jnp.array(list(itertools.product(*xi))) + points.append(face_points) + points = jnp.vstack(points) + if n != len(points): + print( + "Warning: {} points required, but {} points sampled.".format( + n, len(points) + ) + ) + return points + + def random_points(self, n, random="pseudo"): + """ + Generate random points inside the hypercube. + + Args: + n (int): Number of points to generate. + random (str): Type of random number generation ("pseudo" or other). + + Returns: + array-like: Randomly generated points. + """ + x = sample(n, self.dim, random) + return (self.xmax - self.xmin) * x + self.xmin + + def random_boundary_points(self, n, random="pseudo"): + """ + Generate random points on the boundary of the hypercube. + + Args: + n (int): Number of points to generate. + random (str): Type of random number generation ("pseudo" or other). + + Returns: + array-like: Randomly generated boundary points. + """ + x = sample(n, self.dim, random) + # Randomly pick a dimension + rand_dim = bst.random.randint(self.dim, size=n) + # Replace value of the randomly picked dimension with the nearest boundary value (0 or 1) + x[jnp.arange(n), rand_dim] = jnp.round(x[jnp.arange(n), rand_dim]) + return (self.xmax - self.xmin) * x + self.xmin + + def periodic_point(self, x, component): + """ + Map points to their periodic counterparts on the opposite face of the hypercube. + + Args: + x (array-like): Points to map. + component (int): The dimension along which to apply periodicity. + + Returns: + array-like: Mapped periodic points. + """ + y = jnp.copy(x) + _on_xmin = isclose(y[:, component], self.xmin[component]) + _on_xmax = isclose(y[:, component], self.xmax[component]) + y[:, component][_on_xmin] = self.xmax[component] + y[:, component][_on_xmax] = self.xmin[component] + return y + + def boundary_constraint_factor( + self, + x, + smoothness: Literal["C0", "C0+", "Cinf"] = "C0", + where: None = None, + inside: bool = True, + ): + """ + Compute the hard constraint factor at x for the boundary. + + This function is used for the hard-constraint methods in Physics-Informed Neural Networks (PINNs). + The hard constraint factor satisfies the following properties: + + - The function is zero on the boundary and positive elsewhere. + - The function is at least continuous. + + In the ansatz `boundary_constraint_factor(x) * NN(x) + boundary_condition(x)`, when `x` is on the boundary, + `boundary_constraint_factor(x)` will be zero, making the ansatz be the boundary condition, which in + turn makes the boundary condition a "hard constraint". + + Args: + x: A 2D array of shape (n, dim), where `n` is the number of points and + `dim` is the dimension of the geometry. Note that `x` should be a tensor type + of backend (e.g., `tf.Tensor` or `torch.Tensor`), not a numpy array. + smoothness (string, optional): A string to specify the smoothness of the distance function, + e.g., "C0", "C0+", "Cinf". "C0" is the least smooth, "Cinf" is the most smooth. + Default is "C0". + + - C0 + The distance function is continuous but may not be non-differentiable. + But the set of non-differentiable points should have measure zero, + which makes the probability of the collocation point falling in this set be zero. + + - C0+ + The distance function is continuous and differentiable almost everywhere. The + non-differentiable points can only appear on boundaries. If the points in `x` are + all inside or outside the geometry, the distance function is smooth. + + - Cinf + The distance function is continuous and differentiable at any order on any + points. This option may result in a polynomial of HIGH order. + + - WARNING + In current implementation, + numerical underflow may happen for high dimensionalities + when `smoothness="C0+"` or `smoothness="Cinf"`. + + where (string, optional): This option is currently not supported for Hypercube. + inside (bool, optional): The `x` is either inside or outside the geometry. + The cases where there are both points inside and points + outside the geometry are NOT allowed. NOTE: currently only support `inside=True`. + + Returns: + A tensor of a type determined by the backend, which will have a shape of (n, 1). + Each element in the tensor corresponds to the computed distance value for the respective point in `x`. + """ + if where is not None: + raise ValueError("where is currently not supported for Hypercube") + if smoothness not in ["C0", "C0+", "Cinf"]: + raise ValueError("smoothness must be one of C0, C0+, Cinf") + if not inside: + raise ValueError("inside=False is not supported for Hypercube") + + if not hasattr(self, "self.xmin_tensor"): + self.xmin_tensor = jnp.asarray(self.xmin) + self.xmax_tensor = jnp.asarray(self.xmax) + + dist_l = jnp.abs( + (x - self.xmin_tensor) / (self.xmax_tensor - self.xmin_tensor) * 2 + ) + dist_r = jnp.abs( + (x - self.xmax_tensor) / (self.xmax_tensor - self.xmin_tensor) * 2 + ) + if smoothness == "C0": + dist_l = jnp.min(dist_l, axis=-1, keepdims=True) + dist_r = jnp.min(dist_r, axis=-1, keepdims=True) + return jnp.minimum(dist_l, dist_r) + # TODO: fix potential numerical underflow + dist_l = jnp.prod(dist_l, axis=-1, keepdims=True) + dist_r = jnp.prod(dist_r, dim=-1, keepdims=True) + return dist_l * dist_r + + +class Hypersphere(Geometry): + """ + Represents a hypersphere geometry in N-dimensional space. + + This class defines a hypersphere with a specified center and radius. + """ + + def __init__(self, center, radius): + """ + Initialize a Hypersphere object. + + Args: + center (array-like): Coordinates of the center of the hypersphere. + radius (float): Radius of the hypersphere. + """ + self.center = jnp.array(center, dtype=bst.environ.dftype()) + self.radius = radius + super().__init__( + len(center), (self.center - radius, self.center + radius), 2 * radius + ) + + self._r2 = radius**2 + + def inside(self, x): + """ + Check if points are inside the hypersphere. + + Args: + x (array-like): Points to check. + + Returns: + array-like: Boolean array indicating whether each point is inside the hypersphere. + """ + return jnp.linalg.norm(x - self.center, axis=-1) <= self.radius + + def on_boundary(self, x): + """ + Check if points are on the boundary of the hypersphere. + + Args: + x (array-like): Points to check. + + Returns: + array-like: Boolean array indicating whether each point is on the boundary. + """ + return isclose(jnp.linalg.norm(x - self.center, axis=-1), self.radius) + + def distance2boundary_unitdirn(self, x, dirn): + """ + Compute the distance from points to the boundary along a unit direction. + + Args: + x (array-like): Points to compute distance from. + dirn (array-like): Unit direction vector. + + Returns: + array-like: Distances from points to the boundary along the given direction. + """ + xc = x - self.center + ad = jnp.dot(xc, dirn) + return (-ad + (ad**2 - jnp.sum(xc * xc, axis=-1) + self._r2) ** 0.5).astype( + bst.environ.dftype() + ) + + def distance2boundary(self, x, dirn): + """ + Compute the distance from points to the boundary along a given direction. + + Args: + x (array-like): Points to compute distance from. + dirn (array-like): Direction vector (will be normalized). + + Returns: + array-like: Distances from points to the boundary along the given direction. + """ + return self.distance2boundary_unitdirn(x, dirn / jnp.linalg.norm(dirn)) + + def mindist2boundary(self, x): + """ + Compute the minimum distance from points to the boundary. + + Args: + x (array-like): Points to compute distance from. + + Returns: + array-like: Minimum distances from points to the boundary. + """ + return jnp.amin(self.radius - jnp.linalg.norm(x - self.center, axis=-1)) + + def boundary_constraint_factor( + self, x, smoothness: Literal["C0", "C0+", "Cinf"] = "C0+" + ): + """ + Compute the boundary constraint factor for given points. + + Args: + x (array-like): Points to compute the factor for. + smoothness (str): Smoothness of the constraint factor. Options are "C0", "C0+", or "Cinf". + + Returns: + array-like: Boundary constraint factors for the given points. + """ + if smoothness not in ["C0", "C0+", "Cinf"]: + raise ValueError("smoothness must be one of C0, C0+, Cinf") + + if not hasattr(self, "self.center_tensor"): + self.center_tensor = jnp.asarray(self.center) + self.radius_tensor = jnp.asarray(self.radius) + + dist = ( + jnp.linalg.norm(x - self.center_tensor, axis=-1, keepdims=True) + - self.radius + ) + if smoothness == "Cinf": + dist = jnp.square(dist) + else: + dist = jnp.abs(dist) + return dist + + def boundary_normal(self, x): + """ + Compute the normal vectors at boundary points. + + Args: + x (array-like): Points on the boundary. + + Returns: + array-like: Normal vectors at the given points. + """ + _n = x - self.center + l = jnp.linalg.norm(_n, axis=-1, keepdims=True) + _n = _n / l * isclose(l, self.radius) + return _n + + def random_points(self, n, random="pseudo"): + """ + Generate random points inside the hypersphere. + + Args: + n (int): Number of points to generate. + random (str): Type of random number generation ("pseudo" or other). + + Returns: + array-like: Randomly generated points. + """ + if random == "pseudo": + U = bst.random.rand(n, 1).astype(bst.environ.dftype()) + X = bst.random.normal(size=(n, self.dim)).astype(bst.environ.dftype()) + else: + rng = sample(n, self.dim + 1, random) + U, X = rng[:, 0:1], rng[:, 1:] + X = stats.norm.ppf(X).astype(bst.environ.dftype()) + X = preprocessing.normalize(X) + X = U ** (1 / self.dim) * X + return self.radius * X + self.center + + def random_boundary_points(self, n, random="pseudo"): + """ + Generate random points on the boundary of the hypersphere. + + Args: + n (int): Number of points to generate. + random (str): Type of random number generation ("pseudo" or other). + + Returns: + array-like: Randomly generated boundary points. + """ + if random == "pseudo": + X = bst.random.normal(size=(n, self.dim)).astype(bst.environ.dftype()) + else: + U = sample(n, self.dim, random) + X = stats.norm.ppf(U).astype(bst.environ.dftype()) + X = preprocessing.normalize(X) + return self.radius * X + self.center + + def background_points(self, x, dirn, dist2npt, shift): + """ + Generate background points along a direction from given points. + + Args: + x (array-like): Starting points. + dirn (array-like): Direction vector. + dist2npt (callable): Function to determine number of points based on distance. + shift (float): Shift factor for point generation. + + Returns: + array-like: Generated background points. + """ + dirn = dirn / jnp.linalg.norm(dirn) + dx = self.distance2boundary_unitdirn(x, -dirn) + n = max(dist2npt(dx), 1) + h = dx / n + pts = ( + x + - jnp.arange(-shift, n - shift + 1, dtype=bst.environ.dftype())[:, None] + * h + * dirn + ) + return pts diff --git a/deepxde/experimental/geometry/pointcloud.py b/deepxde/experimental/geometry/pointcloud.py new file mode 100644 index 000000000..836c90f6f --- /dev/null +++ b/deepxde/experimental/geometry/pointcloud.py @@ -0,0 +1,150 @@ +import brainstate as bst +import numpy as np + +from deepxde.data.sampler import BatchSampler +from .base import GeometryExperimental as Geometry +from ..utils import isclose + + +class PointCloud(Geometry): + """A geometry represented by a point cloud, i.e., a set of points in space. + + Args: + points: A 2-D NumPy array. If `boundary_points` is not provided, `points` can + include points both inside the geometry or on the boundary; if `boundary_points` + is provided, `points` includes only points inside the geometry. + boundary_points: A 2-D NumPy array representing points on the boundary. + boundary_normals: A 2-D NumPy array representing normal vectors at boundary points. + """ + + def __init__(self, points, boundary_points=None, boundary_normals=None): + self.points = np.asarray(points, dtype=bst.environ.dftype()) + self.num_points = len(points) + self.boundary_points = None + self.boundary_normals = None + all_points = self.points + if boundary_points is not None: + self.boundary_points = np.asarray( + boundary_points, dtype=bst.environ.dftype() + ) + self.num_boundary_points = len(boundary_points) + all_points = np.vstack((self.points, self.boundary_points)) + self.boundary_sampler = BatchSampler(self.num_boundary_points, shuffle=True) + if boundary_normals is not None: + if len(boundary_normals) != len(boundary_points): + raise ValueError( + "the shape of boundary_normals should be the same as boundary_points" + ) + self.boundary_normals = np.asarray( + boundary_normals, dtype=bst.environ.dftype() + ) + super().__init__( + len(points[0]), + (np.amin(all_points, axis=0), np.amax(all_points, axis=0)), + np.inf, + ) + self.sampler = BatchSampler(self.num_points, shuffle=True) + + def inside(self, x): + """ + Check if points are inside the geometry. + + Args: + x (numpy.ndarray): A 2-D array of points to check. + + Returns: + numpy.ndarray: A boolean array indicating whether each point is inside the geometry. + """ + return ( + isclose((x[:, None, :] - self.points[None, :, :]), 0) + .all(axis=2) + .any(axis=1) + ) + + def on_boundary(self, x): + """ + Check if points are on the boundary of the geometry. + + Args: + x (numpy.ndarray): A 2-D array of points to check. + + Returns: + numpy.ndarray: A boolean array indicating whether each point is on the boundary. + + Raises: + ValueError: If boundary_points is not defined. + """ + if self.boundary_points is None: + raise ValueError("boundary_points must be defined to test on_boundary") + return ( + isclose( + (x[:, None, :] - self.boundary_points[None, :, :]), + 0, + ) + .all(axis=2) + .any(axis=1) + ) + + def boundary_normal(self, x): + """ + Get the normal vectors for points on the boundary. + + Args: + x (numpy.ndarray): A 2-D array of points on the boundary. + + Returns: + numpy.ndarray: A 2-D array of normal vectors corresponding to the input points. + + Raises: + ValueError: If boundary_normals is not defined. + """ + if self.boundary_normals is None: + raise ValueError("boundary_normals must be defined for boundary_normal") + boundary_point_matches = isclose( + (self.boundary_points[:, None, :] - x[None, :, :]), 0 + ).all(axis=2) + normals_idx = np.where(boundary_point_matches)[0] + return self.boundary_normals[normals_idx, :] + + def random_points(self, n, random="pseudo"): + """ + Generate random points within the geometry. + + Args: + n (int): Number of random points to generate. + random (str, optional): Type of random number generation. Defaults to "pseudo". + + Returns: + numpy.ndarray: A 2-D array of randomly generated points. + """ + if n <= self.num_points: + indices = self.sampler.get_next(n) + return self.points[indices] + + x = np.tile(self.points, (n // self.num_points, 1)) + indices = self.sampler.get_next(n % self.num_points) + return np.vstack((x, self.points[indices])) + + def random_boundary_points(self, n, random="pseudo"): + """ + Generate random points on the boundary of the geometry. + + Args: + n (int): Number of random boundary points to generate. + random (str, optional): Type of random number generation. Defaults to "pseudo". + + Returns: + numpy.ndarray: A 2-D array of randomly generated boundary points. + + Raises: + ValueError: If boundary_points is not defined. + """ + if self.boundary_points is None: + raise ValueError("boundary_points must be defined to test on_boundary") + if n <= self.num_boundary_points: + indices = self.boundary_sampler.get_next(n) + return self.boundary_points[indices] + + x = np.tile(self.boundary_points, (n // self.num_boundary_points, 1)) + indices = self.boundary_sampler.get_next(n % self.num_boundary_points) + return np.vstack((x, self.boundary_points[indices])) diff --git a/deepxde/experimental/geometry/timedomain.py b/deepxde/experimental/geometry/timedomain.py new file mode 100644 index 000000000..99b52635c --- /dev/null +++ b/deepxde/experimental/geometry/timedomain.py @@ -0,0 +1,292 @@ +import itertools + +import brainstate as bst +import jax.numpy as jnp + +from .base import GeometryExperimental +from .geometry_1d import Interval +from .geometry_2d import Rectangle +from .geometry_3d import Cuboid +from .geometry_nd import Hypercube +from ..utils import isclose + + +class TimeDomain(Interval): + """ + Represents a time domain interval. + + This class extends the Interval class to specifically handle time domains. + It provides functionality to check if a given time point is at the initial time. + + Attributes: + t0 (jnp.ndarray): The start time of the domain. + t1 (jnp.ndarray): The end time of the domain. + """ + + def __init__(self, t0, t1): + """ + Initialize the TimeDomain. + + Parameters: + t0 (float or jnp.ndarray): The start time of the domain. + t1 (float or jnp.ndarray): The end time of the domain. + """ + super().__init__(t0, t1) + self.t0 = jnp.asarray(t0, dtype=bst.environ.dftype()) + self.t1 = jnp.asarray(t1, dtype=bst.environ.dftype()) + + def on_initial(self, t): + """ + Check if the given time point is at the initial time (t0). + + Parameters: + t (jnp.ndarray): The time point(s) to check. + + Returns: + jnp.ndarray: A boolean array indicating whether each time point is at the initial time. + """ + return isclose(t, self.t0).flatten() + + +class GeometryXTime(GeometryExperimental): + """ + Represents a geometry combined with a time domain for spatio-temporal problems. + + This class extends GeometryExperimental to handle both spatial and temporal dimensions. + """ + + def __init__(self, geometry, timedomain): + """ + Initialize the GeometryXTime object. + + Parameters: + geometry (GeometryExperimental): The spatial geometry. + timedomain (TimeDomain): The time domain. + """ + self.geometry = geometry + self.timedomain = timedomain + super().__init__( + geometry.dim + timedomain.dim, + geometry.bbox + timedomain.bbox, + min(geometry.diam, timedomain.diam), + ) + + def inside(self, x): + """ + Check if points are inside the spatio-temporal domain. + + Parameters: + x (jnp.ndarray): Array of points to check. + + Returns: + jnp.ndarray: Boolean array indicating whether each point is inside the domain. + """ + return jnp.logical_and( + self.geometry.inside(x[:, :-1]), self.timedomain.inside(x[:, -1:]) + ) + + def on_boundary(self, x): + """ + Check if points are on the spatial boundary of the domain. + + Parameters: + x (jnp.ndarray): Array of points to check. + + Returns: + jnp.ndarray: Boolean array indicating whether each point is on the boundary. + """ + return self.geometry.on_boundary(x[:, :-1]) + + def on_initial(self, x): + """ + Check if points are at the initial time of the domain. + + Parameters: + x (jnp.ndarray): Array of points to check. + + Returns: + jnp.ndarray: Boolean array indicating whether each point is at the initial time. + """ + return self.timedomain.on_initial(x[:, -1:]) + + def boundary_normal(self, x): + """ + Compute the boundary normal vectors for given points. + + Parameters: + x (jnp.ndarray): Array of points on the boundary. + + Returns: + jnp.ndarray: Array of boundary normal vectors. + """ + _n = self.geometry.boundary_normal(x[:, :-1]) + return jnp.hstack([_n, jnp.zeros((len(_n), 1))]) + + def uniform_points(self, n, boundary=True): + """ + Generate uniform points in the spatio-temporal domain. + + Parameters: + n (int): Number of points to generate. + boundary (bool): Whether to include boundary points. + + Returns: + jnp.ndarray: Array of uniformly distributed points. + """ + nx = int( + jnp.ceil( + ( + n + * jnp.prod(self.geometry.bbox[1] - self.geometry.bbox[0]) + / self.timedomain.diam + ) + ** 0.5 + ) + ) + nt = int(jnp.ceil(n / nx)) + x = self.geometry.uniform_points(nx, boundary=boundary) + nx = len(x) + if boundary: + t = self.timedomain.uniform_points(nt, boundary=True) + else: + t = jnp.linspace( + self.timedomain.t1, + self.timedomain.t0, + num=nt, + endpoint=False, + dtype=bst.environ.dftype(), + )[:, None] + xt = [] + for ti in t: + xt.append(jnp.hstack((x, jnp.full([nx, 1], ti[0])))) + xt = jnp.vstack(xt) + if n != len(xt): + print( + "Warning: {} points required, but {} points sampled.".format(n, len(xt)) + ) + return xt + + def random_points(self, n, random="pseudo"): + """ + Generate random points in the spatio-temporal domain. + + Parameters: + n (int): Number of points to generate. + random (str): Type of random number generation ("pseudo" or "sobol"). + """ + if isinstance(self.geometry, (Cuboid, Hypercube)): + geom = Hypercube( + jnp.append(self.geometry.xmin, self.timedomain.t0), + jnp.append(self.geometry.xmax, self.timedomain.t1), + ) + return geom.random_points(n, random=random) + + x = self.geometry.random_points(n, random=random) + t = self.timedomain.random_points(n, random=random) + t = bst.random.permutation(t) + return jnp.hstack((x, t)) + + def uniform_boundary_points(self, n): + """ + Generate uniform points on the boundary of the spatio-temporal domain. + + Parameters: + n (int): Number of boundary points to generate. + + Returns: + jnp.ndarray: Array of uniformly distributed boundary points. + """ + if self.geometry.dim == 1: + nx = 2 + else: + s = 2 * sum( + map( + lambda l: l[0] * l[1], + itertools.combinations( + self.geometry.bbox[1] - self.geometry.bbox[0], 2 + ), + ) + ) + nx = int((n * s / self.timedomain.diam) ** 0.5) + nt = int(jnp.ceil(n / nx)) + x = self.geometry.uniform_boundary_points(nx) + nx = len(x) + t = jnp.linspace( + self.timedomain.t1, + self.timedomain.t0, + num=nt, + endpoint=False, + dtype=bst.environ.dftype(), + ) + xt = [] + for ti in t: + xt.append(jnp.hstack((x, jnp.full([nx, 1], ti)))) + xt = jnp.vstack(xt) + if n != len(xt): + print( + "Warning: {} points required, but {} points sampled.".format(n, len(xt)) + ) + return xt + + def random_boundary_points(self, n, random="pseudo"): + """ + Generate random points on the boundary of the spatio-temporal domain. + + Parameters: + n (int): Number of boundary points to generate. + random (str): Type of random number generation ("pseudo" or "sobol"). + + Returns: + jnp.ndarray: Array of randomly distributed boundary points. + """ + x = self.geometry.random_boundary_points(n, random=random) + t = self.timedomain.random_points(n, random=random) + t = bst.random.permutation(t) + return jnp.hstack((x, t)) + + def uniform_initial_points(self, n): + """ + Generate uniform points at the initial time of the spatio-temporal domain. + + Parameters: + n (int): Number of initial points to generate. + + Returns: + jnp.ndarray: Array of uniformly distributed initial points. + """ + x = self.geometry.uniform_points(n, True) + t = self.timedomain.t0 + if n != len(x): + print( + "Warning: {} points required, but {} points sampled.".format(n, len(x)) + ) + return jnp.hstack((x, jnp.full([len(x), 1], t, dtype=bst.environ.dftype()))) + + def random_initial_points(self, n, random="pseudo"): + """ + Generate random points at the initial time of the spatio-temporal domain. + + Parameters: + n (int): Number of initial points to generate. + random (str): Type of random number generation ("pseudo" or "sobol"). + + Returns: + jnp.ndarray: Array of randomly distributed initial points. + """ + x = self.geometry.random_points(n, random=random) + t = self.timedomain.t0 + return jnp.hstack((x, jnp.full([n, 1], t, dtype=bst.environ.dftype()))) + + def periodic_point(self, x, component): + """ + Map points to their periodic counterparts in the spatial domain. + + Parameters: + x (jnp.ndarray): Array of points to map. + component (int): The spatial component for which to apply periodicity. + + Returns: + jnp.ndarray: Array of mapped periodic points. + """ + xp = self.geometry.periodic_point(x[:, :-1], component) + return jnp.hstack([xp, x[:, -1:]]) diff --git a/deepxde/experimental/grad.py b/deepxde/experimental/grad.py new file mode 100644 index 000000000..0a0299885 --- /dev/null +++ b/deepxde/experimental/grad.py @@ -0,0 +1,482 @@ +from __future__ import annotations + +from functools import wraps +from typing import Dict, Callable, Sequence, Union, Optional, Tuple, Any, Iterator + +import brainstate as bst +import brainunit as u + +TransformFn = Callable + +__all__ = [ + "jacobian", + "hessian", + "gradient", +] + + +class GradientTransform(bst.util.PrettyRepr): + """ + A class for transforming gradient computations. + + This class wraps a target function and applies a gradient transformation to it. + It handles auxiliary data and state management during the transformation process. + + Attributes: + target (Callable): The target function to be transformed. + _transform (Callable): The transformed function. + _return_value (bool): Flag to determine if the original function value should be returned. + _has_aux (bool): Flag to indicate if the target function returns auxiliary data. + _states_to_be_written (Tuple[bst.State, ...]): States that need to be updated after computation. + """ + + def __init__( + self, + target: Callable, + transform: TransformFn, + return_value: bool = False, + has_aux: bool = False, + transform_params: Optional[Dict[str, Any]] = None, + ): + """ + Initialize the GradientTransform. + + Args: + target (Callable): The target function to be transformed. + transform (TransformFn): The transformation function to be applied. + return_value (bool, optional): If True, return the original function value along with the gradient. Defaults to False. + has_aux (bool, optional): If True, the target function returns auxiliary data. Defaults to False. + transform_params (Optional[Dict[str, Any]], optional): Additional parameters for the transformation. Defaults to None. + """ + self._return_value = return_value + self._has_aux = has_aux + + # target + self.target = target + + # transform + self._states_to_be_written: Tuple[bst.State, ...] = None + _grad_setting = dict() if transform_params is None else transform_params + if self._has_aux: + self._transform = transform( + self._fun_with_aux, has_aux=True, **_grad_setting + ) + else: + self._transform = transform( + self._fun_without_aux, has_aux=True, **_grad_setting + ) + + def __pretty_repr__( + self, + ) -> Iterator[Union[bst.util.PrettyType, bst.util.PrettyAttr]]: + """ + Generate a pretty representation of the GradientTransform instance. + + Returns: + Iterator[Union[bst.util.PrettyType, bst.util.PrettyAttr]]: An iterator of pretty-formatted attributes. + """ + yield bst.util.PrettyType(self.__class__.__name__) + yield bst.util.PrettyAttr("target", self.target) + yield bst.util.PrettyAttr("return_value", self._return_value) + yield bst.util.PrettyAttr("has_aux", self._has_aux) + yield bst.util.PrettyAttr("transform", self._transform) + + def _call_target(self, *args, **kwargs): + """ + Call the target function and collect states to be written. + + Args: + *args: Positional arguments for the target function. + **kwargs: Keyword arguments for the target function. + + Returns: + Any: The output of the target function. + """ + if self._states_to_be_written is None: + with bst.StateTraceStack() as stack: + output = self.target(*args, **kwargs) + self._states_to_be_written = [st for st in stack.get_write_states()] + else: + output = self.target(*args, **kwargs) + return output + + def _fun_with_aux(self, *args, **kwargs): + """ + Wrapper for target function when it returns auxiliary data. + + Args: + *args: Positional arguments for the target function. + **kwargs: Keyword arguments for the target function. + + Returns: + Tuple: A tuple containing the main output and auxiliary data. + """ + outs = self._call_target(*args, **kwargs) + assert ( + self._states_to_be_written is not None + ), "The states to be written should be collected." + return outs[0], (outs, [v.value for v in self._states_to_be_written]) + + def _fun_without_aux(self, *args, **kwargs): + """ + Wrapper for target function when it doesn't return auxiliary data. + + Args: + *args: Positional arguments for the target function. + **kwargs: Keyword arguments for the target function. + + Returns: + Tuple: A tuple containing the output and related data. + """ + out = self._call_target(*args, **kwargs) + assert ( + self._states_to_be_written is not None + ), "The states to be written should be collected." + return out, (out, [v.value for v in self._states_to_be_written]) + + def _return(self, rets): + """ + Process and return the results of the transformation. + + Args: + rets: The results from the transformation. + + Returns: + Tuple: Processed results based on the configuration of return_value and has_aux. + """ + grads, (outputs, new_dyn_vals) = rets + for i, val in enumerate(new_dyn_vals): + self._states_to_be_written[i].value = val + + if self._return_value: + if self._has_aux: + return grads, outputs[0], outputs[1] + else: + return grads, outputs + else: + if self._has_aux: + return grads, outputs[1] + else: + return grads + + def __call__(self, *args, **kwargs): + """ + Call the transformed function and process its results. + + Args: + *args: Positional arguments for the transformed function. + **kwargs: Keyword arguments for the transformed function. + + Returns: + Any: The processed results of the transformation. + """ + rets = self._transform(*args, **kwargs) + return self._return(rets) + + +def _raw_jacrev( + fun: Callable, + has_aux: bool = False, + y: str | Sequence[str] | None = None, + x: str | Sequence[str] | None = None, +) -> Callable: + # process only for y + if isinstance(y, str): + y = [y] + if y is not None: + fun = _format_y(fun, y, has_aux=has_aux) + + # process only for x + if isinstance(x, str): + x = [x] + + def transform(inputs): + if x is not None: + fun2, inputs = _format_x(fun, x, inputs) + return u.autograd.jacrev(fun2, has_aux=has_aux)(inputs) + else: + return u.autograd.jacrev(fun, has_aux=has_aux)(inputs) + + return transform + + +def _raw_jacfwd( + fun: Callable, + has_aux: bool = False, + y: str | Sequence[str] | None = None, + x: str | Sequence[str] | None = None, +) -> Callable: + # process only for y + if isinstance(y, str): + y = [y] + if y is not None: + fun = _format_y(fun, y, has_aux=has_aux) + + # process only for x + if isinstance(x, str): + x = [x] + + def transform(inputs): + if x is not None: + fun2, inputs = _format_x(fun, x, inputs) + return u.autograd.jacfwd(fun2, has_aux=has_aux)(inputs) + else: + return u.autograd.jacfwd(fun, has_aux=has_aux)(inputs) + + return transform + + +def _raw_hessian( + fun: Callable, + has_aux: bool = False, + y: str | Sequence[str] | None = None, + xi: str | Sequence[str] | None = None, + xj: str | Sequence[str] | None = None, +) -> Callable: + r""" + Physical unit-aware version of `jax.hessian `_, + computing Hessian of ``fun`` as a dense array. + + H[y][xi][xj] = d^2y / dxi dxj + + Args: + fun: Function whose Hessian is to be computed. Its arguments at positions + specified by ``argnums`` should be arrays, scalars, or standard Python + containers thereof. It should return arrays, scalars, or standard Python + containers thereof. + has_aux: Optional, bool. Indicates whether ``fun`` returns a pair where the + first element is considered the output of the mathematical function to be + differentiated and the second element is auxiliary data. Default False. + + Returns: + A function with the same arguments as ``fun``, that evaluates the Hessian of + ``fun``. + """ + + inner = _raw_jacrev(fun, has_aux=has_aux, y=y, x=xi) + + # process only for xj + if isinstance(xj, str): + xj = [xj] + + def transform(inputs): + if xj is not None: + fun2, inputs = _format_x(inner, xj, inputs) + return u.autograd.jacfwd(fun2, has_aux=has_aux)(inputs) + else: + return u.autograd.jacfwd(inner, has_aux=has_aux)(inputs) + + return transform + + +def _format_x(fn, x_keys, xs): + assert isinstance(xs, dict), "xs must be a dictionary." + assert isinstance(x_keys, (tuple, list)), "x must be a tuple or list." + assert all( + isinstance(key, str) for key in x_keys + ), "x_keys must be a tuple or list of strings." + others = {key: xs[key] for key in xs if key not in x_keys} + xs = {key: xs[key] for key in x_keys} + + @wraps(fn) + def fn_new(inputs): + return fn({**inputs, **others}) + + return fn_new, xs + + +def _format_y(fn, y, has_aux: bool): + assert isinstance(y, (tuple, list)), "y must be a tuple or list." + assert all( + isinstance(key, str) for key in y + ), "y must be a tuple or list of strings." + + @wraps(fn) + def fn_new(inputs): + if has_aux: + outs, _aux = fn(inputs) + return {key: outs[key] for key in y}, _aux + else: + outs = fn(inputs) + return {key: outs[key] for key in y} + + return fn_new + + +def jacobian( + fn: Callable, + xs: Dict, + y: str | Sequence[str] | None = None, + x: str | Sequence[str] | None = None, + mode: str = "backward", + vmap: bool = True, +): + """ + Compute the Jacobian matrix of a function. + + This function calculates the Jacobian matrix J as J[i, j] = dy_i / dx_j, + where i = 0, ..., dim_y - 1 and j = 0, ..., dim_x - 1. + + Args: + fn (Callable): The function to compute the Jacobian for. + xs (Dict): A dictionary containing the input values for the function. + y (str | Sequence[str] | None, optional): Specifies the output variable(s) for which + to compute the Jacobian. If None, computes for all outputs. Defaults to None. + x (str | Sequence[str] | None, optional): Specifies the input variable(s) with respect + to which the Jacobian is computed. If None, computes for all inputs. Defaults to None. + mode (str, optional): The mode of gradient computation. Either 'backward' or 'forward'. + Defaults to 'backward'. + vmap (bool, optional): Whether to use vectorized mapping. Defaults to True. + + Returns: + The Jacobian matrix. Depending on the inputs, it can be: + - The full Jacobian matrix if both x and y are None or specify all variables. + - A row vector J[i, :] if y specifies a single output and x is None. + - A column vector J[:, j] if x specifies a single input and y is None. + - A scalar J[i, j] if both x and y specify single variables. + + Raises: + ValueError: If an invalid mode is specified. + + Note: + The function uses automatic differentiation techniques to compute the Jacobian. + The 'backward' mode is generally more efficient for functions with more outputs than inputs, + while 'forward' mode is more efficient for functions with more inputs than outputs. + """ + # assert isinstance(xs, dict), 'xs must be a dictionary.' + assert isinstance(mode, str), "mode must be a string." + assert mode in ["backward", "forward"], "mode must be either backward or forward." + + # process only for x + if isinstance(x, str): + x = [x] + + # process only for y + if isinstance(y, str): + y = [y] + + # compute the Jacobian + if mode == "backward": + transform = GradientTransform( + fn, _raw_jacrev, transform_params={"y": y, "x": x} + ) + elif mode == "forward": + transform = GradientTransform( + fn, _raw_jacfwd, transform_params={"y": y, "x": x} + ) + else: + raise ValueError("Invalid mode. Choose between backward and forward.") + if vmap: + return bst.augment.vmap(transform)(xs) + else: + return transform(xs) + + +def hessian( + fn: Callable, + xs: Dict, + y: str | Sequence[str] | None = None, + xi: str | Sequence[str] | None = None, + xj: str | Sequence[str] | None = None, + vmap: bool = True, +): + """ + Compute the Hessian matrix of a function. + + This function calculates the Hessian matrix H as H[i, j] = d^2y / dx_i dx_j, + where i, j = 0, ..., dim_x - 1. + + Args: + fn (Callable): The function for which to compute the Hessian. + xs (Dict): A dictionary containing the input values for the function. + y (str | Sequence[str] | None, optional): Specifies the output variable(s) for which + to compute the Hessian. If None, computes for all outputs. Defaults to None. + xi (str | Sequence[str] | None, optional): Specifies the input variable(s) for the i-th + dimension of the Hessian. If None, computes for all inputs in this dimension. + Defaults to None. + xj (str | Sequence[str] | None, optional): Specifies the input variable(s) for the j-th + dimension of the Hessian. If None, computes for all inputs in this dimension. + Defaults to None. + vmap (bool, optional): Whether to use vectorized mapping. Defaults to True. + + Returns: + The Hessian matrix or a part of it, depending on the specified xi and xj: + - If both xi and xj are None, returns the full Hessian matrix. + - If xi is specified and xj is None, returns the i-th row of the Hessian, H[i, :]. + - If xj is specified and xi is None, returns the j-th column of the Hessian, H[:, j]. + - If both xi and xj are specified, returns the specific element H[i, j]. + + Note: + xi and xj cannot both be None unless the Hessian has only one element. + """ + # assert isinstance(xs, dict), 'xs must be a dictionary.' + transform = GradientTransform( + fn, _raw_hessian, transform_params={"y": y, "xi": xi, "xj": xj} + ) + if vmap: + return bst.augment.vmap(transform)(xs) + else: + return transform(xs) + + +def gradient( + fn: Callable, + xs: Dict, + y: str | Sequence[str] | None = None, + *xi: str | Sequence[str] | None, + order: int = 1, +): + """ + Compute the gradient of a function with respect to specified variables. + + This function calculates the gradient dy/dx of a function y = f(x) with respect to x. + It supports computing higher-order gradients by specifying the 'order' parameter. + + Args: + fn (Callable): The function for which to compute the gradient. + xs (Dict): A dictionary containing the input values for the function. + y (str | Sequence[str] | None, optional): Specifies the output variable(s) to differentiate. + If None, computes for all outputs. Defaults to None. + *xi (str | Sequence[str] | None): Variable-length argument specifying the input variable(s) + to differentiate with respect to. The number of xi arguments should match the 'order' parameter. + order (int, optional): The order of the gradient to compute. Default is 1 (first derivative). + + Returns: + The computed gradient. The structure and dimensions of the output depend on the inputs: + - For first-order gradients (order=1), returns dy/dx. + - For higher-order gradients, returns the corresponding higher-order derivative. + + Raises: + AssertionError: If 'order' is not a positive integer or if the number of 'xi' arguments + doesn't match the specified 'order'. + + Note: + The function uses a combination of reverse-mode (for the first derivative) and + forward-mode (for higher-order derivatives) automatic differentiation. + """ + assert isinstance(order, int), "order must be an integer." + assert order > 0, "order must be positive." + + # process only for y + if isinstance(y, str): + y = [y] + if y is not None: + fn = _format_y(fn, y, has_aux=False) + + # process xi + if len(xi) > 0: + assert len(xi) == order, "The number of xi must be equal to order." + xi = list(xi) + for i in range(order): + if isinstance(xi[i], str): + xi[i] = [xi[i]] + else: + xi = [None] * order + + # compute the gradient + for i, x in enumerate(xi): + if i == 0: + fn = _raw_jacrev(fn, y=y, x=x) + else: + fn = _raw_jacfwd(fn, y=None, x=x) + return bst.augment.vmap(fn)(xs) diff --git a/deepxde/experimental/icbc/__init__.py b/deepxde/experimental/icbc/__init__.py new file mode 100644 index 000000000..0309af16f --- /dev/null +++ b/deepxde/experimental/icbc/__init__.py @@ -0,0 +1,29 @@ +"""Initial conditions and boundary conditions.""" + +__all__ = [ + "ICBC", + "BC", + "DirichletBC", + "Interface2DBC", + "NeumannBC", + "RobinBC", + "PeriodicBC", + "OperatorBC", + "PointSetBC", + "PointSetOperatorBC", + "IC", +] + +from .base import ICBC +from .boundary_conditions import ( + BC, + DirichletBC, + Interface2DBC, + NeumannBC, + RobinBC, + PeriodicBC, + OperatorBC, + PointSetBC, + PointSetOperatorBC, +) +from .initial_conditions import IC diff --git a/deepxde/experimental/icbc/base.py b/deepxde/experimental/icbc/base.py new file mode 100644 index 000000000..fab37345f --- /dev/null +++ b/deepxde/experimental/icbc/base.py @@ -0,0 +1,109 @@ +import abc +from typing import Optional, Dict + +import brainstate as bst + +from deepxde.experimental.geometry.base import GeometryExperimental + + +class ICBC(abc.ABC): + """ + Base class for initial and boundary conditions. + """ + + # A ``experimental.geometry.Geometry`` instance. + geometry: Optional[GeometryExperimental] + problem: Optional["Problem"] + + def apply_geometry(self, geom: GeometryExperimental): + """ + Applies a geometry to the ICBC instance. + + Parameters: + ----------- + geom : GeometryExperimental + The geometry to be applied to the ICBC instance. + + Raises: + ------- + AssertionError + If the provided geometry is not an instance of AbstractGeometry. + """ + assert isinstance( + geom, GeometryExperimental + ), "geometry must be an instance of AbstractGeometry." + self.geometry = geom + + def apply_problem(self, problem: "Problem"): + """ + Applies a problem to the ICBC instance. + + Parameters: + ----------- + problem : Problem + The problem to be applied to the ICBC instance. + + Raises: + ------- + AssertionError + If the provided problem is not an instance of Problem. + """ + from deepxde.experimental.problem.base import Problem + + assert isinstance(problem, Problem), "problem must be an instance of Problem." + self.problem = problem + + @abc.abstractmethod + def filter(self, X): + """ + Filters the input data. + + Parameters: + ----------- + X : array-like + The input data to be filtered. + + Returns: + -------- + array-like + The filtered input data. + """ + pass + + @abc.abstractmethod + def collocation_points(self, X): + """ + Returns the collocation points. + + Parameters: + ----------- + X : array-like + The input data for which to compute collocation points. + + Returns: + -------- + array-like + The computed collocation points. + """ + pass + + @abc.abstractmethod + def error(self, inputs, outputs, **kwargs) -> Dict[str, bst.typing.ArrayLike]: + """ + Returns the loss for each component at the initial or boundary conditions. + + Parameters: + ----------- + inputs : array-like + The input data. + outputs : array-like + The output data. + **kwargs : dict + Additional keyword arguments. + + Returns: + -------- + Dict[str, bst.typing.ArrayLike] + A dictionary containing the loss for each component at the initial or boundary conditions. + """ + pass diff --git a/deepxde/experimental/icbc/boundary_conditions.py b/deepxde/experimental/icbc/boundary_conditions.py new file mode 100644 index 000000000..442fa783e --- /dev/null +++ b/deepxde/experimental/icbc/boundary_conditions.py @@ -0,0 +1,655 @@ +from __future__ import annotations + +from typing import Callable, Dict + +import brainstate as bst +import brainunit as u +import jax +import numpy as np + +from deepxde.data.sampler import BatchSampler +from deepxde.experimental import utils +from deepxde.experimental.nn.model import Model +from .base import ICBC + +__all__ = [ + "BC", + "DirichletBC", + "Interface2DBC", + "NeumannBC", + "OperatorBC", + "PeriodicBC", + "PointSetBC", + "PointSetOperatorBC", + "RobinBC", +] + +X = Dict[str, bst.typing.ArrayLike] +Y = Dict[str, bst.typing.ArrayLike] +F = Dict[str, bst.typing.ArrayLike] +Boundary = Dict[str, bst.typing.ArrayLike] + + +class BC(ICBC): + """ + Boundary condition base class. + + This class serves as the foundation for implementing various boundary conditions in the DeepXDE framework. + It provides methods for filtering collocation points, computing normal derivatives, and handling boundary-related operations. + + Args: + on_boundary (Callable[[X, np.array], np.array]): A function that takes two arguments: + - x: The input points. + - on: A boolean array indicating whether each point is on the boundary. + The function should return a boolean array indicating which points satisfy the boundary condition. + + Attributes: + on_boundary (Callable): A vectorized version of the input `on_boundary` function. + """ + + def __init__( + self, + on_boundary: Callable[[X, np.array], np.array], + ): + self.on_boundary = lambda x, on: jax.vmap(on_boundary)(x, on) + + @utils.check_not_none("geometry") + def filter(self, X): + """ + Filter the collocation points for boundary conditions. + + This method applies the boundary condition filter to the given collocation points. + + Args: + X (Dict[str, bst.typing.ArrayLike]): A dictionary of collocation points. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary of filtered collocation points that satisfy the boundary condition. + """ + positions = self.on_boundary(X, self.geometry.on_boundary(X)) + return jax.tree.map(lambda x: x[positions], X) + + def collocation_points(self, X): + """ + Return the collocation points for boundary conditions. + + This method filters the input collocation points to return only those that satisfy the boundary condition. + + Args: + X (Dict[str, bst.typing.ArrayLike]): A dictionary of collocation points. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary of collocation points that satisfy the boundary condition. + """ + return self.filter(X) + + def normal_derivative(self, inputs) -> Dict[str, bst.typing.ArrayLike]: + """ + Compute the normal derivative of the output. + + This method calculates the normal derivative of the output with respect to the input at the boundary. + + Args: + inputs (Dict[str, bst.typing.ArrayLike]): A dictionary of input points. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary containing the normal derivatives of the output + with respect to each input variable. + + Raises: + AssertionError: If the problem approximator is not an instance of the Model class, + or if the boundary normal or jacobian are not dictionaries. + """ + # first order derivative + assert isinstance(self.problem.approximator, Model), ( + "Normal derivative is only supported " "for Sequential approximator." + ) + dydx = self.problem.approximator.jacobian(inputs) + + # boundary normal + n = self.geometry.boundary_normal(inputs) + + assert isinstance(n, dict), "Boundary normal should be a dictionary." + assert isinstance(dydx, dict), "dydx should be a dictionary." + norms = dict() + for y in dydx: + norm = None + for x in dydx[y]: + if norm is None: + norm = dydx[y][x] * n[x] + else: + norm += dydx[y][x] * n[x] + norms[y] = norm + return norms + + +class DirichletBC(BC): + """ + Dirichlet boundary conditions: ``y(x) = func(x)``. + + This class implements Dirichlet boundary conditions, where the solution is specified + on the boundary of the domain. + + Args: + func (Callable[[X, ...], F] | Callable[[X], F] | F): A function that takes an array of points + and returns an array of values, or a constant value to be applied at all boundary points. + on_boundary (Callable[[X, np.array], np.array], optional): A function that takes two arguments: + x (the input points) and on (a boolean array indicating whether each point is on the boundary). + It should return a boolean array indicating which points satisfy the boundary condition. + Defaults to a function that returns the input 'on' array. + + """ + + def __init__( + self, + func: Callable[[X, ...], F] | Callable[[X], F] | F, + on_boundary: Callable[[X, np.array], np.array] = lambda x, on: on, + ): + super().__init__(on_boundary) + self.func = func if callable(func) else lambda x: func + + def error(self, bc_inputs, bc_outputs, **kwargs): + """ + Calculate the error between the predicted and true values at the boundary. + + Args: + bc_inputs (Dict[str, bst.typing.ArrayLike]): Input points on the boundary. + bc_outputs (Dict[str, bst.typing.ArrayLike]): Predicted output values at the boundary points. + **kwargs: Additional keyword arguments to be passed to self.func. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary containing the errors for each output component. + The keys are the component names, and the values are the differences between + the predicted and true values at the boundary points. + """ + values = self.func(bc_inputs, **kwargs) + errors = dict() + for component in values.keys(): + errors[component] = bc_outputs[component] - values[component] + return errors + + +class NeumannBC(BC): + """ + Neumann boundary conditions: ``dy/dn(x) = func(x)``. + + Args: + func: A function that takes an array of points and returns an array of values. + on_boundary: (x, Geometry.on_boundary(x)) -> True/False. + """ + + def __init__( + self, + func: Callable[[X, ...], F] | Callable[[X], F], + on_boundary: Callable[[X, np.array], np.array] = lambda x, on: on, + ): + super().__init__(on_boundary) + self.func = func + + def error(self, bc_inputs, bc_outputs, **kwargs): + """ + Calculate the error for Neumann boundary conditions. + + This method computes the difference between the normal derivative of the solution + and the specified function values at the boundary points. + + Args: + bc_inputs (Dict[str, bst.typing.ArrayLike]): Input points on the boundary. + bc_outputs (Dict[str, bst.typing.ArrayLike]): Predicted output values at the boundary points. + **kwargs: Additional keyword arguments to be passed to self.func. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary containing the errors for each output component. + The keys are the component names, and the values are the differences between + the normal derivatives and the specified function values at the boundary points. + """ + values = self.func(bc_inputs, **kwargs) + normals = self.normal_derivative(bc_inputs) + return { + component: normals[component] - values[component] + for component in values.keys() + } + + +class RobinBC(BC): + """ + Robin boundary conditions: dy/dn(x) = func(x, y). + + This class implements Robin boundary conditions, which are a combination of + Dirichlet and Neumann boundary conditions. + + Attributes: + func (Callable): The function defining the Robin boundary condition. + """ + + def __init__( + self, + func: Callable[[X, Y, ...], F] | Callable[[X, Y], F], + on_boundary: Callable[[Dict, np.array], np.array] = lambda x, on: on, + ): + """ + Initialize the RobinBC class. + + Args: + func (Callable[[X, Y, ...], F] | Callable[[X, Y], F]): A function that takes + input points (X) and output values (Y) and returns the right-hand side + of the Robin boundary condition equation. + on_boundary (Callable[[Dict, np.array], np.array], optional): A function that + determines which points are on the boundary. Defaults to a function that + returns the input 'on' array. + """ + super().__init__(on_boundary) + self.func = func + + def error(self, bc_inputs, bc_outputs, **kwargs): + """ + Calculate the error for the Robin boundary condition. + + This method computes the difference between the normal derivative of the solution + and the specified function values at the boundary points. + + Args: + bc_inputs (Dict[str, bst.typing.ArrayLike]): Input points on the boundary. + bc_outputs (Dict[str, bst.typing.ArrayLike]): Predicted output values at the boundary points. + **kwargs: Additional keyword arguments to be passed to self.func. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary containing the errors for each output component. + The keys are the component names, and the values are the differences between + the normal derivatives and the specified function values at the boundary points. + """ + values = self.func(bc_inputs, bc_outputs, **kwargs) + normals = self.normal_derivative(bc_inputs) + return { + component: normals[component] - values[component] + for component in values.keys() + } + + +class PeriodicBC(BC): + """ + Implements periodic boundary conditions for a specified component of the solution. + + This class enforces periodicity by ensuring that the values (or derivatives) of the solution + at corresponding points on opposite boundaries are equal. + + Args: + component_y (str): The name of the output component to which the periodic condition is applied. + component_x (str): The name of the input component along which the periodicity is enforced. + on_boundary (Callable[[X, np.array], np.array], optional): A function that takes two arguments: + x (the input points) and on (a boolean array indicating whether each point is on the boundary). + It should return a boolean array indicating which points satisfy the boundary condition. + Defaults to a function that returns the input 'on' array. + derivative_order (int, optional): The order of the derivative for which periodicity is enforced. + Can be 0 (for function values) or 1 (for first derivatives). Defaults to 0. + + Raises: + NotImplementedError: If derivative_order is greater than 1. + """ + + def __init__( + self, + component_y: str, + component_x: str, + on_boundary: Callable[[X, np.array], np.array] = lambda x, on: on, + derivative_order: int = 0, + ): + super().__init__(on_boundary) + self.component_y = component_y + self.component_x = component_x + self.derivative_order = derivative_order + if derivative_order > 1: + raise NotImplementedError( + "PeriodicBC only supports derivative_order 0 or 1." + ) + + @utils.check_not_none("geometry") + def collocation_points(self, X): + """ + Generates collocation points for enforcing periodic boundary conditions. + + This method filters the input points, identifies the periodic points, and concatenates + them to create pairs of points for enforcing periodicity. + + Args: + X (Dict[str, bst.typing.ArrayLike]): A dictionary of input points. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary of collocation points, where each entry + is the concatenation of points on one boundary and their periodic counterparts. + """ + X1 = self.filter(X) + X2 = self.geometry.periodic_point(X1, self.component_x) + return jax.tree.map( + lambda x1, x2: utils.smart_numpy(x1).concatenate((x1, x2), axis=-1), + X1, + X2, + is_leaf=u.math.is_quantity, + ) + + def error(self, bc_inputs, bc_outputs, **kwargs): + """ + Calculates the error for periodic boundary conditions. + + This method computes the difference between the values (or derivatives) of the solution + at corresponding points on opposite boundaries. + + Args: + bc_inputs (Dict[str, bst.typing.ArrayLike]): Input points on the boundary. + bc_outputs (Dict[str, bst.typing.ArrayLike]): Predicted output values at the boundary points. + **kwargs: Additional keyword arguments (unused in this method). + + Returns: + Dict[str, Dict[str, bst.typing.ArrayLike]]: A nested dictionary containing the errors. + The outer key is the output component name, and the inner key is the input component name. + The value is the difference between the left and right boundary values or derivatives. + """ + n_batch = bc_inputs[self.component_x].shape[0] + mid = n_batch // 2 + if self.derivative_order == 0: + yleft = bc_outputs[self.component_y][:mid] + yright = bc_outputs[self.component_y][mid:] + else: + dydx = self.problem.approximator.jacobian( + bc_outputs, y=self.component_y, x=self.component_x + ) + dydx = dydx[self.component_y][self.component_x] + yleft = dydx[:mid] + yright = dydx[mid:] + return {self.component_y: {self.component_x: yleft - yright}} + + +class OperatorBC(BC): + """ + General operator boundary conditions: func(inputs, outputs) = 0. + + Args: + func: A function takes arguments (`inputs`, `outputs`) + and outputs a tensor of size `N x 1`, where `N` is the length of `inputs`. + `inputs` and `outputs` are the network input and output tensors, + respectively; `X` are the NumPy array of the `inputs`. + on_boundary: (x, Geometry.on_boundary(x)) -> True/False. + + Warning: + If you use `X` in `func`, then do not set ``num_test`` when you define + ``experimental.problem.PDE`` or ``experimental.problem.TimePDE``, otherwise DeepXDE would throw an + error. In this case, the training points will be used for testing, and this will + not affect the network training and training loss. This is a bug of DeepXDE, + which cannot be fixed in an easy way for all backends. + """ + + def __init__( + self, + func: Callable[[X, Y, ...], F] | Callable[[X, Y], F], + on_boundary: Callable[[X, np.array], np.array] = lambda x, on: on, + ): + super().__init__(on_boundary) + self.func = func + + def error(self, bc_inputs, bc_outputs, **kwargs): + """ + Calculate the error for the operator boundary condition. + + This method applies the operator function to the boundary inputs and outputs + to compute the error of the boundary condition. + + Args: + bc_inputs (Dict[str, bst.typing.ArrayLike]): A dictionary of input values at the boundary points. + bc_outputs (Dict[str, bst.typing.ArrayLike]): A dictionary of output values at the boundary points. + **kwargs: Additional keyword arguments to be passed to the operator function. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary containing the computed error values + for each component of the boundary condition. + """ + return self.func(bc_inputs, bc_outputs, **kwargs) + + +class PointSetBC(BC): + """ + Dirichlet boundary condition for a set of points. + + Compare the output (that associates with `points`) with `values` (target data). + If more than one component is provided via a list, the resulting loss will + be the addative loss of the provided componets. + + Args: + points (Dict[str, bst.typing.ArrayLike]): A dictionary of arrays representing points + where the corresponding target values are known and used for training. + values (Dict[str, bst.typing.ArrayLike]): A dictionary of scalars or 2D-arrays + representing the exact solution of the problem at the given points. + batch_size (int, optional): The number of points per minibatch, or None to return all points. + This is only supported for the backend PyTorch and PaddlePaddle. Defaults to None. + shuffle (bool, optional): Whether to randomize the order on each pass through the data + when batching. Defaults to True. + + Note: + If you want to use batch size here, you should also set callback + 'experimental.callbacks.PDEPointResampler(bc_points=True)' in training. + """ + + def __init__( + self, + points: Dict[str, bst.typing.ArrayLike], + values: Dict[str, bst.typing.ArrayLike], + batch_size: int = None, + shuffle: bool = True, + ): + super().__init__(lambda x, on: on) + + self.points = points + self.values = values + self.batch_size = batch_size + + if batch_size is not None: # batch iterator and state + self.batch_sampler = BatchSampler(len(self), shuffle=shuffle) + self.batch_indices = None + + def __len__(self): + """ + Get the number of points in the PointSetBC. + + Returns: + int: The number of points in the first value of the points dictionary. + """ + v = tuple(self.points.values())[0] + return v.shape[0] + + def collocation_points(self, X): + """ + Get the collocation points for the boundary condition. + + If batch_size is set, returns a batch of points. Otherwise, returns all points. + + Args: + X: Unused in this method, kept for compatibility with parent class. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary of collocation points, + either a batch or all points depending on the batch_size setting. + """ + if self.batch_size is not None: + self.batch_indices = self.batch_sampler.get_next(self.batch_size) + return jax.tree.map(lambda x: x[self.batch_indices], self.points) + return self.points + + def error(self, bc_inputs, bc_outputs, **kwargs): + """ + Calculate the error between the predicted and true values at the boundary points. + + Args: + bc_inputs: Unused in this method, kept for compatibility with parent class. + bc_outputs (Dict[str, bst.typing.ArrayLike]): A dictionary of predicted output values + at the boundary points. + **kwargs: Additional keyword arguments (unused in this method). + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary containing the errors for each output component. + The keys are the component names, and the values are the differences between + the predicted and true values at the boundary points. + """ + if self.batch_size is not None: + return { + k: bc_outputs[k] - self.values[k][self.batch_indices] + for k in self.values.keys() + } + else: + return {k: bc_outputs[k] - self.values[k] for k in self.values.keys()} + + +class PointSetOperatorBC(BC): + """ + General operator boundary conditions for a set of points. + + Compare the function output, func, (that associates with `points`) + with `values` (target data). + + Args: + points: An array of points where the corresponding target values are + known and used for training. + values: An array of values which output of function should fulfill. + func: A function takes arguments (`inputs`, `outputs`,) + and outputs a tensor of size `N x 1`, where `N` is the length of + `inputs`. `inputs` and `outputs` are the network input and output + tensors, respectively; `X` are the NumPy array of the `inputs`. + """ + + def __init__( + self, + points: Dict[str, bst.typing.ArrayLike], + values: Dict[str, bst.typing.ArrayLike], + func: Callable[[X, Y], F], + ): + super().__init__(lambda x, on: on) + self.points = points + self.values = values + self.func = func + + def collocation_points(self, X): + """ + Return the collocation points for the boundary condition. + + Args: + X: Unused input parameter, kept for compatibility with parent class. + + Returns: + Dict[str, bst.typing.ArrayLike]: The points where the boundary condition is applied. + """ + return self.points + + def error(self, bc_inputs, bc_outputs, **kwargs): + """ + Calculate the error for the operator boundary condition. + + This method applies the operator function to the boundary inputs and outputs, + then computes the difference between the function output and the target values. + + Args: + bc_inputs (Dict[str, bst.typing.ArrayLike]): Input values at the boundary points. + bc_outputs (Dict[str, bst.typing.ArrayLike]): Output values at the boundary points. + **kwargs: Additional keyword arguments to be passed to the operator function. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary containing the computed error values + for each component of the boundary condition. + """ + outs = self.func(bc_inputs, bc_outputs) + return { + component: outs[component] - self.values[component] + for component in outs.keys() + } + + +class Interface2DBC(BC): + """2D interface boundary condition. + + This BC applies to the case with the following conditions: + (1) the network output has two elements, i.e., output = [y1, y2], + (2) the 2D geometry is ``experimental.geometry.Rectangle`` or ``experimental.geometry.Polygon``, which has two edges of the same length, + (3) uniform boundary points are used, i.e., in ``experimental.problem.PDE`` or ``experimental.problem.TimePDE``, ``train_distribution="uniform"``. + For a pair of points on the two edges, compute for the point on the first edge + and for the point on the second edge in the n/t direction ('n' for normal or 't' for tangent). + Here, is the dot product between vectors v1 and v2; + and d1 and d2 are the n/t vectors of the first and second edges, respectively. + In the normal case, d1 and d2 are the outward normal vectors; + and in the tangent case, d1 and d2 are the outward normal vectors rotated 90 degrees clockwise. + The points on the two edges are paired as follows: the boundary points on one edge are sampled clockwise, + and the points on the other edge are sampled counterclockwise. Then, compare the sum with 'values', + i.e., the error is calculated as + - values, + where 'values' is the argument `func` evaluated on the first edge. + + Args: + func: the target discontinuity between edges, evaluated on the first edge, + e.g., ``func=lambda x: 0`` means no discontinuity is wanted. + on_boundary1: First edge func. (x, Geometry.on_boundary(x)) -> True/False. + on_boundary2: Second edge func. (x, Geometry.on_boundary(x)) -> True/False. + direction (string): "normal" or "tangent". + """ + + def __init__( + self, + func: Callable[[X, ...], F] | Callable[[X], F], + on_boundary1: Callable[[X, np.array], np.array] = lambda x, on: on, + on_boundary2: Callable[[X, np.array], np.array] = lambda x, on: on, + direction: str = "normal", + ): + super().__init__(lambda x, on: on) + + self.func = utils.return_tensor(func) + self.on_boundary1 = lambda x, on: np.array( + [on_boundary1(x[i], on[i]) for i in range(len(x))] + ) + self.on_boundary2 = lambda x, on: np.array( + [on_boundary2(x[i], on[i]) for i in range(len(x))] + ) + self.direction = direction + + @utils.check_not_none("geometry") + def collocation_points(self, X): + on_boundary = self.geometry.on_boundary(X) + X1 = X[self.on_boundary1(X, on_boundary)] + X2 = X[self.on_boundary2(X, on_boundary)] + # Flip order of X2 when experimental.geometry.Polygon is used + if self.geometry.__class__.__name__ == "Polygon": + X2 = np.flip(X2, axis=0) + return np.vstack((X1, X2)) + + @utils.check_not_none("geometry") + def error(self, bc_inputs, bc_outputs, **kwargs): + mid = bc_inputs.shape[0] // 2 + if bc_inputs.shape[0] % 2 != 0: + raise RuntimeError( + "There is a different number of points on each edge,\n " + "this is likely because the chosen edges do not have the same length." + ) + aux_var = None + values = self.func(bc_inputs[:mid], **kwargs) + if np.ndim(values) == 2 and np.shape(values)[1] != 1: + raise RuntimeError("BC function should return an array of shape N by 1") + left_n = self.geometry.boundary_normal(bc_inputs[:mid]) + right_n = self.geometry.boundary_normal(bc_inputs[:mid]) + if self.direction == "normal": + left_side = bc_outputs[:mid, :] + right_side = bc_outputs[mid:, :] + left_values = u.math.sum(left_side * left_n, 1, keepdims=True) + right_values = u.math.sum(right_side * right_n, 1, keepdims=True) + + elif self.direction == "tangent": + # Tangent vector is [n[1],-n[0]] on edge 1 + left_side1 = bc_outputs[:mid, 0:1] + left_side2 = bc_outputs[:mid, 1:2] + right_side1 = bc_outputs[mid:, 0:1] + right_side2 = bc_outputs[mid:, 1:2] + left_values_1 = u.math.sum(left_side1 * left_n[:, 1:2], 1, keepdims=True) + left_values_2 = u.math.sum(-left_side2 * left_n[:, 0:1], 1, keepdims=True) + left_values = left_values_1 + left_values_2 + right_values_1 = u.math.sum(right_side1 * right_n[:, 1:2], 1, keepdims=True) + right_values_2 = u.math.sum( + -right_side2 * right_n[:, 0:1], 1, keepdims=True + ) + right_values = right_values_1 + right_values_2 + + else: + raise ValueError("Invalid direction, must be 'normal' or 'tangent'.") + + return left_values + right_values - values diff --git a/deepxde/experimental/icbc/initial_conditions.py b/deepxde/experimental/icbc/initial_conditions.py new file mode 100644 index 000000000..e9c608e30 --- /dev/null +++ b/deepxde/experimental/icbc/initial_conditions.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from typing import Callable, Dict + +import brainstate as bst +import jax +import numpy as np + +from .base import ICBC + +__all__ = ["IC"] + + +class IC(ICBC): + """ + Represents the Initial Conditions (IC) for a differential equation. + + This class defines and handles the initial conditions of the form: + y([x, t0]) = func([x, t0]), where func is a user-defined function. + + Args: + func (Callable[[Dict, ...], Dict] | Callable[[Dict], Dict]): A function that returns the initial conditions. + This function should take a dictionary of collocation points and + return a dictionary of initial conditions. For example: + import brainunit as u + def func(x): + return {'y': -u.math.sin(np.pi * x['x'] / u.meter) * u.meter / u.second} + on_initial (Callable[[Dict, np.array], np.array], optional): A filter function for initial conditions. + This function should take a dictionary of collocation points and + return a boolean array indicating whether the points are initial conditions. + Defaults to lambda x, on: on. For example: + def on_initial(x, on): + return on + """ + + def __init__( + self, + func: Callable[[Dict, ...], Dict] | Callable[[Dict], Dict], + on_initial: Callable[[Dict, np.array], np.array] = lambda x, on: on, + ): + self.func = func + self.on_initial = lambda x, on: jax.vmap(on_initial)(x, on) + + def filter(self, X): + """ + Filters the collocation points for initial conditions. + + Args: + X (Dict): A dictionary of collocation points. + + Returns: + Dict: Filtered collocation points that satisfy the initial conditions. + """ + # the "geometry" should be "TimeDomain" or "GeometryXTime" + positions = self.on_initial(X, self.geometry.on_initial(X)) + return jax.tree.map(lambda x: x[positions], X) + + def collocation_points(self, X): + """ + Returns the collocation points for initial conditions. + + Args: + X (Dict): A dictionary of collocation points. + + Returns: + Dict: Collocation points that satisfy the initial conditions. + """ + return self.filter(X) + + def error(self, inputs, outputs, **kwargs) -> Dict[str, bst.typing.ArrayLike]: + """ + Calculates the error for initial conditions. + + This method compares the initial conditions with the outputs to compute the error. + + Args: + inputs (Dict): A dictionary of collocation points. + outputs (Dict): A dictionary of collocation values. + **kwargs: Additional keyword arguments to be passed to the func method. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary containing the errors for each variable. + The keys correspond to the variable names, and the values are the computed errors. + """ + values = self.func(inputs, **kwargs) + errors = dict() + for key, value in values.items(): + errors[key] = outputs[key] - value + return errors diff --git a/deepxde/experimental/metrics.py b/deepxde/experimental/metrics.py new file mode 100644 index 000000000..1df36a19c --- /dev/null +++ b/deepxde/experimental/metrics.py @@ -0,0 +1,307 @@ +import brainunit as u +import jax + +__all__ = [ + "accuracy", + "l2_relative_error", + "nanl2_relative_error", + "mean_l2_relative_error", + "mean_squared_error", + "mean_absolute_percentage_error", + "max_absolute_percentage_error", + "absolute_percentage_error_std", +] + + +def _accuracy(y_true, y_pred): + return u.math.mean( + u.math.equal(u.math.argmax(y_pred, axis=-1), u.math.argmax(y_true, axis=-1)) + ) + + +def accuracy(y_true, y_pred): + """ + Computes accuracy across nested structures of labels and predictions. + + This function calculates the accuracy by comparing the predicted labels + with the true labels. It can handle nested structures of data. + + Parameters: + ----------- + y_true : array_like or nested structure + The true labels or ground truth values. Can be a single array or a + nested structure of arrays. + y_pred : array_like or nested structure + The predicted labels or values. Should have the same structure as y_true. + + Returns: + -------- + float or nested structure + The computed accuracy. If the input is a nested structure, the output + will have the same structure with accuracy values for each leaf node. + """ + return jax.tree_util.tree_map(_accuracy, y_true, y_pred, is_leaf=u.math.is_quantity) + + +def _l2_relative_error(y_true, y_pred): + return u.linalg.norm(y_true - y_pred) / u.linalg.norm(y_true) + + +def l2_relative_error(y_true, y_pred): + """ + Computes L2 relative error across nested structures of labels and predictions. + + This function calculates the L2 relative error between true values and predicted values. + It can handle nested structures of data by applying the calculation to each leaf node. + + Parameters: + ----------- + y_true : array_like or nested structure + The true values or ground truth. Can be a single array or a nested structure of arrays. + y_pred : array_like or nested structure + The predicted values. Should have the same structure as y_true. + + Returns: + -------- + float or nested structure + The computed L2 relative error. If the input is a nested structure, the output + will have the same structure with L2 relative error values for each leaf node. + """ + return jax.tree_util.tree_map( + _l2_relative_error, y_true, y_pred, is_leaf=u.math.is_quantity + ) + + +def _nanl2_relative_error(y_true, y_pred): + err = y_true - y_pred + err = u.math.nan_to_num(err) + y_true = u.math.nan_to_num(y_true) + return u.linalg.norm(err) / u.linalg.norm(y_true) + + +def nanl2_relative_error(y_true, y_pred): + """ + Computes L2 relative error across nested structures of labels and predictions, + handling NaN values. + + This function calculates the L2 relative error between true values and predicted values, + treating NaN values as zeros. It can handle nested structures of data by applying + the calculation to each leaf node. + + Parameters: + ----------- + y_true : array_like or nested structure + The true values or ground truth. Can be a single array or a nested structure of arrays. + May contain NaN values. + y_pred : array_like or nested structure + The predicted values. Should have the same structure as y_true. + May contain NaN values. + + Returns: + -------- + float or nested structure + The computed L2 relative error with NaN handling. If the input is a nested structure, + the output will have the same structure with L2 relative error values for each leaf node. + """ + return jax.tree_util.tree_map( + _nanl2_relative_error, y_true, y_pred, is_leaf=u.math.is_quantity + ) + + +def _mean_l2_relative_error(y_true, y_pred): + return u.math.mean( + u.linalg.norm(y_true - y_pred, axis=1) / u.linalg.norm(y_true, axis=1) + ) + + +def mean_l2_relative_error(y_true, y_pred): + """ + Computes mean L2 relative error across nested structures of labels and predictions. + + This function calculates the mean L2 relative error between true values and predicted values. + It can handle nested structures of data by applying the calculation to each leaf node. + + Parameters: + ----------- + y_true : array_like or nested structure + The true values or ground truth. Can be a single array or a nested structure of arrays. + y_pred : array_like or nested structure + The predicted values. Should have the same structure as y_true. + + Returns: + -------- + float or nested structure + The computed mean L2 relative error. If the input is a nested structure, the output + will have the same structure with mean L2 relative error values for each leaf node. + """ + return jax.tree_util.tree_map( + _mean_l2_relative_error, y_true, y_pred, is_leaf=u.math.is_quantity + ) + + +def _absolute_percentage_error(y_true, y_pred): + return 100 * u.math.abs((y_true - y_pred) / u.math.abs(y_true)) + + +def mean_absolute_percentage_error(y_true, y_pred): + """ + Computes mean absolute percentage error across nested structures of labels and predictions. + + This function calculates the mean absolute percentage error between true values and predicted values. + It can handle nested structures of data by applying the calculation to each leaf node. + + Parameters: + ----------- + y_true : array_like or nested structure + The true values or ground truth. Can be a single array or a nested structure of arrays. + y_pred : array_like or nested structure + The predicted values. Should have the same structure as y_true. + + Returns: + -------- + float or nested structure + The computed mean absolute percentage error. If the input is a nested structure, the output + will have the same structure with mean absolute percentage error values for each leaf node. + """ + return jax.tree_util.tree_map( + lambda x, y: _absolute_percentage_error(x, y).mean(), + y_true, + y_pred, + is_leaf=u.math.is_quantity, + ) + + +def max_absolute_percentage_error(y_true, y_pred): + """ + Computes maximum absolute percentage error across nested structures of labels and predictions. + + This function calculates the maximum absolute percentage error between true values and predicted values. + It can handle nested structures of data by applying the calculation to each leaf node. + + Parameters: + ----------- + y_true : array_like or nested structure + The true values or ground truth. Can be a single array or a nested structure of arrays. + y_pred : array_like or nested structure + The predicted values. Should have the same structure as y_true. + + Returns: + -------- + float or nested structure + The computed maximum absolute percentage error. If the input is a nested structure, the output + will have the same structure with maximum absolute percentage error values for each leaf node. + """ + return jax.tree_util.tree_map( + lambda x, y: _absolute_percentage_error(x, y).max(), + y_true, + y_pred, + is_leaf=u.math.is_quantity, + ) + + +def absolute_percentage_error_std(y_true, y_pred): + """ + Computes standard deviation of absolute percentage error across nested structures of labels and predictions. + + This function calculates the standard deviation of the absolute percentage error between true values + and predicted values. It can handle nested structures of data by applying the calculation to each leaf node. + + Parameters: + ----------- + y_true : array_like or nested structure + The true values or ground truth. Can be a single array or a nested structure of arrays. + y_pred : array_like or nested structure + The predicted values. Should have the same structure as y_true. + + Returns: + -------- + float or nested structure + The computed standard deviation of absolute percentage error. If the input is a nested structure, + the output will have the same structure with standard deviation values for each leaf node. + """ + return jax.tree_util.tree_map( + lambda x, y: _absolute_percentage_error(x, y).std(), + y_true, + y_pred, + is_leaf=u.math.is_quantity, + ) + + +def _mean_squared_error(y_true, y_pred): + return u.math.mean(u.math.square(y_true - y_pred)) + + +def mean_squared_error(y_true, y_pred): + """ + Computes mean squared error across nested structures of labels and predictions. + + This function calculates the mean squared error between true values and predicted values. + It can handle nested structures of data by applying the calculation to each leaf node. + + Parameters: + ----------- + y_true : array_like or nested structure + The true values or ground truth. Can be a single array or a nested structure of arrays. + y_pred : array_like or nested structure + The predicted values. Should have the same structure as y_true. + + Returns: + -------- + float or nested structure + The computed mean squared error. If the input is a nested structure, the output + will have the same structure with mean squared error values for each leaf node. + """ + return jax.tree_util.tree_map( + _mean_squared_error, y_true, y_pred, is_leaf=u.math.is_quantity + ) + + +def get(identifier): + """ + Retrieves a metric function based on the provided identifier. + + This function maps string identifiers to their corresponding metric functions + or returns the function if a callable is provided directly. + + Parameters: + ----------- + identifier : str or callable + A string identifier for a predefined metric function or a callable metric function. + Accepted string identifiers include: + - "accuracy" + - "l2 relative error" + - "nanl2 relative error" + - "mean l2 relative error" + - "mean squared error" (also "MSE" or "mse") + - "MAPE" + - "max APE" + - "APE SD" + + Returns: + -------- + callable + The metric function corresponding to the provided identifier. + + Raises: + ------- + ValueError + If the provided identifier is neither a recognized string nor a callable. + """ + metric_identifier = { + "accuracy": accuracy, + "l2 relative error": l2_relative_error, + "nanl2 relative error": nanl2_relative_error, + "mean l2 relative error": mean_l2_relative_error, + "mean squared error": mean_squared_error, + "MSE": mean_squared_error, + "mse": mean_squared_error, + "MAPE": mean_absolute_percentage_error, + "max APE": max_absolute_percentage_error, + "APE SD": absolute_percentage_error_std, + } + + if isinstance(identifier, str): + return metric_identifier[identifier] + if callable(identifier): + return identifier + raise ValueError("Could not interpret metric function identifier:", identifier) diff --git a/deepxde/experimental/nn/__init__.py b/deepxde/experimental/nn/__init__.py new file mode 100644 index 000000000..e47881f7d --- /dev/null +++ b/deepxde/experimental/nn/__init__.py @@ -0,0 +1,34 @@ +"""The ``experimental.nn`` package contains framework-specific implementations for different +neural networks. + +Users can directly import ``experimental.nn.`` (e.g., ``experimental.nn.FNN``), and +the package will dispatch the network name to the actual implementation according to the +backend framework currently in use. + +Note that there are coverage differences among frameworks. If you encounter an +``AttributeError: module 'experimental.nn.XXX' has no attribute 'XXX'`` or ``ImportError: +cannot import name 'XXX' from 'experimental.nn.XXX'`` error, that means the network is not +available to the current backend. If you wish a module to appear in DeepXDE, please +create an issue. If you want to contribute a NN module, please create a pull request. +""" + +__all__ = [ + "DictToArray", + "ArrayToDict", + "Model", + "NN", + "FNN", + "DeepONet", + "DeepONetCartesianProd", + "MIONetCartesianProd", + "PFNN", + "PODDeepONet", + "PODMIONet", +] + +from .base import NN +from .convert import DictToArray, ArrayToDict +from .deeponet import DeepONet, DeepONetCartesianProd, PODDeepONet +from .fnn import FNN, PFNN +from .mionet import MIONetCartesianProd, PODMIONet +from .model import Model diff --git a/deepxde/experimental/nn/base.py b/deepxde/experimental/nn/base.py new file mode 100644 index 000000000..566dc1884 --- /dev/null +++ b/deepxde/experimental/nn/base.py @@ -0,0 +1,90 @@ +from typing import Optional, Callable + +import brainstate as bst +import jax.tree + + +class NN(bst.nn.Module): + """Base class for all neural network modules.""" + + def __init__( + self, + input_transform: Optional[Callable] = None, + output_transform: Optional[Callable] = None, + ): + """ + Initialize the NN class. + + Parameters: + ----------- + input_transform : Optional[Callable], default=None + A callable that transforms the input before it's passed to the network. + output_transform : Optional[Callable], default=None + A callable that transforms the output after it's produced by the network. + + Returns: + -------- + None + """ + super().__init__() + self.regularization = None + self._input_transform = input_transform + self._output_transform = output_transform + + def apply_feature_transform(self, transform): + """ + Compute the features by applying a transform to the network inputs. + + This method sets the input transform function, which is applied before + the input is passed to the network, i.e., ``features = transform(inputs)``. + Then, ``outputs = network(features)``. + + Parameters: + ----------- + transform : Callable + The transform function to be applied to the inputs. + + Returns: + -------- + None + """ + self._input_transform = transform + + def apply_output_transform(self, transform): + """ + Apply a transform to the network outputs. + + This method sets the output transform function, which is applied after + the network produces its output, i.e., ``outputs = transform(inputs, outputs)``. + + Parameters: + ----------- + transform : Callable + The transform function to be applied to the outputs. + + Returns: + -------- + None + """ + self._output_transform = transform + + def num_trainable_parameters(self): + """ + Evaluate the number of trainable parameters for the NN. + + This method calculates the total number of trainable parameters in the neural network + by iterating through all parameters in the network's state. + + Parameters: + ----------- + None + + Returns: + -------- + int + The total number of trainable parameters in the neural network. + """ + n_param = 0 + for key, val in self.states(bst.ParamState).items(): + n_param += [v.size for v in jax.tree_leaves(val)] + return n_param diff --git a/deepxde/experimental/nn/convert.py b/deepxde/experimental/nn/convert.py new file mode 100644 index 000000000..41a7e3154 --- /dev/null +++ b/deepxde/experimental/nn/convert.py @@ -0,0 +1,195 @@ +from typing import Dict + +import brainstate as bst +import brainunit as u + +__all__ = [ + "DictToArray", + "ArrayToDict", +] + + +def dict_to_array(d: Dict[str, bst.typing.ArrayLike], axis: int = 1): + """ + Convert a dictionary of array-like values to a single concatenated array. + + This function takes a dictionary where each value is an array-like object, + and concatenates all these arrays along the specified axis to create a + single output array. + + Args: + d (Dict[str, bst.typing.ArrayLike]): A dictionary where keys are strings + and values are array-like objects (e.g., numpy arrays, lists, etc.). + axis (int, optional): The axis along which the arrays should be concatenated. + Default is 1. + + Returns: + ndarray: A single array containing all the input arrays concatenated + along the specified axis. The order of concatenation is determined + by the order of the keys in the input dictionary. + + Example: + >>> d = {'a': [1, 2, 3], 'b': [4, 5, 6]} + >>> dict_to_array(d) + array([[1, 4], + [2, 5], + [3, 6]]) + """ + keys = tuple(d.keys()) + return u.math.stack([d[key] for key in keys], axis=axis) + + +class DictToArray(bst.nn.Module): + """ + DictToArray layer, scaling the input data according to the given units, and merging them into an array. + + This layer takes a dictionary of array-like inputs, scales them according to specified units, + and concatenates them into a single array along a specified axis. + + Args: + axis (int, optional): The axis along which to concatenate the input arrays. Defaults to -1. + **units: Keyword arguments specifying the units for each input. Each unit should be an + instance of ``brainunit.Unit`` or None. + + Attributes: + axis (int): The axis along which concatenation is performed. + units (dict): A dictionary mapping input keys to their corresponding units. + in_size (int): The number of input elements (length of units dictionary). + out_size (int): The number of output elements (same as in_size). + """ + + def __init__(self, axis: int = -1, **units): + super().__init__() + + # axis + assert isinstance( + axis, int + ), f"DictToArray axis must be an integer. Please check the input values." + self.axis = axis + + # unit scale + self.units = units + for val in units.values(): + assert isinstance(val, u.Unit) or val is None, ( + f"DictToArray values must be a unit or None. " + "Please check the input values." + ) + + self.in_size = len(units) + self.out_size = len(units) + + def update(self, x: Dict[str, bst.typing.ArrayLike]): + """ + Scales the input dictionary values according to their units and concatenates them into an array. + + Args: + x (Dict[str, bst.typing.ArrayLike]): A dictionary of input arrays to be scaled and concatenated. + The keys should match those specified in the units dictionary during initialization. + + Returns: + ndarray: A single array containing all the scaled input arrays concatenated along the specified axis. + + Raises: + AssertionError: If the input dictionary keys don't match the units dictionary keys, + or if the input values are not of the expected type (Quantity or dimensionless). + """ + assert set(x.keys()) == set(self.units.keys()), ( + f"DictToArray keys mismatch. " + f"{set(x.keys())} != {set(self.units.keys())}." + ) + + # scale the input + x_dict = dict() + for key in self.units.keys(): + val = x[key] + if isinstance(self.units[key], u.Unit): + assert ( + isinstance(val, u.Quantity) + or self.units[key].dim == u.DIMENSIONLESS + ), ( + f"DictToArray values must be a quantity. " + "Please check the input values." + ) + x_dict[key] = ( + val.to_decimal(self.units[key]) + if isinstance(val, u.Quantity) + else val + ) + else: + x_dict[key] = u.maybe_decimal(val) + + # convert to array + arr = dict_to_array(x_dict, axis=self.axis) + return arr + + +class ArrayToDict(bst.nn.Module): + """ + Output layer, splitting the output data into a dict and assign the corresponding units. + + This class takes an input array and splits it into a dictionary, where each key-value pair + represents a specific output with its corresponding unit. + + Args: + axis (int, optional): The axis along which to split the output data. Defaults to -1. + **units: Keyword arguments specifying the units for each output. Each unit should be an + instance of ``brainunit.Unit`` or None. + + Attributes: + axis (int): The axis along which splitting is performed. + units (dict): A dictionary mapping output keys to their corresponding units. + in_size (int): The number of input elements (length of units dictionary). + out_size (int): The number of output elements (same as in_size). + """ + + def __init__(self, axis: int = -1, **units): + super().__init__() + + assert isinstance(axis, int), f"Output axis must be an integer. " + self.axis = axis + self.units = units + for val in units.values(): + assert isinstance(val, u.Unit) or val is None, ( + f"Input values must be a unit or None. " + "Please check the input values." + ) + self.in_size = len(units) + self.out_size = len(units) + + def update(self, arr: bst.typing.ArrayLike) -> Dict[str, bst.typing.ArrayLike]: + """ + Splits the input array into a dictionary and assigns the corresponding units. + + This method takes an input array, splits it along the specified axis, and creates + a dictionary where each key-value pair represents a specific output with its + corresponding unit. + + Args: + arr (bst.typing.ArrayLike): The input array to be split and converted into a dictionary. + + Returns: + Dict[str, bst.typing.ArrayLike]: A dictionary where keys are the output names and + values are the corresponding split arrays, potentially with units applied. + + Raises: + AssertionError: If the shape of the input array along the specified axis doesn't + match the number of units provided during initialization. + """ + assert arr.shape[self.axis] == len(self.units), ( + f"The number of columns of x must be " + f"equal to the number of units. " + f"Got {arr.shape[self.axis]} != {len(self.units)}. " + "Please check the input values." + ) + shape = list(arr.shape) + shape.pop(self.axis) + xs = u.math.split(arr, len(self.units), axis=self.axis) + + keys = tuple(self.units.keys()) + units = tuple(self.units.values()) + res = dict() + for key, unit, x in zip(keys, units, xs): + res[key] = u.math.squeeze(x, axis=self.axis) + if unit is not None: + res[key] *= unit + return res diff --git a/deepxde/experimental/nn/deeponet.py b/deepxde/experimental/nn/deeponet.py new file mode 100644 index 000000000..75b3087eb --- /dev/null +++ b/deepxde/experimental/nn/deeponet.py @@ -0,0 +1,347 @@ +from typing import Union, Callable, Sequence, Dict, Optional + +import brainstate as bst +import brainunit as u + +from deepxde.nn.deeponet_strategy import ( + DeepONetStrategy, + SingleOutputStrategy, + IndependentStrategy, + SplitBothStrategy, + SplitBranchStrategy, + SplitTrunkStrategy, +) +from deepxde.experimental.utils import get_activation +from .base import NN +from .fnn import FNN + +strategies = { + None: SingleOutputStrategy, + "independent": IndependentStrategy, + "split_both": SplitBothStrategy, + "split_branch": SplitBranchStrategy, + "split_trunk": SplitTrunkStrategy, +} + +__all__ = ["DeepONet", "DeepONetCartesianProd", "PODDeepONet"] + + +class DeepONet(NN): + """ + Deep operator network. + + `Lu et al. Learning nonlinear operators via DeepONet based on the universal + approximation theorem of operators. Nat Mach Intell, 2021. + `_ + + Args: + layer_sizes_branch: A list of integers as the width of a fully connected network, + or `(dim, f)` where `dim` is the input dimension and `f` is a network + function. The width of the last layer in the branch and trunk net + should be the same for all strategies except "split_branch" and "split_trunk". + layer_sizes_trunk (list): A list of integers as the width of a fully connected + network. + activation: If `activation` is a ``string``, then the same activation is used in + both trunk and branch nets. If `activation` is a ``dict``, then the trunk + net uses the activation `activation["trunk"]`, and the branch net uses + `activation["branch"]`. + num_outputs (integer): Number of outputs. In case of multiple outputs, i.e., `num_outputs` > 1, + `multi_output_strategy` below should be set. + multi_output_strategy (str or None): ``None``, "independent", "split_both", "split_branch" or + "split_trunk". It makes sense to set in case of multiple outputs. + + - None + Classical implementation of DeepONet with a single output. + Cannot be used with `num_outputs` > 1. + + - independent + Use `num_outputs` independent DeepONets, and each DeepONet outputs only + one function. + + - split_both + Split the outputs of both the branch net and the trunk net into `num_outputs` + groups, and then the kth group outputs the kth solution. + + - split_branch + Split the branch net and share the trunk net. The width of the last layer + in the branch net should be equal to the one in the trunk net multiplied + by the number of outputs. + + - split_trunk + Split the trunk net and share the branch net. The width of the last layer + in the trunk net should be equal to the one in the branch net multiplied + by the number of outputs. + """ + + def __init__( + self, + layer_sizes_branch: Sequence[int], + layer_sizes_trunk: Sequence[int], + activation: Union[str, Callable, Dict[str, str], Dict[str, Callable]], + kernel_initializer: bst.init.Initializer = bst.init.KaimingUniform(), + num_outputs: int = 1, + multi_output_strategy=None, + input_transform: Optional[Callable] = None, + output_transform: Optional[Callable] = None, + ): + super().__init__( + input_transform=input_transform, output_transform=output_transform + ) + + # activation function + if isinstance(activation, dict): + self.activation_branch = get_activation(activation["branch"]) + self.activation_trunk = get_activation(activation["trunk"]) + else: + self.activation_branch = self.activation_trunk = get_activation(activation) + + # initialize kernel + self.kernel_initializer = kernel_initializer + + self.num_outputs = num_outputs + if self.num_outputs == 1: + if multi_output_strategy is not None: + raise ValueError( + "num_outputs is set to 1, but multi_output_strategy is not None." + ) + elif multi_output_strategy is None: + multi_output_strategy = "independent" + print( + f"Warning: There are {num_outputs} outputs, but no multi_output_strategy selected. " + 'Use "independent" as the multi_output_strategy.' + ) + self.multi_output_strategy: DeepONetStrategy = strategies[ + multi_output_strategy + ](self) + + self.branch, self.trunk = self.multi_output_strategy.build( + layer_sizes_branch, layer_sizes_trunk + ) + self.b = bst.ParamState([0.0 for _ in range(self.num_outputs)]) + + def build_branch_net(self, layer_sizes_branch) -> FNN: + # User-defined network + if callable(layer_sizes_branch[1]): + return layer_sizes_branch[1] + # Fully connected network + return FNN(layer_sizes_branch, self.activation_branch, self.kernel_initializer) + + def build_trunk_net(self, layer_sizes_trunk) -> FNN: + return FNN(layer_sizes_trunk, self.activation_trunk, self.kernel_initializer) + + def merge_branch_trunk(self, x_func, x_loc, index): + y = u.math.sum(x_func * x_loc, axis=-1, keepdims=True) + y += self.b.value[index] + return y + + @staticmethod + def concatenate_outputs(ys): + return u.math.concatenate(ys, axis=1) + + def update(self, inputs): + x_func = inputs[0] + x_loc = inputs[1] + # Trunk net input transform + if self._input_transform is not None: + x_loc = self._input_transform(x_loc) + x = self.multi_output_strategy.call(x_func, x_loc) + if self._output_transform is not None: + x = self._output_transform(inputs, x) + return x + + +class DeepONetCartesianProd(NN): + """ + Deep operator network for dataset in the format of Cartesian product. + + Args: + layer_sizes_branch: A list of integers as the width of a fully connected network, + or `(dim, f)` where `dim` is the input dimension and `f` is a network + function. The width of the last layer in the branch and trunk net + should be the same for all strategies except "split_branch" and "split_trunk". + layer_sizes_trunk (list): A list of integers as the width of a fully connected + network. + activation: If `activation` is a ``string``, then the same activation is used in + both trunk and branch nets. If `activation` is a ``dict``, then the trunk + net uses the activation `activation["trunk"]`, and the branch net uses + `activation["branch"]`. + num_outputs (integer): Number of outputs. In case of multiple outputs, i.e., `num_outputs` > 1, + `multi_output_strategy` below should be set. + multi_output_strategy (str or None): ``None``, "independent", "split_both", "split_branch" or + "split_trunk". It makes sense to set in case of multiple outputs. + + - None + Classical implementation of DeepONet with a single output. + Cannot be used with `num_outputs` > 1. + + - independent + Use `num_outputs` independent DeepONets, and each DeepONet outputs only + one function. + + - split_both + Split the outputs of both the branch net and the trunk net into `num_outputs` + groups, and then the kth group outputs the kth solution. + + - split_branch + Split the branch net and share the trunk net. The width of the last layer + in the branch net should be equal to the one in the trunk net multiplied + by the number of outputs. + + - split_trunk + Split the trunk net and share the branch net. The width of the last layer + in the trunk net should be equal to the one in the branch net multiplied + by the number of outputs. + """ + + def __init__( + self, + layer_sizes_branch: Sequence[int], + layer_sizes_trunk: Sequence[int], + activation: Union[str, Callable, Dict[str, str], Dict[str, Callable]], + kernel_initializer: bst.init.Initializer = bst.init.KaimingUniform(), + num_outputs: int = 1, + multi_output_strategy=None, + input_transform: Optional[Callable] = None, + output_transform: Optional[Callable] = None, + ): + super().__init__( + input_transform=input_transform, output_transform=output_transform + ) + if isinstance(activation, dict): + self.activation_branch = activation["branch"] + self.activation_trunk = get_activation(activation["trunk"]) + else: + self.activation_branch = self.activation_trunk = get_activation(activation) + self.kernel_initializer = kernel_initializer + + self.num_outputs = num_outputs + if self.num_outputs == 1: + if multi_output_strategy is not None: + raise ValueError( + "num_outputs is set to 1, but multi_output_strategy is not None." + ) + elif multi_output_strategy is None: + multi_output_strategy = "independent" + print( + f"Warning: There are {num_outputs} outputs, but no multi_output_strategy selected. " + 'Use "independent" as the multi_output_strategy.' + ) + self.multi_output_strategy = strategies[multi_output_strategy](self) + + self.branch, self.trunk = self.multi_output_strategy.build( + layer_sizes_branch, layer_sizes_trunk + ) + self.b = bst.ParamState([0.0 for _ in range(self.num_outputs)]) + + def build_branch_net(self, layer_sizes_branch): + # User-defined network + if callable(layer_sizes_branch[1]): + return layer_sizes_branch[1] + # Fully connected network + return FNN(layer_sizes_branch, self.activation_branch, self.kernel_initializer) + + def build_trunk_net(self, layer_sizes_trunk): + return FNN(layer_sizes_trunk, self.activation_trunk, self.kernel_initializer) + + def merge_branch_trunk(self, x_func, x_loc, index): + y = u.math.einsum("bi,ni->bn", x_func, x_loc) + y += self.b.value[index] + return y + + @staticmethod + def concatenate_outputs(ys): + return u.math.stack(ys, axis=2) + + def update(self, inputs): + x_func = inputs[0] + x_loc = inputs[1] + # Trunk net input transform + if self._input_transform is not None: + x_loc = self._input_transform(x_loc) + x = self.multi_output_strategy.call(x_func, x_loc) + if self._output_transform is not None: + x = self._output_transform(inputs, x) + return x if x.ndim == 3 else x[..., None] + + +class PODDeepONet(NN): + """ + Deep operator network with proper orthogonal decomposition (POD) for dataset in + the format of Cartesian product. + + Args: + pod_basis: POD basis used in the trunk net. + layer_sizes_branch: A list of integers as the width of a fully connected network, + or `(dim, f)` where `dim` is the input dimension and `f` is a network + function. The width of the last layer in the branch and trunk net should be + equal. + activation: If `activation` is a ``string``, then the same activation is used in + both trunk and branch nets. If `activation` is a ``dict``, then the trunk + net uses the activation `activation["trunk"]`, and the branch net uses + `activation["branch"]`. + layer_sizes_trunk (list): A list of integers as the width of a fully connected + network. If ``None``, then only use POD basis as the trunk net. + + References: + `L. Lu, X. Meng, S. Cai, Z. Mao, S. Goswami, Z. Zhang, & G. E. Karniadakis. A + comprehensive and fair comparison of two neural operators (with practical + extensions) based on FAIR data. arXiv preprint arXiv:2111.05512, 2021 + `_. + """ + + def __init__( + self, + pod_basis, + layer_sizes_branch: Sequence[int], + activation: Union[str, Callable, Dict[str, str], Dict[str, Callable]], + kernel_initializer: bst.init.Initializer = bst.init.KaimingUniform(), + layer_sizes_trunk: Sequence[int] = None, + regularization=None, + input_transform: Optional[Callable] = None, + output_transform: Optional[Callable] = None, + ): + super().__init__( + input_transform=input_transform, output_transform=output_transform + ) + self.regularization = regularization # TODO: currently unused + self.pod_basis = pod_basis + if isinstance(activation, dict): + activation_branch = activation["branch"] + self.activation_trunk = get_activation(activation["trunk"]) + else: + activation_branch = self.activation_trunk = get_activation(activation) + + if callable(layer_sizes_branch[1]): + # User-defined network + self.branch = layer_sizes_branch[1] + else: + # Fully connected network + self.branch = FNN(layer_sizes_branch, activation_branch, kernel_initializer) + + self.trunk = None + if layer_sizes_trunk is not None: + self.trunk = FNN( + layer_sizes_trunk, self.activation_trunk, kernel_initializer + ) + self.b = bst.ParamState(0.0) + + def forward(self, inputs): + x_func = inputs[0] + x_loc = inputs[1] + + # Branch net to encode the input function + x_func = self.branch(x_func) + # Trunk net to encode the domain of the output function + if self.trunk is None: + # POD only + x = u.math.einsum("bi,ni->bn", x_func, self.pod_basis) + else: + x_loc = self.activation_trunk(self.trunk(x_loc)) + x = u.math.einsum( + "bi,ni->bn", x_func, u.math.concatenate((self.pod_basis, x_loc), axis=1) + ) + x += self.b.value + + if self._output_transform is not None: + x = self._output_transform(inputs, x) + return x diff --git a/deepxde/experimental/nn/fnn.py b/deepxde/experimental/nn/fnn.py new file mode 100644 index 000000000..70bfb8e7f --- /dev/null +++ b/deepxde/experimental/nn/fnn.py @@ -0,0 +1,249 @@ +from typing import Union, Callable, Sequence, Optional + +import brainstate as bst +import brainunit as u + +from deepxde.experimental.utils import get_activation +from .base import NN + + +class FNN(NN): + """ + Fully-connected neural network. + + This class implements a fully-connected neural network with customizable layer sizes, + activation functions, and optional input/output transformations. + + Args: + layer_sizes (Sequence[int]): A sequence of integers defining the number of neurons + in each layer, including input and output layers. + activation (Union[str, Callable, Sequence[str], Sequence[Callable]]): Activation + function(s) to use. Can be a single string/callable for all layers, or a + sequence of strings/callables for each layer. + kernel_initializer (bst.init.Initializer, optional): Initializer for the layer weights. + Defaults to bst.init.KaimingUniform(). + input_transform (Optional[Callable], optional): A function to transform the input + before passing it through the network. Defaults to None. + output_transform (Optional[Callable], optional): A function to transform the output + of the network. Defaults to None. + + Raises: + ValueError: If the number of activation functions doesn't match the number of layers + when a sequence of activations is provided. + """ + + def __init__( + self, + layer_sizes: Sequence[int], + activation: Union[str, Callable, Sequence[str], Sequence[Callable]], + kernel_initializer: bst.init.Initializer = bst.init.KaimingUniform(), + input_transform: Optional[Callable] = None, + output_transform: Optional[Callable] = None, + ): + super().__init__( + input_transform=input_transform, output_transform=output_transform + ) + + # activations + if isinstance(activation, (list, tuple)): + if not (len(layer_sizes) - 1) == len(activation): + raise ValueError( + "Total number of activation functions do not match with " + "sum of hidden layers and output layer!" + ) + self.activation = list(map(get_activation, activation)) + else: + self.activation = get_activation(activation) + + # layers + self.layers = [] + for i in range(1, len(layer_sizes)): + self.layers.append( + bst.nn.Linear( + layer_sizes[i - 1], layer_sizes[i], w_init=kernel_initializer + ) + ) + + # output transform + if output_transform is not None: + self.apply_output_transform(output_transform) + + def update(self, inputs): + """ + Perform a forward pass through the neural network. + + This method applies the input transformation (if any), passes the input through + all layers of the network applying activations, and then applies the output + transformation (if any). + + Args: + inputs: The input data to be passed through the network. + + Returns: + The output of the neural network after processing the inputs. + """ + x = inputs + if self._input_transform is not None: + x = self._input_transform(x) + for j, linear in enumerate(self.layers[:-1]): + x = ( + self.activation[j](linear(x)) + if isinstance(self.activation, list) + else self.activation(linear(x)) + ) + x = self.layers[-1](x) + if self._output_transform is not None: + x = self._output_transform(inputs, x) + return x + + +class PFNN(NN): + """ + Parallel fully-connected network that uses independent sub-networks for each + network output. + + This class implements a parallel fully-connected neural network where each output + can have its own independent sub-network. This allows for more flexibility in + network architecture, especially when different outputs require different levels + of complexity. + + Args: + layer_sizes (Sequence[int]): A nested list that defines the architecture of the neural network + (how the layers are connected). If `layer_sizes[i]` is an int, it represents + one layer shared by all the outputs; if `layer_sizes[i]` is a list, it + represents `len(layer_sizes[i])` sub-layers, each of which is exclusively + used by one output. Note that `len(layer_sizes[i])` should equal the number + of outputs. Every number specifies the number of neurons in that layer. + activation (Union[str, Callable, Sequence[str], Sequence[Callable]]): Activation + function(s) to use. Can be a single string/callable for all layers, or a + sequence of strings/callables for each layer. + kernel_initializer (bst.init.Initializer, optional): Initializer for the layer weights. + Defaults to bst.init.KaimingUniform(). + input_transform (Optional[Callable], optional): A function to transform the input + before passing it through the network. Defaults to None. + output_transform (Optional[Callable], optional): A function to transform the output + of the network. Defaults to None. + + Raises: + ValueError: If the layer sizes are not properly specified or if the number of + sub-layers doesn't match the number of outputs. + """ + + def __init__( + self, + layer_sizes: Sequence[int], + activation: Union[str, Callable, Sequence[str], Sequence[Callable]], + kernel_initializer: bst.init.Initializer = bst.init.KaimingUniform(), + input_transform: Optional[Callable] = None, + output_transform: Optional[Callable] = None, + ): + super().__init__( + input_transform=input_transform, output_transform=output_transform + ) + self.activation = get_activation(activation) + + if len(layer_sizes) <= 1: + raise ValueError("must specify input and output sizes") + if not isinstance(layer_sizes[0], int): + raise ValueError("input size must be integer") + if not isinstance(layer_sizes[-1], int): + raise ValueError("output size must be integer") + + n_output = layer_sizes[-1] + + self.layers = [] + for i in range(1, len(layer_sizes) - 1): + prev_layer_size = layer_sizes[i - 1] + curr_layer_size = layer_sizes[i] + if isinstance(curr_layer_size, (list, tuple)): + if len(curr_layer_size) != n_output: + raise ValueError( + "number of sub-layers should equal number of network outputs" + ) + if isinstance(prev_layer_size, (list, tuple)): + # e.g. [8, 8, 8] -> [16, 16, 16] + self.layers.append( + [ + bst.nn.Linear( + prev_layer_size[j], + curr_layer_size[j], + w_init=kernel_initializer, + ) + for j in range(n_output) + ] + ) + else: # e.g. 64 -> [8, 8, 8] + self.layers.append( + [ + bst.nn.Linear( + prev_layer_size, + curr_layer_size[j], + w_init=kernel_initializer, + ) + for j in range(n_output) + ] + ) + else: # e.g. 64 -> 64 + if not isinstance(prev_layer_size, int): + raise ValueError( + "cannot rejoin parallel subnetworks after splitting" + ) + self.layers.append( + bst.nn.Linear( + prev_layer_size, curr_layer_size, w_init=kernel_initializer + ) + ) + + # output layers + if isinstance(layer_sizes[-2], (list, tuple)): # e.g. [3, 3, 3] -> 3 + self.layers.append( + [ + bst.nn.Linear(layer_sizes[-2][j], 1, w_init=kernel_initializer) + for j in range(n_output) + ] + ) + else: + self.layers.append( + bst.nn.Linear(layer_sizes[-2], n_output, w_init=kernel_initializer) + ) + + def update(self, inputs): + """ + Perform a forward pass through the parallel fully-connected neural network. + + This method applies the input transformation (if any), passes the input through + all layers of the network applying activations, and then applies the output + transformation (if any). It handles both shared layers and parallel sub-networks. + + Args: + inputs: The input data to be passed through the network. + + Returns: + The output of the neural network after processing the inputs. The shape of the + output depends on the network architecture defined in the constructor. + """ + + x = inputs + if self._input_transform is not None: + x = self._input_transform(x) + + for layer in self.layers[:-1]: + if isinstance(layer, list): + if isinstance(x, list): + x = [self.activation(f(x_)) for f, x_ in zip(layer, x)] + else: + x = [self.activation(f(x)) for f in layer] + else: + x = self.activation(layer(x)) + + # output layers + if isinstance(x, list): + x = u.math.concatenate( + [f(x_) for f, x_ in zip(self.layers[-1], x)], axis=-1 + ) + else: + x = self.layers[-1](x) + + if self._output_transform is not None: + x = self._output_transform(inputs, x) + return x diff --git a/deepxde/experimental/nn/mionet.py b/deepxde/experimental/nn/mionet.py new file mode 100644 index 000000000..59623ce57 --- /dev/null +++ b/deepxde/experimental/nn/mionet.py @@ -0,0 +1,269 @@ +from typing import Optional, Callable + +import brainstate as bst +import brainunit as u + +from deepxde.experimental.utils import get_activation +from .base import NN +from .fnn import FNN + + +class MIONetCartesianProd(NN): + """ + MIONet with two input functions for Cartesian product format. + """ + + def __init__( + self, + layer_sizes_branch1, + layer_sizes_branch2, + layer_sizes_trunk, + activation, + kernel_initializer, + regularization=None, + trunk_last_activation=False, + merge_operation="mul", + layer_sizes_merger=None, + output_merge_operation="mul", + layer_sizes_output_merger=None, + input_transform: Optional[Callable] = None, + output_transform: Optional[Callable] = None, + ): + super().__init__( + input_transform=input_transform, output_transform=output_transform + ) + + if isinstance(activation, dict): + self.activation_branch1 = get_activation(activation["branch1"]) + self.activation_branch2 = get_activation(activation["branch2"]) + self.activation_trunk = get_activation(activation["trunk"]) + else: + self.activation_branch1 = self.activation_branch2 = ( + self.activation_trunk + ) = get_activation(activation) + if callable(layer_sizes_branch1[1]): + # User-defined network + self.branch1 = layer_sizes_branch1[1] + else: + # Fully connected network + self.branch1 = FNN( + layer_sizes_branch1, self.activation_branch1, kernel_initializer + ) + if callable(layer_sizes_branch2[1]): + # User-defined network + self.branch2 = layer_sizes_branch2[1] + else: + # Fully connected network + self.branch2 = FNN( + layer_sizes_branch2, self.activation_branch2, kernel_initializer + ) + if layer_sizes_merger is not None: + self.activation_merger = get_activation(activation["merger"]) + if callable(layer_sizes_merger[1]): + # User-defined network + self.merger = layer_sizes_merger[1] + else: + # Fully connected network + self.merger = FNN( + layer_sizes_merger, self.activation_merger, kernel_initializer + ) + else: + self.merger = None + if layer_sizes_output_merger is not None: + self.activation_output_merger = get_activation(activation["output merger"]) + if callable(layer_sizes_output_merger[1]): + # User-defined network + self.output_merger = layer_sizes_output_merger[1] + else: + # Fully connected network + self.output_merger = FNN( + layer_sizes_output_merger, + self.activation_output_merger, + kernel_initializer, + ) + else: + self.output_merger = None + self.trunk = FNN(layer_sizes_trunk, self.activation_trunk, kernel_initializer) + self.b = bst.ParamState(0.0) + self.regularizer = regularization + self.trunk_last_activation = trunk_last_activation + self.merge_operation = merge_operation + self.output_merge_operation = output_merge_operation + + def update(self, inputs): + x_func1 = inputs[0] + x_func2 = inputs[1] + x_loc = inputs[2] + # Branch net to encode the input function + y_func1 = self.branch1(x_func1) + y_func2 = self.branch2(x_func2) + if self.merge_operation == "cat": + x_merger = u.math.concatenate((y_func1, y_func2), axis=-1) + else: + if y_func1.shape[-1] != y_func2.shape[-1]: + raise AssertionError( + "Output sizes of branch1 net and branch2 net do not match." + ) + if self.merge_operation == "add": + x_merger = y_func1 + y_func2 + elif self.merge_operation == "mul": + x_merger = u.math.multiply(y_func1, y_func2) + else: + raise NotImplementedError( + f"{self.merge_operation} operation to be implemented" + ) + # Optional merger net + if self.merger is not None: + y_func = self.merger(x_merger) + else: + y_func = x_merger + # Trunk net to encode the domain of the output function + if self._input_transform is not None: + x_loc = self._input_transform(x_loc) + y_loc = self.trunk(x_loc) + if self.trunk_last_activation: + y_loc = self.activation_trunk(y_loc) + # Dot product + if y_func.shape[-1] != y_loc.shape[-1]: + raise AssertionError( + "Output sizes of merger net and trunk net do not match." + ) + # output merger net + if self.output_merger is None: + y = u.math.einsum("ip,jp->ij", y_func, y_loc) + else: + y_func = y_func[:, None, :] + y_loc = y_loc[None, :] + if self.output_merge_operation == "mul": + y = u.math.multiply(y_func, y_loc) + elif self.output_merge_operation == "add": + y = y_func + y_loc + elif self.output_merge_operation == "cat": + y_func = y_func.repeat(1, y_loc.shape[1], 1) + y_loc = y_loc.repeat(y_func.shape[0], 1, 1) + y = u.math.concatenate((y_func, y_loc), axis=2) + shape0 = y.shape[0] + shape1 = y.shape[1] + y = y.reshape(shape0 * shape1, -1) + y = self.output_merger(y) + y = y.reshape(shape0, shape1) + # Add bias + y += self.b + if self._output_transform is not None: + y = self._output_transform(inputs, y) + return y + + +class PODMIONet(NN): + """MIONet with two input functions and proper orthogonal decomposition (POD) + for Cartesian product format.""" + + def __init__( + self, + pod_basis, + layer_sizes_branch1, + layer_sizes_branch2, + activation, + kernel_initializer, + layer_sizes_trunk=None, + regularization=None, + trunk_last_activation=False, + merge_operation="mul", + layer_sizes_merger=None, + input_transform: Optional[Callable] = None, + output_transform: Optional[Callable] = None, + ): + super().__init__( + input_transform=input_transform, output_transform=output_transform + ) + + if isinstance(activation, dict): + self.activation_branch1 = get_activation(activation["branch1"]) + self.activation_branch2 = get_activation(activation["branch2"]) + self.activation_trunk = get_activation(activation["trunk"]) + self.activation_merger = get_activation(activation["merger"]) + else: + self.activation_branch1 = self.activation_branch2 = ( + self.activation_trunk + ) = get_activation(activation) + self.pod_basis = pod_basis + if callable(layer_sizes_branch1[1]): + # User-defined network + self.branch1 = layer_sizes_branch1[1] + else: + # Fully connected network + self.branch1 = FNN( + layer_sizes_branch1, self.activation_branch1, kernel_initializer + ) + if callable(layer_sizes_branch2[1]): + # User-defined network + self.branch2 = layer_sizes_branch2[1] + else: + # Fully connected network + self.branch2 = FNN( + layer_sizes_branch2, self.activation_branch2, kernel_initializer + ) + if layer_sizes_merger is not None: + if callable(layer_sizes_merger[1]): + # User-defined network + self.merger = layer_sizes_merger[1] + else: + # Fully connected network + self.merger = FNN( + layer_sizes_merger, self.activation_merger, kernel_initializer + ) + else: + self.merger = None + self.trunk = None + if layer_sizes_trunk is not None: + self.trunk = FNN( + layer_sizes_trunk, self.activation_trunk, kernel_initializer + ) + self.b = bst.ParamState(0.0) + self.regularizer = regularization + self.trunk_last_activation = trunk_last_activation + self.merge_operation = merge_operation + + def update(self, inputs): + x_func1 = inputs[0] + x_func2 = inputs[1] + x_loc = inputs[2] + # Branch net to encode the input function + y_func1 = self.branch1(x_func1) + y_func2 = self.branch2(x_func2) + # connect two branch outputs + if self.merge_operation == "cat": + x_merger = u.math.concatenate((y_func1, y_func2), 1) + else: + if y_func1.shape[-1] != y_func2.shape[-1]: + raise AssertionError( + "Output sizes of branch1 net and branch2 net do not match." + ) + if self.merge_operation == "add": + x_merger = y_func1 + y_func2 + elif self.merge_operation == "mul": + x_merger = u.math.multiply(y_func1, y_func2) + else: + raise NotImplementedError( + f"{self.merge_operation} operation to be implemented" + ) + # Optional merger net + if self.merger is not None: + y_func = self.merger(x_merger) + else: + y_func = x_merger + # Dot product + if self.trunk is None: + # POD only + y = u.math.einsum("bi,ni->bn", y_func, self.pod_basis) + else: + y_loc = self.trunk(x_loc) + if self.trunk_last_activation: + y_loc = self.activation_trunk(y_loc) + y = u.math.einsum( + "bi,ni->bn", y_func, u.math.concatenate((self.pod_basis, y_loc), axis=1) + ) + y += self.b + if self._output_transform is not None: + y = self._output_transform(inputs, y) + return y diff --git a/deepxde/experimental/nn/model.py b/deepxde/experimental/nn/model.py new file mode 100644 index 000000000..336244e71 --- /dev/null +++ b/deepxde/experimental/nn/model.py @@ -0,0 +1,139 @@ +from __future__ import annotations + +from typing import Dict, Sequence + +import brainstate as bst + +from deepxde.experimental.grad import jacobian, hessian, gradient +from .convert import DictToArray, ArrayToDict + +__all__ = [ + "Model", +] + + +class Model(bst.nn.Module): + """ + A neural network approximator. + + Args: + input: The input check. + approx: The neural network model. + output: The output unit. + + """ + + def __init__( + self, + input: DictToArray, + approx: bst.nn.Module, + output: ArrayToDict, + *args, + ): + """ + Initialize the Model. + + Args: + input (DictToArray): The input converter that transforms dictionary inputs to arrays. + approx (bst.nn.Module): The neural network model used for approximation. + output (ArrayToDict): The output converter that transforms array outputs to dictionaries. + *args: Additional arguments (not used). + + Raises: + AssertionError: If input is not an instance of DictToArray, approx is not an instance of bst.nn.Module, + or output is not an instance of ArrayToDict. + """ + super().__init__() + + assert isinstance( + input, DictToArray + ), "input must be an instance of DictToArray." + self.input = input + + assert isinstance( + approx, bst.nn.Module + ), "approx must be an instance of nn.Module." + self.approx = approx + + assert isinstance(output, ArrayToDict), "output must be an instance of Output." + self.output = output + + @bst.compile.jit(static_argnums=(0,)) + def update(self, x): + """ + Update the model by passing input through the neural network. + + Args: + x: The input data to be processed. + + Returns: + The output of the neural network after passing through input conversion, + approximation, and output conversion stages. + """ + return self.output(self.approx(self.input(x))) + + def jacobian( + self, + inputs: Dict[str, bst.typing.ArrayLike], + y: str | Sequence[str] | None = None, + x: str | Sequence[str] | None = None, + ): + """ + Compute the Jacobian of the approximation neural networks. + + Args: + inputs: The input data. + y: The output variables. + x: The input variables. + + Returns: + The Jacobian of the approximation neural networks. + """ + return jacobian(self, inputs, y=y, x=x) + + def hessian( + self, + inputs: Dict[str, bst.typing.ArrayLike], + y: str | Sequence[str] | None = None, + xi: str | Sequence[str] | None = None, + xj: str | Sequence[str] | None = None, + ): + """ + Compute the Hessian of the approximator. + + Compute: `H[y][xi][xj] = d^2y / dxi dxj = d^2y / dxj dxi` + + Args: + inputs: The input data. + y: The output variables. + xi: The first input variables. + xj: The second input variables. + + Returns: + The Hessian of the approximator. + """ + return hessian(self, inputs, y=y, xi=xi, xj=xj) + + def gradient( + self, + inputs: Dict[str, bst.typing.ArrayLike], + order: int, + y: str | Sequence[str] | None = None, + *xi: str | Sequence[str] | None, + ): + """ + Compute the gradient of the approximator. + + Args: + inputs: The input data. + order: The order of the gradient. + y: The output variables. + xi: The input variables. + + Returns: + The gradient of the approximator. + """ + assert ( + isinstance(order, int) and order >= 1 + ), "order must be an integer greater than or equal to 1." + return gradient(self, inputs, y, *xi, order=order) diff --git a/deepxde/experimental/problem/__init__.py b/deepxde/experimental/problem/__init__.py new file mode 100644 index 000000000..735fe9e73 --- /dev/null +++ b/deepxde/experimental/problem/__init__.py @@ -0,0 +1,25 @@ +__all__ = [ + "Problem", + "DataSet", + "Function", + "QuadrupleDataset", + "TripleDataset", + "TripleCartesianProd", + "IDE", + "PDE", + "TimePDE", + "FPDE", + "TimeFPDE", + "PDEOperator", + "PDEOperatorCartesianProd", +] + +from .base import Problem +from .dataset_function import Function +from .dataset_general import DataSet +from .dataset_quadruple import QuadrupleDataset +from .dataset_triple import TripleDataset, TripleCartesianProd +from .fpde import FPDE, TimeFPDE +from .ide import IDE +from .pde import PDE, TimePDE +from .pde_operator import PDEOperator, PDEOperatorCartesianProd diff --git a/deepxde/experimental/problem/base.py b/deepxde/experimental/problem/base.py new file mode 100644 index 000000000..aaa5552c8 --- /dev/null +++ b/deepxde/experimental/problem/base.py @@ -0,0 +1,179 @@ +from __future__ import annotations + +import abc +from typing import Callable, Sequence, Any, Tuple + +import brainstate as bst +import jax + +from deepxde.experimental.utils.losses import get_loss + +Inputs = Any +Targets = Any +Auxiliary = Any +Outputs = Any +LOSS = jax.typing.ArrayLike + +__all__ = [ + "Problem", +] + + +class Problem(abc.ABC): + """ + Base Problem Class. + + A problem is defined by the approximator and the loss function. + + Attributes: + approximator: The approximator. + loss_fn: The loss function. + loss_weights: A list specifying scalar coefficients (Python floats) to + weight the loss contributions. The loss value that will be minimized by + the trainer will then be the weighted sum of all individual losses, + weighted by the `loss_weights` coefficients. + """ + + approximator: bst.nn.Module + loss_fn: Callable | Sequence[Callable] + + def __init__( + self, + approximator: bst.nn.Module = None, + loss_fn: str | Callable[[Inputs, Outputs], LOSS] = "MSE", + loss_weights: Sequence[float] = None, + ): + """ + Initialize the problem. + + Args: + approximator (bst.nn.Module, optional): The approximator. Defaults to None. + loss_fn (str | Callable[[Inputs, Outputs], LOSS], optional): The loss function. + If the same loss is used for all errors, then `loss` is a String name of a loss function + or a loss function. If different errors use different losses, then `loss` is a list + whose size is equal to the number of errors. Defaults to 'MSE'. + loss_weights (Sequence[float], optional): A list specifying scalar coefficients (Python floats) to + weight the loss contributions. The loss value that will be minimized by + the trainer will then be the weighted sum of all individual losses, + weighted by the `loss_weights` coefficients. Defaults to None. + """ + # approximator + if approximator is not None: + self.define_approximator(approximator) + else: + self.approximator = None + + # loss function + self.loss_fn = get_loss(loss_fn) + + # loss weights + if loss_weights is not None: + assert isinstance( + loss_weights, (list, tuple) + ), "loss_weights must be a list or tuple." + self.loss_weights = loss_weights + + def define_approximator( + self, + approximator: bst.nn.Module, + ) -> Problem: + """ + Define the approximator for the problem. + + Args: + approximator (bst.nn.Module): The approximator to be used in the problem. + + Returns: + Problem: The current Problem instance with the defined approximator. + + Raises: + AssertionError: If the approximator is not an instance of bst.nn.Module. + """ + assert isinstance( + approximator, bst.nn.Module + ), "approximator must be an instance of bst.nn.Module." + self.approximator = approximator + return self + + def losses(self, inputs, outputs, targets, **kwargs): + """ + Calculate and return a list of losses (constraints) for the problem. + + Args: + inputs: The input data. + outputs: The output data. + targets: The target data. + **kwargs: Additional keyword arguments. + + Returns: + A list of calculated losses. + + Raises: + NotImplementedError: This method should be implemented by subclasses. + """ + raise NotImplementedError("Problem.losses is not implemented.") + + def losses_train(self, inputs, outputs, targets, **kwargs): + """ + Calculate and return a list of losses for the training dataset. + + This method sets the environment context to training mode before calculating losses. + + Args: + inputs: The input data for training. + outputs: The output data for training. + targets: The target data for training. + **kwargs: Additional keyword arguments. + + Returns: + A list of calculated losses for the training dataset. + """ + with bst.environ.context(fit=True): + return self.losses(inputs, outputs, targets, **kwargs) + + def losses_test(self, inputs, outputs, targets, **kwargs): + """ + Calculate and return a list of losses for the test dataset. + + This method sets the environment context to testing mode before calculating losses. + + Args: + inputs: The input data for testing. + outputs: The output data for testing. + targets: The target data for testing. + **kwargs: Additional keyword arguments. + + Returns: + A list of calculated losses for the test dataset. + """ + with bst.environ.context(fit=False): + return self.losses(inputs, outputs, targets, **kwargs) + + @abc.abstractmethod + def train_next_batch( + self, batch_size=None + ) -> Tuple[Inputs, Targets] | Tuple[Inputs, Targets, Auxiliary]: + """ + Generate and return the next batch of training data. + + This method should be implemented by subclasses to provide the next batch of training data. + + Args: + batch_size (int, optional): The size of the batch to be returned. Defaults to None. + + Returns: + Tuple[Inputs, Targets] | Tuple[Inputs, Targets, Auxiliary]: A tuple containing the inputs and targets + for the next training batch. May also include auxiliary data if applicable. + """ + + @abc.abstractmethod + def test(self) -> Tuple[Inputs, Targets] | Tuple[Inputs, Targets, Auxiliary]: + """ + Generate and return the test dataset. + + This method should be implemented by subclasses to provide the test dataset. + + Returns: + Tuple[Inputs, Targets] | Tuple[Inputs, Targets, Auxiliary]: A tuple containing the inputs and targets + for the test dataset. May also include auxiliary data if applicable. + """ diff --git a/deepxde/experimental/problem/dataset_function.py b/deepxde/experimental/problem/dataset_function.py new file mode 100644 index 000000000..6e85d3048 --- /dev/null +++ b/deepxde/experimental/problem/dataset_function.py @@ -0,0 +1,109 @@ +from typing import Callable, Sequence + +import brainstate as bst + +from deepxde.experimental.geometry.base import GeometryExperimental +from deepxde.utils.internal import run_if_any_none +from .base import Problem + +__all__ = [ + "Function", +] + + +class Function(Problem): + """ + Approximate a function via a network. + + Args: + geometry (GeometryExperimental): The domain of the function. Instance of ``Geometry``. + function (Callable): The function to be approximated. A callable function takes a NumPy array as the input and returns the + a NumPy array of corresponding function values. + num_train (int): The number of training points sampled inside the domain. + num_test (int): The number of points for testing. + train_distribution (str, optional): The distribution to sample training points. One of the following: "uniform" + (equispaced grid), "pseudo" (pseudorandom), "LHS" (Latin hypercube sampling), "Halton" (Halton sequence), + "Hammersley" (Hammersley sequence), or "Sobol" (Sobol sequence). Defaults to "uniform". + online (bool, optional): If ``True``, resample the pseudorandom training points every training step, otherwise, use the + same training points. Defaults to False. + approximator (bst.nn.Module, optional): The neural network module to use as an approximator. Defaults to None. + loss_fn (str, optional): The loss function to use. Defaults to 'MSE'. + loss_weights (Sequence[float], optional): The weights for different loss components. Defaults to None. + """ + + def __init__( + self, + geometry: GeometryExperimental, + function: Callable, + num_train: int, + num_test: int, + train_distribution: str = "uniform", + online: bool = False, + approximator: bst.nn.Module = None, + loss_fn: str = "MSE", + loss_weights: Sequence[float] = None, + ): + super().__init__( + approximator=approximator, loss_fn=loss_fn, loss_weights=loss_weights + ) + + self.geom = geometry + self.func = function + self.num_train = num_train + self.num_test = num_test + self.dist_train = train_distribution + self.online = online + + if online and train_distribution != "pseudo": + print("Warning: Online learning should use pseudorandom sampling.") + self.dist_train = "pseudo" + + self.train_x, self.train_y = None, None + self.test_x, self.test_y = None, None + + def losses(self, inputs, outputs, targets, **kwargs): + """ + Compute the loss between the predicted outputs and the target values. + + Args: + inputs: The input data. + outputs: The predicted output from the model. + targets: The target values. + **kwargs: Additional keyword arguments. + + Returns: + The computed loss value. + """ + return self.loss_fn(targets, outputs) + + def train_next_batch(self, batch_size=None): + """ + Generate the next batch of training data. + + Args: + batch_size (int, optional): The size of the batch to generate. Defaults to None. + + Returns: + tuple: A tuple containing the input features (train_x) and target values (train_y) for training. + """ + if self.train_x is None or self.online: + if self.dist_train == "uniform": + self.train_x = self.geom.uniform_points(self.num_train, boundary=True) + else: + self.train_x = self.geom.random_points( + self.num_train, random=self.dist_train + ) + self.train_y = self.func(self.train_x) + return self.train_x, self.train_y + + @run_if_any_none("test_x", "test_y") + def test(self): + """ + Generate test data points and their corresponding function values. + + Returns: + tuple: A tuple containing the test input features (test_x) and their corresponding function values (test_y). + """ + self.test_x = self.geom.uniform_points(self.num_test, boundary=True) + self.test_y = self.func(self.test_x) + return self.test_x, self.test_y diff --git a/deepxde/experimental/problem/dataset_general.py b/deepxde/experimental/problem/dataset_general.py new file mode 100644 index 000000000..e29a1aa53 --- /dev/null +++ b/deepxde/experimental/problem/dataset_general.py @@ -0,0 +1,104 @@ +from typing import Sequence, Dict + +import brainstate as bst +import jax +import numpy as np + +from deepxde.experimental import utils +from .base import Problem + +__all__ = ["DataSet"] + + +class DataSet(Problem): + """ + Fitting Problem set for handling dataset-based machine learning problems. + + This class extends the Problem class to handle dataset-based machine learning tasks, + including data preprocessing, loss calculation, and batch generation for training. + + Args: + X_train (Dict[str, bst.typing.ArrayLike]): Dictionary of training input data. + y_train (Dict[str, bst.typing.ArrayLike]): Dictionary of training output data. + X_test (Dict[str, bst.typing.ArrayLike]): Dictionary of testing input data. + y_test (Dict[str, bst.typing.ArrayLike]): Dictionary of testing output data. + standardize (bool, optional): Whether to standardize input data. Defaults to False. + approximator (bst.nn.Module, optional): The neural network module to use. Defaults to None. + loss_fn (str, optional): The loss function to use. Defaults to 'MSE'. + loss_weights (Sequence[float], optional): Weights for different loss components. Defaults to None. + + Attributes: + train_x (Dict[str, bst.typing.ArrayLike]): Processed training input data. + train_y (Dict[str, bst.typing.ArrayLike]): Processed training output data. + test_x (Dict[str, bst.typing.ArrayLike]): Processed testing input data. + test_y (Dict[str, bst.typing.ArrayLike]): Processed testing output data. + scaler_x (object): Scaler used for standardization, if applied. + """ + + def __init__( + self, + X_train: Dict[str, bst.typing.ArrayLike], + y_train: Dict[str, bst.typing.ArrayLike], + X_test: Dict[str, bst.typing.ArrayLike], + y_test: Dict[str, bst.typing.ArrayLike], + standardize: bool = False, + approximator: bst.nn.Module = None, + loss_fn: str = "MSE", + loss_weights: Sequence[float] = None, + ): + super().__init__( + approximator=approximator, loss_fn=loss_fn, loss_weights=loss_weights + ) + + self.train_x = X_train + self.train_y = y_train + self.test_x = X_test + self.test_y = y_test + self.scaler_x = None + if standardize: + r = jax.tree.map( + lambda train, test: utils.standardize(train, test), + self.train_x, + self.test_x, + ) + self.train_x = dict() + self.test_x = dict() + for key, val in r.items(): + self.train_x[key] = val[0] + self.test_x[key] = val[1] + + def losses(self, inputs, outputs, targets, **kwargs): + """ + Calculate the loss between the model outputs and the target values. + + Args: + inputs: The input data (not used in this method). + outputs: The model's output predictions. + targets: The true target values. + **kwargs: Additional keyword arguments. + + Returns: + The calculated loss value. + """ + return self.loss_fn(targets, outputs) + + def train_next_batch(self, batch_size=None): + """ + Get the next batch of training data. + + Args: + batch_size (int, optional): The size of the batch to return. If None, returns all training data. + + Returns: + tuple: A tuple containing the batch of training inputs (self.train_x) and outputs (self.train_y). + """ + return self.train_x, self.train_y + + def test(self): + """ + Get the test dataset. + + Returns: + tuple: A tuple containing the test inputs (self.test_x) and outputs (self.test_y). + """ + return self.test_x, self.test_y diff --git a/deepxde/experimental/problem/dataset_quadruple.py b/deepxde/experimental/problem/dataset_quadruple.py new file mode 100644 index 000000000..efcf5eee6 --- /dev/null +++ b/deepxde/experimental/problem/dataset_quadruple.py @@ -0,0 +1,92 @@ +from typing import Sequence + +import brainstate as bst + +from deepxde.data.sampler import BatchSampler +from .base import Problem + +__all__ = [ + "QuadrupleDataset", +] + + +class QuadrupleDataset(Problem): + """ + Dataset with each data point as a quadruple. + + The couple of the first three elements are the input, and the fourth element is the + output. This dataset can be used with the network ``MIONet`` for operator + learning. + + Args: + X_train (tuple): A tuple of three NumPy arrays representing the input training data. + y_train (numpy.ndarray): A NumPy array representing the output training data. + X_test (tuple): A tuple of three NumPy arrays representing the input testing data. + y_test (numpy.ndarray): A NumPy array representing the output testing data. + approximator (bst.nn.Module, optional): The neural network module used for approximation. Defaults to None. + loss_fn (str, optional): The loss function to be used. Defaults to 'MSE'. + loss_weights (Sequence[float], optional): Weights for the loss function. Defaults to None. + """ + + def __init__( + self, + X_train, + y_train, + X_test, + y_test, + approximator: bst.nn.Module = None, + loss_fn: str = "MSE", + loss_weights: Sequence[float] = None, + ): + super().__init__( + approximator=approximator, loss_fn=loss_fn, loss_weights=loss_weights + ) + self.train_x = X_train + self.train_y = y_train + self.test_x = X_test + self.test_y = y_test + + self.train_sampler = BatchSampler(len(self.train_y), shuffle=True) + + def losses(self, inputs, outputs, targets, **kwargs): + """ + Calculate the loss between the predicted outputs and the target values. + + Args: + inputs: The input data (not used in this method). + outputs: The predicted output values. + targets: The target output values. + **kwargs: Additional keyword arguments. + + Returns: + The calculated loss value. + """ + return self.loss_fn(targets, outputs) + + def train_next_batch(self, batch_size=None): + """ + Get the next batch of training data. + + Args: + batch_size (int, optional): The size of the batch to return. If None, returns all training data. + + Returns: + tuple: A tuple containing the input data (as a tuple of arrays) and the corresponding output data. + """ + if batch_size is None: + return self.train_x, self.train_y + indices = self.train_sampler.get_next(batch_size) + return ( + (self.train_x[0][indices], self.train_x[1][indices]), + self.train_x[2][indices], + self.train_y[indices], + ) + + def test(self): + """ + Get the testing data. + + Returns: + tuple: A tuple containing the input testing data and the corresponding output testing data. + """ + return self.test_x, self.test_y diff --git a/deepxde/experimental/problem/dataset_triple.py b/deepxde/experimental/problem/dataset_triple.py new file mode 100644 index 000000000..eb9f7c3c9 --- /dev/null +++ b/deepxde/experimental/problem/dataset_triple.py @@ -0,0 +1,214 @@ +from typing import Sequence + +import brainstate as bst + +from deepxde.data.sampler import BatchSampler +from .base import Problem + +__all__ = ["TripleDataset", "TripleCartesianProd"] + + +class TripleDataset(Problem): + """ + Dataset with each data point as a triple. + + The couple of the first two elements are the input, and the third element is the + output. This dataset can be used with the network ``DeepONet`` for operator + learning. + + Args: + X_train (tuple): A tuple of two NumPy arrays representing the input training data. + y_train (numpy.ndarray): A NumPy array representing the output training data. + X_test (tuple): A tuple of two NumPy arrays representing the input testing data. + y_test (numpy.ndarray): A NumPy array representing the output testing data. + approximator (bst.nn.Module, optional): The neural network module used for approximation. Defaults to None. + loss_fn (str, optional): The loss function to be used. Defaults to 'MSE'. + loss_weights (Sequence[float], optional): Weights for the loss function. Defaults to None. + + References: + `L. Lu, P. Jin, G. Pang, Z. Zhang, & G. E. Karniadakis. Learning nonlinear + operators via DeepONet based on the universal approximation theorem of + operators. Nature Machine Intelligence, 3, 218--229, 2021 + `_. + """ + + def __init__( + self, + X_train, + y_train, + X_test, + y_test, + approximator: bst.nn.Module = None, + loss_fn: str = "MSE", + loss_weights: Sequence[float] = None, + ): + super().__init__( + approximator=approximator, loss_fn=loss_fn, loss_weights=loss_weights + ) + self.train_x = X_train + self.train_y = y_train + self.test_x = X_test + self.test_y = y_test + + self.train_sampler = BatchSampler(len(self.train_y), shuffle=True) + + def losses(self, inputs, outputs, targets, **kwargs): + """ + Compute the loss between the model outputs and the targets. + + Args: + inputs: The input data (not used in this method). + outputs: The model outputs. + targets: The target values. + **kwargs: Additional keyword arguments. + + Returns: + The computed loss value. + """ + return self.loss_fn(targets, outputs) + + def train_next_batch(self, batch_size=None): + """ + Get the next batch of training data. + + Args: + batch_size (int, optional): The size of the batch to return. If None, returns all training data. + + Returns: + tuple: A tuple containing two elements: + - A tuple of two arrays representing the input training data for the batch. + - An array representing the output training data for the batch. + """ + if batch_size is None: + return self.train_x, self.train_y + indices = self.train_sampler.get_next(batch_size) + return ( + (self.train_x[0][indices], self.train_x[1][indices]), + self.train_y[indices], + ) + + def test(self): + """ + Get the testing data. + + Returns: + tuple: A tuple containing two elements: + - The input testing data. + - The output testing data. + """ + return self.test_x, self.test_y + + +class TripleCartesianProd(Problem): + """ + Dataset with each data point as a triple. The ordered pair of the first two + elements are created from a Cartesian product of the first two lists. If we compute + the Cartesian product of the first two arrays, then we have a ``TripleDataset`` dataset. + + This dataset can be used with the network ``DeepONetCartesianProd`` for operator + learning. + + Args: + X_train: A tuple of two NumPy arrays. The first element has the shape (`N1`, + `dim1`), and the second element has the shape (`N2`, `dim2`). + y_train: A NumPy array of shape (`N1`, `N2`). + """ + + def __init__( + self, + X_train, + y_train, + X_test, + y_test, + approximator: bst.nn.Module = None, + loss_fn: str = "MSE", + loss_weights: Sequence[float] = None, + ): + """ + Initialize the TripleCartesianProd dataset. + + Args: + X_train (tuple): A tuple of two NumPy arrays for training input data. + y_train (numpy.ndarray): A NumPy array for training output data. + X_test (tuple): A tuple of two NumPy arrays for testing input data. + y_test (numpy.ndarray): A NumPy array for testing output data. + approximator (bst.nn.Module, optional): The neural network module used for approximation. Defaults to None. + loss_fn (str, optional): The loss function to be used. Defaults to 'MSE'. + loss_weights (Sequence[float], optional): Weights for the loss function. Defaults to None. + + Raises: + ValueError: If the training or testing dataset does not have the format of Cartesian product. + """ + super().__init__( + approximator=approximator, loss_fn=loss_fn, loss_weights=loss_weights + ) + + if len(X_train[0]) != y_train.shape[0] or len(X_train[1]) != y_train.shape[1]: + raise ValueError( + "The training dataset does not have the format of Cartesian product." + ) + if len(X_test[0]) != y_test.shape[0] or len(X_test[1]) != y_test.shape[1]: + raise ValueError( + "The testing dataset does not have the format of Cartesian product." + ) + self.train_x, self.train_y = X_train, y_train + self.test_x, self.test_y = X_test, y_test + + self.branch_sampler = BatchSampler(len(X_train[0]), shuffle=True) + self.trunk_sampler = BatchSampler(len(X_train[1]), shuffle=True) + + def losses(self, inputs, outputs, targets, **kwargs): + """ + Compute the loss between the model outputs and the targets. + + Args: + inputs: The input data (not used in this method). + outputs: The model outputs. + targets: The target values. + **kwargs: Additional keyword arguments. + + Returns: + The computed loss value. + """ + return self.loss_fn(targets, outputs) + + def train_next_batch(self, batch_size=None): + """ + Get the next batch of training data. + + Args: + batch_size (int, tuple, or list, optional): The size of the batch to return. + If None, returns all training data. + If int, returns a batch with the specified size for branch data and all trunk data. + If tuple or list, returns a batch with specified sizes for both branch and trunk data. + + Returns: + tuple: A tuple containing two elements: + - A tuple of two arrays representing the input training data for the batch. + - An array representing the output training data for the batch. + """ + if batch_size is None: + return self.train_x, self.train_y + if not isinstance(batch_size, (tuple, list)): + indices = self.branch_sampler.get_next(batch_size) + return (self.train_x[0][indices], self.train_x[1]), self.train_y[indices] + indices_branch = self.branch_sampler.get_next(batch_size[0]) + indices_trunk = self.trunk_sampler.get_next(batch_size[1]) + return ( + ( + self.train_x[0][indices_branch], + self.train_x[1][indices_trunk], + ), + self.train_y[indices_branch, indices_trunk], + ) + + def test(self): + """ + Get the testing data. + + Returns: + tuple: A tuple containing two elements: + - The input testing data. + - The output testing data. + """ + return self.test_x, self.test_y diff --git a/deepxde/experimental/problem/fpde.py b/deepxde/experimental/problem/fpde.py new file mode 100644 index 000000000..f8e32d2b5 --- /dev/null +++ b/deepxde/experimental/problem/fpde.py @@ -0,0 +1,718 @@ +from __future__ import annotations + +import warnings +from typing import Callable, Sequence, Optional, Dict, Any + +import brainstate as bst +import brainunit as u +import jax +import numpy as np + +from deepxde.data.fpde import ( + Scheme, + Fractional as FractionalBase, + FractionalTime as FractionalTimeBase, +) +from deepxde.experimental.geometry import GeometryXTime, DictPointGeometry +from deepxde.experimental.icbc.base import ICBC +from deepxde.experimental.utils import array_ops +from deepxde.utils.internal import run_if_all_none +from .pde import PDE + +__all__ = ["FPDE", "TimeFPDE"] + +X = Dict[str, bst.typing.ArrayLike] +Y = Dict[str, bst.typing.ArrayLike] +InitMat = bst.typing.ArrayLike + + +class FPDE(PDE): + r""" + Fractional PDE solver. + + This class implements a solver for Fractional Partial Differential Equations (FPDEs) using the Physics-Informed Neural Network (PINN) approach. + + D-dimensional fractional Laplacian of order alpha/2 (1 < alpha < 2) is defined as: + (-Delta)^(alpha/2) u(x) = C(alpha, D) \int_{||theta||=1} D_theta^alpha u(x) d theta, + where C(alpha, D) = gamma((1-alpha)/2) * gamma((D+alpha)/2) / (2 pi^((D+1)/2)), + D_theta^alpha is the Riemann-Liouville directional fractional derivative, + and theta is the differentiation direction vector. + The solution u(x) is assumed to be identically zero in the boundary and exterior of the domain. + When D = 1, C(alpha, D) = 1 / (2 cos(alpha * pi / 2)). + + This solver does not consider C(alpha, D) in the fractional Laplacian, + and only discretizes \int_{||theta||=1} D_theta^alpha u(x) d theta. + D_theta^alpha is approximated by Grunwald-Letnikov formula. + + Parameters: + ----------- + geometry : DictPointGeometry + The geometry of the problem domain. + pde : Callable[[X, Y, InitMat], Any] + The PDE to be solved. + alpha : float | bst.State[float] + The order of the fractional derivative. + constraints : ICBC | Sequence[ICBC] + The initial and boundary conditions. + resolution : Sequence[int] + The resolution for discretization. + approximator : Optional[bst.nn.Module], default=None + The neural network approximator. + meshtype : str, default="dynamic" + The type of mesh to use ("static" or "dynamic"). + num_domain : int, default=0 + The number of domain points. + num_boundary : int, default=0 + The number of boundary points. + train_distribution : str, default="Hammersley" + The distribution method for training points. + anchors : Any, default=None + Anchor points for the domain. + solution : Callable[[Dict], Dict], default=None + The analytical solution of the PDE, if available. + num_test : int, default=None + The number of test points. + loss_fn : str | Callable, default='MSE' + The loss function to use. + loss_weights : Sequence[float], default=None + The weights for different components of the loss. + + References: + ----------- + G. Pang, L. Lu, & G. E. Karniadakis. fPINNs: Fractional physics-informed neural + networks. SIAM Journal on Scientific Computing, 41(4), A2603--A2626, 2019 + . + """ + + def __init__( + self, + geometry: DictPointGeometry, + pde: Callable[[X, Y, InitMat], Any], + alpha: float | bst.State[float], + constraints: ICBC | Sequence[ICBC], + resolution: Sequence[int], + approximator: Optional[bst.nn.Module] = None, + meshtype: str = "dynamic", + num_domain: int = 0, + num_boundary: int = 0, + train_distribution: str = "Hammersley", + anchors=None, + solution: Callable[[Dict], Dict] = None, + num_test: int = None, + loss_fn: str | Callable = "MSE", + loss_weights: Sequence[float] = None, + ): + self.alpha = alpha + self.disc = Scheme(meshtype, resolution) + self.frac_train, self.frac_test = None, None + self.int_mat_train = None + + super().__init__( + geometry, + pde, + constraints, + approximator=approximator, + num_domain=num_domain, + num_boundary=num_boundary, + train_distribution=train_distribution, + anchors=anchors, + solution=solution, + num_test=num_test, + loss_fn=loss_fn, + loss_weights=loss_weights, + ) + + def call_pde_errors(self, inputs, outputs, **kwargs): + bcs_start = np.cumsum([0] + self.num_bcs) + + # # PDE inputs and outputs + # pde_inputs = jax.tree.map(lambda x: x[bcs_start[-1]:], inputs) + # pde_outputs = jax.tree.map(lambda x: x[bcs_start[-1]:], outputs) + + # do not cache int_mat when alpha is a learnable parameter + fit = bst.environ.get("fit") + + if fit: + if isinstance(self.alpha, bst.State): + int_mat = self.get_int_matrix(True) + else: + if self.int_mat_train is not None: + # use cached int_mat + int_mat = self.int_mat_train + else: + # initialize self.int_mat_train with int_mat + int_mat = self.get_int_matrix(True) + self.int_mat_train = int_mat + else: + int_mat = self.get_int_matrix(False) + + # computing PDE losses + # pde_errors = self.pde(pde_inputs, pde_outputs, int_mat, **kwargs) + # return pde_errors + pde_errors = self.pde(inputs, outputs, int_mat, **kwargs) + return jax.tree.map(lambda x: x[bcs_start[-1] :], pde_errors) + + def call_bc_errors(self, loss_fns, loss_weights, inputs, outputs, **kwargs): + return super().call_bc_errors(loss_fns, loss_weights, inputs, outputs, **kwargs) + # fit = bst.environ.get('fit') + # if fit: + # return super().call_bc_errors(loss_fns, loss_weights, inputs, outputs, **kwargs) + # else: + # return [u.math.zeros((), dtype=bst.environ.dftype()) for _ in self.constraints] + + @run_if_all_none("train_x", "train_y") + def train_next_batch(self, batch_size=None): + alpha = self.alpha.value if isinstance(self.alpha, bst.State) else self.alpha + + # do not cache train data when alpha is a learnable parameter + if self.disc.meshtype == "static": + if self.geometry.geom.idstr != "Interval": + raise ValueError("Only Interval supports static mesh.") + + self.frac_train = Fractional(alpha, self.geometry.geom, self.disc, None) + X = self.frac_train.get_x() + X = self.geometry.arr_to_dict(u.math.roll(X, -1)) + + # FPDE is only applied to the domain points. + # Boundary points are auxiliary points, and appended in the end. + self.train_x_all = X + if self.anchors is not None: + self.train_x_all = jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=-1), + self.anchors, + self.train_x_all, + ) + x_bc = self.bc_points() + + elif self.disc.meshtype == "dynamic": + self.train_x_all = self.train_points() + x_bc = self.bc_points() + + # FPDE is only applied to the domain points. + train_x_all = self.geometry.dict_to_arr(self.train_x_all) + x_f = train_x_all[~self.geometry.on_boundary(self.train_x_all)] + self.frac_train = Fractional(alpha, self.geometry.geom, self.disc, x_f) + X = self.geometry.arr_to_dict(self.frac_train.get_x()) + + else: + raise ValueError("Unknown meshtype %s" % self.disc.meshtype) + + self.train_x = jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=-1), + x_bc, + X, + is_leaf=u.math.is_quantity, + ) + self.train_y = self.solution(self.train_x) if self.solution else None + return self.train_x, self.train_y + + @run_if_all_none("test_x", "test_y") + def test(self): + # do not cache test data when alpha is a learnable parameter + if self.disc.meshtype == "static" and self.num_test is not None: + raise ValueError("Cannot use test points in static mesh.") + + if self.num_test is None: + # assign the training points to the testing points + num_bc = sum(self.num_bcs) + self.test_x = jax.tree_map(lambda x: x[num_bc:], self.train_x) + self.frac_test = self.frac_train + else: + alpha = ( + self.alpha.value if isinstance(self.alpha, bst.State) else self.alpha + ) + + # Generate `self.test_x`, resampling the test points + self.test_x = self.test_points() + not_boundary = ~self.geometry.on_boundary(self.test_x) + x_f = self.geometry.dict_to_arr(self.test_x)[not_boundary] + self.frac_test = Fractional(alpha, self.geometry.geom, self.disc, x_f) + self.test_x = self.geometry.arr_to_dict(self.frac_test.get_x()) + + self.test_y = self.solution(self.test_x) if self.solution else None + return self.test_x, self.test_y + + def test_points(self): + return self.geometry.uniform_points(self.num_test, True) + + def get_int_matrix(self, training): + if training: + int_mat = self.frac_train.get_matrix(sparse=True) + num_bc = sum(self.num_bcs) + else: + int_mat = self.frac_test.get_matrix(sparse=True) + num_bc = 0 + + if self.disc.meshtype == "static": + int_mat = np.roll(int_mat, -1, 1) + int_mat = int_mat[1:-1] + + int_mat = array_ops.zero_padding(int_mat, ((num_bc, 0), (num_bc, 0))) + return int_mat + + +class TimeFPDE(FPDE): + r"""Time-dependent fractional PDE solver. + + D-dimensional fractional Laplacian of order alpha/2 (1 < alpha < 2) is defined as: + (-Delta)^(alpha/2) u(x) = C(alpha, D) \int_{||theta||=1} D_theta^alpha u(x) d theta, + where C(alpha, D) = gamma((1-alpha)/2) * gamma((D+alpha)/2) / (2 pi^((D+1)/2)), + D_theta^alpha is the Riemann-Liouville directional fractional derivative, + and theta is the differentiation direction vector. + The solution u(x) is assumed to be identically zero in the boundary and exterior of the domain. + When D = 1, C(alpha, D) = 1 / (2 cos(alpha * pi / 2)). + + This solver does not consider C(alpha, D) in the fractional Laplacian, + and only discretizes \int_{||theta||=1} D_theta^alpha u(x) d theta. + D_theta^alpha is approximated by Grunwald-Letnikov formula. + + References: + `G. Pang, L. Lu, & G. E. Karniadakis. fPINNs: Fractional physics-informed neural + networks. SIAM Journal on Scientific Computing, 41(4), A2603--A2626, 2019 + `_. + """ + + def __init__( + self, + geometry: DictPointGeometry, + pde: Callable[[X, Y, InitMat], Any], + alpha: float | bst.State[float], + constraints: ICBC | Sequence[ICBC], + resolution: Sequence[int], + approximator: Optional[bst.nn.Module] = None, + meshtype: str = "dynamic", + num_domain: int = 0, + num_boundary: int = 0, + num_initial: int = 0, + train_distribution: str = "Hammersley", + anchors=None, + solution=None, + num_test: int = None, + loss_fn: str | Callable = "MSE", + loss_weights: Sequence[float] = None, + ): + self.num_initial = num_initial + assert isinstance( + geometry, DictPointGeometry + ), f"DictPointGeometry is required. But got {geometry}" + super().__init__( + geometry, + pde, + alpha, + constraints, + resolution, + approximator=approximator, + meshtype=meshtype, + num_domain=num_domain, + num_boundary=num_boundary, + train_distribution=train_distribution, + anchors=anchors, + solution=solution, + num_test=num_test, + loss_fn=loss_fn, + loss_weights=loss_weights, + ) + + @run_if_all_none("train_x", "train_y") + def train_next_batch(self, batch_size=None): + assert isinstance( + self.geometry.geom, GeometryXTime + ), "GeometryXTime is required." + geometry = self.geometry.geom + alpha = self.alpha.value if isinstance(self.alpha, bst.State) else self.alpha + + if self.disc.meshtype == "static": + if geometry.geometry.idstr != "Interval": + raise ValueError("Only Interval supports static mesh.") + + nt = int(round(self.num_domain / (self.disc.resolution[0] - 2))) + 1 + self.frac_train = FractionalTime( + alpha, + geometry.geometry, + geometry.timedomain.t0, + geometry.timedomain.t1, + self.disc, + nt, + None, + ) + X = self.geometry.arr_to_dict(self.frac_train.get_x()) + self.train_x_all = X + if self.anchors is not None: + self.train_x_all = jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=-1), + self.anchors, + self.train_x_all, + ) + x_bc = self.bc_points() + + # Remove the initial and boundary points at the beginning of X, + # which are not considered in the integral matrix. + n_start = self.disc.resolution[0] + 2 * nt - 2 + X = jax.tree.map(lambda x: x[n_start:], X) + + elif self.disc.meshtype == "dynamic": + self.train_x_all = self.train_points() + train_x_all = self.geometry.dict_to_arr(self.train_x_all) + x_bc = self.bc_points() + + # FPDE is only applied to the non-boundary points. + x_f = train_x_all[~geometry.on_boundary(train_x_all)] + self.frac_train = FractionalTime( + alpha, + geometry.geometry, + geometry.timedomain.t0, + geometry.timedomain.t1, + self.disc, + None, + x_f, + ) + X = self.geometry.arr_to_dict(self.frac_train.get_x()) + + else: + raise ValueError("Unknown meshtype %s" % self.disc.meshtype) + + self.train_x = jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=-1), + x_bc, + X, + is_leaf=u.math.is_quantity, + ) + self.train_y = self.solution(self.train_x) if self.solution else None + return self.train_x, self.train_y + + @run_if_all_none("test_x", "test_y") + def test(self): + alpha = self.alpha.value if isinstance(self.alpha, bst.State) else self.alpha + assert isinstance( + self.geometry.geom, GeometryXTime + ), "GeometryXTime is required." + geometry = self.geometry.geom + if self.disc.meshtype == "static" and self.num_test is not None: + raise ValueError("Cannot use test points in static mesh.") + + if self.num_test is None: + n_bc = sum(self.num_bcs) + self.test_x = jax.tree.map(lambda x: x[n_bc:], self.train_x) + self.frac_test = self.frac_train + + else: + self.test_x = self.test_points() + test_x = self.geometry.dict_to_arr(self.test_x) + x_f = test_x[~geometry.on_boundary(test_x)] + self.frac_test = FractionalTime( + alpha, + geometry.geometry, + geometry.timedomain.t0, + geometry.timedomain.t1, + self.disc, + None, + x_f, + ) + self.test_x = self.geometry.arr_to_dict(self.frac_test.get_x()) + self.test_y = self.solution(self.test_x) if self.solution else None + return self.test_x, self.test_y + + def train_points(self): + X = super().train_points() + if self.num_initial > 0: + if self.train_distribution == "uniform": + tmp = self.geometry.uniform_initial_points(self.num_initial) + else: + tmp = self.geometry.random_initial_points( + self.num_initial, random=self.train_distribution + ) + X = jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=-1), + tmp, + X, + is_leaf=u.math.is_quantity, + ) + return X + + def get_int_matrix(self, training): + if training: + int_mat = self.frac_train.get_matrix(sparse=True) + num_bc = sum(self.num_bcs) + else: + int_mat = self.frac_test.get_matrix(sparse=True) + num_bc = 0 + + int_mat = array_ops.zero_padding(int_mat, ((num_bc, 0), (num_bc, 0))) + return int_mat + + +class Fractional(FractionalBase): + """Fractional derivative. + + Args: + x0: If ``disc.meshtype = static``, then x0 should be None; + if ``disc.meshtype = 'dynamic'``, then x0 are non-boundary points. + """ + + def _check_dynamic_stepsize(self): + h = 1 / self.disc.resolution[-1] + min_h = self.geom.mindist2boundary(self.x0) + if min_h < h: + warnings.warn( + "Warning: mesh step size %f is larger than the boundary distance %f." + % (h, min_h), + UserWarning, + ) + + def _init_weights(self): + """If ``disc.meshtype = 'static'``, then n is number of points; + if ``disc.meshtype = 'dynamic'``, then n is resolution lambda. + """ + n = ( + self.disc.resolution[0] + if self.disc.meshtype == "static" + else self.dynamic_dist2npts(self.geom.diam) + 1 + ) + w = [1.0] + for j in range(1, n): + w.append(w[-1] * (j - 1 - self.alpha) / j) + return np.asarray(w) + + def get_x_dynamic(self): + if np.any(self.geom.on_boundary(self.x0)): + raise ValueError("x0 contains boundary points.") + if self.geom.dim == 1: + dirns, dirn_w = [-1, 1], [1, 1] + elif self.geom.dim == 2: + gauss_x, gauss_w = np.polynomial.legendre.leggauss(self.disc.resolution[0]) + gauss_x, gauss_w = gauss_x.astype(bst.environ.dftype()), gauss_w.astype( + bst.environ.dftype() + ) + thetas = np.pi * gauss_x + np.pi + dirns = np.vstack((np.cos(thetas), np.sin(thetas))).T + dirn_w = np.pi * gauss_w + elif self.geom.dim == 3: + gauss_x, gauss_w = np.polynomial.legendre.leggauss( + max(self.disc.resolution[:2]) + ) + gauss_x, gauss_w = gauss_x.astype(bst.environ.dftype()), gauss_w.astype( + bst.environ.dftype() + ) + thetas = (np.pi * gauss_x[: self.disc.resolution[0]] + np.pi) / 2 + phis = np.pi * gauss_x[: self.disc.resolution[1]] + np.pi + dirns, dirn_w = [], [] + for i in range(self.disc.resolution[0]): + for j in range(self.disc.resolution[1]): + dirns.append( + [ + np.sin(thetas[i]) * np.cos(phis[j]), + np.sin(thetas[i]) * np.sin(phis[j]), + np.cos(thetas[i]), + ] + ) + dirn_w.append(gauss_w[i] * gauss_w[j] * np.sin(thetas[i])) + dirn_w = np.pi**2 / 2 * np.array(dirn_w) + x, self.w = [], [] + for x0i in self.x0: + xi = list( + map( + lambda dirn: self.geom.background_points( + x0i, dirn, self.dynamic_dist2npts, 0 + ), + dirns, + ) + ) + wi = list( + map( + lambda i: dirn_w[i] + * np.linalg.norm(xi[i][1] - xi[i][0]) ** (-self.alpha) + * self.get_weight(len(xi[i]) - 1), + range(len(dirns)), + ) + ) + # first order + xi, wi = zip(*map(self.modify_first_order, xi, wi)) + # second order + # xi, wi = zip(*map(self.modify_second_order, xi, wi)) + # third order + # xi, wi = zip(*map(self.modify_third_order, xi, wi)) + x.append(np.vstack(xi)) + self.w.append(array_ops.hstack(wi)) + self.xindex_start = np.hstack(([0], np.cumsum(list(map(len, x))))) + len( + self.x0 + ) + return np.vstack([self.x0] + x) + + def modify_first_order(self, x, w): + x = np.vstack(([2 * x[0] - x[1]], x[:-1])) + if not self.geom.inside(x[0:1])[0]: + return x[1:], w[1:] + return x, w + + def modify_second_order(self, x=None, w=None): + w0 = np.hstack(([bst.environ.dftype()(0)], w)) + w1 = np.hstack((w, [bst.environ.dftype()(0)])) + beta = 1 - self.alpha / 2 + w = beta * w0 + (1 - beta) * w1 + if x is None: + return w + x = np.vstack(([2 * x[0] - x[1]], x)) + if not self.geom.inside(x[0:1])[0]: + return x[1:], w[1:] + return x, w + + def modify_third_order(self, x=None, w=None): + w0 = np.hstack(([bst.environ.dftype()(0)], w)) + w1 = np.hstack((w, [bst.environ.dftype()(0)])) + w2 = np.hstack(([bst.environ.dftype()(0)] * 2, w[:-1])) + beta = 1 - self.alpha / 2 + w = ( + (-6 * beta**2 + 11 * beta + 1) / 6 * w0 + + (11 - 6 * beta) * (1 - beta) / 12 * w1 + + (6 * beta + 1) * (beta - 1) / 12 * w2 + ) + if x is None: + return w + x = np.vstack(([2 * x[0] - x[1]], x)) + if not self.geom.inside(x[0:1])[0]: + return x[1:], w[1:] + return x, w + + def get_matrix_static(self): + if not isinstance(self.alpha, (np.ndarray, jax.Array)): + int_mat = np.zeros( + (self.disc.resolution[0], self.disc.resolution[0]), + dtype=bst.environ.dftype(), + ) + h = self.geom.diam / (self.disc.resolution[0] - 1) + for i in range(1, self.disc.resolution[0] - 1): + # first order + int_mat[i, 1 : i + 2] = np.flipud(self.get_weight(i)) + int_mat[i, i - 1 : -1] += self.get_weight( + self.disc.resolution[0] - 1 - i + ) + # second order + # int_mat[i, 0:i+2] = np.flipud(self.modify_second_order(w=self.get_weight(i))) + # int_mat[i, i-1:] += self.modify_second_order(w=self.get_weight(self.disc.resolution[0]-1-i)) + # third order + # int_mat[i, 0:i+2] = np.flipud(self.modify_third_order(w=self.get_weight(i))) + # int_mat[i, i-1:] += self.modify_third_order(w=self.get_weight(self.disc.resolution[0]-1-i)) + return h ** (-self.alpha) * int_mat + int_mat = np.zeros((1, self.disc.resolution[0]), dtype=bst.environ.dftype()) + for i in range(1, self.disc.resolution[0] - 1): + # shifted + row = np.concatenate( + [ + np.zeros(1, dtype=bst.environ.dftype()), + np.flip(self.get_weight(i), (0,)), + np.zeros( + self.disc.resolution[0] - i - 2, dtype=bst.environ.dftype() + ), + ], + 0, + ) + row += np.concatenate( + [ + np.zeros(i - 1, dtype=bst.environ.dftype()), + self.get_weight(self.disc.resolution[0] - 1 - i), + np.zeros(1, dtype=bst.environ.dftype()), + ], + 0, + ) + row = np.expand_dims(row, 0) + int_mat = np.concatenate([int_mat, row], 0) + int_mat = np.concatenate( + [ + int_mat, + np.zeros([1, self.disc.resolution[0]], dtype=bst.environ.dftype()), + ], + 0, + ) + h = self.geom.diam / (self.disc.resolution[0] - 1) + return h ** (-self.alpha) * int_mat + + def get_matrix_dynamic(self, sparse): + if self.x is None: + raise AssertionError("No dynamic points") + + if sparse: + print("Generating sparse fractional matrix...") + dense_shape = (self.x0.shape[0], self.x.shape[0]) + indices, values = [], [] + beg = self.x0.shape[0] + for i in range(self.x0.shape[0]): + for _ in range(self.w[i].shape[0]): + indices.append([i, beg]) + beg += 1 + values = array_ops.hstack((values, self.w[i])) + return indices, values, dense_shape + + print("Generating dense fractional matrix...") + int_mat = np.zeros( + (self.x0.shape[0], self.x.shape[0]), dtype=bst.environ.dftype() + ) + beg = self.x0.shape[0] + for i in range(self.x0.shape[0]): + int_mat[i, beg : beg + self.w[i].size] = self.w[i] + beg += self.w[i].size + return int_mat + + +class FractionalTime(FractionalTimeBase): + """Fractional derivative with time. + + Args: + nt: If ``disc.meshtype = static``, then nt is the number of t points; + if ``disc.meshtype = 'dynamic'``, then nt is None. + x0: If ``disc.meshtype = static``, then x0 should be None; + if ``disc.meshtype = 'dynamic'``, then x0 are non-boundary points. + + Attributes: + nx: If ``disc.meshtype = static``, then nx is the number of x points; + if ``disc.meshtype = dynamic``, then nx is the resolution lambda. + """ + + def get_x_static(self): + # Points are ordered as initial --> boundary --> inside + x = self.geom.uniform_points(self.disc.resolution[0], True) + x = np.roll(x, 1)[:, 0] + dt = (self.tmax - self.tmin) / (self.nt - 1) + d = np.empty( + (self.disc.resolution[0] * self.nt, self.geom.dim + 1), dtype=x.dtype + ) + d[0 : self.disc.resolution[0], 0] = x + d[0 : self.disc.resolution[0], 1] = self.tmin + beg = self.disc.resolution[0] + for i in range(1, self.nt): + d[beg : beg + 2, 0] = x[:2] + d[beg : beg + 2, 1] = self.tmin + i * dt + beg += 2 + for i in range(1, self.nt): + d[beg : beg + self.disc.resolution[0] - 2, 0] = x[2:] + d[beg : beg + self.disc.resolution[0] - 2, 1] = self.tmin + i * dt + beg += self.disc.resolution[0] - 2 + return d + + def get_x_dynamic(self): + self.fracx = Fractional(self.alpha, self.geom, self.disc, self.x0[:, :-1]) + xx = self.fracx.get_x() + x = np.empty((len(xx), self.geom.dim + 1), dtype=xx.dtype) + x[: len(self.x0)] = self.x0 + beg = len(self.x0) + for i in range(len(self.x0)): + tmp = xx[self.fracx.xindex_start[i] : self.fracx.xindex_start[i + 1]] + x[beg : beg + len(tmp), :1] = tmp + x[beg : beg + len(tmp), -1] = self.x0[i, -1] + beg += len(tmp) + return x + + def get_matrix_static(self): + # Only consider the inside points + print("Warning: assume zero boundary condition.") + n = (self.disc.resolution[0] - 2) * (self.nt - 1) + int_mat = np.zeros((n, n), dtype=bst.environ.dftype()) + self.fracx = Fractional(self.alpha, self.geom, self.disc, None) + int_mat_one = self.fracx.get_matrix() + beg = 0 + for _ in range(self.nt - 1): + int_mat[ + beg : beg + self.disc.resolution[0] - 2, + beg : beg + self.disc.resolution[0] - 2, + ] = int_mat_one[1:-1, 1:-1] + beg += self.disc.resolution[0] - 2 + return int_mat diff --git a/deepxde/experimental/problem/ide.py b/deepxde/experimental/problem/ide.py new file mode 100644 index 000000000..57a2acf04 --- /dev/null +++ b/deepxde/experimental/problem/ide.py @@ -0,0 +1,182 @@ +from __future__ import annotations + +from typing import Callable, Sequence, Union, Optional, Dict, Any + +import brainstate as bst +import brainunit as u +import jax +import numpy as np + +from deepxde.experimental.geometry import DictPointGeometry +from deepxde.experimental.icbc.base import ICBC +from deepxde.utils.internal import run_if_all_none +from .pde import PDE + +__all__ = [ + "IDE", +] + +X = Dict[str, bst.typing.ArrayLike] +Y = Dict[str, bst.typing.ArrayLike] +InitMat = Any + + +class IDE(PDE): + """ + Integro-Differential Equation (IDE) solver class. + + This class extends the PDE solver to handle Integro-Differential Equations. + It specifically focuses on solving 1D problems with integral terms of the form: + int_0^x K(x, t) y(t) dt, where K is the kernel function. + + The IDE solver uses a Physics-Informed Neural Network (PINN) approach, + combining neural networks with numerical integration techniques to + approximate solutions to IDEs. + + Attributes: + kernel (Callable): The kernel function K(x, t) used in the integral term. + quad_deg (int): The degree of quadrature used for numerical integration. + quad_x (np.ndarray): Quadrature points for Gauss-Legendre quadrature. + quad_w (np.ndarray): Quadrature weights for Gauss-Legendre quadrature. + + Inherits from: + PDE: Base class for partial differential equation solvers. + + Note: + - This implementation currently supports only 1D problems. + - The solver uses Gauss-Legendre quadrature for numerical integration. + - The neural network approximator and other PDE-related functionalities + are inherited from the parent PDE class. + """ + + def __init__( + self, + geometry: DictPointGeometry, + ide: Callable[[X, Y, InitMat], Any], + constraints: Union[ICBC, Sequence[ICBC]], + quad_deg: int, + approximator: Optional[bst.nn.Module] = None, + kernel: Callable = None, + num_domain: int = 0, + num_boundary: int = 0, + train_distribution: str = "Hammersley", + anchors=None, + solution=None, + num_test: int = None, + loss_fn: str | Callable = "MSE", + loss_weights: Sequence[float] = None, + ): + """ + Initialize the IDE (Integro-Differential Equation) solver. + + Args: + geometry (DictPointGeometry): The geometry of the problem domain. + ide (Callable[[X, Y, InitMat], Any]): The IDE function to be solved. + constraints (Union[ICBC, Sequence[ICBC]]): Initial and boundary conditions. + quad_deg (int): The degree of quadrature for numerical integration. + approximator (Optional[bst.nn.Module], optional): The neural network approximator. Defaults to None. + kernel (Callable, optional): The kernel function for the integral term. Defaults to None. + num_domain (int, optional): Number of domain points. Defaults to 0. + num_boundary (int, optional): Number of boundary points. Defaults to 0. + train_distribution (str, optional): Distribution method for training points. Defaults to "Hammersley". + anchors (optional): Anchor points for the geometry. Defaults to None. + solution (optional): The analytical solution if available. Defaults to None. + num_test (int, optional): Number of test points. Defaults to None. + loss_fn (str | Callable, optional): Loss function to be used. Defaults to 'MSE'. + loss_weights (Sequence[float], optional): Weights for different components of the loss. Defaults to None. + + Returns: + None + """ + self.kernel = kernel or (lambda x, *args: np.ones((len(x), 1))) + self.quad_deg = quad_deg + self.quad_x, self.quad_w = np.polynomial.legendre.leggauss(quad_deg) + self.quad_x = self.quad_x.astype(bst.environ.dftype()) + self.quad_w = self.quad_w.astype(bst.environ.dftype()) + + super().__init__( + geometry, + ide, + constraints, + approximator=approximator, + num_domain=num_domain, + num_boundary=num_boundary, + train_distribution=train_distribution, + anchors=anchors, + solution=solution, + num_test=num_test, + loss_fn=loss_fn, + loss_weights=loss_weights, + ) + + def call_pde_errors(self, inputs, outputs, **kwargs): + bcs_start = np.cumsum([0] + self.num_bcs) + fit = bst.environ.get("fit") + int_mat = self.get_int_matrix(fit) + pde_errors = self.pde(inputs, outputs, int_mat, **kwargs) + return jax.tree.map(lambda x: x[bcs_start[-1] :], pde_errors) + + @run_if_all_none("train_x", "train_y") + def train_next_batch(self, batch_size=None): + self.train_x_all = self.train_points() + x_bc = self.bc_points() + x_quad = self.quad_points(self.train_x_all) + self.train_x = jax.tree.map( + lambda x, y, z: u.math.concatenate((x, y, z), axis=0), + x_bc, + self.train_x_all, + x_quad, + is_leaf=u.math.is_quantity, + ) + self.train_y = self.solution(self.train_x) if self.solution else None + return self.train_x, self.train_y + + @run_if_all_none("test_x", "test_y") + def test(self): + if self.num_test is None: + self.test_x = self.train_x_all + else: + self.test_x = self.test_points() + x_quad = self.quad_points(self.test_x) + self.test_x = jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=0), + self.test_x, + x_quad, + is_leaf=u.math.is_quantity, + ) + self.test_y = self.solution(self.test_x) if self.solution else None + return self.test_x, self.test_y + + def test_points(self): + return self.geometry.uniform_points(self.num_test, True) + + def quad_points(self, X): + fn = lambda xs: (jax.vmap(lambda x: (self.quad_x + 1) * x / 2)(xs)).flatten() + return jax.tree.map(fn, X, is_leaf=u.math.is_quantity) + + def get_int_matrix(self, training): + def get_quad_weights(x): + return self.quad_w * x / 2 + + with jax.ensure_compile_time_eval(): + if training: + num_bc = sum(self.num_bcs) + X = self.train_x + else: + num_bc = 0 + X = self.test_x + + X = np.asarray(self.geometry.dict_to_arr(X)) + if training or self.num_test is None: + num_f = tuple(self.train_x_all.values())[0].shape[0] + else: + num_f = self.num_test + + int_mat = np.zeros((num_bc + num_f, X.size), dtype=bst.environ.dftype()) + for i in range(num_f): + x = X[i + num_bc, 0] + beg = num_f + num_bc + self.quad_deg * i + end = beg + self.quad_deg + K = np.ravel(self.kernel(np.full((self.quad_deg, 1), x), X[beg:end])) + int_mat[i + num_bc, beg:end] = get_quad_weights(x) * K + return int_mat diff --git a/deepxde/experimental/problem/pde.py b/deepxde/experimental/problem/pde.py new file mode 100644 index 000000000..2ba970595 --- /dev/null +++ b/deepxde/experimental/problem/pde.py @@ -0,0 +1,562 @@ +from __future__ import annotations + +from typing import Callable, Sequence, Union, Optional, Dict, List + +import brainstate as bst +import brainunit as u +import jax.tree +import numpy as np + +from deepxde.experimental import utils +from deepxde.experimental.geometry import GeometryXTime, DictPointGeometry +from deepxde.utils.internal import run_if_all_none +from .base import Problem +from ..icbc.base import ICBC + +__all__ = ["PDE", "TimePDE"] + + +class PDE(Problem): + """ODE or time-independent PDE solver. + + Args: + geometry: Instance of ``Geometry``. + constraints: A boundary condition or a list of boundary conditions. Use ``[]`` if no + boundary condition. + approximator: A neural network trainer for approximating the solution. + num_domain (int): The number of training points sampled inside the domain. + num_boundary (int): The number of training points sampled on the boundary. + train_distribution (string): The distribution to sample training points. One of + the following: "uniform" (equispaced grid), "pseudo" (pseudorandom), "LHS" + (Latin hypercube sampling), "Halton" (Halton sequence), "Hammersley" + (Hammersley sequence), or "Sobol" (Sobol sequence). + anchors: A Numpy array of training points, in addition to the `num_domain` and + `num_boundary` sampled points. + exclusions: A Numpy array of points to be excluded for training. + solution: The reference solution. + num_test: The number of points sampled inside the domain for testing PDE loss. + The testing points for BCs/ICs are the same set of points used for training. + If ``None``, then the training points will be used for testing. + + Warning: + The testing points include points inside the domain and points on the boundary, + and they may not have the same density, and thus the entire testing points may + not be uniformly distributed. As a result, if you have a reference solution + (`solution`) and would like to compute a metric such as + + .. code-block:: python + + Trainer.compile(metrics=["l2 relative error"]) + + then the metric may not be very accurate. To better compute a metric, you can + sample the points manually, and then use ``Trainer.predict()`` to predict the + solution on these points and compute the metric: + + .. code-block:: python + + x = geometry.uniform_points(num, boundary=True) + y_true = ... + y_pred = trainer.predict(x) + error= experimental.metrics.l2_relative_error(y_true, y_pred) + + Attributes: + train_x_all: A Numpy array of points for PDE training. `train_x_all` is + unordered, and does not have duplication. If there is PDE, then + `train_x_all` is used as the training points of PDE. + train_x_bc: A Numpy array of the training points for BCs. `train_x_bc` is + constructed from `train_x_all` at the first step of training, by default it + won't be updated when `train_x_all` changes. To update `train_x_bc`, set it + to `None` and call `bc_points`, and then update the loss function by + ``trainer.compile()``. + num_bcs (list): `num_bcs[i]` is the number of points for `constraints[i]`. + train_x: A Numpy array of the points fed into the network for training. + `train_x` is ordered from BC points (`train_x_bc`) to PDE points + (`train_x_all`), and may have duplicate points. + test_x: A Numpy array of the points fed into the network for testing, ordered + from BCs to PDE. The BC points are exactly the same points in `train_x_bc`. + """ + + def __init__( + self, + geometry: DictPointGeometry, + pde: Callable, + constraints: Union[ICBC, Sequence[ICBC]], + approximator: Optional[bst.nn.Module] = None, + solution: Callable[[bst.typing.PyTree], bst.typing.PyTree] = None, + loss_fn: str | Callable = "MSE", + num_domain: int = 0, + num_boundary: int = 0, + num_test: int = None, + train_distribution: str = "Hammersley", + anchors: Optional[bst.typing.ArrayLike] = None, + exclusions=None, + loss_weights: Sequence[float] = None, + ): + super().__init__( + approximator=approximator, loss_fn=loss_fn, loss_weights=loss_weights + ) + + assert isinstance( + geometry, DictPointGeometry + ), f"Expected DictPointGeometry, got {type(geometry)}" + # geometry is a Geometry object + self.geometry = geometry + + # PDE function + self._pde = pde + if pde is not None: + assert callable(pde), f"Expected callable, got {type(pde)}" + + # initial and boundary conditions + self.constraints = ( + constraints if isinstance(constraints, (list, tuple)) else [constraints] + ) + for bc in self.constraints: + assert isinstance(bc, ICBC), f"Expected ICBC, got {type(bc)}" + bc.apply_geometry(self.geometry) + bc.apply_problem(self) + + # anchors + self.anchors = ( + None + if anchors is None + else jax.tree.map(lambda x: x.astype(bst.environ.dftype()), anchors) + ) + + # solution + if solution is not None: + assert callable(solution), f"Expected callable, got {type(solution)}" + self.solution = solution + + # exclusions + self.exclusions = exclusions + + # others + self.num_domain = num_domain + self.num_boundary = num_boundary + self.num_test = num_test + self.train_distribution = train_distribution + + # training data + self.train_x_all: Dict[str, bst.typing.ArrayLike] = None + self.train_x_bc: Dict[str, bst.typing.ArrayLike] = None + self.num_bcs: List[int] = None + + # these include both BC and PDE points + self.train_x: Dict[str, bst.typing.ArrayLike] = None + self.train_y: Dict[str, bst.typing.ArrayLike] = None + self.test_x: Dict[str, bst.typing.ArrayLike] = None + self.test_y: Dict[str, bst.typing.ArrayLike] = None + + # generate training data and testing data + self.train_next_batch() + self.test() + + def pde(self, *args, **kwargs): + """ + Compute the PDE residual. + """ + if self._pde is not None: + return self._pde(*args, **kwargs) + else: + raise NotImplementedError("PDE is not defined.") + + def call_pde_errors(self, inputs, outputs, **kwargs): + bcs_start = np.cumsum([0] + self.num_bcs) + + # PDE inputs and outputs, computing PDE losses + pde_inputs = jax.tree.map(lambda x: x[bcs_start[-1] :], inputs) + pde_outputs = jax.tree.map(lambda x: x[bcs_start[-1] :], outputs) + pde_kwargs = jax.tree.map(lambda x: x[bcs_start[-1] :], kwargs) + + # error + pde_errors = self.pde(pde_inputs, pde_outputs, **pde_kwargs) + return pde_errors + + def call_bc_errors(self, loss_fns, loss_weights, inputs, outputs, **kwargs): + bcs_start = np.cumsum([0] + self.num_bcs) + losses = [] + for i, bc in enumerate(self.constraints): + # ICBC inputs and outputs, computing ICBC losses + beg, end = bcs_start[i], bcs_start[i + 1] + icbc_inputs = jax.tree.map(lambda x: x[beg:end], inputs) + icbc_outputs = jax.tree.map(lambda x: x[beg:end], outputs) + icbc_kwargs = jax.tree.map(lambda x: x[beg:end], kwargs) + + # error + error: Dict = bc.error(icbc_inputs, icbc_outputs, **icbc_kwargs) + + # loss and weights + f_loss = loss_fns[i] + if loss_weights is not None: + w = loss_weights[i] + bc_loss = jax.tree.map( + lambda err: f_loss(u.math.zeros_like(err), err) * w, error + ) + else: + bc_loss = jax.tree.map( + lambda err: f_loss(u.math.zeros_like(err), err), error + ) + + # append to losses + losses.append({f"ibc{i}": bc_loss}) + return losses + + @utils.check_not_none("num_bcs") + def losses(self, inputs, outputs, targets, **kwargs): + # PDE inputs and outputs, computing PDE losses + pde_errors = self.call_pde_errors(inputs, outputs, **kwargs) + if not isinstance(pde_errors, (list, tuple)): + pde_errors = [pde_errors] + + # loss functions + if not isinstance(self.loss_fn, (list, tuple)): + loss_fn = [self.loss_fn] * (len(pde_errors) + len(self.constraints)) + else: + loss_fn = self.loss_fn + if len(loss_fn) != len(pde_errors) + len(self.constraints): + raise ValueError( + f"There are {len(pde_errors) + len(self.constraints)} errors, " + f"but only {len(loss_fn)} losses." + ) + + # PDE loss + losses = [ + loss_fn[i](u.math.zeros_like(error), error) + for i, error in enumerate(pde_errors) + ] + if self.loss_weights is not None: + n_loss = len(losses) + len(self.constraints) + if len(self.loss_weights) != len(losses) + len(self.constraints): + raise ValueError( + f"Expected {n_loss} weights, got {len(self.loss_weights)}. " + f"There are {len(losses)} PDE losses and {len(self.constraints)} IC+BC losses." + ) + del n_loss + losses = [ + w * loss for w, loss in zip(self.loss_weights[: len(losses)], losses) + ] + + # loss of boundary or initial conditions + bc_errors = self.call_bc_errors( + loss_fn[len(pde_errors) :], + ( + self.loss_weights[len(pde_errors) :] + if self.loss_weights is not None + else None + ), + inputs, + outputs, + **kwargs, + ) + losses.extend(bc_errors) + return losses + + @run_if_all_none("train_x", "train_y") + def train_next_batch(self, batch_size=None): + # Generate `self.train_x_all` + self.train_points() + + # Generate `self.num_bcs` and `self.train_x_bc` + self.bc_points() + + if self.pde is not None: + # include data in boundary, initial conditions, and PDE + if len(self.train_x_bc): + self.train_x = jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=0), + self.train_x_bc, + self.train_x_all, + ) + else: + self.train_x = self.train_x_all + + else: + # only include data in boundary or initial conditions + self.train_x = self.train_x_bc + + self.train_y = ( + self.solution(self.train_x) if self.solution is not None else None + ) + return self.train_x, self.train_y + + @run_if_all_none("test_x", "test_y") + def test(self): + if self.num_test is None: + # assign the training points to the testing points + self.test_x = self.train_x + else: + # Generate `self.test_x`, resampling the test points + self.test_x = self.test_points() + + # solution on the test points + self.test_y = self.solution(self.test_x) if self.solution is not None else None + return self.test_x, self.test_y + + def resample_train_points(self, pde_points=True, bc_points=True): + """Resample the training points for PDE and/or BC.""" + if pde_points: + self.train_x_all = None + if bc_points: + self.train_x_bc = None + self.train_x, self.train_y = None, None + self.train_next_batch() + + def add_anchors(self, anchors: bst.typing.PyTree): + """ + Add new points for training PDE losses. + + The BC points will not be updated. + """ + anchors = jax.tree.map(lambda x: x.astype(bst.environ.dftype()), anchors) + if self.anchors is None: + self.anchors = anchors + else: + self.anchors = jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=-1), self.anchors, anchors + ) + + # include anchors in the training points + self.train_x_all = jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=-1), anchors, self.train_x_all + ) + + if self.pde is not None: + # include data in boundary, initial conditions, and PDE + self.train_x = jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=-1), + self.bc_points(), + self.train_x_all, + ) + + else: + # only include data in boundary or initial conditions + self.train_x = self.bc_points() + + # solution on the training points + self.train_y = ( + self.solution(self.train_x) if self.solution is not None else None + ) + + def replace_with_anchors(self, anchors): + """Replace the current PDE training points with anchors. + + The BC points will not be changed. + """ + self.anchors = jax.tree.map(lambda x: x.astype(bst.environ.dftype()), anchors) + self.train_x_all = self.anchors + + if self.pde is not None: + # include data in boundary, initial conditions, and PDE + self.train_x = jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=-1), + self.bc_points(), + self.train_x_all, + ) + else: + # only include data in boundary or initial conditions + self.train_x = self.bc_points() + + # solution on the training points + self.train_y = ( + self.solution(self.train_x) if self.solution is not None else None + ) + + @run_if_all_none("train_x_all") + def train_points(self): + X = None + + # sampling points in the domain + if self.num_domain > 0: + if self.train_distribution == "uniform": + X = self.geometry.uniform_points(self.num_domain, boundary=False) + else: + X = self.geometry.random_points( + self.num_domain, random=self.train_distribution + ) + + # sampling points on the boundary + if self.num_boundary > 0: + if self.train_distribution == "uniform": + tmp = self.geometry.uniform_boundary_points(self.num_boundary) + else: + tmp = self.geometry.random_boundary_points( + self.num_boundary, random=self.train_distribution + ) + X = ( + tmp + if X is None + else jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=0), X, tmp + ) + ) + + # add anchors + if self.anchors is not None: + X = ( + self.anchors + if X is None + else jax.tree.map( + lambda x, y: u.math.concatenate((x, y), axis=0), self.anchors, X + ) + ) + + # exclude points + if self.exclusions is not None: + raise NotImplementedError + + # TODO: Check if this is correct + def is_not_excluded(x): + return not np.any([np.allclose(x, y) for y in self.exclusions]) + + X = np.array(list(filter(is_not_excluded, X))) + + # save the training points + self.train_x_all = X + return X + + @run_if_all_none("train_x_bc") + def bc_points(self): + """ + Generate boundary condition points. + + Returns: + np.ndarray: The boundary condition points. + """ + x_bcs = [bc.collocation_points(self.train_x_all) for bc in self.constraints] + # self.num_bcs = list([len(x[self.geometry.names[0]]) for x in x_bcs]) + self.num_bcs = list([len(tuple(x.values())[0]) for x in x_bcs]) + if len(self.num_bcs): + self.train_x_bc = jax.tree.map( + lambda *x: u.math.concatenate(x, axis=0), *x_bcs + ) + else: + self.train_x_bc = dict() + return self.train_x_bc + + def test_points(self): + # different points from self.train_x_all + x = self.geometry.uniform_points(self.num_test, boundary=False) + + # # different BC points from self.train_x_bc + # x_bcs = [bc.collocation_points(x) for bc in self.constraints] + # x_bcs = jax.tree.map(lambda *x: u.math.vstack(x), *x_bcs) + + # reuse the same BC points + if len(self.num_bcs): + x_bcs = self.train_x_bc + x = jax.tree.map( + lambda x_, y_: u.math.concatenate((x_, y_), axis=0), x_bcs, x + ) + return x + + +class TimePDE(PDE): + """Time-dependent PDE solver. + + This class extends the PDE solver to handle time-dependent partial differential equations. + It provides functionality to generate training points for both spatial and temporal domains, + including initial condition points. + + Args: + geometry (DictPointGeometry): The geometry of the problem domain, including both spatial and temporal dimensions. + pde (Callable): The partial differential equation to be solved. + constraints (Union[ICBC, Sequence[ICBC]]): Initial and boundary conditions for the PDE. + approximator (Optional[bst.nn.Module]): The neural network used to approximate the solution. Defaults to None. + num_domain (int): Number of training points in the domain. Defaults to 0. + num_boundary (int): Number of training points on the boundary. Defaults to 0. + num_initial (int): Number of training points for the initial condition. Defaults to 0. + train_distribution (str): Method for distributing training points. Options include "uniform" and "Hammersley". Defaults to "Hammersley". + anchors (Optional): Specific points to include in the training set. Defaults to None. + exclusions (Optional): Points to exclude from the training set. Defaults to None. + solution (Optional): The analytical solution to the PDE, if known. Defaults to None. + num_test (Optional[int]): Number of test points. If None, training points are used for testing. Defaults to None. + loss_fn (Union[str, Callable]): Loss function for training. Can be a string identifier or a callable. Defaults to 'MSE'. + loss_weights (Optional[Sequence[float]]): Weights for different components of the loss function. Defaults to None. + + Attributes: + num_initial (int): Number of initial condition points. + geometry (GeometryXTime): The geometry of the problem, including time. + + Methods: + train_points(): Generates training points for the time-dependent PDE, including initial condition points. + + Note: + This class is specifically designed for time-dependent PDEs and extends the functionality + of the base PDE class to handle the temporal aspect of the problem. + """ + + def __init__( + self, + geometry: DictPointGeometry, + pde: Callable, + constraints: Union[ICBC, Sequence[ICBC]], + approximator: Optional[bst.nn.Module] = None, + num_domain: int = 0, + num_boundary: int = 0, + num_initial: int = 0, + train_distribution: str = "Hammersley", + anchors=None, + exclusions=None, + solution=None, + num_test: int = None, + loss_fn: str | Callable = "MSE", + loss_weights: Sequence[float] = None, + ): + self.num_initial = num_initial + super().__init__( + geometry, + pde, + constraints, + num_domain=num_domain, + num_boundary=num_boundary, + train_distribution=train_distribution, + anchors=anchors, + exclusions=exclusions, + solution=solution, + num_test=num_test, + approximator=approximator, + loss_fn=loss_fn, + loss_weights=loss_weights, + ) + + @run_if_all_none("train_x_all") + def train_points(self): + """ + Generate training points for the time-dependent PDE solver. + + This method extends the base PDE class's train_points method by adding + initial condition points for time-dependent problems. + + Returns: + X (Dict[str, bst.typing.ArrayLike]): A dictionary containing the generated training points. + The keys are the names of the spatial dimensions and time, and the values are + the corresponding coordinates. + + Note: + - The method uses the geometry attribute (of type GeometryXTime) to generate points. + - If num_initial > 0, it adds initial condition points to the training set. + - The distribution of initial points can be either uniform or based on the specified + train_distribution. + - If exclusions are specified, the method filters out excluded points. + """ + self.geometry: GeometryXTime + + X = super().train_points() + + if self.num_initial > 0: + if self.train_distribution == "uniform": + tmp = self.geometry.uniform_initial_points(self.num_initial) + else: + tmp = self.geometry.random_initial_points( + self.num_initial, random=self.train_distribution + ) + if self.exclusions is not None: + + def is_not_excluded(x): + return not np.any([np.allclose(x, y) for y in self.exclusions]) + + tmp = np.array(list(filter(is_not_excluded, tmp))) + X = jax.tree.map(lambda x, y: u.math.concatenate((x, y), axis=0), X, tmp) + self.train_x_all = X + return X diff --git a/deepxde/experimental/problem/pde_operator.py b/deepxde/experimental/problem/pde_operator.py new file mode 100644 index 000000000..8860d50ba --- /dev/null +++ b/deepxde/experimental/problem/pde_operator.py @@ -0,0 +1,388 @@ +from __future__ import annotations + +from typing import Callable, Sequence, Union, Optional, Any, Dict + +import brainstate as bst +import brainunit as u +import jax +import numpy as np + +from deepxde.data.function_spaces import FunctionSpace +from deepxde.data.sampler import BatchSampler +from deepxde.experimental.geometry import DictPointGeometry +from deepxde.experimental.icbc.base import ICBC +from deepxde.utils.internal import run_if_all_none +from .pde import TimePDE + +__all__ = [ + "PDEOperator", + "PDEOperatorCartesianProd", +] + +Inputs = Any +Outputs = Any +Auxiliary = Any +Residual = Any + + +class PDEOperator(TimePDE): + """ + PDE solution operator. + + Args: + function_space: Instance of ``experimental.fnspace.FunctionSpace``. + evaluation_points: A NumPy array of shape (n_points, dim). Discretize the input + function sampled from `function_space` using point-wise evaluations at a set + of points as the input of the branch net. + num_function (int): The number of functions for training. + function_variables: ``None`` or a list of integers. The functions in the + `function_space` may not have the same domain as the PDE. For example, the + PDE is defined on a spatio-temporal domain (`x`, `t`), but the function is + IC, which is only a function of `x`. In this case, we need to specify the + variables of the function by `function_variables=[0]`, where `0` indicates + the first variable `x`. If ``None``, then we assume the domains of the + function and the PDE are the same. + num_fn_test: The number of functions for testing PDE loss. The testing functions + for BCs/ICs are the same functions used for training. If ``None``, then the + training functions will be used for testing. + """ + + def __init__( + self, + geometry: DictPointGeometry, + pde: Callable[[Inputs, Outputs, Auxiliary], Residual], + constraints: Union[ICBC, Sequence[ICBC]], + function_space: FunctionSpace, + evaluation_points, + num_function: int, + function_variables: Optional[Sequence[int]] = None, + num_test: int = None, + approximator: Optional[bst.nn.Module] = None, + solution: Callable[[bst.typing.PyTree], bst.typing.PyTree] = None, + num_domain: int = 0, # for space PDE + num_boundary: int = 0, # for space PDE + num_initial: int = 0, # for time PDE + num_fn_test: int = None, + train_distribution: str = "Hammersley", + anchors: Optional[bst.typing.ArrayLike] = None, + exclusions=None, + loss_fn: str | Callable = "MSE", + loss_weights: Sequence[float] = None, + ): + + assert isinstance(function_space, FunctionSpace), ( + f"Expected `function_space` to be an instance of `FunctionSpace`, " + f"but got {type(function_space)}." + ) + self.fn_space = function_space + self.eval_pts = evaluation_points + self.func_vars = ( + function_variables + if function_variables is not None + else list(range(geometry.dim)) + ) + + self.num_fn = num_function + self.num_fn_test = num_fn_test + + self.fn_train_bc = None + self.fn_train_x = None + self.fn_train_y = None + self.fn_train_aux_vars = None + self.fn_test_x = None + self.fn_test_y = None + self.fn_test_aux_vars = None + + super().__init__( + geometry=geometry, + pde=pde, + constraints=constraints, + approximator=approximator, + loss_fn=loss_fn, + loss_weights=loss_weights, + num_initial=num_initial, + num_domain=num_domain, + num_boundary=num_boundary, + train_distribution=train_distribution, + anchors=anchors, + exclusions=exclusions, + solution=solution, + num_test=num_test, + ) + + def call_pde_errors(self, inputs, outputs, **kwargs): + num_bcs = self.num_bcs + self.num_bcs = self.num_fn_bcs + losses = super().call_pde_errors(inputs, outputs, **kwargs) + self.num_bcs = num_bcs + return losses + + def call_bc_errors(self, loss_fns, loss_weights, inputs, outputs, **kwargs): + num_bcs = self.num_bcs + self.num_bcs = self.num_fn_bcs + losses = super().call_bc_errors( + loss_fns, loss_weights, inputs, outputs, **kwargs + ) + self.num_bcs = num_bcs + return losses + + @run_if_all_none("fn_train_x", "fn_train_y", "fn_train_aux_vars") + def train_next_batch(self, batch_size=None): + super().train_next_batch(batch_size) + + self.num_fn_bcs = [n * self.num_fn for n in self.num_bcs] + func_feats = self.fn_space.random(self.num_fn) + func_vals = self.fn_space.eval_batch(func_feats, self.eval_pts) + v, x, vx = self.bc_inputs(func_feats, func_vals) + + if self._pde is not None: + v_pde, x_pde, vx_pde = self.gen_inputs( + func_feats, func_vals, self.geometry.dict_to_arr(self.train_x_all) + ) + v = np.vstack((v, v_pde)) + x = np.vstack((x, x_pde)) + vx = np.vstack((vx, vx_pde)) + self.fn_train_x = (v, x) + self.fn_train_aux_vars = {"aux": vx} + return self.fn_train_x, self.fn_train_x, self.fn_train_aux_vars + + @run_if_all_none("fn_test_x", "fn_test_y", "fn_test_aux_vars") + def test(self): + super().test() + + if self.num_fn_test is None: + self.fn_test_x = self.fn_train_x + self.fn_test_aux_vars = self.fn_train_aux_vars + + else: + func_feats = self.fn_space.random(self.num_fn_test) + func_vals = self.fn_space.eval_batch(func_feats, self.eval_pts) + # TODO: Use different BC data from self.fn_train_x + v, x, vx = self.train_bc + if self._pde is not None: + test_x = self.geometry.dict_to_arr(self.test_x) + v_pde, x_pde, vx_pde = self.gen_inputs( + func_feats, func_vals, test_x[sum(self.num_bcs) :] + ) + v = np.vstack((v, v_pde)) + x = np.vstack((x, x_pde)) + vx = np.vstack((vx, vx_pde)) + self.fn_test_x = (v, x) + self.fn_test_aux_vars = {"aux": vx} + return self.fn_test_x, self.fn_test_y, self.fn_test_aux_vars + + def gen_inputs(self, func_feats, func_vals, points): + # Format: + # v1, x_1 + # ... + # v1, x_N1 + # v2, x_1 + # ... + # v2, x_N1 + v = np.repeat(func_vals, len(points), axis=0) + x = np.tile(points, (len(func_feats), 1)) + vx = self.fn_space.eval_batch(func_feats, points[:, self.func_vars]).reshape( + -1, 1 + ) + return v, x, vx + + def bc_inputs(self, func_feats, func_vals): + if len(self.constraints) == 0: + self.train_bc = ( + np.empty((0, len(self.eval_pts)), dtype=bst.environ.dftype()), + np.empty((0, self.geometry.dim), dtype=bst.environ.dftype()), + np.empty((0, 1), dtype=bst.environ.dftype()), + ) + return self.train_bc + + v, x, vx = [], [], [] + bcs_start = np.cumsum([0] + self.num_bcs) + train_x_bc = self.geometry.dict_to_arr(self.train_x_bc) + for i, _ in enumerate(self.num_bcs): + beg, end = bcs_start[i], bcs_start[i + 1] + vi, xi, vxi = self.gen_inputs(func_feats, func_vals, train_x_bc[beg:end]) + v.append(vi) + x.append(xi) + vx.append(vxi) + self.train_bc = (np.vstack(v), np.vstack(x), np.vstack(vx)) + return self.train_bc + + def resample_train_points(self, pde_points=True, bc_points=True): + """ + Resample the training points for the operator. + """ + super().resample_train_points(pde_points=pde_points, bc_points=bc_points) + + self.fn_train_x, self.fn_train_x, self.fn_train_aux_vars = None, None, None + self.train_next_batch() + + +class PDEOperatorCartesianProd(TimePDE): + """ + PDE solution operator with problem in the format of Cartesian product. + + Args: + pde: Instance of ``experimental.problem.PDE`` or ``experimental.problem.TimePDE``. + function_space: Instance of ``experimental.problem.FunctionSpace``. + evaluation_points: A NumPy array of shape (n_points, dim). Discretize the input + function sampled from `function_space` using pointwise evaluations at a set + of points as the input of the branch net. + num_function (int): The number of functions for training. + function_variables: ``None`` or a list of integers. The functions in the + `function_space` may not have the same domain as the PDE. For example, the + PDE is defined on a spatio-temporal domain (`x`, `t`), but the function is + IC, which is only a function of `x`. In this case, we need to specify the + variables of the function by `function_variables=[0]`, where `0` indicates + the first variable `x`. If ``None``, then we assume the domains of the + function and the PDE are the same. + num_test: The number of functions for testing PDE loss. The testing functions + for BCs/ICs are the same functions used for training. If ``None``, then the + training functions will be used for testing. + batch_size: Integer or ``None``. + + Attributes: + train_x: A tuple of two Numpy arrays (v, x) fed into PIDeepONet for training. v + is the function input to the branch net and has the shape (`N1`, `dim1`); x + is the point input to the trunk net and has the shape (`N2`, `dim2`). + """ + + def __init__( + self, + geometry: DictPointGeometry, + pde: Callable[[Inputs, Outputs, Auxiliary], Residual], + constraints: Union[ICBC, Sequence[ICBC]], + function_space: FunctionSpace, + evaluation_points, + num_function: int, + function_variables: Optional[Sequence[int]] = None, + num_test: int = None, + approximator: Optional[bst.nn.Module] = None, + solution: Callable[[bst.typing.PyTree], bst.typing.PyTree] = None, + num_domain: int = 0, # for space PDE + num_boundary: int = 0, # for space PDE + num_initial: int = 0, # for time PDE + num_fn_test: int = None, # for function space + train_distribution: str = "Hammersley", + anchors: Optional[bst.typing.ArrayLike] = None, + exclusions=None, + loss_fn: str | Callable = "MSE", + loss_weights: Sequence[float] = None, + batch_size: int = None, + ): + + assert isinstance(function_space, FunctionSpace), ( + f"Expected `function_space` to be an instance of `FunctionSpace`, " + f"but got {type(function_space)}." + ) + self.fn_space = function_space + self.eval_pts = evaluation_points + self.func_vars = ( + function_variables + if function_variables is not None + else list(range(geometry.dim)) + ) + self.num_fn = num_function + self.num_fn_test = num_fn_test + + self.train_sampler = BatchSampler(self.num_fn, shuffle=True) + self.batch_size = batch_size + + self.fn_train_bc = None + self.fn_train_x = None + self.fn_train_y = None + self.fn_train_aux_vars = None + self.fn_test_x = None + self.fn_test_y = None + self.fn_test_aux_vars = None + + super().__init__( + geometry=geometry, + pde=pde, + constraints=constraints, + approximator=approximator, + loss_fn=loss_fn, + loss_weights=loss_weights, + num_initial=num_initial, + num_domain=num_domain, + num_boundary=num_boundary, + train_distribution=train_distribution, + anchors=anchors, + exclusions=exclusions, + solution=solution, + num_test=num_test, + ) + + def call_pde_errors(self, inputs, outputs, **kwargs): + bcs_start = np.cumsum([0] + self.num_bcs) + + # PDE inputs and outputs, computing PDE losses + pde_inputs = (inputs[0], jax.tree.map(lambda x: x[bcs_start[-1] :], inputs[1])) + pde_outputs = jax.tree.map(lambda x: x[:, bcs_start[-1] :], outputs) + pde_kwargs = jax.tree.map(lambda x: x[:, bcs_start[-1] :], kwargs) + + # error + pde_errors = self.pde(pde_inputs, pde_outputs, **pde_kwargs) + return pde_errors + + def call_bc_errors(self, loss_fns, loss_weights, inputs, outputs, **kwargs): + bcs_start = np.cumsum([0] + self.num_bcs) + losses = [] + for i, bc in enumerate(self.constraints): + # ICBC inputs and outputs, computing ICBC losses + beg, end = bcs_start[i], bcs_start[i + 1] + icbc_inputs = (inputs[0], jax.tree.map(lambda x: x[beg:end], inputs[1])) + icbc_outputs = jax.tree.map(lambda x: x[:, beg:end], outputs) + icbc_kwargs = jax.tree.map(lambda x: x[:, beg:end], kwargs) + + # error + error: Dict = bc.error(icbc_inputs, icbc_outputs, **icbc_kwargs) + + # loss and weights + f_loss = loss_fns[i] + if loss_weights is not None: + w = loss_weights[i] + bc_loss = jax.tree.map( + lambda err: f_loss(u.math.zeros_like(err), err) * w, error + ) + else: + bc_loss = jax.tree.map( + lambda err: f_loss(u.math.zeros_like(err), err), error + ) + + # append to losses + losses.append({f"ibc{i}": bc_loss}) + return losses + + def train_next_batch(self, batch_size=None): + super().train_next_batch(batch_size) + + if self.fn_train_x is None: + train_x = self.geometry.dict_to_arr(self.train_x) + func_feats = self.fn_space.random(self.num_fn) + func_vals = self.fn_space.eval_batch(func_feats, self.eval_pts) + vx = self.fn_space.eval_batch(func_feats, train_x[:, self.func_vars]) + self.fn_train_x = (func_vals, train_x) + self.fn_train_aux_vars = {"aux": vx} + + if self.batch_size is None: + return self.fn_train_x, self.train_y, self.fn_train_aux_vars + + indices = self.train_sampler.get_next(self.batch_size) + train_x = (self.fn_train_x[0][indices], self.fn_train_x[1]) + return train_x, self.train_y, {"aux": self.fn_train_aux_vars["aux"][indices]} + + @run_if_all_none("fn_test_x", "test_y", "fn_test_aux_vars") + def test(self): + super().test() + + if self.num_fn_test is None: + self.fn_test_x = self.fn_train_x + self.fn_test_aux_vars = self.fn_train_aux_vars + else: + test_x = self.geometry.dict_to_arr(self.test_x) + func_feats = self.fn_space.random(self.num_fn_test) + func_vals = self.fn_space.eval_batch(func_feats, self.eval_pts) + vx = self.fn_space.eval_batch(func_feats, test_x[:, self.func_vars]) + self.fn_test_x = (func_vals, test_x) + self.fn_test_aux_vars = {"aux": vx} + return self.fn_test_x, self.test_y, {"aux": self.fn_test_aux_vars} diff --git a/deepxde/experimental/utils/__init__.py b/deepxde/experimental/utils/__init__.py new file mode 100644 index 000000000..fa5ce829e --- /dev/null +++ b/deepxde/experimental/utils/__init__.py @@ -0,0 +1,6 @@ +"""Internal utilities.""" + +from . import array_ops +from ._convert import * +from .external import * +from .internal import * diff --git a/deepxde/experimental/utils/_convert.py b/deepxde/experimental/utils/_convert.py new file mode 100644 index 000000000..d3ed6240c --- /dev/null +++ b/deepxde/experimental/utils/_convert.py @@ -0,0 +1,52 @@ +from typing import Sequence, Dict + +import brainstate as bst +import brainunit as u +import numpy as np + +__all__ = [ + "array_to_dict", + "dict_to_array", +] + + +def array_to_dict( + x: bst.typing.ArrayLike, names: Sequence[str], keep_dim: bool = False +): + """ + Convert args to a dictionary. + + """ + if x.shape[-1] != len(names): + raise ValueError( + "The number of columns of x must be equal to the number of names." + ) + + if keep_dim: + return {key: x[..., i : i + 1] for i, key in enumerate(names)} + else: + return {key: x[..., i] for i, key in enumerate(names)} + + +def dict_to_array(d: Dict[str, bst.typing.ArrayLike], keep_dim: bool = False): + """ + Convert a dictionary to an array. + + Args: + d (dict): The dictionary. + keep_dim (bool): Whether to keep the dimension. + + Returns: + ndarray: The array. + """ + keys = tuple(d.keys()) + if isinstance(d[keys[0]], np.ndarray): + if keep_dim: + return np.concatenate([d[key] for key in keys], axis=-1) + else: + return np.stack([d[key] for key in keys], axis=-1) + else: + if keep_dim: + return u.math.concatenate([d[key] for key in keys], axis=-1) + else: + return u.math.stack([d[key] for key in keys], axis=-1) diff --git a/deepxde/experimental/utils/array_ops.py b/deepxde/experimental/utils/array_ops.py new file mode 100644 index 000000000..694f2edc0 --- /dev/null +++ b/deepxde/experimental/utils/array_ops.py @@ -0,0 +1,44 @@ +from typing import Sequence + +import brainstate as bst +import brainunit as u +import jax +import numpy as np + + +def is_tensor(obj): + return isinstance(obj, (jax.Array, u.Quantity, np.ndarray)) + + +def istensorlist(values): + return any(map(is_tensor, values)) + + +def convert_to_array(value: Sequence): + """Convert a list of numpy arrays or tensors to a numpy array or a tensor.""" + if istensorlist(value): + return np.stack(value, axis=0) + return np.array(value, dtype=bst.environ.dftype()) + + +def hstack(tup): + if not is_tensor(tup[0]) and isinstance(tup[0], list) and tup[0] == []: + tup = list(tup) + if istensorlist(tup[1:]): + tup[0] = np.asarray([], dtype=bst.environ.dftype()) + else: + tup[0] = np.array([], dtype=bst.environ.dftype()) + return np.concatenate(tup, 0) if is_tensor(tup[0]) else np.hstack(tup) + + +def zero_padding(array, pad_width): + # SparseTensor + if isinstance(array, (list, tuple)) and len(array) == 3: + indices, values, dense_shape = array + indices = [(i + pad_width[0][0], j + pad_width[1][0]) for i, j in indices] + dense_shape = ( + dense_shape[0] + sum(pad_width[0]), + dense_shape[1] + sum(pad_width[1]), + ) + return indices, values, dense_shape + return np.pad(array, pad_width) diff --git a/deepxde/experimental/utils/display.py b/deepxde/experimental/utils/display.py new file mode 100644 index 000000000..6b537986e --- /dev/null +++ b/deepxde/experimental/utils/display.py @@ -0,0 +1,115 @@ +import sys +from pprint import pformat + +import brainunit as u +import jax.tree + +from deepxde.experimental.utils import tree_repr + + +class TrainingDisplay: + """ + Display training progress. + """ + + def __init__(self): + self.len_train = None + self.len_test = None + self.len_metric = None + self.is_header_print = False + + def print_one(self, s1, s2, s3, s4): + s1 = s1.split("\n") + s2 = s2.split("\n") + s3 = s3.split("\n") + s4 = s4.split("\n") + + lines = [] + for i in range(max([len(s1), len(s2), len(s3), len(s4)])): + s1_ = s1[i] if i < len(s1) else "" + s2_ = s2[i] if i < len(s2) else "" + s3_ = s3[i] if i < len(s3) else "" + s4_ = s4[i] if i < len(s4) else "" + lines.append( + "{:{l1}s}{:{l2}s}{:{l3}s}{:{l4}s}".format( + s1_, + s2_, + s3_, + s4_, + l1=10, + l2=self.len_train, + l3=self.len_test, + l4=self.len_metric, + ) + ) + + print("\n".join(lines)) + sys.stdout.flush() + + def header(self): + self.print_one("Step", "Train loss", "Test loss", "Test metric") + self.is_header_print = True + + def __call__(self, train_state): + train_loss_repr = pformat(train_state.loss_train, width=40) + test_loss_repr = pformat(train_state.loss_test, width=40) + test_metrics_repr = pformat(train_state.metrics_test, width=40) + + if not self.is_header_print: + train_loss_repr_max = max( + [len(line) for line in train_loss_repr.split("\n") if line] + ) + test_loss_repr_max = max( + [len(line) for line in test_loss_repr.split("\n") if line] + ) + test_metrics_repr_max = max( + [len(line) for line in test_metrics_repr.split("\n") if line] + ) + self.len_train = train_loss_repr_max + 10 + self.len_test = test_loss_repr_max + 10 + self.len_metric = test_metrics_repr_max + 10 + self.header() + + self.print_one( + str(train_state.step), + train_loss_repr, + test_loss_repr, + test_metrics_repr, + ) + + def summary(self, train_state): + print("Best trainer at step {}:".format(train_state.best_step)) + print(" train loss: {}".format(train_state.best_loss_train)) + print(" test loss: {}".format(train_state.best_loss_test)) + print(" test metric: {}".format(tree_repr(train_state.best_metrics))) + if train_state.best_ystd is not None: + print(" Uncertainty:") + print( + " l2: {}".format( + jax.tree.map(lambda x: u.linalg.norm(x), train_state.best_ystd) + ) + ) + print( + " l_infinity: {}".format( + jax.tree_map( + lambda x: u.linalg.norm(x, ord=u.math.inf), + train_state.best_ystd, + is_leaf=u.math.is_quantity, + ) + ) + ) + if len(train_state.best_ystd) == 1: + index = u.math.argmax(tuple(train_state.best_ystd.values())[0]) + print( + " max uncertainty location:", + jax.tree_map( + lambda test: test[index], + train_state.X_test, + is_leaf=u.math.is_quantity, + ), + ) + print("") + self.is_header_print = False + + +training_display = TrainingDisplay() diff --git a/deepxde/experimental/utils/external.py b/deepxde/experimental/utils/external.py new file mode 100644 index 000000000..3dc2b0c26 --- /dev/null +++ b/deepxde/experimental/utils/external.py @@ -0,0 +1,362 @@ +"""External utilities.""" + +import csv +import os +from multiprocessing import Pool + +import braintools +import brainunit as u +import jax +import jax.numpy as jnp +import matplotlib.pyplot as plt +import numpy as np +from mpl_toolkits.mplot3d import Axes3D +from sklearn import preprocessing + + +def apply(func, args=None, kwds=None): + """Launch a new process to call the function. + + This can be used to clear Tensorflow GPU memory after trainer execution: + https://stackoverflow.com/questions/39758094/clearing-tensorflow-gpu-memory-after-model-execution + """ + with Pool(1) as p: + if args is None and kwds is None: + r = p.apply(func) + elif kwds is None: + r = p.apply(func, args=args) + elif args is None: + r = p.apply(func, kwds=kwds) + else: + r = p.apply(func, args=args, kwds=kwds) + return r + + +def standardize(X_train, X_test): + """Standardize features by removing the mean and scaling to unit variance. + + The mean and std are computed from the training data `X_train` using + `sklearn.preprocessing.StandardScaler `_, + and then applied to the testing data `X_test`. + + Args: + X_train: A NumPy array of shape (n_samples, n_features). The data used to + compute the mean and standard deviation used for later scaling along the + features axis. + X_test: A NumPy array. + + Returns: + scaler: Instance of ``sklearn.preprocessing.StandardScaler``. + X_train: Transformed training data. + X_test: Transformed testing data. + """ + + train_exp_dim = False + if u.math.ndim(X_train) == 1: + train_exp_dim = True + X_train = X_train.reshape(-1, 1) + test_exp_dim = False + if u.math.ndim(X_test) == 1: + test_exp_dim = True + X_test = X_test.reshape(-1, 1) + + scaler = preprocessing.StandardScaler(with_mean=True, with_std=True) + X_train = scaler.fit_transform(X_train) + X_test = scaler.transform(X_test) + if train_exp_dim: + X_train = X_train.flatten() + if test_exp_dim: + X_test = X_test.flatten() + return X_train, X_test + + +def saveplot( + loss_history, + train_state, + issave=True, + isplot=True, + loss_fname="loss.dat", + train_fname="train.dat", + test_fname="test.dat", + output_dir=None, +): + """Save/plot the loss history and best trained result. + + This function is used to quickly check your results. To better investigate your + result, use ``save_loss_history()`` and ``save_best_state()``. + + Args: + loss_history: ``LossHistory`` instance. The first variable returned from + ``Trainer.train()``. + train_state: ``TrainState`` instance. The second variable returned from + ``Trainer.train()``. + issave (bool): Set ``True`` (default) to save the loss, training points, + and testing points. + isplot (bool): Set ``True`` (default) to plot loss, metric, and the predicted + solution. + loss_fname (string): Name of the file to save the loss in. + train_fname (string): Name of the file to save the training points in. + test_fname (string): Name of the file to save the testing points in. + output_dir (string): If ``None``, use the current working directory. + """ + if output_dir is None: + output_dir = os.getcwd() + if not os.path.exists(output_dir): + print(f"Warning: Directory {output_dir} doesn't exist. Creating it.") + os.mkdir(output_dir) + + if issave: + loss_fname = os.path.join(output_dir, loss_fname) + train_fname = os.path.join(output_dir, train_fname) + test_fname = os.path.join(output_dir, test_fname) + save_loss_history(loss_history, loss_fname) + save_best_state(train_state, train_fname, test_fname) + + if isplot: + plot_loss_history(loss_history) + plot_best_state(train_state) + plt.show() + + +def plot_loss_history(loss_history, fname=None): + """Plot the training and testing loss history. + + Note: + You need to call ``plt.show()`` to show the figure. + + Args: + loss_history: ``LossHistory`` instance. The first variable returned from + ``Trainer.train()``. + fname (string): If `fname` is a string (e.g., 'loss_history.png'), then save the + figure to the file of the file name `fname`. + """ + # np.sum(loss_history.loss_train, axis=1) is error-prone for arrays of varying lengths. + # Handle irregular array sizes. + loss_train = jnp.array( + [ + jnp.sum(jnp.asarray(jax.tree.leaves(loss))) + for loss in loss_history.loss_train + ] + ) + loss_test = jnp.array( + [jnp.sum(jnp.asarray(jax.tree.leaves(loss))) for loss in loss_history.loss_test] + ) + + plt.figure() + plt.semilogy(loss_history.steps, loss_train, label="Train loss") + plt.semilogy(loss_history.steps, loss_test, label="Test loss") + metric_tests = jax.tree.map( + lambda *a: u.math.asarray(a), *loss_history.metrics_test + ) + + for i in range(len(loss_history.metrics_test[0])): + if isinstance(metric_tests[i], dict): + for k, v in metric_tests[i].items(): + plt.semilogy(loss_history.steps, v, label=f"Test metric {k}") + else: + plt.semilogy(loss_history.steps, metric_tests[i], label=f"Test metric {i}") + plt.xlabel("# Steps") + plt.legend() + + if isinstance(fname, str): + plt.savefig(fname) + + +def save_loss_history(loss_history, fname): + """Save the training and testing loss history to a file.""" + print("Saving loss history to {} ...".format(fname)) + + train_losses = jax.tree.map(lambda *a: u.math.asarray(a), *loss_history.loss_train) + braintools.file.msgpack_save(fname, train_losses) + + +def _pack_data(train_state): + def merge_values(values): + if values is None: + return None + return jnp.hstack(values) if isinstance(values, (list, tuple)) else values + + # y_train = merge_values(train_state.y_train) + # y_test = merge_values(train_state.y_test) + # best_y = merge_values(train_state.best_y) + # best_ystd = merge_values(train_state.best_ystd) + y_train = train_state.y_train + y_test = train_state.y_test + best_y = train_state.best_y + best_ystd = train_state.best_ystd + return y_train, y_test, best_y, best_ystd + + +def plot_best_state(train_state): + """Plot the best result of the smallest training loss. + + This function only works for 1D and 2D problems. For other problems and to better + customize the figure, use ``save_best_state()``. + + Note: + You need to call ``plt.show()`` to show the figure. + + Args: + train_state: ``TrainState`` instance. The second variable returned from + ``Trainer.train()``. + """ + if isinstance(train_state.X_train, (list, tuple)): + print( + "Error: The network has multiple inputs, and plotting such result hasn't been implemented." + ) + return + + y_train, y_test, best_y, best_ystd = _pack_data(train_state) + xkeys = tuple(train_state.X_test.keys()) + + # Regression plot + # 1D + if len(train_state.X_test) == 1: + idx = u.math.argsort(train_state.X_test[xkeys[0]]) + X = train_state.X_test[xkeys[0]][idx] + plt.figure() + for ykey in best_y: + if y_train is not None: + plt.plot( + train_state.X_train[xkeys[0]], y_train[ykey], "ok", label="Train" + ) + if y_test is not None: + plt.plot(X, y_test[ykey], "-k", label="True") + y_val, y_unit = u.split_mantissa_unit(best_y[ykey]) + plt.plot( + X, + y_val, + "--r", + label=( + f"{ykey} Prediction" + if y_unit.is_unitless + else f"{ykey} Prediction [{y_unit}]" + ), + ) + if best_ystd is not None: + ystd_val = u.get_magnitude(u.Quantity(best_ystd[ykey], unit=y_unit)) + plt.plot(X, y_val + 1.96 * ystd_val, "-b", label="95% CI") + plt.plot(X, y_val - 1.96 * ystd_val, "-b") + plt.xlabel("x") + plt.ylabel("y") + plt.legend() + + # 2D + elif len(train_state.X_test) == 2: + for ykey in best_y: + plt.figure() + ax = plt.axes(projection=Axes3D.name) + ax.plot3D( + u.get_magnitude(train_state.X_test[xkeys[0]]), + u.get_magnitude(train_state.X_test[xkeys[1]]), + u.get_magnitude(best_y[ykey]), + ".", + ) + unit = u.get_unit(train_state.X_test[xkeys[0]]) + if unit.is_unitless: + ax.set_xlabel(f"{xkeys[0]}") + else: + ax.set_xlabel(f"{xkeys[0]} [{unit}]") + unit = u.get_unit(train_state.X_test[xkeys[1]]) + if unit.is_unitless: + ax.set_ylabel(f"{xkeys[1]}") + else: + ax.set_ylabel(f"{xkeys[1]} [{unit}]") + unit = u.get_unit(best_y[ykey]) + if unit.is_unitless: + ax.set_zlabel(f"{ykey}") + else: + ax.set_zlabel(f"{ykey} [{unit}]") + + # Residual plot + # Not necessary to plot + # if y_test is not None: + # plt.figure() + # residual = y_test[:, 0] - best_y[:, 0] + # plt.plot(best_y[:, 0], residual, "o", zorder=1) + # plt.hlines(0, plt.xlim()[0], plt.xlim()[1], linestyles="dashed", zorder=2) + # plt.xlabel("Predicted") + # plt.ylabel("Residual = Observed - Predicted") + # plt.tight_layout() + + # Uncertainty plot + # Not necessary to plot + # if best_ystd is not None: + # plt.figure() + # for i in range(y_dim): + # plt.plot(train_state.X_test[:, 0], best_ystd[:, i], "-b") + # plt.plot( + # train_state.X_train[:, 0], + # np.interp( + # train_state.X_train[:, 0], train_state.X_test[:, 0], best_ystd[:, i] + # ), + # "ok", + # ) + # plt.xlabel("x") + # plt.ylabel("std(y)") + + +def save_best_state(train_state, fname_train, fname_test): + """Save the best result of the smallest training loss to a file.""" + if isinstance(train_state.X_train, (list, tuple)): + print( + "Error: The network has multiple inputs, and saving such result han't been implemented." + ) + return + + print("Saving training data to {} ...".format(fname_train)) + y_train, y_test, best_y, best_ystd = _pack_data(train_state) + if y_train is None: + data = {"X_train": train_state.X_train} + else: + data = {"X_train": train_state.X_train, "y_train": y_train} + braintools.file.msgpack_save(fname_train, data) + + print("Saving test data to {} ...".format(fname_test)) + if y_test is None: + data = {"X_test": train_state.X_test, "best_y": best_y} + if best_ystd is not None: + data["best_ystd"] = best_ystd + braintools.file.msgpack_save(fname_test, data) + else: + data = {"X_test": train_state.X_test, "best_y": best_y, "y_test": y_test} + if best_ystd is not None: + data["best_ystd"] = best_ystd + braintools.file.msgpack_save(fname_test, data) + + +def isclose(a, b): + """A modified version of `np.isclose` for DeepXDE. + + This function changes the value of `atol` due to the dtype of `a` and `b`. + If the dtype is float16, `atol` is `1e-4`. + If it is float32, `atol` is `1e-6`. + Otherwise (for float64), the default is `1e-8`. + If you want to manually set `atol` for some reason, use `np.isclose` instead. + + Args: + a, b (array like): DictToArray arrays to compare. + """ + pack = smart_numpy(a) + a_dtype = a.dtype + a_unit = u.get_unit(a) + if a_dtype == jnp.float32: + atol = u.maybe_decimal(u.Quantity(1e-6, unit=a_unit)) + elif a_dtype == jnp.float16: + atol = u.maybe_decimal(u.Quantity(1e-4, unit=a_unit)) + else: + atol = u.maybe_decimal(u.Quantity(1e-8, unit=a_unit)) + return pack.isclose(a, b, atol=atol) + + +def smart_numpy(x): + if isinstance(x, jnp.ndarray): + return jnp + elif isinstance(x, jax.Array): + return jax.numpy + elif isinstance(x, u.Quantity): + return u.math + elif isinstance(x, np.ndarray): + return np + else: + raise TypeError(f"Unknown type {type(x)}.") diff --git a/deepxde/experimental/utils/internal.py b/deepxde/experimental/utils/internal.py new file mode 100644 index 000000000..8fbf63ecc --- /dev/null +++ b/deepxde/experimental/utils/internal.py @@ -0,0 +1,50 @@ +"""Internal utilities.""" + +from functools import wraps +from typing import Callable, Union + +import brainstate as bst +import brainunit as u +import numpy as np + + +def check_not_none(*attr): + def decorator(func): + @wraps(func) + def wrapper(self, *args, **kwargs): + is_none = [] + for a in attr: + if not hasattr(self, a): + raise ValueError(f"{a} must be an attribute of the class.") + is_none.append(getattr(self, a) is None) + if any(is_none): + raise ValueError(f"{attr} must not be None.") + return func(self, *args, **kwargs) + + return wrapper + + return decorator + + +def return_tensor(func): + """Convert the output to a Tensor.""" + + @wraps(func) + def wrapper(*args, **kwargs): + return u.math.asarray(func(*args, **kwargs), dtype=bst.environ.dftype()) + + return wrapper + + +def tree_repr(tree, precision: int = 2): + with np.printoptions(precision=precision, suppress=True, threshold=5): + return repr(tree) + # return repr(jax.tree.map(lambda x: repr(x), tree, is_leaf=u.math.is_quantity)) + + +def get_activation(activation: Union[str, Callable]): + """Get the activation function.""" + if isinstance(activation, str): + return getattr(bst.functional, activation) + else: + return activation diff --git a/deepxde/experimental/utils/losses.py b/deepxde/experimental/utils/losses.py new file mode 100644 index 000000000..65f4258a5 --- /dev/null +++ b/deepxde/experimental/utils/losses.py @@ -0,0 +1,74 @@ +import braintools +import brainunit as u +import jax + + +def mean_absolute_error(y_true, y_pred): + return jax.tree.map( + lambda x, y: braintools.metric.absolute_error(x, y).mean(), + y_true, + y_pred, + is_leaf=u.math.is_quantity, + ) + + +def mean_squared_error(y_true, y_pred): + return jax.tree.map( + lambda x, y: braintools.metric.squared_error(x, y).mean(), + y_true, + y_pred, + is_leaf=u.math.is_quantity, + ) + + +def mean_l2_relative_error(y_true, y_pred): + return jax.tree.map( + lambda x, y: braintools.metric.l2_norm(x, y).mean(), + y_true, + y_pred, + is_leaf=u.math.is_quantity, + ) + + +def softmax_cross_entropy(y_true, y_pred): + return jax.tree.map( + lambda x, y: braintools.metric.softmax_cross_entropy(x, y).mean(), + y_true, + y_pred, + is_leaf=u.math.is_quantity, + ) + + +LOSS_DICT = { + # mean absolute error + "mean absolute error": mean_absolute_error, + "MAE": mean_absolute_error, + "mae": mean_absolute_error, + # mean squared error + "mean squared error": mean_squared_error, + "MSE": mean_squared_error, + "mse": mean_squared_error, + # mean l2 relative error + "mean l2 relative error": mean_l2_relative_error, + # softmax cross entropy + "softmax cross entropy": softmax_cross_entropy, +} + + +def get_loss(identifier): + """Retrieves a loss function. + + Args: + identifier: A loss identifier. String name of a loss function, or a loss function. + + Returns: + A loss function. + """ + if isinstance(identifier, (list, tuple)): + return list(map(get_loss, identifier)) + + if isinstance(identifier, str): + return LOSS_DICT[identifier] + if callable(identifier): + return identifier + raise ValueError("Could not interpret loss function identifier:", identifier) diff --git a/deepxde/nn/__init__.py b/deepxde/nn/__init__.py index 965b4defd..a4083f5a6 100644 --- a/deepxde/nn/__init__.py +++ b/deepxde/nn/__init__.py @@ -16,6 +16,7 @@ import os import sys +from . import deeponet_strategy from ..backend import backend_name diff --git a/docs/conf.py b/docs/conf.py index 49de18a47..45637e823 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,7 +14,6 @@ # from importlib.metadata import version - # -- Project information ----------------------------------------------------- project = "DeepXDE" @@ -26,7 +25,6 @@ # The full version, including alpha/beta/rc tags release = version - # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -42,8 +40,13 @@ "sphinx.ext.napoleon", "sphinx.ext.viewcode", "sphinx_copybutton", + 'sphinx_autodoc_typehints', + 'myst_nb', + 'sphinx_thebe', + 'sphinx_design', + 'sphinx_math_dollar', ] - +jupyter_execute_notebooks = "off" # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -51,7 +54,7 @@ # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = ".rst" +source_suffix = ['.rst', '.ipynb', '.md'] # The master toctree document. master_doc = "index" @@ -71,7 +74,6 @@ # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" - # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -108,7 +110,6 @@ # Output file base name for HTML help builder. htmlhelp_basename = "DeepXDEdoc" - # -- Options for LaTeX output ------------------------------------------------ latex_elements = { @@ -129,14 +130,12 @@ (master_doc, "DeepXDE.tex", "DeepXDE Documentation", "Lu Lu", "manual") ] - # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "deepxde", "DeepXDE Documentation", [author], 1)] - # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples @@ -154,5 +153,4 @@ ) ] - # -- Extension configuration ------------------------------------------------- diff --git a/docs/experimental_docs/index.rst b/docs/experimental_docs/index.rst new file mode 100644 index 000000000..1d4a545ae --- /dev/null +++ b/docs/experimental_docs/index.rst @@ -0,0 +1,38 @@ +Examples of ``deepxde.experimental`` +==================================== + + +PINN Forward Examples +--------------------- + +.. toctree:: + :maxdepth: 1 + + unit-examples-forward/Beltrami_flow.ipynb + unit-examples-forward/diffusion_1d.ipynb + unit-examples-forward/Euler_beam.ipynb + unit-examples-forward/Helmholtz_Dirichlet_2d.ipynb + unit-examples-forward/burgers.ipynb + unit-examples-forward/Burgers_RAR.ipynb + unit-examples-forward/heat.ipynb + unit-examples-forward/heat_resample.ipynb + unit-examples-forward/Laplace_disk.ipynb + + + +PINN Inverse Examples +--------------------- + +.. toctree:: + :maxdepth: 1 + + unit-examples-inverse/elliptic_inverse_filed.ipynb + unit-examples-inverse/brinkman_forchheimer.ipynb + unit-examples-inverse/diffusion_reaction_rate.ipynb + unit-examples-inverse/reaction_inverse.ipynb + unit-examples-inverse/diffusion_1d_inverse.ipynb + unit-examples-inverse/Navier_Stokes_inverse.ipynb + + + + diff --git a/docs/experimental_docs/unit-examples-forward/Beltrami_flow.ipynb b/docs/experimental_docs/unit-examples-forward/Beltrami_flow.ipynb new file mode 100644 index 000000000..263ddeff7 --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/Beltrami_flow.ipynb @@ -0,0 +1,591 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "17296f2332f77ca7", + "metadata": {}, + "source": [ + "# Three-dimensional unsteady Navier-Stokes Equations\n", + "\n", + "\n", + "\n", + "## Problem Statement\n", + "\n", + "### 1. Momentum Equations\n", + "\n", + "The Navier-Stokes equations describe the conservation of momentum in fluid dynamics. For an incompressible fluid, the equation is:\n", + "\n", + "$$\n", + "\\frac{\\partial \\mathbf{u}}{\\partial t} + \\mathbf{u} \\cdot \\nabla \\mathbf{u} = - \\nabla p + \\frac{1}{Re} \\nabla^2 \\mathbf{u}\n", + "$$\n", + "\n", + "Where:\n", + "- $\\mathbf{u} = (u, v, w)$ is the velocity field,\n", + "- $p$ is the pressure field,\n", + "- $\\nabla$ is the gradient operator,\n", + "- $\\nabla^2$ is the Laplacian operator,\n", + "- $\\mu$ is the dynamic viscosity.\n", + "\n", + "The momentum equations in the code are written for each of the three spatial components (x, y, z). Specifically, the equations correspond to the following:\n", + "\n", + "\n", + "\n", + "- **$x$-direction momentum equation** (`momentum_x` in the code):\n", + "\n", + "$$\n", + "\\rho\\left[\\frac{\\partial u}{\\partial t}+\\frac{\\partial u}{\\partial x} u+\\frac{\\partial u}{\\partial y} v+\\frac{\\partial u}{\\partial z} w\\right]=-\\frac{\\partial p}{\\partial x}+\\mu\\left(\\frac{\\partial^2 u}{\\partial x^2}+\\frac{\\partial^2 u}{\\partial y^2}+\\frac{\\partial^2 u}{\\partial z^2}\\right)+\\rho g_x\n", + "$$\n", + "\n", + "- **$y$-direction momentum equation** (`momentum_y` in the code):\n", + "\n", + "$$\n", + "\\rho\\left[\\frac{\\partial v}{\\partial t}+\\frac{\\partial v}{\\partial x} u+\\frac{\\partial v}{\\partial y} v+\\frac{\\partial v}{\\partial z} w\\right]=-\\frac{\\partial p}{\\partial y}+\\mu\\left(\\frac{\\partial^2 v}{\\partial x^2}+\\frac{\\partial^2 v}{\\partial y^2}+\\frac{\\partial^2 v}{\\partial z^2}\\right)+\\rho g_y\n", + "$$\n", + "\n", + "- **$z$-direction momentum equation** (`momentum_z` in the code):\n", + "\n", + "$$\n", + "\\rho\\left[\\frac{\\partial w}{\\partial t}+\\frac{\\partial w}{\\partial x} u+\\frac{\\partial w}{\\partial y} v+\\frac{\\partial w}{\\partial z} w\\right]=-\\frac{\\partial p}{\\partial z}+\\mu\\left(\\frac{\\partial^2 w}{\\partial x^2}+\\frac{\\partial^2 w}{\\partial y^2}+\\frac{\\partial^2 w}{\\partial z^2}\\right)+\\rho g_z\n", + "$$\n", + "\n", + "### 2. Continuity Equation\n", + "\n", + "The continuity equation represents the conservation of mass, ensuring that the flow is incompressible (i.e., the divergence of the velocity field is zero). The equation is:\n", + "\n", + "$$\n", + "\\nabla \\cdot \\mathbf{u} = 0\n", + "$$\n", + "\n", + "In the code, the continuity equation corresponds to:\n", + "\n", + "$$\n", + "\\frac{\\partial u}{\\partial x} + \\frac{\\partial v}{\\partial y} + \\frac{\\partial w}{\\partial z} = 0\n", + "$$\n", + "\n", + "This guarantees that the volume of the fluid remains constant and the flow is incompressible.\n", + "\n", + "### 3. Initial and Boundary Conditions (IC and BC)\n", + "\n", + "The function `icbc_cond_func` defines the initial conditions (IC) and boundary conditions (BC) for the velocity and pressure fields.\n", + "\n", + "- **Initial velocity fields**:\n", + " The velocity components are given as functions of spatial variables $x$, $y$, and $z$, as well as time $t$. Specifically, the velocity components $u$, $v$, and $w$ are defined as:\n", + "\n", + "$$\n", + "u = -a \\left( e^{a x} \\sin(a y + d z) + e^{a z} \\cos(a x + d y) \\right) e^{-d^2 t}\n", + "$$\n", + "\n", + "$$\n", + "v = -a \\left( e^{a y} \\sin(a z + d x) + e^{a x} \\cos(a y + d z) \\right) e^{-d^2 t}\n", + "$$\n", + "\n", + "$$\n", + "w = -a \\left( e^{a z} \\sin(a x + d y) + e^{a y} \\cos(a z + d x) \\right) e^{-d^2 t}\n", + "$$\n", + "\n", + "- **Initial pressure field**:\n", + " The pressure field $p$ is given by a more complex expression, involving exponentials and trigonometric functions of the spatial variables $x$, $y$, and $z$:\n", + "\n", + "$$\n", + "\\begin{aligned}\n", + "p(x, y, z, t)= & -\\frac{1}{2} a^2\\left[e^{2 a x}+e^{2 a y}+e^{2 a z}\\right. \\\\\n", + "& +2 \\sin (a x+d y) \\cos (a z+d x) e^{a(y+z)} \\\\\n", + "& +2 \\sin (a y+d z) \\cos (a x+d y) e^{a(z+x)} \\\\\n", + "& \\left.+2 \\sin (a z+d x) \\cos (a y+d z) e^{a(x+y)}\\right] e^{-2 d^2 t}\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "### 4. Final Formulation: 3D Navier-Stokes Equations\n", + "\n", + "The Navier-Stokes equations, based on the code, are as follows:\n", + "\n", + "#### Momentum Equation (in three dimensions):\n", + "\n", + "$$\n", + "\\frac{\\partial \\mathbf{u}}{\\partial t} + \\mathbf{u} \\cdot \\nabla \\mathbf{u} = - \\nabla p + \\frac{1}{Re} \\nabla^2 \\mathbf{u}\n", + "$$\n", + "\n", + "Where:\n", + "- $\\mathbf{u} = (u, v, w)$ is the velocity field,\n", + "- $p$ is the pressure field,\n", + "- $Re$ is the Reynolds number,\n", + "- $\\nabla^2$ is the Laplacian operator.\n", + "\n", + "#### Continuity Equation (for incompressibility):\n", + "\n", + "$$\n", + "\\nabla \\cdot \\mathbf{u} = 0\n", + "$$\n", + "\n", + "This ensures that the flow remains incompressible.\n", + "\n", + "### Conclusion\n", + "\n", + "The Python code essentially implements the 3D Navier-Stokes equations for an incompressible fluid, where the momentum equations are resolved in each spatial direction, and the continuity equation ensures mass conservation. The initial conditions for velocity and pressure are specified, and boundary conditions are likely handled by the methods in `icbc_cond_func`." + ] + }, + { + "cell_type": "markdown", + "id": "ad99ef67a441b25d", + "metadata": {}, + "source": [ + "## Dimensional Analysis\n", + "\n", + "Summary of Physical Units\n", + "\n", + "- **Velocity ($u, v, w$)**: $[u] = [v] = [w] = \\text{m/s}$\n", + "- **Time ($t$)**: $[t] = \\text{s}$\n", + "- **Pressure ($p$)**: $[p] = \\text{kg/m} \\cdot \\text{s}^2$\n", + "- **Reynolds number ($Re$)**: Dimensionless\n", + "- **Laplacian of velocity ($\\nabla^2 \\mathbf{u}$)**: $\\text{s}^{-2}$\n", + "- **Density ($\\rho$)**: $\\text{kg/m}^3$\n", + "- **Dynamic viscosity ($\\mu$)**: $\\text{kg/m} \\cdot \\text{s}$\n", + "\n", + "The analysis confirms that the Navier-Stokes equations, including the momentum and continuity equations, are dimensionally consistent and properly describe the physical quantities involved in fluid flow.\n" + ] + }, + { + "cell_type": "markdown", + "id": "b2ff0e8c04269c5c", + "metadata": {}, + "source": [ + "## Code Implementation\n", + "\n", + "First, we import the necessary libraries for the implementation:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "9da3a0a3f6e0cfdd", + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:35:41.570500Z", + "start_time": "2024-12-17T13:35:39.215212Z" + } + }, + "outputs": [], + "source": [ + "import brainstate as bst\n", + "import brainunit as u\n", + "import jax.tree\n", + "import numpy as np\n", + "\n", + "import deepxde.experimental as deepxde\n" + ] + }, + { + "cell_type": "markdown", + "id": "55d79554e448349a", + "metadata": {}, + "source": [ + "Define the physical units for the problem:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a7d41ee1906c7370", + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:35:41.578528Z", + "start_time": "2024-12-17T13:35:41.574897Z" + } + }, + "outputs": [], + "source": [ + "unit_of_space = u.meter\n", + "unit_of_speed = u.meter / u.second\n", + "unit_of_t = u.second\n", + "unit_of_pressure = u.pascal" + ] + }, + { + "cell_type": "markdown", + "id": "7346e281a5e40f06", + "metadata": {}, + "source": [ + "Define the spatial and temporal domains for the problem:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c1ebb34b6f25d0a8", + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:35:41.632619Z", + "start_time": "2024-12-17T13:35:41.629331Z" + } + }, + "outputs": [], + "source": [ + "spatial_domain = deepxde.geometry.Cuboid(xmin=[-1, -1, -1], xmax=[1, 1, 1])\n", + "temporal_domain = deepxde.geometry.TimeDomain(0, 1)\n", + "spatio_temporal_domain = deepxde.geometry.GeometryXTime(spatial_domain, temporal_domain)\n", + "spatio_temporal_domain = spatio_temporal_domain.to_dict_point(\n", + " x=unit_of_space,\n", + " y=unit_of_space,\n", + " z=unit_of_space,\n", + " t=unit_of_t,\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "id": "a20e646545ec2cfd", + "metadata": {}, + "source": [ + "Define the neural network model for the problem:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "13fd9b17a2ad3161", + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:35:41.978316Z", + "start_time": "2024-12-17T13:35:41.638695Z" + } + }, + "outputs": [], + "source": [ + "net = deepxde.nn.Model(\n", + " deepxde.nn.DictToArray(x=unit_of_space,\n", + " y=unit_of_space,\n", + " z=unit_of_space,\n", + " t=unit_of_t),\n", + " deepxde.nn.FNN([4] + 4 * [50] + [4], \"tanh\", bst.init.KaimingUniform()),\n", + " deepxde.nn.ArrayToDict(u_vel=unit_of_speed,\n", + " v_vel=unit_of_speed,\n", + " w_vel=unit_of_speed,\n", + " p=unit_of_pressure),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "c49c4dd168704ad2", + "metadata": {}, + "source": [ + "Define the PDE residual function for the Navier-Stokes equations:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ba050fc459ae4389", + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:35:42.026077Z", + "start_time": "2024-12-17T13:35:41.988476Z" + } + }, + "outputs": [], + "source": [ + "\n", + "a = 1\n", + "d = 1\n", + "Re = 1\n", + "rho = 1 * u.kilogram / u.meter ** 3\n", + "mu = 1 * u.pascal * u.second\n", + "\n", + "\n", + "@bst.compile.jit\n", + "def pde(x, u):\n", + " jacobian = net.jacobian(x)\n", + " x_hessian = net.hessian(x, y=['u_vel', 'v_vel', 'w_vel'], xi=['x'], xj=['x'])\n", + " y_hessian = net.hessian(x, y=['u_vel', 'v_vel', 'w_vel'], xi=['y'], xj=['y'])\n", + " z_hessian = net.hessian(x, y=['u_vel', 'v_vel', 'w_vel'], xi=['z'], xj=['z'])\n", + "\n", + " u_vel, v_vel, w_vel, p = u['u_vel'], u['v_vel'], u['w_vel'], u['p']\n", + "\n", + " du_vel_dx = jacobian['u_vel']['x']\n", + " du_vel_dy = jacobian['u_vel']['y']\n", + " du_vel_dz = jacobian['u_vel']['z']\n", + " du_vel_dt = jacobian['u_vel']['t']\n", + " du_vel_dx_dx = x_hessian['u_vel']['x']['x']\n", + " du_vel_dy_dy = y_hessian['u_vel']['y']['y']\n", + " du_vel_dz_dz = z_hessian['u_vel']['z']['z']\n", + "\n", + " dv_vel_dx = jacobian['v_vel']['x']\n", + " dv_vel_dy = jacobian['v_vel']['y']\n", + " dv_vel_dz = jacobian['v_vel']['z']\n", + " dv_vel_dt = jacobian['v_vel']['t']\n", + " dv_vel_dx_dx = x_hessian['v_vel']['x']['x']\n", + " dv_vel_dy_dy = y_hessian['v_vel']['y']['y']\n", + " dv_vel_dz_dz = z_hessian['v_vel']['z']['z']\n", + "\n", + " dw_vel_dx = jacobian['w_vel']['x']\n", + " dw_vel_dy = jacobian['w_vel']['y']\n", + " dw_vel_dz = jacobian['w_vel']['z']\n", + " dw_vel_dt = jacobian['w_vel']['t']\n", + " dw_vel_dx_dx = x_hessian['w_vel']['x']['x']\n", + " dw_vel_dy_dy = y_hessian['w_vel']['y']['y']\n", + " dw_vel_dz_dz = z_hessian['w_vel']['z']['z']\n", + "\n", + " dp_dx = jacobian['p']['x']\n", + " dp_dy = jacobian['p']['y']\n", + " dp_dz = jacobian['p']['z']\n", + "\n", + " momentum_x = (\n", + " rho * (du_vel_dt + (u_vel * du_vel_dx + v_vel * du_vel_dy + w_vel * du_vel_dz))\n", + " + dp_dx - mu * (du_vel_dx_dx + du_vel_dy_dy + du_vel_dz_dz)\n", + " )\n", + " momentum_y = (\n", + " rho * (dv_vel_dt + (u_vel * dv_vel_dx + v_vel * dv_vel_dy + w_vel * dv_vel_dz))\n", + " + dp_dy - mu * (dv_vel_dx_dx + dv_vel_dy_dy + dv_vel_dz_dz)\n", + " )\n", + " momentum_z = (\n", + " rho * (dw_vel_dt + (u_vel * dw_vel_dx + v_vel * dw_vel_dy + w_vel * dw_vel_dz))\n", + " + dp_dz - mu * (dw_vel_dx_dx + dw_vel_dy_dy + dw_vel_dz_dz)\n", + " )\n", + " continuity = du_vel_dx + dv_vel_dy + dw_vel_dz\n", + "\n", + " return [momentum_x, momentum_y, momentum_z, continuity]\n" + ] + }, + { + "cell_type": "markdown", + "id": "7827b37aac032ab8", + "metadata": {}, + "source": [ + "Define the initial and boundary conditions for the problem:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "30611c1e29ef58b8", + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:35:42.059708Z", + "start_time": "2024-12-17T13:35:42.050288Z" + } + }, + "outputs": [], + "source": [ + "\n", + "@bst.compile.jit(static_argnums=1)\n", + "def icbc_cond_func(x, include_p: bool = False):\n", + " x = {k: v.mantissa for k, v in x.items()}\n", + "\n", + " u_ = (\n", + " -a\n", + " * (u.math.exp(a * x['x']) * u.math.sin(a * x['y'] + d * x['z'])\n", + " + u.math.exp(a * x['z']) * u.math.cos(a * x['x'] + d * x['y']))\n", + " * u.math.exp(-(d ** 2) * x['t'])\n", + " )\n", + " v = (\n", + " -a\n", + " * (u.math.exp(a * x['y']) * u.math.sin(a * x['z'] + d * x['x'])\n", + " + u.math.exp(a * x['x']) * u.math.cos(a * x['y'] + d * x['z']))\n", + " * u.math.exp(-(d ** 2) * x['t'])\n", + " )\n", + " w = (\n", + " -a\n", + " * (u.math.exp(a * x['z']) * u.math.sin(a * x['x'] + d * x['y'])\n", + " + u.math.exp(a * x['y']) * u.math.cos(a * x['z'] + d * x['x']))\n", + " * u.math.exp(-(d ** 2) * x['t'])\n", + " )\n", + " p = (\n", + " -0.5\n", + " * a ** 2\n", + " * (\n", + " u.math.exp(2 * a * x['x'])\n", + " + u.math.exp(2 * a * x['y'])\n", + " + u.math.exp(2 * a * x['z'])\n", + " + 2\n", + " * u.math.sin(a * x['x'] + d * x['y'])\n", + " * u.math.cos(a * x['z'] + d * x['x'])\n", + " * u.math.exp(a * (x['y'] + x['z']))\n", + " + 2\n", + " * u.math.sin(a * x['y'] + d * x['z'])\n", + " * u.math.cos(a * x['x'] + d * x['y'])\n", + " * u.math.exp(a * (x['z'] + x['x']))\n", + " + 2\n", + " * u.math.sin(a * x['z'] + d * x['x'])\n", + " * u.math.cos(a * x['y'] + d * x['z'])\n", + " * u.math.exp(a * (x['x'] + x['y']))\n", + " )\n", + " * u.math.exp(-2 * d ** 2 * x['t'])\n", + " )\n", + "\n", + " r = {'u_vel': u_ * unit_of_speed,\n", + " 'v_vel': v * unit_of_speed,\n", + " 'w_vel': w * unit_of_speed}\n", + " if include_p:\n", + " r['p'] = p * unit_of_pressure\n", + " return r\n", + "\n", + "\n", + "bc = deepxde.icbc.DirichletBC(icbc_cond_func)\n", + "ic = deepxde.icbc.IC(icbc_cond_func)" + ] + }, + { + "cell_type": "markdown", + "id": "d5663ac4d9b3ae59", + "metadata": {}, + "source": [ + "Define the problem as a TimePDE object:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "3f1612bbaeb56010", + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:35:43.072978Z", + "start_time": "2024-12-17T13:35:42.121586Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning: 283 points required, but 343 points sampled.\n", + "Warning: 10000 points required, but 12348 points sampled.\n" + ] + } + ], + "source": [ + "problem = deepxde.problem.TimePDE(\n", + " spatio_temporal_domain,\n", + " pde,\n", + " [bc, ic],\n", + " net,\n", + " num_domain=50000,\n", + " num_boundary=5000,\n", + " num_initial=5000,\n", + " num_test=10000,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "f472396f52fcb8a5", + "metadata": {}, + "source": [ + "Train the model using the problem data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71f97c909111b8e1", + "metadata": { + "ExecuteTime": { + "start_time": "2024-12-17T13:35:43.085506Z" + }, + "jupyter": { + "is_executing": true + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Compiling trainer...\n", + "'compile' took 0.051972 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "0 [12.974215 * (kilogram / klitre * (meter / second) / second) ** 2, [14.700201 * (kilogram / klitre * (meter / second) / second) ** 2, [] \n", + " 24.321922 * (kilogram / klitre * (meter / second) / second) ** 2, 29.931065 * (kilogram / klitre * (meter / second) / second) ** 2, \n", + " 13.350433 * (kilogram / klitre * (meter / second) / second) ** 2, 17.096483 * (kilogram / klitre * (meter / second) / second) ** 2, \n", + " 1.2527013 * becquerel2, 1.3801537 * becquerel2, \n", + " {'ibc0': {'u_vel': 2.5884202 * meter / second, {'ibc0': {'u_vel': 2.5884202 * meter / second, \n", + " 'v_vel': 1.5904388 * meter / second, 'v_vel': 1.5904388 * meter / second, \n", + " 'w_vel': 1.7298671 * meter / second}}, 'w_vel': 1.7298671 * meter / second}}, \n", + " {'ibc1': {'u_vel': 4.1043954 * meter / second, {'ibc1': {'u_vel': 4.1043954 * meter / second, \n", + " 'v_vel': 2.08325 * meter / second, 'v_vel': 2.08325 * meter / second, \n", + " 'w_vel': 2.6199307 * meter / second}}] 'w_vel': 2.6199307 * meter / second}}] \n" + ] + } + ], + "source": [ + "model = deepxde.Trainer(problem)\n", + "\n", + "model.compile(bst.optim.Adam(1e-3)).train(iterations=30000)\n", + "model.compile(bst.optim.LBFGS(1e-3)).train(5000, display_every=200)" + ] + }, + { + "cell_type": "markdown", + "id": "becbb0f8333344e9", + "metadata": {}, + "source": [ + "Verify the results by plotting the loss history and the predicted solution:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca417f07847d6081", + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z = np.meshgrid(np.linspace(-1, 1, 10), np.linspace(-1, 1, 10), np.linspace(-1, 1, 10))\n", + "t_0 = np.zeros(1000)\n", + "t_1 = np.ones(1000)\n", + "X_0 = dict(\n", + " x=np.ravel(x) * unit_of_space,\n", + " y=np.ravel(y) * unit_of_space,\n", + " z=np.ravel(z) * unit_of_space,\n", + " t=t_0 * unit_of_t\n", + ")\n", + "X_1 = dict(\n", + " x=np.ravel(x) * unit_of_space,\n", + " y=np.ravel(y) * unit_of_space,\n", + " z=np.ravel(z) * unit_of_space,\n", + " t=t_1 * unit_of_t\n", + ")\n", + "output_0 = model.predict(X_0)\n", + "output_1 = model.predict(X_1)\n", + "\n", + "out_exact_0 = icbc_cond_func(X_0, True)\n", + "out_exact_1 = icbc_cond_func(X_1, True)\n", + "\n", + "f_0 = pde(X_0, output_0)\n", + "f_1 = pde(X_1, output_1)\n", + "residual_0 = jax.tree.map(lambda x: np.mean(np.absolute(x)), f_0)\n", + "residual_1 = jax.tree.map(lambda x: np.mean(np.absolute(x)), f_1)\n", + "\n", + "print(\"Accuracy at t = 0:\")\n", + "print(\"Mean residual:\", residual_0)\n", + "print(\"L2 relative error:\", deepxde.metrics.l2_relative_error(output_0, out_exact_0))\n", + "print(\"\\n\")\n", + "print(\"Accuracy at t = 1:\")\n", + "print(\"Mean residual:\", residual_1)\n", + "print(\"L2 relative error:\", deepxde.metrics.l2_relative_error(output_1, out_exact_1))\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/experimental_docs/unit-examples-forward/Beltrami_flow.py b/docs/experimental_docs/unit-examples-forward/Beltrami_flow.py new file mode 100644 index 000000000..a90f90d64 --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/Beltrami_flow.py @@ -0,0 +1,202 @@ +import brainstate as bst +import brainunit as u +import jax.tree +import numpy as np + +import deepxde.experimental as deepxde + +unit_of_space = u.meter +unit_of_speed = u.meter / u.second +unit_of_t = u.second +unit_of_pressure = u.pascal + +spatial_domain = deepxde.geometry.Cuboid(xmin=[-1, -1, -1], xmax=[1, 1, 1]) +temporal_domain = deepxde.geometry.TimeDomain(0, 1) +spatio_temporal_domain = deepxde.geometry.GeometryXTime(spatial_domain, temporal_domain) +spatio_temporal_domain = spatio_temporal_domain.to_dict_point( + x=unit_of_space, + y=unit_of_space, + z=unit_of_space, + t=unit_of_t, +) + +net = deepxde.nn.Model( + deepxde.nn.DictToArray(x=unit_of_space, + y=unit_of_space, + z=unit_of_space, + t=unit_of_t), + deepxde.nn.FNN([4] + 4 * [50] + [4], "tanh", bst.init.KaimingUniform()), + deepxde.nn.ArrayToDict(u_vel=unit_of_speed, + v_vel=unit_of_speed, + w_vel=unit_of_speed, + p=unit_of_pressure), +) + +a = 1 +d = 1 +Re = 1 +rho = 1 * u.kilogram / u.meter ** 3 +mu = 1 * u.pascal * u.second + + +@bst.compile.jit +def pde(x, u): + jacobian = net.jacobian(x) + x_hessian = net.hessian(x, y=['u_vel', 'v_vel', 'w_vel'], xi=['x'], xj=['x']) + y_hessian = net.hessian(x, y=['u_vel', 'v_vel', 'w_vel'], xi=['y'], xj=['y']) + z_hessian = net.hessian(x, y=['u_vel', 'v_vel', 'w_vel'], xi=['z'], xj=['z']) + + u_vel, v_vel, w_vel, p = u['u_vel'], u['v_vel'], u['w_vel'], u['p'] + + du_vel_dx = jacobian['u_vel']['x'] + du_vel_dy = jacobian['u_vel']['y'] + du_vel_dz = jacobian['u_vel']['z'] + du_vel_dt = jacobian['u_vel']['t'] + du_vel_dx_dx = x_hessian['u_vel']['x']['x'] + du_vel_dy_dy = y_hessian['u_vel']['y']['y'] + du_vel_dz_dz = z_hessian['u_vel']['z']['z'] + + dv_vel_dx = jacobian['v_vel']['x'] + dv_vel_dy = jacobian['v_vel']['y'] + dv_vel_dz = jacobian['v_vel']['z'] + dv_vel_dt = jacobian['v_vel']['t'] + dv_vel_dx_dx = x_hessian['v_vel']['x']['x'] + dv_vel_dy_dy = y_hessian['v_vel']['y']['y'] + dv_vel_dz_dz = z_hessian['v_vel']['z']['z'] + + dw_vel_dx = jacobian['w_vel']['x'] + dw_vel_dy = jacobian['w_vel']['y'] + dw_vel_dz = jacobian['w_vel']['z'] + dw_vel_dt = jacobian['w_vel']['t'] + dw_vel_dx_dx = x_hessian['w_vel']['x']['x'] + dw_vel_dy_dy = y_hessian['w_vel']['y']['y'] + dw_vel_dz_dz = z_hessian['w_vel']['z']['z'] + + dp_dx = jacobian['p']['x'] + dp_dy = jacobian['p']['y'] + dp_dz = jacobian['p']['z'] + + momentum_x = ( + rho * (du_vel_dt + (u_vel * du_vel_dx + v_vel * du_vel_dy + w_vel * du_vel_dz)) + + dp_dx - mu * (du_vel_dx_dx + du_vel_dy_dy + du_vel_dz_dz) + ) + momentum_y = ( + rho * (dv_vel_dt + (u_vel * dv_vel_dx + v_vel * dv_vel_dy + w_vel * dv_vel_dz)) + + dp_dy - mu * (dv_vel_dx_dx + dv_vel_dy_dy + dv_vel_dz_dz) + ) + momentum_z = ( + rho * (dw_vel_dt + (u_vel * dw_vel_dx + v_vel * dw_vel_dy + w_vel * dw_vel_dz)) + + dp_dz - mu * (dw_vel_dx_dx + dw_vel_dy_dy + dw_vel_dz_dz) + ) + continuity = du_vel_dx + dv_vel_dy + dw_vel_dz + + return [momentum_x, momentum_y, momentum_z, continuity] + + +@bst.compile.jit(static_argnums=1) +def icbc_cond_func(x, include_p: bool = False): + x = {k: v.mantissa for k, v in x.items()} + + u_ = ( + -a + * (u.math.exp(a * x['x']) * u.math.sin(a * x['y'] + d * x['z']) + + u.math.exp(a * x['z']) * u.math.cos(a * x['x'] + d * x['y'])) + * u.math.exp(-(d ** 2) * x['t']) + ) + v = ( + -a + * (u.math.exp(a * x['y']) * u.math.sin(a * x['z'] + d * x['x']) + + u.math.exp(a * x['x']) * u.math.cos(a * x['y'] + d * x['z'])) + * u.math.exp(-(d ** 2) * x['t']) + ) + w = ( + -a + * (u.math.exp(a * x['z']) * u.math.sin(a * x['x'] + d * x['y']) + + u.math.exp(a * x['y']) * u.math.cos(a * x['z'] + d * x['x'])) + * u.math.exp(-(d ** 2) * x['t']) + ) + p = ( + -0.5 + * a ** 2 + * ( + u.math.exp(2 * a * x['x']) + + u.math.exp(2 * a * x['y']) + + u.math.exp(2 * a * x['z']) + + 2 + * u.math.sin(a * x['x'] + d * x['y']) + * u.math.cos(a * x['z'] + d * x['x']) + * u.math.exp(a * (x['y'] + x['z'])) + + 2 + * u.math.sin(a * x['y'] + d * x['z']) + * u.math.cos(a * x['x'] + d * x['y']) + * u.math.exp(a * (x['z'] + x['x'])) + + 2 + * u.math.sin(a * x['z'] + d * x['x']) + * u.math.cos(a * x['y'] + d * x['z']) + * u.math.exp(a * (x['x'] + x['y'])) + ) + * u.math.exp(-2 * d ** 2 * x['t']) + ) + + r = { + 'u_vel': u_ * unit_of_speed, + 'v_vel': v * unit_of_speed, + 'w_vel': w * unit_of_speed + } + if include_p: + r['p'] = p * unit_of_pressure + return r + + +bc = deepxde.icbc.DirichletBC(icbc_cond_func) +ic = deepxde.icbc.IC(icbc_cond_func) + +problem = deepxde.problem.TimePDE( + spatio_temporal_domain, + pde, + [bc, ic], + net, + num_domain=50000, + num_boundary=5000, + num_initial=5000, + num_test=10000, +) + +model = deepxde.Trainer(problem) + +model.compile(bst.optim.Adam(1e-3)).train(iterations=30000) +model.compile(bst.optim.LBFGS(1e-3)).train(5000, display_every=200) + +x, y, z = np.meshgrid(np.linspace(-1, 1, 10), np.linspace(-1, 1, 10), np.linspace(-1, 1, 10)) +t_0 = np.zeros(1000) +t_1 = np.ones(1000) +X_0 = dict( + x=np.ravel(x) * unit_of_space, + y=np.ravel(y) * unit_of_space, + z=np.ravel(z) * unit_of_space, + t=t_0 * unit_of_t +) +X_1 = dict( + x=np.ravel(x) * unit_of_space, + y=np.ravel(y) * unit_of_space, + z=np.ravel(z) * unit_of_space, + t=t_1 * unit_of_t +) +output_0 = model.predict(X_0) +output_1 = model.predict(X_1) + +out_exact_0 = icbc_cond_func(X_0, True) +out_exact_1 = icbc_cond_func(X_1, True) + +f_0 = pde(X_0, output_0) +f_1 = pde(X_1, output_1) +residual_0 = jax.tree.map(lambda x: np.mean(np.absolute(x)), f_0) +residual_1 = jax.tree.map(lambda x: np.mean(np.absolute(x)), f_1) + +print("Accuracy at t = 0:") +print("Mean residual:", residual_0) +print("L2 relative error:", deepxde.metrics.l2_relative_error(output_0, out_exact_0)) +print("\n") +print("Accuracy at t = 1:") +print("Mean residual:", residual_1) +print("L2 relative error:", deepxde.metrics.l2_relative_error(output_1, out_exact_1)) diff --git a/docs/experimental_docs/unit-examples-forward/Burgers.py b/docs/experimental_docs/unit-examples-forward/Burgers.py new file mode 100644 index 000000000..af7633aaf --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/Burgers.py @@ -0,0 +1,68 @@ +import brainstate as bst +import brainunit as u +import numpy as np + +import deepxde.experimental as deepxde + +geometry = deepxde.geometry.GeometryXTime( + geometry=deepxde.geometry.Interval(-1, 1.), + timedomain=deepxde.geometry.TimeDomain(0, 0.99) +).to_dict_point(x=u.meter, t=u.second) + +uy = u.meter / u.second +bc = deepxde.icbc.DirichletBC(lambda x: {'y': 0. * uy}) +ic = deepxde.icbc.IC(lambda x: {'y': -u.math.sin(u.math.pi * x['x'] / u.meter) * uy}) + +v = 0.01 / u.math.pi * u.meter ** 2 / u.second + + +def pde(x, y): + jacobian = approximator.jacobian(x) + hessian = approximator.hessian(x) + dy_x = jacobian['y']['x'] + dy_t = jacobian['y']['t'] + dy_xx = hessian['y']['x']['x'] + residual = dy_t + y['y'] * dy_x - v * dy_xx + return residual + + +approximator = deepxde.nn.Model( + deepxde.nn.DictToArray(x=u.meter, t=u.second), + deepxde.nn.FNN( + [geometry.dim] + [20] * 3 + [1], + "tanh", + bst.init.KaimingUniform() + ), + deepxde.nn.ArrayToDict(y=uy) +) + +problem = deepxde.problem.TimePDE( + geometry, + pde, + [bc, ic], + approximator, + num_domain=2540, + num_boundary=80, + num_initial=160, +) + +trainer = deepxde.Trainer(problem) +trainer.compile(bst.optim.Adam(1e-3)).train(iterations=15000) +trainer.compile(bst.optim.LBFGS(1e-3)).train(2000, display_every=500) +trainer.saveplot(issave=True, isplot=True) + + +def gen_testdata(): + data = np.load("../dataset/Burgers.npz") + t, x, exact = data["t"], data["x"], data["usol"].T + xx, tt = np.meshgrid(x, t) + X = {'x': np.ravel(xx) * u.meter, 't': np.ravel(tt) * u.second} + y = exact.flatten()[:, None] + return X, y * uy + + +X, y_true = gen_testdata() +y_pred = trainer.predict(X) +f = pde(X, y_pred) +print("Mean residual:", u.math.mean(u.math.absolute(f))) +print("L2 relative error:", deepxde.metrics.l2_relative_error(y_true, y_pred['y'])) diff --git a/docs/experimental_docs/unit-examples-forward/Burgers_RAR.ipynb b/docs/experimental_docs/unit-examples-forward/Burgers_RAR.ipynb new file mode 100644 index 000000000..160f8ff73 --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/Burgers_RAR.ipynb @@ -0,0 +1,1424 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Burgers equation with residual-based adaptive refinement\n", + "\n", + "\n", + "## Problem setup\n", + "\n", + "\n", + "We will solve a Burgers equation:\n", + "\n", + "$$\n", + "\\frac{\\partial u}{\\partial t} + u\\frac{\\partial u}{\\partial x} = \\nu\\frac{\\partial^2u}{\\partial x^2}, \\qquad x \\in [-1, 1], \\quad t \\in [0, 1]\n", + "$$\n", + "\n", + "\n", + "with the Dirichlet boundary conditions and initial conditions\n", + "\n", + "$$\n", + "u(-1,t)=u(1,t)=0, \\quad u(x,0) = - \\sin(\\pi x).\n", + "$$\n", + "\n", + "## Dimensional Analysis\n", + "\n", + "### Step 1: Assign Dimensions to Variables\n", + "\n", + "1. **Spatial Coordinate $x$:**\n", + " - The dimension of $x$ is length:\n", + "\n", + " $$\n", + " [x] = L.\n", + " $$\n", + "\n", + "2. **Time $t$:**\n", + " - The dimension of time is:\n", + "\n", + " $$\n", + " [t] = T.\n", + " $$\n", + "\n", + "3. **Velocity $u$:**\n", + " - Velocity has dimensions of length per unit time:\n", + "\n", + " $$\n", + " [u] = L / T.\n", + " $$\n", + "\n", + "4. **Viscosity $\\nu$:**\n", + " - The term $\\nu \\frac{\\partial^2 u}{\\partial x^2}$ involves the second spatial derivative of velocity, which must have the same dimensions as the time derivative $\\frac{\\partial u}{\\partial t}$.\n", + "\n", + "---\n", + "\n", + "### Step 2: Analyze the Dimensions of Each Term\n", + "\n", + "1. **Time Derivative Term:**\n", + " - The time derivative $\\frac{\\partial u}{\\partial t}$ has dimensions:\n", + "\n", + " $$\n", + " \\left[\\frac{\\partial u}{\\partial t}\\right] = \\frac{[u]}{[t]} = \\frac{L / T}{T} = \\frac{L}{T^2}.\n", + " $$\n", + "\n", + "2. **Advection Term:**\n", + " - The advection term $u \\frac{\\partial u}{\\partial x}$ involves the spatial derivative of velocity:\n", + "\n", + " $$\n", + " \\left[u \\frac{\\partial u}{\\partial x}\\right] = [u] \\cdot \\frac{[u]}{[x]} = \\frac{L}{T} \\cdot \\frac{L / T}{L} = \\frac{L}{T^2}.\n", + " $$\n", + "\n", + "3. **Diffusion Term:**\n", + " - The diffusion term $\\nu \\frac{\\partial^2 u}{\\partial x^2}$ involves the second spatial derivative of velocity:\n", + "\n", + " $$\n", + " \\left[\\frac{\\partial^2 u}{\\partial x^2}\\right] = \\frac{[u]}{[x]^2} = \\frac{L / T}{L^2} = \\frac{1}{L T}.\n", + " \n", + " $$\n", + " - Therefore, the diffusion term has dimensions:\n", + "\n", + " $$\n", + " \\left[\\nu \\frac{\\partial^2 u}{\\partial x^2}\\right] = [\\nu] \\cdot \\frac{1}{L T} = \\frac{L}{T^2}.\n", + " $$\n", + "\n", + "---\n", + "\n", + "### Step 3: Determine the Dimensions of $\\nu$\n", + "\n", + "- The diffusion term $\\nu \\frac{\\partial^2 u}{\\partial x^2}$ must have the same dimensions as the time derivative $\\frac{\\partial u}{\\partial t}$:\n", + "\n", + " $$\n", + " [\\nu] \\cdot \\frac{1}{L T} = \\frac{L}{T^2} \\implies [\\nu] = \\frac{L^2}{T}.\n", + " $$\n", + "- Therefore, the viscosity $\\nu$ has dimensions of kinematic viscosity:\n", + "\n", + " $$\n", + " [\\nu] = \\frac{L^2}{T}.\n", + " $$\n", + "\n", + "---\n", + "\n", + "### Step 4: Summary of Dimensions\n", + "\n", + "| Variable/Parameter | Physical Meaning | Dimensions |\n", + "|------------------------|-----------------------------------|-----------------------|\n", + "| $x$ | Spatial coordinate | $L$ |\n", + "| $t$ | Time | $T$ |\n", + "| $u$ | Velocity | $L / T$ |\n", + "| $\\nu$ | Kinematic viscosity | $L^2 / T$ |\n", + "\n", + "---\n", + "\n", + "### Step 5: Initial and Boundary Conditions\n", + "\n", + "1. **Boundary Conditions:**\n", + " - The boundary conditions $u(-1,t) = u(1,t) = 0$ are given in meters per second:\n", + "\n", + " $$\n", + " [u(-1,t)] = [u(1,t)] = L / T.\n", + " $$\n", + "\n", + "2. **Initial Condition:**\n", + " - The initial condition $u(x,0) = -\\sin(\\pi x)$ is given in meters per second:\n", + " \n", + " $$\n", + " [u(x,0)] = L / T.\n", + " $$\n", + " - The term $\\sin(\\pi x)$ is dimensionless because $x$ is in meters, and $\\pi$ is a dimensionless constant." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This description goes through the implementation of a solver for the above described Burgers equation step-by-step.\n", + "\n", + "First, import the libraries we need:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "import brainstate as bst\n", + "import brainunit as u\n", + "import numpy as np\n", + "import jax\n", + "import deepxde.experimental as deepxde" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We begin by defining a computational geometry and time domain. We can use a built-in class ``Interval`` and ``TimeDomain`` and we combine both the domains using ``GeometryXTime`` as follows:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "geomtime = deepxde.geometry.GeometryXTime(\n", + " geometry=deepxde.geometry.Interval(-1., 1.),\n", + " timedomain=deepxde.geometry.TimeDomain(0., 0.99)\n", + ").to_dict_point(x=u.meter, t=u.second)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we express the PDE residual of the Burgers equation:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "v = 0.01 / u.math.pi * u.meter ** 2 / u.second\n", + "\n", + "\n", + "def pde(x, y):\n", + " jacobian = approximator.jacobian(x)\n", + " hessian = approximator.hessian(x)\n", + " dy_x = jacobian['y']['x']\n", + " dy_t = jacobian['y']['t']\n", + " dy_xx = hessian['y']['x']['x']\n", + " residual = dy_t + y['y'] * dy_x - v * dy_xx\n", + " return residual" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we consider the boundary/initial condition. ``on_boundary`` is chosen here to use the whole boundary of the computational domain in considered as the boundary condition. We include the ``geomtime`` space, time geometry created above and ``on_boundary`` as the BCs in the ``DirichletBC`` function of DeepXDE. We also define ``IC`` which is the inital condition for the burgers equation and we use the computational domain, initial function, and ``on_initial`` to specify the IC.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "uy = u.meter / u.second\n", + "\n", + "bc = deepxde.icbc.DirichletBC(lambda x: {'y': 0. * uy})\n", + "ic = deepxde.icbc.IC(lambda x: {'y': -u.math.sin(u.math.pi * x['x'] / u.meter) * uy})\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we choose the network. Here, we use a fully connected neural network of depth 4 (i.e., 3 hidden layers) and width 20:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "approximator = deepxde.nn.Model(\n", + " deepxde.nn.DictToArray(x=u.meter, t=u.second),\n", + " deepxde.nn.FNN(\n", + " [geometry.dim] + [20] * 3 + [1],\n", + " \"tanh\",\n", + " bst.init.KaimingUniform()\n", + " ),\n", + " deepxde.nn.ArrayToDict(y=uy)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we have specified the geometry, PDE residual, and boundary/initial condition. We then define the ``TimePDE`` problem as\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "problem = deepxde.problem.TimePDE(\n", + " geometry,\n", + " pde,\n", + " [bc, ic],\n", + " approximator,\n", + " num_domain=2540,\n", + " num_boundary=80,\n", + " num_initial=160,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The number 2540 is the number of training residual points sampled inside the domain, and the number 80 is the number of training points sampled on the boundary. We also include 160 initial residual points for the initial conditions.\n", + "\n", + "Now, we have the PDE problem and the network. We build a ``Trainer`` and choose the optimizer and learning rate:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Compiling trainer...\n", + "'compile' took 0.003058 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "0 [2.1140547 * 10.0^0 * ((meter / second) / second) ** 2, [2.1140547 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 0.09046214 * meter / second}}, {'ibc0': {'y': 0.09046214 * meter / second}}, \n", + " {'ibc1': {'y': 0.23645507 * meter / second}}] {'ibc1': {'y': 0.23645507 * meter / second}}] \n", + "1000 [0.05101593 * 10.0^0 * ((meter / second) / second) ** 2, [0.05101593 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 0.00244636 * meter / second}}, {'ibc0': {'y': 0.00244636 * meter / second}}, \n", + " {'ibc1': {'y': 0.06664469 * meter / second}}] {'ibc1': {'y': 0.06664469 * meter / second}}] \n", + "2000 [0.04578441 * 10.0^0 * ((meter / second) / second) ** 2, [0.04578441 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 0.00104335 * meter / second}}, {'ibc0': {'y': 0.00104335 * meter / second}}, \n", + " {'ibc1': {'y': 0.05536607 * meter / second}}] {'ibc1': {'y': 0.05536607 * meter / second}}] \n", + "3000 [0.04083971 * 10.0^0 * ((meter / second) / second) ** 2, [0.04083971 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 0.00048144 * meter / second}}, {'ibc0': {'y': 0.00048144 * meter / second}}, \n", + " {'ibc1': {'y': 0.05146844 * meter / second}}] {'ibc1': {'y': 0.05146844 * meter / second}}] \n", + "4000 [0.03656165 * 10.0^0 * ((meter / second) / second) ** 2, [0.03656165 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 0.00020483 * meter / second}}, {'ibc0': {'y': 0.00020483 * meter / second}}, \n", + " {'ibc1': {'y': 0.04795899 * meter / second}}] {'ibc1': {'y': 0.04795899 * meter / second}}] \n", + "5000 [0.03201985 * 10.0^0 * ((meter / second) / second) ** 2, [0.03201985 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 5.5738237e-05 * meter / second}}, {'ibc0': {'y': 5.5738237e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.04417896 * meter / second}}] {'ibc1': {'y': 0.04417896 * meter / second}}] \n", + "6000 [0.01897248 * 10.0^0 * ((meter / second) / second) ** 2, [0.01897248 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.411638e-05 * meter / second}}, {'ibc0': {'y': 3.411638e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.02110803 * meter / second}}] {'ibc1': {'y': 0.02110803 * meter / second}}] \n", + "7000 [0.0083445 * 10.0^0 * ((meter / second) / second) ** 2, [0.0083445 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9857232e-05 * meter / second}}, {'ibc0': {'y': 1.9857232e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00840012 * meter / second}}] {'ibc1': {'y': 0.00840012 * meter / second}}] \n", + "8000 [0.00405152 * 10.0^0 * ((meter / second) / second) ** 2, [0.00405152 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.13869e-05 * meter / second}}, {'ibc0': {'y': 3.13869e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.0031565 * meter / second}}] {'ibc1': {'y': 0.0031565 * meter / second}}] \n", + "9000 [0.00258377 * 10.0^0 * ((meter / second) / second) ** 2, [0.00258377 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.9788125e-05 * meter / second}}, {'ibc0': {'y': 2.9788125e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00185996 * meter / second}}] {'ibc1': {'y': 0.00185996 * meter / second}}] \n", + "10000 [0.00182265 * 10.0^0 * ((meter / second) / second) ** 2, [0.00182265 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8763618e-05 * meter / second}}, {'ibc0': {'y': 1.8763618e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00121202 * meter / second}}] {'ibc1': {'y': 0.00121202 * meter / second}}] \n", + "11000 [0.00131502 * 10.0^0 * ((meter / second) / second) ** 2, [0.00131502 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1222968e-05 * meter / second}}, {'ibc0': {'y': 1.1222968e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00087704 * meter / second}}] {'ibc1': {'y': 0.00087704 * meter / second}}] \n", + "12000 [0.00102354 * 10.0^0 * ((meter / second) / second) ** 2, [0.00102354 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 7.5733656e-06 * meter / second}}, {'ibc0': {'y': 7.5733656e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00069363 * meter / second}}] {'ibc1': {'y': 0.00069363 * meter / second}}] \n", + "13000 [0.00085795 * 10.0^0 * ((meter / second) / second) ** 2, [0.00085795 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 6.1193127e-06 * meter / second}}, {'ibc0': {'y': 6.1193127e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00059203 * meter / second}}] {'ibc1': {'y': 0.00059203 * meter / second}}] \n", + "14000 [0.00075481 * 10.0^0 * ((meter / second) / second) ** 2, [0.00075481 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 5.336154e-06 * meter / second}}, {'ibc0': {'y': 5.336154e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00052162 * meter / second}}] {'ibc1': {'y': 0.00052162 * meter / second}}] \n", + "15000 [0.00067973 * 10.0^0 * ((meter / second) / second) ** 2, [0.00067973 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.3045325e-06 * meter / second}}, {'ibc0': {'y': 4.3045325e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00046943 * meter / second}}] {'ibc1': {'y': 0.00046943 * meter / second}}] \n", + "\n", + "Best trainer at step 15000:\n", + " train loss: 1.15e-03\n", + " test loss: 1.15e-03\n", + " test metric: []\n", + "\n", + "'train' took 60.923828 s\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trainer = deepxde.Trainer(problem)\n", + "trainer.compile(bst.optim.Adam(1e-3)).train(iterations=15000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After we train the network using Adam, we continue to train the network using L-BFGS to achieve a smaller loss:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Compiling trainer...\n", + "'compile' took 0.023359 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "15000 [0.00067973 * 10.0^0 * ((meter / second) / second) ** 2, [0.00067973 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.3045325e-06 * meter / second}}, {'ibc0': {'y': 4.3045325e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00046943 * meter / second}}] {'ibc1': {'y': 0.00046943 * meter / second}}] \n", + "15200 [0.00068126 * 10.0^0 * ((meter / second) / second) ** 2, [0.00068126 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.169301e-06 * meter / second}}, {'ibc0': {'y': 4.169301e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00046952 * meter / second}}] {'ibc1': {'y': 0.00046952 * meter / second}}] \n", + "15400 [0.00068003 * 10.0^0 * ((meter / second) / second) ** 2, [0.00068003 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.1923004e-06 * meter / second}}, {'ibc0': {'y': 4.1923004e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00046954 * meter / second}}] {'ibc1': {'y': 0.00046954 * meter / second}}] \n", + "15600 [0.00067914 * 10.0^0 * ((meter / second) / second) ** 2, [0.00067914 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.2128318e-06 * meter / second}}, {'ibc0': {'y': 4.2128318e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00046957 * meter / second}}] {'ibc1': {'y': 0.00046957 * meter / second}}] \n", + "15800 [0.00067831 * 10.0^0 * ((meter / second) / second) ** 2, [0.00067831 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.2393303e-06 * meter / second}}, {'ibc0': {'y': 4.2393303e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0004696 * meter / second}}] {'ibc1': {'y': 0.0004696 * meter / second}}] \n", + "16000 [0.00067745 * 10.0^0 * ((meter / second) / second) ** 2, [0.00067745 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.397528e-06 * meter / second}}, {'ibc0': {'y': 4.397528e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0004696 * meter / second}}] {'ibc1': {'y': 0.0004696 * meter / second}}] \n", + "16200 [0.00067771 * 10.0^0 * ((meter / second) / second) ** 2, [0.00067771 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.1383482e-06 * meter / second}}, {'ibc0': {'y': 4.1383482e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00046968 * meter / second}}] {'ibc1': {'y': 0.00046968 * meter / second}}] \n", + "16400 [0.00067749 * 10.0^0 * ((meter / second) / second) ** 2, [0.00067749 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.25508e-06 * meter / second}}, {'ibc0': {'y': 4.25508e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00046969 * meter / second}}] {'ibc1': {'y': 0.00046969 * meter / second}}] \n", + "16600 [0.00067738 * 10.0^0 * ((meter / second) / second) ** 2, [0.00067738 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.3410837e-06 * meter / second}}, {'ibc0': {'y': 4.3410837e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00046968 * meter / second}}] {'ibc1': {'y': 0.00046968 * meter / second}}] \n", + "16800 [0.0006782 * 10.0^0 * ((meter / second) / second) ** 2, [0.0006782 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.657258e-06 * meter / second}}, {'ibc0': {'y': 3.657258e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00047006 * meter / second}}] {'ibc1': {'y': 0.00047006 * meter / second}}] \n", + "17000 [0.00067788 * 10.0^0 * ((meter / second) / second) ** 2, [0.00067788 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.8210997e-06 * meter / second}}, {'ibc0': {'y': 3.8210997e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00046992 * meter / second}}] {'ibc1': {'y': 0.00046992 * meter / second}}] \n", + "\n", + "Best trainer at step 16600:\n", + " train loss: 1.15e-03\n", + " test loss: 1.15e-03\n", + " test metric: []\n", + "\n", + "'train' took 9.051486 s\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trainer.compile(bst.optim.LBFGS(1e-3)).train(2000, display_every=200)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because we only use 2500 residual points for training, the accuracy is low. Next, we improve the accuracy by the residual-based adaptive refinement (RAR) method. Because the Burgers equation has a sharp front, intuitively, we should put more points near the sharp front. First, we randomly generate 100000 points from our domain to calculate the PDE residual." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "X = geomtime.random_points(100000)\n", + "err = 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will repeatedly add points while the mean residual is greater than 0.005. Each iteration, we use our model to generate predictions for inputs in `X` and compute the absolute values of the errors. We then print the mean residual. Next, we find the points where the residual is greatest and add these new points for training PDE loss. Furthermore, we define a callback function to check whether the network converges. If there is significant improvement in the model’s accuracy, as judged by the callback function, we continue to train the model. As before, after we train the network using Adam, we continue to train the network using L-BFGS to achieve a smaller loss:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mean residual: 0.018 * (meter / second) / second\n", + "Adding new point: {'t': ArrayImpl([0.31030682], dtype=float32) * second, 'x': ArrayImpl([0.00072277], dtype=float32) * meter} \n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.013387 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "17000 [0.00295319 * 10.0^0 * ((meter / second) / second) ** 2, [0.00067788 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.8210997e-06 * meter / second}}, {'ibc0': {'y': 3.8210997e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00046992 * meter / second}}] {'ibc1': {'y': 0.00046992 * meter / second}}] \n", + "18000 [0.00108327 * 10.0^0 * ((meter / second) / second) ** 2, [0.0009432 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 5.890686e-06 * meter / second}}, {'ibc0': {'y': 5.890686e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00053691 * meter / second}}] {'ibc1': {'y': 0.00053691 * meter / second}}] \n", + "19000 [0.00092558 * 10.0^0 * ((meter / second) / second) ** 2, [0.000839 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.3144605e-06 * meter / second}}, {'ibc0': {'y': 4.3144605e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00054824 * meter / second}}] {'ibc1': {'y': 0.00054824 * meter / second}}] \n", + "20000 [0.00081564 * 10.0^0 * ((meter / second) / second) ** 2, [0.00076047 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.5706555e-06 * meter / second}}, {'ibc0': {'y': 3.5706555e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00053703 * meter / second}}] {'ibc1': {'y': 0.00053703 * meter / second}}] \n", + "21000 [0.00096415 * 10.0^0 * ((meter / second) / second) ** 2, [0.00090121 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.095076e-06 * meter / second}}, {'ibc0': {'y': 4.095076e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00050534 * meter / second}}] {'ibc1': {'y': 0.00050534 * meter / second}}] \n", + "22000 [0.00064556 * 10.0^0 * ((meter / second) / second) ** 2, [0.00062304 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.185172e-06 * meter / second}}, {'ibc0': {'y': 3.185172e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00046558 * meter / second}}] {'ibc1': {'y': 0.00046558 * meter / second}}] \n", + "23000 [0.00058509 * 10.0^0 * ((meter / second) / second) ** 2, [0.00056849 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.078764e-06 * meter / second}}, {'ibc0': {'y': 3.078764e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00042695 * meter / second}}] {'ibc1': {'y': 0.00042695 * meter / second}}] \n", + "24000 [0.00053798 * 10.0^0 * ((meter / second) / second) ** 2, [0.00052545 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.944127e-06 * meter / second}}, {'ibc0': {'y': 2.944127e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0003891 * meter / second}}] {'ibc1': {'y': 0.0003891 * meter / second}}] \n", + "25000 [0.00053603 * 10.0^0 * ((meter / second) / second) ** 2, [0.00052135 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.57443e-06 * meter / second}}, {'ibc0': {'y': 3.57443e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00035367 * meter / second}}] {'ibc1': {'y': 0.00035367 * meter / second}}] \n", + "26000 [0.00046754 * 10.0^0 * ((meter / second) / second) ** 2, [0.00046 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.5451907e-06 * meter / second}}, {'ibc0': {'y': 2.5451907e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00032271 * meter / second}}] {'ibc1': {'y': 0.00032271 * meter / second}}] \n", + "27000 [0.00044109 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043435 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.2023457e-06 * meter / second}}, {'ibc0': {'y': 2.2023457e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029436 * meter / second}}] {'ibc1': {'y': 0.00029436 * meter / second}}] \n", + "\n", + "Best trainer at step 27000:\n", + " train loss: 7.38e-04\n", + " test loss: 7.31e-04\n", + " test metric: []\n", + "\n", + "'train' took 38.123230 s\n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.009922 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "27000 [0.00044109 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043435 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.2023457e-06 * meter / second}}, {'ibc0': {'y': 2.2023457e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029436 * meter / second}}] {'ibc1': {'y': 0.00029436 * meter / second}}] \n", + "27100 [0.00044241 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043749 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.3348348e-06 * meter / second}}, {'ibc0': {'y': 2.3348348e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029449 * meter / second}}] {'ibc1': {'y': 0.00029449 * meter / second}}] \n", + "27200 [0.00044184 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043681 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.326817e-06 * meter / second}}, {'ibc0': {'y': 2.326817e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029447 * meter / second}}] {'ibc1': {'y': 0.00029447 * meter / second}}] \n", + "27300 [0.00044135 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043622 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.3192913e-06 * meter / second}}, {'ibc0': {'y': 2.3192913e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029445 * meter / second}}] {'ibc1': {'y': 0.00029445 * meter / second}}] \n", + "27400 [0.00044094 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043571 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.3149416e-06 * meter / second}}, {'ibc0': {'y': 2.3149416e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029443 * meter / second}}] {'ibc1': {'y': 0.00029443 * meter / second}}] \n", + "27500 [0.00044026 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043484 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.307417e-06 * meter / second}}, {'ibc0': {'y': 2.307417e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029437 * meter / second}}] {'ibc1': {'y': 0.00029437 * meter / second}}] \n", + "27600 [0.0004401 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043462 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.3055607e-06 * meter / second}}, {'ibc0': {'y': 2.3055607e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029435 * meter / second}}] {'ibc1': {'y': 0.00029435 * meter / second}}] \n", + "27700 [0.00044003 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043452 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.3062646e-06 * meter / second}}, {'ibc0': {'y': 2.3062646e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029433 * meter / second}}] {'ibc1': {'y': 0.00029433 * meter / second}}] \n", + "27800 [0.00043955 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043361 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.3343398e-06 * meter / second}}, {'ibc0': {'y': 2.3343398e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029413 * meter / second}}] {'ibc1': {'y': 0.00029413 * meter / second}}] \n", + "27900 [0.00043953 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043358 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.3186947e-06 * meter / second}}, {'ibc0': {'y': 2.3186947e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029415 * meter / second}}] {'ibc1': {'y': 0.00029415 * meter / second}}] \n", + "28000 [0.00070291 * 10.0^0 * ((meter / second) / second) ** 2, [0.00070311 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.3909661e-05 * meter / second}}, {'ibc0': {'y': 1.3909661e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00030785 * meter / second}}] {'ibc1': {'y': 0.00030785 * meter / second}}] \n", + "\n", + "Best trainer at step 27900:\n", + " train loss: 7.36e-04\n", + " test loss: 7.30e-04\n", + " test metric: []\n", + "\n", + "'train' took 4.621998 s\n", + "\n", + "Mean residual: 0.016 * (meter / second) / second\n", + "Adding new point: {'t': ArrayImpl([0.98801452], dtype=float32) * second, 'x': ArrayImpl([-0.00032282], dtype=float32) * meter} \n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.013920 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "28000 [0.00197229 * 10.0^0 * ((meter / second) / second) ** 2, [0.00070311 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.3909661e-05 * meter / second}}, {'ibc0': {'y': 1.3909661e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00030785 * meter / second}}] {'ibc1': {'y': 0.00030785 * meter / second}}] \n", + "29000 [0.00051649 * 10.0^0 * ((meter / second) / second) ** 2, [0.00050734 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.4825522e-06 * meter / second}}, {'ibc0': {'y': 2.4825522e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0002885 * meter / second}}] {'ibc1': {'y': 0.0002885 * meter / second}}] \n", + "30000 [0.00048944 * 10.0^0 * ((meter / second) / second) ** 2, [0.00048262 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.2519919e-06 * meter / second}}, {'ibc0': {'y': 2.2519919e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00028214 * meter / second}}] {'ibc1': {'y': 0.00028214 * meter / second}}] \n", + "31000 [0.00046857 * 10.0^0 * ((meter / second) / second) ** 2, [0.00046308 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.1248854e-06 * meter / second}}, {'ibc0': {'y': 2.1248854e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0002748 * meter / second}}] {'ibc1': {'y': 0.0002748 * meter / second}}] \n", + "Epoch 31000: early stopping\n", + "\n", + "Best trainer at step 31000:\n", + " train loss: 7.45e-04\n", + " test loss: 7.40e-04\n", + " test metric: []\n", + "\n", + "'train' took 12.569310 s\n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.009631 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "31000 [0.00046857 * 10.0^0 * ((meter / second) / second) ** 2, [0.00046308 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.1248854e-06 * meter / second}}, {'ibc0': {'y': 2.1248854e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0002748 * meter / second}}] {'ibc1': {'y': 0.0002748 * meter / second}}] \n", + "31100 [0.00046857 * 10.0^0 * ((meter / second) / second) ** 2, [0.00046308 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.1262192e-06 * meter / second}}, {'ibc0': {'y': 2.1262192e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0002748 * meter / second}}] {'ibc1': {'y': 0.0002748 * meter / second}}] \n", + "31200 [0.00046849 * 10.0^0 * ((meter / second) / second) ** 2, [0.00046293 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.1473877e-06 * meter / second}}, {'ibc0': {'y': 2.1473877e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027483 * meter / second}}] {'ibc1': {'y': 0.00027483 * meter / second}}] \n", + "31300 [0.00073493 * 10.0^0 * ((meter / second) / second) ** 2, [0.0007051 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 6.5290965e-06 * meter / second}}, {'ibc0': {'y': 6.5290965e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029808 * meter / second}}] {'ibc1': {'y': 0.00029808 * meter / second}}] \n", + "31400 [0.00068256 * 10.0^0 * ((meter / second) / second) ** 2, [0.00065663 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 5.5905107e-06 * meter / second}}, {'ibc0': {'y': 5.5905107e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029581 * meter / second}}] {'ibc1': {'y': 0.00029581 * meter / second}}] \n", + "31500 [0.00063919 * 10.0^0 * ((meter / second) / second) ** 2, [0.00061659 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.833671e-06 * meter / second}}, {'ibc0': {'y': 4.833671e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029365 * meter / second}}] {'ibc1': {'y': 0.00029365 * meter / second}}] \n", + "31600 [0.00060404 * 10.0^0 * ((meter / second) / second) ** 2, [0.00058418 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.2318925e-06 * meter / second}}, {'ibc0': {'y': 4.2318925e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029175 * meter / second}}] {'ibc1': {'y': 0.00029175 * meter / second}}] \n", + "31700 [0.0005764 * 10.0^0 * ((meter / second) / second) ** 2, [0.00055869 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.7593527e-06 * meter / second}}, {'ibc0': {'y': 3.7593527e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029007 * meter / second}}] {'ibc1': {'y': 0.00029007 * meter / second}}] \n", + "31800 [0.00055442 * 10.0^0 * ((meter / second) / second) ** 2, [0.00053848 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.3909535e-06 * meter / second}}, {'ibc0': {'y': 3.3909535e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00028856 * meter / second}}] {'ibc1': {'y': 0.00028856 * meter / second}}] \n", + "31900 [0.00053654 * 10.0^0 * ((meter / second) / second) ** 2, [0.00052207 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.0938609e-06 * meter / second}}, {'ibc0': {'y': 3.0938609e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00028721 * meter / second}}] {'ibc1': {'y': 0.00028721 * meter / second}}] \n", + "32000 [0.00052238 * 10.0^0 * ((meter / second) / second) ** 2, [0.00050915 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.8530642e-06 * meter / second}}, {'ibc0': {'y': 2.8530642e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00028593 * meter / second}}] {'ibc1': {'y': 0.00028593 * meter / second}}] \n", + "\n", + "Best trainer at step 31200:\n", + " train loss: 7.45e-04\n", + " test loss: 7.40e-04\n", + " test metric: []\n", + "\n", + "'train' took 4.693850 s\n", + "\n", + "Mean residual: 0.014 * (meter / second) / second\n", + "Adding new point: {'t': ArrayImpl([0.40964028], dtype=float32) * second, 'x': ArrayImpl([-0.00796556], dtype=float32) * meter} \n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.013628 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "32000 [0.00079346 * 10.0^0 * ((meter / second) / second) ** 2, [0.00050915 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.8530642e-06 * meter / second}}, {'ibc0': {'y': 2.8530642e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00028593 * meter / second}}] {'ibc1': {'y': 0.00028593 * meter / second}}] \n", + "33000 [0.00055445 * 10.0^0 * ((meter / second) / second) ** 2, [0.0004924 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.0238429e-06 * meter / second}}, {'ibc0': {'y': 2.0238429e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029025 * meter / second}}] {'ibc1': {'y': 0.00029025 * meter / second}}] \n", + "34000 [0.00052658 * 10.0^0 * ((meter / second) / second) ** 2, [0.00047557 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.0058028e-06 * meter / second}}, {'ibc0': {'y': 2.0058028e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029186 * meter / second}}] {'ibc1': {'y': 0.00029186 * meter / second}}] \n", + "35000 [0.00050164 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045865 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9805689e-06 * meter / second}}, {'ibc0': {'y': 1.9805689e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0002901 * meter / second}}] {'ibc1': {'y': 0.0002901 * meter / second}}] \n", + "Epoch 35000: early stopping\n", + "\n", + "Best trainer at step 35000:\n", + " train loss: 7.94e-04\n", + " test loss: 7.51e-04\n", + " test metric: []\n", + "\n", + "'train' took 11.863162 s\n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.009882 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "35000 [0.00050164 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045865 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9805689e-06 * meter / second}}, {'ibc0': {'y': 1.9805689e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0002901 * meter / second}}] {'ibc1': {'y': 0.0002901 * meter / second}}] \n", + "35100 [0.00050166 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045857 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9694924e-06 * meter / second}}, {'ibc0': {'y': 1.9694924e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029009 * meter / second}}] {'ibc1': {'y': 0.00029009 * meter / second}}] \n", + "35200 [0.00050164 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045859 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9775282e-06 * meter / second}}, {'ibc0': {'y': 1.9775282e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0002901 * meter / second}}] {'ibc1': {'y': 0.0002901 * meter / second}}] \n", + "35300 [0.00050156 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045861 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.0046225e-06 * meter / second}}, {'ibc0': {'y': 2.0046225e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029016 * meter / second}}] {'ibc1': {'y': 0.00029016 * meter / second}}] \n", + "35400 [0.00050155 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045858 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.0021143e-06 * meter / second}}, {'ibc0': {'y': 2.0021143e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029016 * meter / second}}] {'ibc1': {'y': 0.00029016 * meter / second}}] \n", + "35500 [0.00050156 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045858 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.0010755e-06 * meter / second}}, {'ibc0': {'y': 2.0010755e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029016 * meter / second}}] {'ibc1': {'y': 0.00029016 * meter / second}}] \n", + "35600 [0.00050155 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045855 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9991612e-06 * meter / second}}, {'ibc0': {'y': 1.9991612e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029016 * meter / second}}] {'ibc1': {'y': 0.00029016 * meter / second}}] \n", + "35700 [0.00050155 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045855 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9982401e-06 * meter / second}}, {'ibc0': {'y': 1.9982401e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029016 * meter / second}}] {'ibc1': {'y': 0.00029016 * meter / second}}] \n", + "35800 [0.00050156 * 10.0^0 * ((meter / second) / second) ** 2, [0.0004586 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9993226e-06 * meter / second}}, {'ibc0': {'y': 1.9993226e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029015 * meter / second}}] {'ibc1': {'y': 0.00029015 * meter / second}}] \n", + "35900 [0.00050156 * 10.0^0 * ((meter / second) / second) ** 2, [0.0004586 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9988458e-06 * meter / second}}, {'ibc0': {'y': 1.9988458e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029015 * meter / second}}] {'ibc1': {'y': 0.00029015 * meter / second}}] \n", + "36000 [0.00050155 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045858 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9998658e-06 * meter / second}}, {'ibc0': {'y': 1.9998658e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029015 * meter / second}}] {'ibc1': {'y': 0.00029015 * meter / second}}] \n", + "\n", + "Best trainer at step 36000:\n", + " train loss: 7.94e-04\n", + " test loss: 7.51e-04\n", + " test metric: []\n", + "\n", + "'train' took 4.908738 s\n", + "\n", + "Mean residual: 0.013 * (meter / second) / second\n", + "Adding new point: {'t': ArrayImpl([0.2315986], dtype=float32) * second, 'x': ArrayImpl([-0.00680643], dtype=float32) * meter} \n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.015565 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "36000 [0.00074802 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045858 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9998658e-06 * meter / second}}, {'ibc0': {'y': 1.9998658e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029015 * meter / second}}] {'ibc1': {'y': 0.00029015 * meter / second}}] \n", + "37000 [0.00059534 * 10.0^0 * ((meter / second) / second) ** 2, [0.00048061 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9475647e-06 * meter / second}}, {'ibc0': {'y': 1.9475647e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00031009 * meter / second}}] {'ibc1': {'y': 0.00031009 * meter / second}}] \n", + "38000 [0.0005734 * 10.0^0 * ((meter / second) / second) ** 2, [0.00047273 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9098984e-06 * meter / second}}, {'ibc0': {'y': 1.9098984e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00030421 * meter / second}}] {'ibc1': {'y': 0.00030421 * meter / second}}] \n", + "39000 [0.00054813 * 10.0^0 * ((meter / second) / second) ** 2, [0.00046036 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8778497e-06 * meter / second}}, {'ibc0': {'y': 1.8778497e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029654 * meter / second}}] {'ibc1': {'y': 0.00029654 * meter / second}}] \n", + "Epoch 39000: early stopping\n", + "\n", + "Best trainer at step 39000:\n", + " train loss: 8.47e-04\n", + " test loss: 7.59e-04\n", + " test metric: []\n", + "\n", + "'train' took 12.797805 s\n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.010421 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "39000 [0.00054813 * 10.0^0 * ((meter / second) / second) ** 2, [0.00046036 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8778497e-06 * meter / second}}, {'ibc0': {'y': 1.8778497e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029654 * meter / second}}] {'ibc1': {'y': 0.00029654 * meter / second}}] \n", + "39100 [0.00054806 * 10.0^0 * ((meter / second) / second) ** 2, [0.00046043 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.870236e-06 * meter / second}}, {'ibc0': {'y': 1.870236e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029661 * meter / second}}] {'ibc1': {'y': 0.00029661 * meter / second}}] \n", + "39200 [0.00054929 * 10.0^0 * ((meter / second) / second) ** 2, [0.0004634 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.0574782e-06 * meter / second}}, {'ibc0': {'y': 2.0574782e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029533 * meter / second}}] {'ibc1': {'y': 0.00029533 * meter / second}}] \n", + "39300 [0.00054904 * 10.0^0 * ((meter / second) / second) ** 2, [0.00046287 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.0242942e-06 * meter / second}}, {'ibc0': {'y': 2.0242942e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029551 * meter / second}}] {'ibc1': {'y': 0.00029551 * meter / second}}] \n", + "39400 [0.00054891 * 10.0^0 * ((meter / second) / second) ** 2, [0.00046254 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.004753e-06 * meter / second}}, {'ibc0': {'y': 2.004753e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029562 * meter / second}}] {'ibc1': {'y': 0.00029562 * meter / second}}] \n", + "39500 [0.00054882 * 10.0^0 * ((meter / second) / second) ** 2, [0.0004622 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.994075e-06 * meter / second}}, {'ibc0': {'y': 1.994075e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0002957 * meter / second}}] {'ibc1': {'y': 0.0002957 * meter / second}}] \n", + "39600 [0.0005721 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045579 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.6710437e-06 * meter / second}}, {'ibc0': {'y': 1.6710437e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00030883 * meter / second}}] {'ibc1': {'y': 0.00030883 * meter / second}}] \n", + "39700 [0.00056654 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045358 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.5904287e-06 * meter / second}}, {'ibc0': {'y': 1.5904287e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00030726 * meter / second}}] {'ibc1': {'y': 0.00030726 * meter / second}}] \n", + "39800 [0.00056229 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045216 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.5325267e-06 * meter / second}}, {'ibc0': {'y': 1.5325267e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00030581 * meter / second}}] {'ibc1': {'y': 0.00030581 * meter / second}}] \n", + "39900 [0.0005591 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045143 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.498971e-06 * meter / second}}, {'ibc0': {'y': 1.498971e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00030449 * meter / second}}] {'ibc1': {'y': 0.00030449 * meter / second}}] \n", + "40000 [0.00055651 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045115 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.4806845e-06 * meter / second}}, {'ibc0': {'y': 1.4806845e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00030329 * meter / second}}] {'ibc1': {'y': 0.00030329 * meter / second}}] \n", + "\n", + "Best trainer at step 39500:\n", + " train loss: 8.47e-04\n", + " test loss: 7.60e-04\n", + " test metric: []\n", + "\n", + "'train' took 4.848265 s\n", + "\n", + "Mean residual: 0.013 * (meter / second) / second\n", + "Adding new point: {'t': ArrayImpl([0.98284292], dtype=float32) * second, 'x': ArrayImpl([0.00254142], dtype=float32) * meter} \n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.014243 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "40000 [0.0006681 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045115 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.4806845e-06 * meter / second}}, {'ibc0': {'y': 1.4806845e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00030329 * meter / second}}] {'ibc1': {'y': 0.00030329 * meter / second}}] \n", + "41000 [0.00057995 * 10.0^0 * ((meter / second) / second) ** 2, [0.00048127 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.0367063e-06 * meter / second}}, {'ibc0': {'y': 2.0367063e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029667 * meter / second}}] {'ibc1': {'y': 0.00029667 * meter / second}}] \n", + "42000 [0.00055732 * 10.0^0 * ((meter / second) / second) ** 2, [0.000462 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8838784e-06 * meter / second}}, {'ibc0': {'y': 1.8838784e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0002966 * meter / second}}] {'ibc1': {'y': 0.0002966 * meter / second}}] \n", + "43000 [0.0005333 * 10.0^0 * ((meter / second) / second) ** 2, [0.00044418 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8350134e-06 * meter / second}}, {'ibc0': {'y': 1.8350134e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00029156 * meter / second}}] {'ibc1': {'y': 0.00029156 * meter / second}}] \n", + "44000 [0.00050091 * 10.0^0 * ((meter / second) / second) ** 2, [0.0004224 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.826639e-06 * meter / second}}, {'ibc0': {'y': 1.826639e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027955 * meter / second}}] {'ibc1': {'y': 0.00027955 * meter / second}}] \n", + "Epoch 44000: early stopping\n", + "\n", + "Best trainer at step 44000:\n", + " train loss: 7.82e-04\n", + " test loss: 7.04e-04\n", + " test metric: []\n", + "\n", + "'train' took 14.967950 s\n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.009629 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "44000 [0.00050091 * 10.0^0 * ((meter / second) / second) ** 2, [0.0004224 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.826639e-06 * meter / second}}, {'ibc0': {'y': 1.826639e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027955 * meter / second}}] {'ibc1': {'y': 0.00027955 * meter / second}}] \n", + "44100 [0.00050114 * 10.0^0 * ((meter / second) / second) ** 2, [0.00042319 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8824794e-06 * meter / second}}, {'ibc0': {'y': 1.8824794e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027945 * meter / second}}] {'ibc1': {'y': 0.00027945 * meter / second}}] \n", + "44200 [0.00145139 * 10.0^0 * ((meter / second) / second) ** 2, [0.00122295 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 8.942031e-06 * meter / second}}, {'ibc0': {'y': 8.942031e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027929 * meter / second}}] {'ibc1': {'y': 0.00027929 * meter / second}}] \n", + "44300 [0.00127814 * 10.0^0 * ((meter / second) / second) ** 2, [0.00108249 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 7.864425e-06 * meter / second}}, {'ibc0': {'y': 7.864425e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027888 * meter / second}}] {'ibc1': {'y': 0.00027888 * meter / second}}] \n", + "44400 [0.00113658 * 10.0^0 * ((meter / second) / second) ** 2, [0.00096735 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 6.9546295e-06 * meter / second}}, {'ibc0': {'y': 6.9546295e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027857 * meter / second}}] {'ibc1': {'y': 0.00027857 * meter / second}}] \n", + "44500 [0.00102089 * 10.0^0 * ((meter / second) / second) ** 2, [0.00087287 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 6.1936867e-06 * meter / second}}, {'ibc0': {'y': 6.1936867e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027836 * meter / second}}] {'ibc1': {'y': 0.00027836 * meter / second}}] \n", + "44600 [0.00092632 * 10.0^0 * ((meter / second) / second) ** 2, [0.00079524 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 5.5573573e-06 * meter / second}}, {'ibc0': {'y': 5.5573573e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027821 * meter / second}}] {'ibc1': {'y': 0.00027821 * meter / second}}] \n", + "44700 [0.00084903 * 10.0^0 * ((meter / second) / second) ** 2, [0.00073138 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 5.0196286e-06 * meter / second}}, {'ibc0': {'y': 5.0196286e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027812 * meter / second}}] {'ibc1': {'y': 0.00027812 * meter / second}}] \n", + "44800 [0.0007858 * 10.0^0 * ((meter / second) / second) ** 2, [0.00067878 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.56894e-06 * meter / second}}, {'ibc0': {'y': 4.56894e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027808 * meter / second}}] {'ibc1': {'y': 0.00027808 * meter / second}}] \n", + "44900 [0.00073408 * 10.0^0 * ((meter / second) / second) ** 2, [0.00063543 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.191006e-06 * meter / second}}, {'ibc0': {'y': 4.191006e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027807 * meter / second}}] {'ibc1': {'y': 0.00027807 * meter / second}}] \n", + "45000 [0.00069176 * 10.0^0 * ((meter / second) / second) ** 2, [0.00059965 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.8737767e-06 * meter / second}}, {'ibc0': {'y': 3.8737767e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027808 * meter / second}}] {'ibc1': {'y': 0.00027808 * meter / second}}] \n", + "\n", + "Best trainer at step 44000:\n", + " train loss: 7.82e-04\n", + " test loss: 7.04e-04\n", + " test metric: []\n", + "\n", + "'train' took 4.975001 s\n", + "\n", + "Mean residual: 0.014 * (meter / second) / second\n", + "Adding new point: {'t': ArrayImpl([0.47824991], dtype=float32) * second, 'x': ArrayImpl([0.00034535], dtype=float32) * meter} \n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.013878 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "45000 [0.00086286 * 10.0^0 * ((meter / second) / second) ** 2, [0.00059965 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 3.8737767e-06 * meter / second}}, {'ibc0': {'y': 3.8737767e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027808 * meter / second}}] {'ibc1': {'y': 0.00027808 * meter / second}}] \n", + "46000 [0.00051184 * 10.0^0 * ((meter / second) / second) ** 2, [0.00043078 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.951535e-06 * meter / second}}, {'ibc0': {'y': 1.951535e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00028326 * meter / second}}] {'ibc1': {'y': 0.00028326 * meter / second}}] \n", + "47000 [0.00049468 * 10.0^0 * ((meter / second) / second) ** 2, [0.00041779 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8175754e-06 * meter / second}}, {'ibc0': {'y': 1.8175754e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027674 * meter / second}}] {'ibc1': {'y': 0.00027674 * meter / second}}] \n", + "48000 [0.00047711 * 10.0^0 * ((meter / second) / second) ** 2, [0.00040669 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8042014e-06 * meter / second}}, {'ibc0': {'y': 1.8042014e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00026785 * meter / second}}] {'ibc1': {'y': 0.00026785 * meter / second}}] \n", + "Epoch 48000: early stopping\n", + "\n", + "Best trainer at step 48000:\n", + " train loss: 7.47e-04\n", + " test loss: 6.76e-04\n", + " test metric: []\n", + "\n", + "'train' took 11.456536 s\n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.009981 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "48000 [0.00047711 * 10.0^0 * ((meter / second) / second) ** 2, [0.00040669 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8042014e-06 * meter / second}}, {'ibc0': {'y': 1.8042014e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00026785 * meter / second}}] {'ibc1': {'y': 0.00026785 * meter / second}}] \n", + "48100 [0.00047705 * 10.0^0 * ((meter / second) / second) ** 2, [0.00040699 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8382888e-06 * meter / second}}, {'ibc0': {'y': 1.8382888e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00026787 * meter / second}}] {'ibc1': {'y': 0.00026787 * meter / second}}] \n", + "48200 [0.00047711 * 10.0^0 * ((meter / second) / second) ** 2, [0.00040684 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.838244e-06 * meter / second}}, {'ibc0': {'y': 1.838244e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00026778 * meter / second}}] {'ibc1': {'y': 0.00026778 * meter / second}}] \n", + "48300 [0.00055811 * 10.0^0 * ((meter / second) / second) ** 2, [0.00051886 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.0354166e-06 * meter / second}}, {'ibc0': {'y': 2.0354166e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027301 * meter / second}}] {'ibc1': {'y': 0.00027301 * meter / second}}] \n", + "48400 [0.00054285 * 10.0^0 * ((meter / second) / second) ** 2, [0.00050191 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.7836052e-06 * meter / second}}, {'ibc0': {'y': 1.7836052e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027229 * meter / second}}] {'ibc1': {'y': 0.00027229 * meter / second}}] \n", + "48500 [0.00053009 * 10.0^0 * ((meter / second) / second) ** 2, [0.00048734 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.5927877e-06 * meter / second}}, {'ibc0': {'y': 1.5927877e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027169 * meter / second}}] {'ibc1': {'y': 0.00027169 * meter / second}}] \n", + "48600 [0.00051951 * 10.0^0 * ((meter / second) / second) ** 2, [0.0004748 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.4500241e-06 * meter / second}}, {'ibc0': {'y': 1.4500241e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027117 * meter / second}}] {'ibc1': {'y': 0.00027117 * meter / second}}] \n", + "48700 [0.00051096 * 10.0^0 * ((meter / second) / second) ** 2, [0.00046426 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.344583e-06 * meter / second}}, {'ibc0': {'y': 1.344583e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027073 * meter / second}}] {'ibc1': {'y': 0.00027073 * meter / second}}] \n", + "48800 [0.0005042 * 10.0^0 * ((meter / second) / second) ** 2, [0.00045563 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.2793502e-06 * meter / second}}, {'ibc0': {'y': 1.2793502e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027043 * meter / second}}] {'ibc1': {'y': 0.00027043 * meter / second}}] \n", + "48900 [0.00049869 * 10.0^0 * ((meter / second) / second) ** 2, [0.00044829 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.239052e-06 * meter / second}}, {'ibc0': {'y': 1.239052e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027017 * meter / second}}] {'ibc1': {'y': 0.00027017 * meter / second}}] \n", + "49000 [0.00049421 * 10.0^0 * ((meter / second) / second) ** 2, [0.00044207 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.218723e-06 * meter / second}}, {'ibc0': {'y': 1.218723e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00026993 * meter / second}}] {'ibc1': {'y': 0.00026993 * meter / second}}] \n", + "\n", + "Best trainer at step 48200:\n", + " train loss: 7.47e-04\n", + " test loss: 6.76e-04\n", + " test metric: []\n", + "\n", + "'train' took 4.382472 s\n", + "\n", + "Mean residual: 0.013 * (meter / second) / second\n", + "Adding new point: {'t': ArrayImpl([0.32790023], dtype=float32) * second, 'x': ArrayImpl([-0.00789118], dtype=float32) * meter} \n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.013897 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "49000 [0.00057817 * 10.0^0 * ((meter / second) / second) ** 2, [0.00044207 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.218723e-06 * meter / second}}, {'ibc0': {'y': 1.218723e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00026993 * meter / second}}] {'ibc1': {'y': 0.00026993 * meter / second}}] \n", + "50000 [0.00050032 * 10.0^0 * ((meter / second) / second) ** 2, [0.00041502 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.859967e-06 * meter / second}}, {'ibc0': {'y': 1.859967e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00028114 * meter / second}}] {'ibc1': {'y': 0.00028114 * meter / second}}] \n", + "51000 [0.00047413 * 10.0^0 * ((meter / second) / second) ** 2, [0.0004074 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8621943e-06 * meter / second}}, {'ibc0': {'y': 1.8621943e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00027061 * meter / second}}] {'ibc1': {'y': 0.00027061 * meter / second}}] \n", + "52000 [0.00043118 * 10.0^0 * ((meter / second) / second) ** 2, [0.00039883 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9477718e-06 * meter / second}}, {'ibc0': {'y': 1.9477718e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00025804 * meter / second}}] {'ibc1': {'y': 0.00025804 * meter / second}}] \n", + "53000 [0.00039573 * 10.0^0 * ((meter / second) / second) ** 2, [0.00037706 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8887133e-06 * meter / second}}, {'ibc0': {'y': 1.8887133e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00023655 * meter / second}}] {'ibc1': {'y': 0.00023655 * meter / second}}] \n", + "54000 [0.00036355 * 10.0^0 * ((meter / second) / second) ** 2, [0.00035133 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.6823096e-06 * meter / second}}, {'ibc0': {'y': 1.6823096e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00021243 * meter / second}}] {'ibc1': {'y': 0.00021243 * meter / second}}] \n", + "55000 [0.00035543 * 10.0^0 * ((meter / second) / second) ** 2, [0.0003454 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.3884645e-06 * meter / second}}, {'ibc0': {'y': 1.3884645e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019187 * meter / second}}] {'ibc1': {'y': 0.00019187 * meter / second}}] \n", + "Epoch 55000: early stopping\n", + "\n", + "Best trainer at step 55000:\n", + " train loss: 5.49e-04\n", + " test loss: 5.39e-04\n", + " test metric: []\n", + "\n", + "'train' took 28.337766 s\n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.010395 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "55000 [0.00035543 * 10.0^0 * ((meter / second) / second) ** 2, [0.0003454 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.3884645e-06 * meter / second}}, {'ibc0': {'y': 1.3884645e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019187 * meter / second}}] {'ibc1': {'y': 0.00019187 * meter / second}}] \n", + "55100 [0.00038491 * 10.0^0 * ((meter / second) / second) ** 2, [0.00036448 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.1007363e-06 * meter / second}}, {'ibc0': {'y': 2.1007363e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019324 * meter / second}}] {'ibc1': {'y': 0.00019324 * meter / second}}] \n", + "55200 [0.00037617 * 10.0^0 * ((meter / second) / second) ** 2, [0.00035767 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.0267723e-06 * meter / second}}, {'ibc0': {'y': 2.0267723e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019314 * meter / second}}] {'ibc1': {'y': 0.00019314 * meter / second}}] \n", + "55300 [0.00036902 * 10.0^0 * ((meter / second) / second) ** 2, [0.00035212 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9594597e-06 * meter / second}}, {'ibc0': {'y': 1.9594597e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019307 * meter / second}}] {'ibc1': {'y': 0.00019307 * meter / second}}] \n", + "55400 [0.00036314 * 10.0^0 * ((meter / second) / second) ** 2, [0.00034759 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.9019135e-06 * meter / second}}, {'ibc0': {'y': 1.9019135e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019301 * meter / second}}] {'ibc1': {'y': 0.00019301 * meter / second}}] \n", + "55500 [0.00035832 * 10.0^0 * ((meter / second) / second) ** 2, [0.00034388 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8527855e-06 * meter / second}}, {'ibc0': {'y': 1.8527855e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019295 * meter / second}}] {'ibc1': {'y': 0.00019295 * meter / second}}] \n", + "55600 [0.00035437 * 10.0^0 * ((meter / second) / second) ** 2, [0.00034088 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.8089495e-06 * meter / second}}, {'ibc0': {'y': 1.8089495e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019291 * meter / second}}] {'ibc1': {'y': 0.00019291 * meter / second}}] \n", + "55700 [0.00035103 * 10.0^0 * ((meter / second) / second) ** 2, [0.00033836 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.7687397e-06 * meter / second}}, {'ibc0': {'y': 1.7687397e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019289 * meter / second}}] {'ibc1': {'y': 0.00019289 * meter / second}}] \n", + "55800 [0.00034844 * 10.0^0 * ((meter / second) / second) ** 2, [0.00033642 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.7341235e-06 * meter / second}}, {'ibc0': {'y': 1.7341235e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019286 * meter / second}}] {'ibc1': {'y': 0.00019286 * meter / second}}] \n", + "55900 [0.00034628 * 10.0^0 * ((meter / second) / second) ** 2, [0.00033482 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.7029475e-06 * meter / second}}, {'ibc0': {'y': 1.7029475e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019285 * meter / second}}] {'ibc1': {'y': 0.00019285 * meter / second}}] \n", + "56000 [0.00034438 * 10.0^0 * ((meter / second) / second) ** 2, [0.00033343 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.6735721e-06 * meter / second}}, {'ibc0': {'y': 1.6735721e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019284 * meter / second}}] {'ibc1': {'y': 0.00019284 * meter / second}}] \n", + "\n", + "Best trainer at step 56000:\n", + " train loss: 5.39e-04\n", + " test loss: 5.28e-04\n", + " test metric: []\n", + "\n", + "'train' took 5.173902 s\n", + "\n", + "Mean residual: 0.012 * (meter / second) / second\n", + "Adding new point: {'t': ArrayImpl([0.5695765], dtype=float32) * second, 'x': ArrayImpl([-0.00540644], dtype=float32) * meter} \n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.015050 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "56000 [0.00045088 * 10.0^0 * ((meter / second) / second) ** 2, [0.00033343 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.6735721e-06 * meter / second}}, {'ibc0': {'y': 1.6735721e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019284 * meter / second}}] {'ibc1': {'y': 0.00019284 * meter / second}}] \n", + "57000 [0.0003596 * 10.0^0 * ((meter / second) / second) ** 2, [0.00033415 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.4792597e-06 * meter / second}}, {'ibc0': {'y': 1.4792597e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019963 * meter / second}}] {'ibc1': {'y': 0.00019963 * meter / second}}] \n", + "58000 [0.00034447 * 10.0^0 * ((meter / second) / second) ** 2, [0.0003254 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.3338099e-06 * meter / second}}, {'ibc0': {'y': 1.3338099e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0001965 * meter / second}}] {'ibc1': {'y': 0.0001965 * meter / second}}] \n", + "59000 [0.00033271 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031697 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.3009029e-06 * meter / second}}, {'ibc0': {'y': 1.3009029e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00019046 * meter / second}}] {'ibc1': {'y': 0.00019046 * meter / second}}] \n", + "60000 [0.00031924 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030644 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.2355371e-06 * meter / second}}, {'ibc0': {'y': 1.2355371e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00018091 * meter / second}}] {'ibc1': {'y': 0.00018091 * meter / second}}] \n", + "Epoch 60000: early stopping\n", + "\n", + "Best trainer at step 60000:\n", + " train loss: 5.01e-04\n", + " test loss: 4.89e-04\n", + " test metric: []\n", + "\n", + "'train' took 16.605614 s\n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.009775 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "60000 [0.00031924 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030644 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.2355371e-06 * meter / second}}, {'ibc0': {'y': 1.2355371e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00018091 * meter / second}}] {'ibc1': {'y': 0.00018091 * meter / second}}] \n", + "60100 [0.00031926 * 10.0^0 * ((meter / second) / second) ** 2, [0.0003067 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.2158225e-06 * meter / second}}, {'ibc0': {'y': 1.2158225e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00018093 * meter / second}}] {'ibc1': {'y': 0.00018093 * meter / second}}] \n", + "60200 [0.0003193 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030668 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.2317463e-06 * meter / second}}, {'ibc0': {'y': 1.2317463e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00018085 * meter / second}}] {'ibc1': {'y': 0.00018085 * meter / second}}] \n", + "60300 [0.00032016 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030574 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 8.9220623e-07 * meter / second}}, {'ibc0': {'y': 8.9220623e-07 * meter / second}}, \n", + " {'ibc1': {'y': 0.0001815 * meter / second}}] {'ibc1': {'y': 0.0001815 * meter / second}}] \n", + "60400 [0.00031992 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030578 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 9.2139237e-07 * meter / second}}, {'ibc0': {'y': 9.2139237e-07 * meter / second}}, \n", + " {'ibc1': {'y': 0.00018143 * meter / second}}] {'ibc1': {'y': 0.00018143 * meter / second}}] \n", + "60500 [0.00031974 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030583 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 9.5220133e-07 * meter / second}}, {'ibc0': {'y': 9.5220133e-07 * meter / second}}, \n", + " {'ibc1': {'y': 0.00018136 * meter / second}}] {'ibc1': {'y': 0.00018136 * meter / second}}] \n", + "60600 [0.00031961 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030594 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 9.865097e-07 * meter / second}}, {'ibc0': {'y': 9.865097e-07 * meter / second}}, \n", + " {'ibc1': {'y': 0.00018126 * meter / second}}] {'ibc1': {'y': 0.00018126 * meter / second}}] \n", + "60700 [0.00031952 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030598 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.0199747e-06 * meter / second}}, {'ibc0': {'y': 1.0199747e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00018119 * meter / second}}] {'ibc1': {'y': 0.00018119 * meter / second}}] \n", + "60800 [0.00031941 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030598 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.0453716e-06 * meter / second}}, {'ibc0': {'y': 1.0453716e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00018118 * meter / second}}] {'ibc1': {'y': 0.00018118 * meter / second}}] \n", + "60900 [0.00031924 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030594 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.0795525e-06 * meter / second}}, {'ibc0': {'y': 1.0795525e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0001812 * meter / second}}] {'ibc1': {'y': 0.0001812 * meter / second}}] \n", + "61000 [0.00040084 * 10.0^0 * ((meter / second) / second) ** 2, [0.00037745 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.2552299e-05 * meter / second}}, {'ibc0': {'y': 1.2552299e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017717 * meter / second}}] {'ibc1': {'y': 0.00017717 * meter / second}}] \n", + "\n", + "Best trainer at step 60200:\n", + " train loss: 5.01e-04\n", + " test loss: 4.89e-04\n", + " test metric: []\n", + "\n", + "'train' took 4.717136 s\n", + "\n", + "Mean residual: 0.012 * (meter / second) / second\n", + "Adding new point: {'t': ArrayImpl([0.38778442], dtype=float32) * second, 'x': ArrayImpl([-0.0043211], dtype=float32) * meter} \n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.015250 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "61000 [0.00046435 * 10.0^0 * ((meter / second) / second) ** 2, [0.00037745 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.2552299e-05 * meter / second}}, {'ibc0': {'y': 1.2552299e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017717 * meter / second}}] {'ibc1': {'y': 0.00017717 * meter / second}}] \n", + "62000 [0.00034305 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031215 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.2617899e-06 * meter / second}}, {'ibc0': {'y': 1.2617899e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00018198 * meter / second}}] {'ibc1': {'y': 0.00018198 * meter / second}}] \n", + "63000 [0.0003293 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030744 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1898037e-06 * meter / second}}, {'ibc0': {'y': 1.1898037e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017979 * meter / second}}] {'ibc1': {'y': 0.00017979 * meter / second}}] \n", + "64000 [0.00032155 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030236 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1578732e-06 * meter / second}}, {'ibc0': {'y': 1.1578732e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017604 * meter / second}}] {'ibc1': {'y': 0.00017604 * meter / second}}] \n", + "Epoch 64000: early stopping\n", + "\n", + "Best trainer at step 64000:\n", + " train loss: 4.99e-04\n", + " test loss: 4.80e-04\n", + " test metric: []\n", + "\n", + "'train' took 11.294281 s\n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.010299 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "64000 [0.00032155 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030236 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1578732e-06 * meter / second}}, {'ibc0': {'y': 1.1578732e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017604 * meter / second}}] {'ibc1': {'y': 0.00017604 * meter / second}}] \n", + "64100 [0.00032129 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030189 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.2672714e-06 * meter / second}}, {'ibc0': {'y': 1.2672714e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017607 * meter / second}}] {'ibc1': {'y': 0.00017607 * meter / second}}] \n", + "64200 [0.00032125 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030183 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.230208e-06 * meter / second}}, {'ibc0': {'y': 1.230208e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017615 * meter / second}}] {'ibc1': {'y': 0.00017615 * meter / second}}] \n", + "64300 [0.00032105 * 10.0^0 * ((meter / second) / second) ** 2, [0.0003016 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1040507e-06 * meter / second}}, {'ibc0': {'y': 1.1040507e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0001765 * meter / second}}] {'ibc1': {'y': 0.0001765 * meter / second}}] \n", + "64400 [0.00032116 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030174 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1353521e-06 * meter / second}}, {'ibc0': {'y': 1.1353521e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017633 * meter / second}}] {'ibc1': {'y': 0.00017633 * meter / second}}] \n", + "64500 [0.00032118 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030177 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1384761e-06 * meter / second}}, {'ibc0': {'y': 1.1384761e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0001763 * meter / second}}] {'ibc1': {'y': 0.0001763 * meter / second}}] \n", + "64600 [0.00032113 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030171 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1360961e-06 * meter / second}}, {'ibc0': {'y': 1.1360961e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017638 * meter / second}}] {'ibc1': {'y': 0.00017638 * meter / second}}] \n", + "64700 [0.00032106 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030168 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1283665e-06 * meter / second}}, {'ibc0': {'y': 1.1283665e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017646 * meter / second}}] {'ibc1': {'y': 0.00017646 * meter / second}}] \n", + "64800 [0.00032214 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030261 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1278934e-06 * meter / second}}, {'ibc0': {'y': 1.1278934e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017566 * meter / second}}] {'ibc1': {'y': 0.00017566 * meter / second}}] \n", + "64900 [0.00032201 * 10.0^0 * ((meter / second) / second) ** 2, [0.0003025 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1321267e-06 * meter / second}}, {'ibc0': {'y': 1.1321267e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0001757 * meter / second}}] {'ibc1': {'y': 0.0001757 * meter / second}}] \n", + "65000 [0.00032184 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030237 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1423148e-06 * meter / second}}, {'ibc0': {'y': 1.1423148e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017577 * meter / second}}] {'ibc1': {'y': 0.00017577 * meter / second}}] \n", + "\n", + "Best trainer at step 64500:\n", + " train loss: 4.99e-04\n", + " test loss: 4.79e-04\n", + " test metric: []\n", + "\n", + "'train' took 4.623570 s\n", + "\n", + "Mean residual: 0.012 * (meter / second) / second\n", + "Adding new point: {'t': ArrayImpl([0.40250915], dtype=float32) * second, 'x': ArrayImpl([-0.010427], dtype=float32) * meter} \n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.014968 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "65000 [0.00039208 * 10.0^0 * ((meter / second) / second) ** 2, [0.00030237 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1423148e-06 * meter / second}}, {'ibc0': {'y': 1.1423148e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017577 * meter / second}}] {'ibc1': {'y': 0.00017577 * meter / second}}] \n", + "66000 [0.00035875 * 10.0^0 * ((meter / second) / second) ** 2, [0.00032071 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.144454e-06 * meter / second}}, {'ibc0': {'y': 1.144454e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00018135 * meter / second}}] {'ibc1': {'y': 0.00018135 * meter / second}}] \n", + "67000 [0.00034588 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031595 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.0716824e-06 * meter / second}}, {'ibc0': {'y': 1.0716824e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017891 * meter / second}}] {'ibc1': {'y': 0.00017891 * meter / second}}] \n", + "Epoch 67001: early stopping\n", + "\n", + "Best trainer at step 67000:\n", + " train loss: 5.26e-04\n", + " test loss: 4.96e-04\n", + " test metric: []\n", + "\n", + "'train' took 7.938136 s\n", + "\n", + "Compiling trainer...\n", + "'compile' took 0.014745 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "67001 [0.00034587 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031594 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.0716063e-06 * meter / second}}, {'ibc0': {'y': 1.0716063e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017891 * meter / second}}] {'ibc1': {'y': 0.00017891 * meter / second}}] \n", + "67100 [0.00034577 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031621 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.0872557e-06 * meter / second}}, {'ibc0': {'y': 1.0872557e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.000179 * meter / second}}] {'ibc1': {'y': 0.000179 * meter / second}}] \n", + "67200 [0.00034568 * 10.0^0 * ((meter / second) / second) ** 2, [0.000316 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.0810584e-06 * meter / second}}, {'ibc0': {'y': 1.0810584e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017908 * meter / second}}] {'ibc1': {'y': 0.00017908 * meter / second}}] \n", + "67300 [0.00034568 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031598 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.0880661e-06 * meter / second}}, {'ibc0': {'y': 1.0880661e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017907 * meter / second}}] {'ibc1': {'y': 0.00017907 * meter / second}}] \n", + "67400 [0.00034624 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031566 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1337318e-06 * meter / second}}, {'ibc0': {'y': 1.1337318e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017865 * meter / second}}] {'ibc1': {'y': 0.00017865 * meter / second}}] \n", + "67500 [0.00034618 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031566 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1352137e-06 * meter / second}}, {'ibc0': {'y': 1.1352137e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017867 * meter / second}}] {'ibc1': {'y': 0.00017867 * meter / second}}] \n", + "67600 [0.00034612 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031567 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1311693e-06 * meter / second}}, {'ibc0': {'y': 1.1311693e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.0001787 * meter / second}}] {'ibc1': {'y': 0.0001787 * meter / second}}] \n", + "67700 [0.00034608 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031568 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1283073e-06 * meter / second}}, {'ibc0': {'y': 1.1283073e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017872 * meter / second}}] {'ibc1': {'y': 0.00017872 * meter / second}}] \n", + "67800 [0.00034614 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031567 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1323336e-06 * meter / second}}, {'ibc0': {'y': 1.1323336e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017869 * meter / second}}] {'ibc1': {'y': 0.00017869 * meter / second}}] \n", + "67900 [0.00034607 * 10.0^0 * ((meter / second) / second) ** 2, [0.00031569 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1263944e-06 * meter / second}}, {'ibc0': {'y': 1.1263944e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017873 * meter / second}}] {'ibc1': {'y': 0.00017873 * meter / second}}] \n", + "68000 [0.00034603 * 10.0^0 * ((meter / second) / second) ** 2, [0.0003157 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1240935e-06 * meter / second}}, {'ibc0': {'y': 1.1240935e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017874 * meter / second}}] {'ibc1': {'y': 0.00017874 * meter / second}}] \n", + "68001 [0.00034604 * 10.0^0 * ((meter / second) / second) ** 2, [0.0003157 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.1240547e-06 * meter / second}}, {'ibc0': {'y': 1.1240547e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00017874 * meter / second}}] {'ibc1': {'y': 0.00017874 * meter / second}}] \n", + "\n", + "Best trainer at step 67300:\n", + " train loss: 5.26e-04\n", + " test loss: 4.96e-04\n", + " test metric: []\n", + "\n", + "'train' took 4.620428 s\n", + "\n" + ] + } + ], + "source": [ + "while u.get_magnitude(err) > 0.012:\n", + " f = trainer.predict(X, operator=pde)\n", + " err_eq = u.math.absolute(f)\n", + " err = u.math.mean(err_eq)\n", + " print(f\"Mean residual: {err:.3f}\")\n", + "\n", + " x_id = u.math.argmax(err_eq)\n", + " new_xs = jax.tree.map(lambda x: x[[x_id]], X)\n", + " print(\"Adding new point:\", new_xs, \"\\n\")\n", + " problem.add_anchors(new_xs)\n", + " early_stopping = deepxde.callbacks.EarlyStopping(min_delta=1e-4, patience=2000)\n", + " trainer.compile(bst.optim.Adam(1e-3)).train(iterations=10000,\n", + " disregard_previous_best=True,\n", + " callbacks=[early_stopping])\n", + " trainer.compile(bst.optim.LBFGS(1e-3)).train(1000, display_every=100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's visualize and save the data." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving loss history to /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/loss.dat ...\n", + "Saving checkpoint into /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/loss.dat\n", + "Saving training data to /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/train.dat ...\n", + "Saving checkpoint into /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/train.dat\n", + "Saving test data to /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/test.dat ...\n", + "Saving checkpoint into /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/test.dat\n" + ] + }, + { + "data": { + "image/png": "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p9NpduvWzfzb3/52VC3vvfee2a9fP9PhcJiDBg0yP/vss1Z730c61vsHzDfeeMOzTUVFhXnrrbeakZGRZlBQkHnxxReb+/btq7efnTt3mpMmTTIDAwPN6Oho86677jJramrqbfPtt9+aw4YNMx0Oh9mrV696xzjMys/U9ddfbyYkJJgOh8Ps3LmzedZZZ3mCjml2nPNwLP8bdjrCuZgyZYrZtWtX0+FwmN26dTOnTJlSb26ZjnAODvvkk0/MwYMHm06n0xwwYID5z3/+s97rHeFv5WFffvmlCRz1/kyz/X0mDNM0zca1BYmIiIi0H+qzIyIiIj5NYUdERER8msKOiIiI+DSFHREREfFpCjsiIiLi0xR2RERExKcp7IiIiIhPU9gRERERn6awIyIiIj5NYUdEWtz+/ftxOByUlZVRU1NDcHCwZ+Xo4ykvL+e+++6jd+/eBAQE0LlzZ8444ww++ugjzzaJiYk888wzrVy9iPgaP6sLEBHfs3TpUpKTkwkODmbZsmVERUXRo0ePE/7OLbfcwrJly3j++edJSkriwIED/PDDDxw4cKCNqhYRX6WWHRFpcT/88APjxo0D4Pvvv/fcP5GPP/6Y+++/n/POO4/ExERGjhzJbbfdxvXXXw/AmWeeya5du/jd736HYRgYhuH53e+//57x48cTGBhIfHw8t99+O2VlZZ7XExMT+ctf/sKVV15JcHAw3bp1Iy0tzfO6aZo89NBD9OjRA6fTSVxcHLfffntLnQ4RsZgWAhWRFpGVlcXQoUMB9yUpu92O0+mkoqICwzAICAjgqquu4sUXXzzm7w8YMIDk5GRee+01QkNDj3r94MGDJCcn85vf/IabbroJgNjYWLZv305ycjKPPvoo559/Pvv372fGjBkkJyfzxhtvAO6wc/DgQe6//34uueQSvvzyS373u9/x+eefc/bZZ/PBBx9www03MHv2bAYNGkROTg5r1qzxHEdE2jeFHRFpEbW1tezevZvi4mJGjRrFypUrCQ4OZtiwYXz22Wf06NGDkJAQoqOjj/n7ixcv5uqrryY3N5fk5GROO+00LrvssnqtQomJidx5553ceeednuduvPFG7HY7r7zyiue577//njPOOIOysjICAgJITExk4MCBfP75555tfvWrX1FcXMy8efN4+umneeWVV1i/fj3+/v4tf3JExFK6jCUiLcLPz4/ExEQ2bdpESkoKQ4cOJScnh5iYGE4//XQSExOPG3QATj/9dHbs2MGCBQu47LLL2LBhA+PHj+cvf/nLCY+7Zs0aZs2aRUhIiOc2ceJEXC4XmZmZnu3Gjh1b7/fGjh3Lxo0bAbj88supqKigV69e3HTTTcydO5fa2tpmnA0R8SbqoCwiLWLQoEHs2rWLmpoaXC4XISEh1NbWUltbS0hICAkJCWzYsOGE+/D392f8+PGMHz+ee+65h0cffZRHHnmEe+65B4fDcczfKS0t5eabbz5mH5uTdYo+LD4+ns2bN/P1118zf/58br31Vp588kkWLVqklh4RH6CwIyItYt68edTU1HDWWWfxxBNPMHLkSH71q18xbdo0zj333CaFhqSkJGpra6msrMThcOBwOKirq6u3zYgRI8jIyKBPnz4n3NePP/541OOBAwd6HgcGBjJ58mQmT57M9OnTGTBgAOvWrWPEiBGNrltEvIv67IhIi8nJySExMZHCwkIMwyAiIoIdO3bQtWvXk/7umWeeyZVXXsmoUaPo1KkTGRkZzJw5k27durFgwQIAzjnnHAIDA3nxxRdxOp1ER0ezdu1aTjnlFK6//npuvPFGgoODycjIYP78+bzwwguAu69PQUEBf/zjH7nooouYP38+d9xxB5999hkTJ05k1qxZ1NXVMWbMGIKCgnjjjTd46qmnyM7OplOnTq16zkSk9anPjoi0mIULF5KSkkJAQADLly+ne/fuDQo6ABMnTuTNN9/knHPOYeDAgdx2221MnDiR9957z7PNI488ws6dO+nduzedO3cGYOjQoSxatIgtW7Ywfvx4hg8fzgMPPEBcXFy9/d91112sXLmS4cOH8+ijj/L0008zceJEACIiInj11VcZN24cQ4cO5euvv+aTTz5R0BHxEWrZERGfd6xRXCLScahlR0RERHyawo6IiIj4NF3GEhEREZ+mlh0RERHxaQo7IiIi4tMUdkRERMSnKeyIiIiIT1PYEREREZ+msCMiIiI+TWFHREREfJrCjoiIiPi0/w+bko551WnOVQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "trainer.saveplot(issave=True, isplot=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also test the model with the data:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "def gen_testdata():\n", + " data = np.load(\"../dataset/Burgers.npz\")\n", + " t, x, exact = data[\"t\"], data[\"x\"], data[\"usol\"].T\n", + " xx, tt = np.meshgrid(x, t)\n", + " X = {'x': np.ravel(xx) * u.meter, 't': np.ravel(tt) * u.second}\n", + " y = exact.flatten()[:, None]\n", + " return X, y * uy" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mean residual: 0.01163746 * (meter / second) / second\n", + "L2 relative error: 225.97165\n" + ] + } + ], + "source": [ + "X, y_true = gen_testdata()\n", + "y_pred = trainer.predict(X)\n", + "f = pde(X, y_pred)\n", + "print(\"Mean residual:\", u.math.mean(u.math.absolute(f)))\n", + "print(\"L2 relative error:\", deepxde.metrics.l2_relative_error(y_true, y_pred['y']))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "pinnx", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/experimental_docs/unit-examples-forward/Burgers_RAR.py b/docs/experimental_docs/unit-examples-forward/Burgers_RAR.py new file mode 100644 index 000000000..5bab95d61 --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/Burgers_RAR.py @@ -0,0 +1,81 @@ +import brainstate as bst +import brainunit as u +import jax.tree +import numpy as np + +import deepxde.experimental as deepxde + +geom = deepxde.geometry.Interval(-1, 1) +timedomain = deepxde.geometry.TimeDomain(0, 0.99) +geomtime = deepxde.geometry.GeometryXTime(geom, timedomain) +geomtime = geomtime.to_dict_point(x=u.meter, t=u.second) + +net = deepxde.nn.Model( + deepxde.nn.DictToArray(x=u.meter, t=u.second), + deepxde.nn.FNN([2] + [20] * 3 + [1], "tanh", bst.init.KaimingUniform()), + deepxde.nn.ArrayToDict(y=u.meter / u.second), +) +v = 0.01 / u.math.pi * u.meter ** 2 / u.second + + +def pde(x, y): + jacobian = net.jacobian(x) + hessian = net.hessian(x, xi='x', xj='x') + + dy_x = jacobian['y']['x'] + dy_t = jacobian['y']['t'] + dy_xx = hessian['y']['x']['x'] + return dy_t + y['y'] * dy_x - v * dy_xx + + +bc = deepxde.icbc.DirichletBC(lambda x: {'y': 0 * u.meter / u.second}) +ic = deepxde.icbc.IC(lambda x: {'y': -u.math.sin(u.math.pi * x['x'] / u.meter) * u.meter / u.second}) + +problem = deepxde.problem.TimePDE( + geomtime, + pde, + [bc, ic], + net, + num_domain=2500, + num_boundary=100, + num_initial=160 +) + +trainer = deepxde.Trainer(problem) + +trainer.compile(bst.optim.Adam(1e-3)).train(iterations=10000) +trainer.compile(bst.optim.LBFGS(1e-3)).train(1000) + +X = geomtime.random_points(100000) +err = 1 +while u.get_magnitude(err) > 0.012: + f = trainer.predict(X, operator=pde) + err_eq = u.math.absolute(f) + err = u.math.mean(err_eq) + print(f"Mean residual: {err:.3f}") + + x_id = u.math.argmax(err_eq) + new_xs = jax.tree.map(lambda x: x[[x_id]], X) + print("Adding new point:", new_xs, "\n") + problem.add_anchors(new_xs) + early_stopping = deepxde.callbacks.EarlyStopping(min_delta=1e-4, patience=2000) + trainer.compile(bst.optim.Adam(1e-3)).train(iterations=10000, + disregard_previous_best=True, + callbacks=[early_stopping]) + trainer.compile(bst.optim.LBFGS(1e-3)).train(1000, display_every=100) + +trainer.saveplot(issave=True, isplot=True) + + +def gen_testdata(): + data = np.load("../dataset/Burgers.npz") + t, x, exact = data["t"], data["x"], data["usol"].T + xx, tt = np.meshgrid(x, t) + X = {'x': np.ravel(xx) * u.meter, 't': np.ravel(tt) * u.second} + y = {'y': exact.flatten() * u.meter / u.second} + return X, y + + +X, y_true = gen_testdata() +y_pred = trainer.predict(X) +print("L2 relative error:", deepxde.metrics.l2_relative_error(y_true, y_pred)) diff --git a/docs/experimental_docs/unit-examples-forward/Euler_beam.ipynb b/docs/experimental_docs/unit-examples-forward/Euler_beam.ipynb new file mode 100644 index 000000000..1dd7a143e --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/Euler_beam.ipynb @@ -0,0 +1,469 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Euler-Bernoulli Beam Equation\n", + "\n", + "## Problem setup\n", + "\n", + "We will solve a Euler beam problem:\n", + "\n", + "$$\n", + "EI\\frac{d^{4}u}{dx^{4}}=p, \\qquad x \\in [0, 1],\n", + "$$\n", + "\n", + "with two boundary conditions on the right boundary,\n", + "\n", + "$$\n", + "u''(1)=0, u'''(1)=0\n", + "$$\n", + "\n", + "and one Dirichlet boundary condition on the left boundary,\n", + "\n", + "$$\n", + "u(0)=0\n", + "$$\n", + "\n", + "along with one Neumann boundary condition on the left boundary,\n", + "\n", + "$$\n", + "u'(0)=0\n", + "$$\n", + "\n", + "The exact solution is $u(x) = -\\frac{1}{24}x^4+\\frac{1}{6}x^3-\\frac{1}{4}x^2.$\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dimensional Analysis\n", + "\n", + "### **Boundary Conditions:**\n", + "1. **Right Boundary (at $ x = 1 $):**\n", + "\n", + " $$\n", + " u''(1) = 0, \\quad u'''(1) = 0\n", + " $$\n", + "\n", + "2. **Left Boundary (at $ x = 0 $):**\n", + "\n", + " $$\n", + " u(0) = 0, \\quad u'(0) = 0\n", + " $$\n", + "\n", + "### **Assigning Physical Units:**\n", + "\n", + "Let's identify and assign physical units to each variable and parameter in the equation.\n", + "\n", + "| **Variable/Parameter** | **Symbol** | **Physical Quantity** | **Unit (SI)** | **Dimension** |\n", + "|------------------------|------------|-----------------------------------|---------------------|--------------------------|\n", + "| **Displacement** | $ u $ | Beam deflection | meters (m) | Length $[L]$ |\n", + "| **Position** | $ x $ | Spatial coordinate along the beam | meters (m) | Length $[L]$ |\n", + "| **Young's Modulus** | $ E $ | Material property (stiffness) | pascals (Pa) | Pressure $[M][L]^{-1}[T]^{-2}$ |\n", + "| **Second Moment of Area** | $ I $ | Geometric property of the beam | meters$^4$ (m$^4$) | Length $^4$ $[L]^4$ |\n", + "| **Flexural Rigidity** | $ EI $ | Product of $ E $ and $ I $ | newton-meter squared (N·m$^2$) | $[EI] = [E][I] = [M][L]^3[T]^{-2}$ |\n", + "| **Load per Unit Length** | $ p $ | Distributed load on the beam | newtons per meter (N/m) | Force per Length $[M][L][T]^{-2}[L]^{-1} = [M][T]^{-2}$ |\n", + "\n", + "### **Dimensional Consistency Check:**\n", + "\n", + "To ensure the equation is dimensionally consistent, both sides must have the same dimensions.\n", + "\n", + "1. **Left Side ($ EI \\frac{d^4 u}{dx^4} $):**\n", + " - $ EI $ has units of N·m$^2$ and dimensions $[M][L]^3[T]^{-2}$.\n", + " - $ \\frac{d^4 u}{dx^4} $ involves four derivatives with respect to $ x $, each introducing a factor of $[L]^{-1}$.\n", + " - Thus, $ \\frac{d^4 u}{dx^4} $ has dimensions $[L]^{-4} \\times [L] = [L]^{-3}$.\n", + " - Multiplying by $ EI $: $[M][L]^3[T]^{-2} \\times [L]^{-3} = [M][T]^{-2}$.\n", + "\n", + "2. **Right Side ($ p $):**\n", + " - $ p $ has units of N/m and dimensions $[M][T]^{-2}$.\n", + "\n", + "Both sides have the same dimensions $[M][T]^{-2}$, confirming dimensional consistency.\n", + "\n", + "### **Summary of Physical Units:**\n", + "\n", + "- **$ u $** (Displacement): meters (m)\n", + "- **$ x $** (Position): meters (m)\n", + "- **$ E $** (Young's Modulus): pascals (Pa) = N/m$^2$\n", + "- **$ I $** (Second Moment of Area): meters$^4$ (m$^4$)\n", + "- **$ EI $** (Flexural Rigidity): newton-meter squared (N·m$^2$)\n", + "- **$ p $** (Load per Unit Length): newtons per meter (N/m)\n", + "\n", + "### **Boundary Conditions Units:**\n", + "\n", + "1. **$ u''(1) = 0 $:**\n", + " - $ u'' $ involves two derivatives: $[L] \\times [L]^{-2} = [L]^{-1}$\n", + " - Units: 1/m\n", + "\n", + "2. **$ u'''(1) = 0 $:**\n", + " - $ u''' $ involves three derivatives: $[L] \\times [L]^{-3} = [L]^{-2}$\n", + " - Units: 1/m$^2$\n", + "\n", + "3. **$ u(0) = 0 $:**\n", + " - Units: meters (m)\n", + "\n", + "4. **$ u'(0) = 0 $:**\n", + " - $ u' $ involves one derivative: $[L] \\times [L]^{-1} = \\text{dimensionless}$ (often interpreted as radians in small-angle approximations)\n", + "\n", + "### **Conclusion:**\n", + "\n", + "All variables and parameters in the Euler-Bernoulli Beam Equation have been assigned consistent physical units, ensuring dimensional integrity of the equation and its boundary conditions.\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code Implementation\n", + "\n", + "First, we import the necessary libraries and define the physical units for the problem." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:02:06.289401Z", + "start_time": "2024-12-17T14:02:02.480109Z" + } + }, + "outputs": [], + "source": [ + "import brainstate as bst\n", + "import brainunit as u\n", + "\n", + "import deepxde.experimental as deepxde" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the physical units for the problem." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:02:06.300312Z", + "start_time": "2024-12-17T14:02:06.295891Z" + } + }, + "outputs": [], + "source": [ + "unit_of_u = u.meter\n", + "unit_of_x = u.meter\n", + "unit_of_E = u.pascal\n", + "unit_of_I = u.meter ** 4\n", + "unit_of_p = u.kilogram / u.second ** 2\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the parameters for the problem." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:02:06.403154Z", + "start_time": "2024-12-17T14:02:06.386503Z" + } + }, + "outputs": [], + "source": [ + "E = 1 * unit_of_E\n", + "I = 1 * unit_of_I\n", + "p = -1. * unit_of_p\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the PDE for the Euler beam problem." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:02:06.423666Z", + "start_time": "2024-12-17T14:02:06.419808Z" + } + }, + "outputs": [], + "source": [ + "def pde(x, y):\n", + " dy_xxxx = net.gradient(x, order=4)['y']['x']['x']['x']['x']\n", + " return E * I * dy_xxxx - p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the geometric domain for the problem." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:02:06.431507Z", + "start_time": "2024-12-17T14:02:06.428077Z" + } + }, + "outputs": [], + "source": [ + "\n", + "geom = deepxde.geometry.Interval(0, 1).to_dict_point(x=unit_of_x)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the boundary conditions for the problem." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:02:06.444358Z", + "start_time": "2024-12-17T14:02:06.439478Z" + } + }, + "outputs": [], + "source": [ + "\n", + "def boundary_l(x, on_boundary):\n", + " return u.math.logical_and(on_boundary, deepxde.utils.isclose(x['x'] / unit_of_x, 0))\n", + "\n", + "\n", + "def boundary_r(x, on_boundary):\n", + " return u.math.logical_and(on_boundary, deepxde.utils.isclose(x['x'] / unit_of_x, 1))\n", + "\n", + "\n", + "bc1 = deepxde.icbc.DirichletBC(lambda x: {'y': 0 * unit_of_u}, boundary_l)\n", + "bc2 = deepxde.icbc.NeumannBC(lambda x: {'y': 0 * unit_of_u}, boundary_l)\n", + "bc3 = deepxde.icbc.OperatorBC(lambda x, y: net.hessian(x)['y']['x']['x'] / u.meter, boundary_r)\n", + "bc4 = deepxde.icbc.OperatorBC(lambda x, y: net.gradient(x, order=3)['y']['x']['x']['x'] / u.meter ** 2, boundary_r)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the neural network model for the problem." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:02:06.903923Z", + "start_time": "2024-12-17T14:02:06.458887Z" + } + }, + "outputs": [], + "source": [ + "net = deepxde.nn.Model(\n", + " deepxde.nn.DictToArray(x=unit_of_x),\n", + " deepxde.nn.FNN([1] + [20] * 3 + [1], \"tanh\"),\n", + " deepxde.nn.ArrayToDict(y=unit_of_u),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the exact solution for the problem." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:02:07.977054Z", + "start_time": "2024-12-17T14:02:06.913042Z" + } + }, + "outputs": [], + "source": [ + "def func(x):\n", + " x = x['x'] / unit_of_x\n", + " y = -(x ** 4) / 24 + x ** 3 / 6 - x ** 2 / 4\n", + " return {'y': y * unit_of_u}\n", + "\n", + "\n", + "data = deepxde.problem.PDE(\n", + " geom,\n", + " pde,\n", + " [bc1, bc2, bc3, bc4],\n", + " net,\n", + " num_domain=100,\n", + " num_boundary=20,\n", + " solution=func,\n", + " num_test=100,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Train the model and evaluate the results." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:02:36.927956Z", + "start_time": "2024-12-17T14:02:08.206744Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Compiling trainer...\n", + "'compile' took 0.058168 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "0 [172697.52 * kilogram ** 2 * second ** -4, [198196.31 * kilogram ** 2 * second ** -4, [{'y': Array(0.49183828, dtype=float32)}] \n", + " {'ibc0': {'y': 0. * meter}}, {'ibc0': {'y': 0. * meter}}, \n", + " {'ibc1': {'y': 0.34124637 * meter}}, {'ibc1': {'y': 0.34124637 * meter}}, \n", + " {'ibc2': 0.02639124 * metre ** -2}, {'ibc2': 0.02639124 * metre ** -2}, \n", + " {'ibc3': 1.2099838 * metre ** -4}] {'ibc3': 1.2099838 * metre ** -4}] \n", + "1000 [8.933943 * kilogram ** 2 * second ** -4, [10.233379 * kilogram ** 2 * second ** -4, [{'y': Array(1.6421293, dtype=float32)}] \n", + " {'ibc0': {'y': 9.626521e-11 * meter}}, {'ibc0': {'y': 9.626521e-11 * meter}}, \n", + " {'ibc1': {'y': 0.17711917 * meter}}, {'ibc1': {'y': 0.17711917 * meter}}, \n", + " {'ibc2': 0.03371554 * metre ** -2}, {'ibc2': 0.03371554 * metre ** -2}, \n", + " {'ibc3': 0.3698493 * metre ** -4}] {'ibc3': 0.3698493 * metre ** -4}] \n", + "2000 [5.0301304 * kilogram ** 2 * second ** -4, [5.7454376 * kilogram ** 2 * second ** -4, [{'y': Array(1.2512381, dtype=float32)}] \n", + " {'ibc0': {'y': 5.3002607e-12 * meter}}, {'ibc0': {'y': 5.3002607e-12 * meter}}, \n", + " {'ibc1': {'y': 0.12062849 * meter}}, {'ibc1': {'y': 0.12062849 * meter}}, \n", + " {'ibc2': 0.02302191 * metre ** -2}, {'ibc2': 0.02302191 * metre ** -2}, \n", + " {'ibc3': 0.1910549 * metre ** -4}] {'ibc3': 0.1910549 * metre ** -4}] \n", + "3000 [2.1895514 * kilogram ** 2 * second ** -4, [2.4801166 * kilogram ** 2 * second ** -4, [{'y': Array(0.97356707, dtype=float32)}] \n", + " {'ibc0': {'y': 1.2275463e-11 * meter}}, {'ibc0': {'y': 1.2275463e-11 * meter}}, \n", + " {'ibc1': {'y': 0.08320159 * meter}}, {'ibc1': {'y': 0.08320159 * meter}}, \n", + " {'ibc2': 0.0165317 * metre ** -2}, {'ibc2': 0.0165317 * metre ** -2}, \n", + " {'ibc3': 0.08161929 * metre ** -4}] {'ibc3': 0.08161929 * metre ** -4}] \n", + "4000 [0.72300434 * kilogram ** 2 * second ** -4, [0.8063759 * kilogram ** 2 * second ** -4, [{'y': Array(0.8491821, dtype=float32)}] \n", + " {'ibc0': {'y': 8.0052675e-12 * meter}}, {'ibc0': {'y': 8.0052675e-12 * meter}}, \n", + " {'ibc1': {'y': 0.06257136 * meter}}, {'ibc1': {'y': 0.06257136 * meter}}, \n", + " {'ibc2': 0.01523586 * metre ** -2}, {'ibc2': 0.01523586 * metre ** -2}, \n", + " {'ibc3': 0.03002642 * metre ** -4}] {'ibc3': 0.03002642 * metre ** -4}] \n", + "5000 [0.30583796 * kilogram ** 2 * second ** -4, [0.3329344 * kilogram ** 2 * second ** -4, [{'y': Array(0.89385283, dtype=float32)}] \n", + " {'ibc0': {'y': 4.751356e-12 * meter}}, {'ibc0': {'y': 4.751356e-12 * meter}}, \n", + " {'ibc1': {'y': 0.05717417 * meter}}, {'ibc1': {'y': 0.05717417 * meter}}, \n", + " {'ibc2': 0.01894331 * metre ** -2}, {'ibc2': 0.01894331 * metre ** -2}, \n", + " {'ibc3': 0.01662517 * metre ** -4}] {'ibc3': 0.01662517 * metre ** -4}] \n", + "6000 [0.25211135 * kilogram ** 2 * second ** -4, [0.24109833 * kilogram ** 2 * second ** -4, [{'y': Array(0.9684854, dtype=float32)}] \n", + " {'ibc0': {'y': 6.294266e-10 * meter}}, {'ibc0': {'y': 6.294266e-10 * meter}}, \n", + " {'ibc1': {'y': 0.05709113 * meter}}, {'ibc1': {'y': 0.05709113 * meter}}, \n", + " {'ibc2': 0.02338268 * metre ** -2}, {'ibc2': 0.02338268 * metre ** -2}, \n", + " {'ibc3': 0.01612406 * metre ** -4}] {'ibc3': 0.01612406 * metre ** -4}] \n", + "7000 [0.88259137 * kilogram ** 2 * second ** -4, [0.624831 * kilogram ** 2 * second ** -4, [{'y': Array(0.99016815, dtype=float32)}] \n", + " {'ibc0': {'y': 6.6010295e-09 * meter}}, {'ibc0': {'y': 6.6010295e-09 * meter}}, \n", + " {'ibc1': {'y': 0.05649563 * meter}}, {'ibc1': {'y': 0.05649563 * meter}}, \n", + " {'ibc2': 0.02561221 * metre ** -2}, {'ibc2': 0.02561221 * metre ** -2}, \n", + " {'ibc3': 0.01722029 * metre ** -4}] {'ibc3': 0.01722029 * metre ** -4}] \n", + "8000 [0.21604834 * kilogram ** 2 * second ** -4, [0.19982578 * kilogram ** 2 * second ** -4, [{'y': Array(0.98142815, dtype=float32)}] \n", + " {'ibc0': {'y': 1.7951907e-10 * meter}}, {'ibc0': {'y': 1.7951907e-10 * meter}}, \n", + " {'ibc1': {'y': 0.05653544 * meter}}, {'ibc1': {'y': 0.05653544 * meter}}, \n", + " {'ibc2': 0.02618827 * metre ** -2}, {'ibc2': 0.02618827 * metre ** -2}, \n", + " {'ibc3': 0.01875774 * metre ** -4}] {'ibc3': 0.01875774 * metre ** -4}] \n", + "9000 [0.10451799 * kilogram ** 2 * second ** -4, [0.10789017 * kilogram ** 2 * second ** -4, [{'y': Array(0.93547827, dtype=float32)}] \n", + " {'ibc0': {'y': 4.1144904e-12 * meter}}, {'ibc0': {'y': 4.1144904e-12 * meter}}, \n", + " {'ibc1': {'y': 0.05326409 * meter}}, {'ibc1': {'y': 0.05326409 * meter}}, \n", + " {'ibc2': 0.02588784 * metre ** -2}, {'ibc2': 0.02588784 * metre ** -2}, \n", + " {'ibc3': 0.02023843 * metre ** -4}] {'ibc3': 0.02023843 * metre ** -4}] \n", + "10000 [0.09200532 * kilogram ** 2 * second ** -4, [0.09282021 * kilogram ** 2 * second ** -4, [{'y': Array(0.8544506, dtype=float32)}] \n", + " {'ibc0': {'y': 1.0899624e-12 * meter}}, {'ibc0': {'y': 1.0899624e-12 * meter}}, \n", + " {'ibc1': {'y': 0.04875687 * meter}}, {'ibc1': {'y': 0.04875687 * meter}}, \n", + " {'ibc2': 0.0242439 * metre ** -2}, {'ibc2': 0.0242439 * metre ** -2}, \n", + " {'ibc3': 0.02106431 * metre ** -4}] {'ibc3': 0.02106431 * metre ** -4}] \n", + "\n", + "Best trainer at step 10000:\n", + " train loss: 1.86e-01\n", + " test loss: 1.87e-01\n", + " test metric: [{'y': Array(0.85, dtype=float32)}]\n", + "\n", + "'train' took 27.923877 s\n", + "\n", + "Saving loss history to D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\loss.dat ...\n", + "Saving checkpoint into D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\loss.dat\n", + "Saving training data to D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\train.dat ...\n", + "Saving checkpoint into D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\train.dat\n", + "Saving test data to D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\test.dat ...\n", + "Saving checkpoint into D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\test.dat\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAi4AAAGwCAYAAACOzu5xAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABriElEQVR4nO3deXxU5d338c/MJJnsG5A9IeyQAGFH3K1RQMW6W/WuiIqtBTdaq9aWpdrirZWHqrFaewu11YJad3HBKCKL7EEgrBJIgCQsIfs6M+f5Y8hAZDHAJLPk++5rXsmcOXPOb45p8uU6v3Mdk2EYBiIiIiI+wOzpAkRERERaS8FFREREfIaCi4iIiPgMBRcRERHxGQouIiIi4jMUXERERMRnKLiIiIiIzwjwdAHu5nA42LdvHxEREZhMJk+XIyIiIq1gGAZVVVUkJSVhNp98XMXvgsu+fftITU31dBkiIiJyBoqKikhJSTnp634XXCIiIgDnB4+MjPRwNSIiItIalZWVpKamuv6On4zfBZfm00ORkZEKLiIiIj7mx9o81JwrIiIiPkPBRURERHyGgouIiIj4DL/rcREREc+x2+00NTV5ugzxQoGBgVgslrPejt8El5ycHHJycrDb7Z4uRUSkwzEMg5KSEsrLyz1dinix6OhoEhISzmqeNZNhGIYba/K4yspKoqKiqKio0FVFIiLtpLi4mPLycuLi4ggNDdUEoNKCYRjU1tayf/9+oqOjSUxMPG6d1v799psRFxER8Qy73e4KLZ06dfJ0OeKlQkJCANi/fz9xcXFnfNpIzbkiInJWmntaQkNDPVyJeLvmn5Gz6YNScBEREbfQ6SH5Me74GVFwEREREZ+h4CIiIiI+Q8FFRETETdLT05k9e7bHt+HPFFxay+GAg9uh+oCnKxERkbNkMplO+Zg+ffoZbXfVqlXcc8897i1WWtDl0K319h2Q/z6M+V8455eerkZERM5CcXGx6/v58+czdepUtm7d6loWHh7u+t4wDOx2OwEBP/4ns0uXLu4tVI7jNyMuOTk5ZGRkMHz48LbZQZd+zq8l37XN9kVE/IhhGNQ22tr90do5VRMSElyPqKgoTCaT6/mWLVuIiIjgk08+YejQoVitVpYsWcL333/PT3/6U+Lj4wkPD2f48OF88cUXLbb7w9M8JpOJf/zjH1x77bWEhobSq1cvPvjgg9M6loWFhfz0pz8lPDycyMhIbrrpJkpLS12vr1+/nksuuYSIiAgiIyMZOnQoq1evBmD37t2MGzeOmJgYwsLCyMzMZMGCBae1f2/jNyMukyZNYtKkSa6Z99wuYYDza7GCi4jIj6lrspMx9bN232/+H0cTGuSeP22PPvoof/nLX+jevTsxMTEUFRVxxRVX8Kc//Qmr1cprr73GuHHj2Lp1K2lpaSfdzowZM3j66ad55plneP7557ntttvYvXs3sbGxP1qDw+FwhZavv/4am83GpEmTuPnmm1m0aBEAt912G4MHD+Zvf/sbFouFvLw8AgMDAeffxsbGRhYvXkxYWBj5+fktRpN8kd8El7b22u4obgcc+zdjtjVCQJCnSxIRkTb0xz/+kcsuu8z1PDY2lqysLNfzJ554gnfffZcPPviAyZMnn3Q7d9xxB7fccgsAf/7zn3nuuedYuXIlY8aM+dEacnNz2bBhAwUFBaSmpgLw2muvkZmZyapVqxg+fDiFhYU8/PDD9O3bF4BevXq53l9YWMj111/PgAHOf3x37979NI6Ad1JwaaWdDTGUG2FEUwMHNkNi1o+/SUSkgwoJtJD/x9Ee2a+7DBs2rMXz6upqpk+fzscff0xxcTE2m426ujoKCwtPuZ2BAwe6vg8LCyMyMpL9+/e3qobNmzeTmprqCi0AGRkZREdHs3nzZoYPH86UKVO4++67+de//kV2djY33ngjPXr0AOD+++/n3nvv5fPPPyc7O5vrr7++RT2+yG96XNpaZnIU+Y6uziclGzxbjIiIlzOZTIQGBbT7w52z94aFhbV4/pvf/IZ3332XP//5z3zzzTfk5eUxYMAAGhsbT7md5tM2xx4bh8PhtjqnT5/Opk2buPLKK/nyyy/JyMjg3XffBeDuu+9m586d/PznP2fDhg0MGzaM559/3m379gQFl1bKTIoi33AGF6N4vYerERGR9rZ06VLuuOMOrr32WgYMGEBCQgK7du1q033269ePoqIiioqKXMvy8/MpLy8nIyPDtax379489NBDfP7551x33XXMmTPH9Vpqaiq//OUveeedd/j1r3/NK6+80qY1tzUFl1bqGRfOFroB0LBHwUVEpKPp1asX77zzDnl5eaxfv55bb73VrSMnJ5Kdnc2AAQO47bbbWLt2LStXruT222/noosuYtiwYdTV1TF58mQWLVrE7t27Wbp0KatWraJfP+eVsA8++CCfffYZBQUFrF27lq+++sr1mq9ScGmloAAztbHOdBuwf6NzQjoREekwZs2aRUxMDOeeey7jxo1j9OjRDBkypE33aTKZeP/994mJieHCCy8kOzub7t27M3/+fAAsFguHDh3i9ttvp3fv3tx0002MHTuWGTNmAGC325k0aRL9+vVjzJgx9O7dmxdffLFNa25rJqO1F737iObLoSsqKoiMjHTrth99aw0zNo7GamqC+9ZCpx5u3b6IiC+qr6+noKCAbt26ERwc7OlyxIud6meltX+/NeJyGvomxbLFONLZrQZdERGRdqfgchpaXlmkiehERETam1fO45Kenk5kZCRms5mYmBi++uorT5cEQN+ECN430gFo3LseTUEnIiLSvrwyuAAsW7bM66YljggO5HBEH6gHQ1P/i4iItDudKjpN1uSBOAwT1rr9UN26mQ9FRETEPdweXBYvXsy4ceNISkrCZDLx3nvvHbdOTk4O6enpBAcHM3LkSFauXNnidZPJxEUXXcTw4cN5/fXX3V3iWemREk+BkeB8oj4XERGRduX24FJTU0NWVhY5OTknfH3+/PlMmTKFadOmsXbtWrKyshg9enSL+zYsWbKENWvW8MEHH/DnP/+Z7747eUBoaGigsrKyxaMtZSRFumbQ1Z2iRURE2pfbg8vYsWN58sknufbaa0/4+qxZs5g4cSITJkwgIyODl156idDQUF599VXXOsnJyQAkJiZyxRVXsHbt2pPub+bMmURFRbkex96Iqi1kJkayyZEOgG2fgouIiEh7atcel8bGRtasWUN2dvbRAsxmsrOzWb58OeAcsamqqgKcd+L88ssvyczMPOk2H3vsMSoqKlyPY+/n0BbiIoPZY+0JgG2vpv4XEZHTt2vXLkwmE3l5eZ4uxee0a3A5ePAgdrud+Pj4Fsvj4+MpKSkBoLS0lPPPP5+srCzOOeccbr/9doYPH37SbVqtViIjI1s82pop0XlLcGtlATRUt/n+RETEvUwm0ykf06dPP6ttn6i/U9zD6y6H7t69O+vXn/5IRk5ODjk5Odjt9jaoqqWU1K6U7okm3lQOpZsgbWSb71NERNynuLjY9f38+fOZOnUqW7dudS3ztuk45Kh2HXHp3LkzFouF0tLSFstLS0tJSEg4q21PmjSJ/Px8Vq1adVbbaY3MpKN9LrqySETE9yQkJLgeUVFRmEymFsvmzZtHv379CA4Opm/fvi1uTNjY2MjkyZNJTEwkODiYrl27MnPmTMA5gSrAtddei8lkcj1vja+//poRI0ZgtVpJTEzk0UcfxWazuV5/++23GTBgACEhIXTq1Ins7GxqamoAWLRoESNGjCAsLIzo6GjOO+88du/effYHygu164hLUFAQQ4cOJTc3l2uuuQYAh8NBbm4ukydPbs9SzkpmUhQfGen8hDwcxd9pMhwRkR8yDGiqbf/9BoaCyXRWm3j99deZOnUqL7zwAoMHD2bdunVMnDiRsLAwxo8fz3PPPccHH3zAm2++SVpaGkVFRa7+ylWrVhEXF8ecOXMYM2YMFoulVfvcu3cvV1xxBXfccQevvfYaW7ZsYeLEiQQHBzN9+nSKi4u55ZZbePrpp7n22mupqqrim2++wTAMbDYb11xzDRMnTuQ///kPjY2NrFy5EtNZHgdv5fbgUl1dzY4dO1zPCwoKyMvLIzY2lrS0NKZMmcL48eMZNmwYI0aMYPbs2dTU1DBhwoSz2m97nirqGhvKTnM3ABr35KF7oYqI/EBTLfw5qf33+7t9EBR2VpuYNm0azz77LNdddx0A3bp1Iz8/n5dffpnx48dTWFhIr169OP/88zGZTHTt2tX13i5dugAQHR19WmcSXnzxRVJTU3nhhRcwmUz07duXffv28cgjjzB16lSKi4ux2Wxcd911rv0NGDAAgLKyMioqKrjqqqvo0aMHAP369TurY+DN3B5cVq9ezSWXXOJ6PmXKFADGjx/P3Llzufnmmzlw4ABTp06lpKSEQYMG8emnnx7XsHu6Jk2axKRJk1y3xW5LZrOJxi794RAEHtoC9iawBLbpPkVEpO3V1NTw/fffc9dddzFx4kTXcpvN5vrbcscdd3DZZZfRp08fxowZw1VXXcXll19+VvvdvHkzo0aNajFKct5551FdXc2ePXvIysri0ksvZcCAAYwePZrLL7+cG264gZiYGGJjY7njjjsYPXo0l112GdnZ2dx0000kJiaeVU3eyu3B5eKLL8YwjFOuM3nyZJ86NXQinVN7U3UwhAhHHRzcBvEnv2RbRKTDCQx1jn54Yr9nobraeaXoK6+8wsiRLS+8aD7tM2TIEAoKCvjkk0/44osvuOmmm8jOzubtt98+q32fisViYeHChSxbtozPP/+c559/nscff5wVK1bQrVs35syZw/3338+nn37K/Pnz+f3vf8/ChQs555xz2qwmT/Gb9oycnBwyMjJOeem0O2UkRx+dQbdkQ7vsU0TEZ5hMzlM27f04y76O+Ph4kpKS2LlzJz179mzx6Natm2u9yMhIbr75Zl555RXmz5/Pf//7X8rKygAIDAw87baFfv36sXz58hb/8F+6dCkRERGkpKQcOaQmzjvvPGbMmMG6desICgri3Xffda0/ePBgHnvsMZYtW0b//v154403zuZQeC2vuxz6TLXnqSJwNuiucnRlpHkLRvF6TFk/a/N9iohI25sxYwb3338/UVFRjBkzhoaGBlavXs3hw4eZMmUKs2bNIjExkcGDB2M2m3nrrbdISEggOjoacF5ZlJuby3nnnYfVaiUmJuZH9/mrX/2K2bNnc9999zF58mS2bt3KtGnTmDJlCmazmRUrVpCbm8vll19OXFwcK1as4MCBA/Tr14+CggL+/ve/c/XVV5OUlMTWrVvZvn07t99+exsfKc/wm+DS3nrFh/Mv0gFng67Vs+WIiIib3H333YSGhvLMM8/w8MMPExYWxoABA3jwwQcBiIiI4Omnn2b79u1YLBaGDx/OggULMJudJzGeffZZpkyZwiuvvEJycjK7du360X0mJyezYMECHn74YbKysoiNjeWuu+7i97//PeAc4Vm8eDGzZ8+msrKSrl278uyzzzJ27FhKS0vZsmUL//znPzl06BCJiYlMmjSJX/ziF211iDzKZPxYQ4qPaR5xqaioaPNZdH/1l7m8WP0ATYGRBP6u8KyHKEVEfFF9fT0FBQV069aN4GBdZyknd6qfldb+/VaPy1kIT+1Po2EhsKkSygvbbb8iIiIdld8El/acObdZn+RObDecTVNq0BUREWl7fhNcPCEzKZJ8R/OVRZr6X0REpK0puJyFfomRbDLSAWjae/o3hhQREZHT4zfBxRM9LlEhgRwM7w2AY5+Ci4iISFvzm+DiiR4XgICkLACstcVQW9au+xYREelo/Ca4eEr3lER2OY7cZ0l9LiIiIm1KweUsZSZFHp36v1jBRUREpC0puJylzKQoNjnSAbAruIiIiLQpBZezFB9ppcjaA4CmPXmeLUZEROQ0pKenM3v2bE+XcVr8Jrh44qoicN6t00gYCEBQ+ffQVNeu+xcRkdNnMplO+Zg+ffpZbfu9995zW62nY9euXZhMJvLy8lq1/qpVq7jnnnvatig385ubLLb33aGPlZSczsG9kXQ2VUJpPqQMbdf9i4jI6SkuLnZ9P3/+fKZOncrWrVtdy8LDwz1RVrtpbGwkKCiILl26eLqU0+Y3Iy6elJEcdcwMuprPRUTE2yUkJLgeUVFRmEymFsvmzZtHv379CA4Opm/fvrz44ouu9zY2NjJ58mQSExMJDg6ma9euzJw5E3CeegG49tprMZlMruc/1Dwy8uabb3LBBRcQEhLC8OHD2bZtG6tWrWLYsGGEh4czduxYDhw40OK9//jHP05aW7du3QAYPHgwJpOJiy++GIA77riDa665hj/96U8kJSXRp08fV73HnioqLy/nF7/4BfHx8QQHB9O/f38++uijE36GO++8k6uuuqrFsqamJuLi4vi///u/U/8HOAt+M+LiSZlJUSw00rmQDTiKNygNikiHZxgGdbb2P3UeEhCCyWQ6q228/vrrTJ06lRdeeIHBgwezbt06Jk6cSFhYGOPHj+e5557jgw8+4M033yQtLY2ioiKKiooA56mXuLg45syZw5gxY7BYLKfc17Rp05g9ezZpaWnceeed3HrrrURERPDXv/6V0NBQbrrpJqZOncrf/va3VtW2cuVKRowYwRdffEFmZiZBQUGufeXm5hIZGcnChQtPWIvD4WDs2LFUVVXx73//mx49epCfn3/Sz3D33Xdz4YUXUlxcTGJiIgAfffQRtbW13Hzzzad93FtLwcUNunUOY4fZmXIb96xDN3UXkY6uzlbHyDdGtvt+V9y6gtDA0LPaxrRp03j22We57rrrAOcoRn5+Pi+//DLjx4+nsLCQXr16cf7552Mymejatavrvc2nXqKjo0lISPjRff3mN79h9OjRADzwwAPccsst5Obmct555wFw1113MXfu3FbX1rz/Tp06Hbf/sLAw/vGPf7QIM8f64osvWLlyJZs3b6Z3b+es8N27dz9p7eeeey59+vThX//6F7/97W8BmDNnDjfeeGObnmrT4IAbWMwm6jv3ByDg4GZw2D1ckYiInImamhq+//577rrrLsLDw12PJ598ku+//x5wnnbJy8ujT58+3H///Xz++ednvL+BAwe6vo+Pd05mOmDAgBbL9u/f3+raTmXAgAEnDS0AeXl5pKSkuEJLa9x9993MmTMHgNLSUj755BPuvPPO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pC2pOVdEzlpmZia7d++mqakJh8NBeHg4NpsNm81GeHg4Xbt2ZdOmTafcRmBgIBdccAEXXHABjzzyCE8++SR//OMfeeSRRwgKCjrhe6qrq/nFL35xwp6UH2sIbpaamsrWrVv54osvWLhwIb/61a945pln+PrrrzUCI+KFFFxE5KwtWLCApqYmLr30Up5++mmGDh3Kz372M+644w7GjBlzRgEgIyMDm81GfX09QUFBBAUFYbfbW6wzZMgQ8vPz6dmz5ym39e233x73vF+/fq7nISEhjBs3jnHjxjFp0iT69u3Lhg0bGDJkyGnXLSJtSz0uIuIWJSUlpKenU15ejslkIjo6mp07d5KYmPij77344ou55ZZbGDZsGJ06dSI/P58pU6aQnJxMbm4uAJdffjkhISG8+OKLWK1WOnfuzHfffcc555zDnXfeyd13301YWBj5+fksXLiQF154AXD2xhw+fJjHH3+ca665hoULF/LAAw/w8ccfM3r0aObOnYvdbmfkyJGEhoYyZ84cnn32WYqKiujUqVObHjMROX3qcRERt1i0aBHDhw8nODiYlStXkpKS0qrQAjB69Gj++c9/cvnll9OvXz/uu+8+Ro8ezZtvvula549//CO7du2iR48edOnSBYCBAwfy9ddfs23bNi644AIGDx7M1KlTSUpKarH9X//616xevZrBgwfz5JNPMmvWLEaPHg1AdHQ0r7zyCueddx4DBw7kiy++4MMPP1RoEfFSGnEREb92oquRRMR3acRFREREfIaCi4iIiPgMnSoSERERn6ERFxEREfEZCi4iIiLiMxRcRERExGcouIiIiIjPUHARERERn6HgIiIiIj5DwUVERER8hoKLiIiI+Iz/D8FGshXCp+pWAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "trainer = deepxde.Trainer(data)\n", + "trainer.compile(bst.optim.Adam(0.001), metrics=[\"l2 relative error\"]).train(iterations=10000)\n", + "trainer.saveplot(issave=True, isplot=True)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/experimental_docs/unit-examples-forward/Euler_beam.py b/docs/experimental_docs/unit-examples-forward/Euler_beam.py new file mode 100644 index 000000000..111ddce50 --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/Euler_beam.py @@ -0,0 +1,63 @@ +import brainstate as bst +import brainunit as u + +import deepxde.experimental as deepxde + +unit_of_u = u.meter +unit_of_x = u.meter +unit_of_E = u.pascal +unit_of_I = u.meter ** 4 +unit_of_p = u.kilogram / u.second ** 2 + +geom = deepxde.geometry.Interval(0, 1).to_dict_point(x=unit_of_x) + +E = 1 * unit_of_E +I = 1 * unit_of_I +p = -1. * unit_of_p + + +def pde(x, y): + dy_xxxx = net.gradient(x, order=4)['y']['x']['x']['x']['x'] + return E * I * dy_xxxx - p + + +def boundary_l(x, on_boundary): + return u.math.logical_and(on_boundary, deepxde.utils.isclose(x['x'] / unit_of_x, 0)) + + +def boundary_r(x, on_boundary): + return u.math.logical_and(on_boundary, deepxde.utils.isclose(x['x'] / unit_of_x, 1)) + + +bc1 = deepxde.icbc.DirichletBC(lambda x: {'y': 0 * unit_of_u}, boundary_l) +bc2 = deepxde.icbc.NeumannBC(lambda x: {'y': 0 * unit_of_u}, boundary_l) +bc3 = deepxde.icbc.OperatorBC(lambda x, y: net.hessian(x)['y']['x']['x'] / u.meter, boundary_r) +bc4 = deepxde.icbc.OperatorBC(lambda x, y: net.gradient(x, order=3)['y']['x']['x']['x'] / u.meter ** 2, boundary_r) + +net = deepxde.nn.Model( + deepxde.nn.DictToArray(x=unit_of_x), + deepxde.nn.FNN([1] + [20] * 3 + [1], "tanh"), + deepxde.nn.ArrayToDict(y=unit_of_u), +) + + +def func(x): + x = x['x'] / unit_of_x + y = -(x ** 4) / 24 + x ** 3 / 6 - x ** 2 / 4 + return {'y': y * unit_of_u} + + +data = deepxde.problem.PDE( + geom, + pde, + [bc1, bc2, bc3, bc4], + net, + num_domain=100, + num_boundary=20, + solution=func, + num_test=100, +) + +trainer = deepxde.Trainer(data) +trainer.compile(bst.optim.Adam(0.001), metrics=["l2 relative error"]).train(iterations=10000) +trainer.saveplot(issave=True, isplot=True) diff --git a/docs/experimental_docs/unit-examples-forward/Helmholtz_Dirichlet_2d.ipynb b/docs/experimental_docs/unit-examples-forward/Helmholtz_Dirichlet_2d.ipynb new file mode 100644 index 000000000..ac1491782 --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/Helmholtz_Dirichlet_2d.ipynb @@ -0,0 +1,356 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Helmholtz equation over a 2D square domain\n", + "\n", + "## Problem setup\n", + "For a wave number $k_0 = 2\\pi n$ with $n = 2$, we will solve a Helmholtz equation:\n", + "\n", + "$$\n", + "- u_{xx}-u_{yy} - k_0^2 u = f, \\qquad \\Omega = [0,1]^2\n", + "$$\n", + "\n", + "with the Dirichlet boundary conditions\n", + "\n", + "$$\n", + "u(x,y)=0, \\qquad (x,y)\\in \\partial \\Omega\n", + "$$\n", + "\n", + "and a source term $f(x,y) = k_0^2 \\sin(k_0 x)\\sin(k_0 y)$.\n", + "\n", + "Remark that the exact solution reads:\n", + "$$\n", + "u(x,y)= \\sin(k_0 x)\\sin(k_0 y)\n", + "$$\n", + "\n", + "\n", + "## Dimensional Analysis\n", + "\n", + "### **Assigning Physical Units:**\n", + "\n", + "To perform dimensional analysis, we will assign physical units to each variable and parameter in the equation. We'll ensure that both sides of the Helmholtz equation have consistent dimensions.\n", + "\n", + "#### **Variables and Parameters:**\n", + "\n", + "| **Variable/Parameter** | **Symbol** | **Physical Quantity** | **Unit (SI)** | **Dimension** |\n", + "|------------------------|------------|-----------------------------------|---------------------------------------------------------------------------------|--------------------------|\n", + "| **Field Variable** | $ u $ | Scalar field (e.g., displacement, pressure) | **Dimensionless** or [U] [Depends on Physical Context] | $[U]$ |\n", + "| **Spatial Coordinate** | $ x, y $ | Position in space | meters (m) | Length $[L]$ |\n", + "| **Wave Number** | $ k_0 $ | Spatial frequency | inverse meters (1/m) | $[L]^{-1}$ |\n", + "| **Source Term** | $ f $ | External forcing or source | Depends on $ u $'s units (e.g., if $ u $ is dimensionless, f has units of 1/m²) | $[U][L]^{-2}$ |\n", + "\n", + "> **Note:** The units of $ u $ can vary based on the physical context of the problem. However, based on the exact solution provided, $ u(x,y) = \\sin(k_0 x) \\sin(k_0 y) $, it suggests that $ u $ is **dimensionless**. Therefore, for this analysis, we'll assume $ u $ is dimensionless.\n", + "\n", + "#### **Detailed Assignments:**\n", + "\n", + "1. **Field Variable ($ u $):**\n", + " - **Physical Quantity:** Scalar field (e.g., displacement, pressure)\n", + " - **Unit:** **Dimensionless**\n", + " - **Dimension:** $[1]$\n", + " \n", + "2. **Spatial Coordinates ($ x, y $):**\n", + " - **Physical Quantity:** Position in space\n", + " - **Unit:** meters (m)\n", + " - **Dimension:** Length $[L]$\n", + " \n", + "3. **Wave Number ($ k_0 $):**\n", + " - **Physical Quantity:** Spatial frequency\n", + " - **Unit:** inverse meters (1/m)\n", + " - **Dimension:** $[L]^{-1}$\n", + " \n", + "4. **Source Term ($ f $):**\n", + " - **Physical Quantity:** External forcing or source\n", + " - **Unit:** inverse meters squared (1/m²)\n", + " - **Dimension:** $[L]^{-2}$\n", + " \n", + "#### **Dimensional Consistency Check:**\n", + "\n", + "To ensure the Helmholtz equation is dimensionally consistent, both sides of the equation must have the same dimensions.\n", + "\n", + "1. **Left Side ($ -u_{xx} - u_{yy} - k_0^2 u $):**\n", + " - $ u_{xx} = \\frac{\\partial^2 u}{\\partial x^2} $: \n", + " - Dimension: $\\frac{[U]}{[L]^2}$ \n", + " - Since $ u $ is dimensionless: $[U] = 1$, so $ u_{xx} $ has dimension $[L]^{-2}$.\n", + " \n", + " - $ u_{yy} = \\frac{\\partial^2 u}{\\partial y^2} $:\n", + " - Dimension: Same as $ u_{xx} $, i.e., $[L]^{-2}$.\n", + " \n", + " - $ k_0^2 u $:\n", + " - Dimension: $[k_0]^2 [U] = [L]^{-2} \\times 1 = [L]^{-2}$.\n", + " \n", + " - **Combined Left Side:** Each term has dimension $[L]^{-2}$, ensuring consistency.\n", + " \n", + "2. **Right Side ($ f $):**\n", + " - Dimension: $[L]^{-2}$.\n", + " \n", + " - **Conclusion:** Both sides of the equation have the same dimension $[L]^{-2}$, confirming dimensional consistency.\n", + "\n", + "### **Summary of Physical Units:**\n", + "\n", + "| **Symbol** | **Physical Quantity** | **Unit (SI)** | **Dimension** |\n", + "|------------|-------------------------------------------|---------------------|--------------------------|\n", + "| $ u $ | Scalar field (dimensionless) | Dimensionless | $[1]$ |\n", + "| $ x, y $ | Spatial coordinates | meters (m) | Length $[L]$ |\n", + "| $ k_0 $ | Wave number | inverse meters (1/m)| $[L]^{-1}$ |\n", + "| $ f $ | Source term | inverse meters squared (1/m²)| $[L]^{-2}$ |\n", + "\n", + "### **Boundary Conditions Units:**\n", + "\n", + "1. **Dirichlet Boundary Conditions ($ u(x,y) = 0 $):**\n", + " - **Units:** Same as $ u $, which is **dimensionless**.\n", + " \n", + "2. **Exact Solution ($ u(x,y) = \\sin(k_0 x) \\sin(k_0 y) $):**\n", + " - **Units:** Dimensionless, consistent with $ u $'s units.\n", + "\n", + "### **Conclusion:**\n", + "\n", + "All variables and parameters in the Helmholtz equation have been assigned consistent physical units, ensuring the dimensional integrity of the equation and its boundary conditions. Specifically:\n", + "\n", + "- **$ u $** is dimensionless.\n", + "- **$ x $** and **$ y $** are measured in meters (m).\n", + "- **$ k_0 $** has units of inverse meters (1/m).\n", + "- **$ f $** has units of inverse meters squared (1/m²).\n", + "\n", + "This dimensional assignment ensures that the Helmholtz equation is dimensionally consistent and the boundary conditions are appropriately defined.\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Code Implementation\n", + "\n", + "First, import the necessary libraries and modules for the problem setup and solution:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:18:10.846957Z", + "start_time": "2024-12-17T14:18:07.057723Z" + } + }, + "outputs": [], + "source": [ + "import brainstate as bst\n", + "import brainunit as u\n", + "import numpy as np\n", + "\n", + "import deepxde.experimental as deepxde" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the physical units and parameters for the Helmholtz equation:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:18:10.855991Z", + "start_time": "2024-12-17T14:18:10.850982Z" + } + }, + "outputs": [], + "source": [ + "unit_of_u = u.UNITLESS\n", + "unit_of_x = u.meter\n", + "unit_of_y = u.meter\n", + "unit_of_k0 = 1 / unit_of_x\n", + "unit_of_f = 1 / u.meter ** 2\n", + "\n", + "# General parameters\n", + "n = 2\n", + "precision_train = 10\n", + "precision_test = 30\n", + "hard_constraint = True # True or False\n", + "weights = 100 # if hard_constraint == False\n", + "iterations = 5000\n", + "parameters = [1e-3, 3, 150]\n", + "\n", + "learning_rate, num_dense_layers, num_dense_nodes = parameters\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the PDE function for the Helmholtz equation:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:18:10.873024Z", + "start_time": "2024-12-17T14:18:10.867395Z" + } + }, + "outputs": [], + "source": [ + "geom = deepxde.geometry.Rectangle([0, 0], [1, 1]).to_dict_point(x=unit_of_x, y=unit_of_y)\n", + "k0 = 2 * np.pi * n\n", + "wave_len = 1 / n\n", + "\n", + "hx_train = wave_len / precision_train\n", + "nx_train = int(1 / hx_train)\n", + "\n", + "hx_test = wave_len / precision_test\n", + "nx_test = int(1 / hx_test)\n", + "\n", + "\n", + "def pde(x, y):\n", + " hessian = net.hessian(x)\n", + "\n", + " dy_xx = hessian[\"y\"][\"x\"][\"x\"]\n", + " dy_yy = hessian[\"y\"][\"y\"][\"y\"]\n", + "\n", + " f = k0 ** 2 * u.math.sin(k0 * x['x'] / unit_of_x) * u.math.sin(k0 * x['y'] / unit_of_y)\n", + " return -dy_xx - dy_yy - (k0 * unit_of_k0) ** 2 * y['y'] - f * unit_of_f\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the boundary conditions for the Helmholtz equation:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:18:11.385027Z", + "start_time": "2024-12-17T14:18:10.883839Z" + } + }, + "outputs": [], + "source": [ + "\n", + "\n", + "if hard_constraint:\n", + " bc = []\n", + "else:\n", + " bc = deepxde.icbc.DirichletBC(lambda x: {'y': 0 * unit_of_u})\n", + "\n", + "net = deepxde.nn.Model(\n", + " deepxde.nn.DictToArray(x=unit_of_x, y=unit_of_y),\n", + " deepxde.nn.FNN([2] + [num_dense_nodes] * num_dense_layers + [1],\n", + " u.math.sin,\n", + " bst.init.KaimingUniform()),\n", + " deepxde.nn.ArrayToDict(y=unit_of_u),\n", + ")\n", + "\n", + "if hard_constraint:\n", + " def transform(x, y):\n", + " x = deepxde.utils.array_to_dict(x, [\"x\", \"y\"], keep_dim=True)\n", + " res = x['x'] * (1 - x['x']) * x['y'] * (1 - x['y'])\n", + " return res * y\n", + "\n", + "\n", + " net.approx.apply_output_transform(transform)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the problem and train the model to solve the Helmholtz equation:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:18:11.811162Z", + "start_time": "2024-12-17T14:18:11.398719Z" + } + }, + "outputs": [], + "source": [ + "problem = deepxde.problem.PDE(\n", + " geom,\n", + " pde,\n", + " bc,\n", + " net,\n", + " num_domain=nx_train ** 2,\n", + " num_boundary=4 * nx_train,\n", + " solution=lambda x: {'y': u.math.sin(k0 * x['x'] / unit_of_x) * u.math.sin(k0 * x['y'] / unit_of_y) * unit_of_u},\n", + " num_test=nx_test ** 2,\n", + " loss_weights=None if hard_constraint else [1, weights],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Train the model using the Adam optimizer and the specified learning rate:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T14:19:14.913372200Z", + "start_time": "2024-12-17T14:18:11.822636Z" + }, + "jupyter": { + "is_executing": true + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Compiling trainer...\n", + "'compile' took 0.059387 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "0 [5213.1675 * metre ** -4] [6450.17 * metre ** -4] [{'y': Array(1.0007389, dtype=float32)}] \n", + "1000 [115.11537 * metre ** -4] [164.17776 * metre ** -4] [{'y': Array(0.50004345, dtype=float32)}] \n" + ] + } + ], + "source": [ + "trainer = deepxde.Trainer(problem)\n", + "trainer.compile(bst.optim.Adam(learning_rate), metrics=[\"l2 relative error\"]).train(iterations=iterations)\n", + "trainer.saveplot(issave=True, isplot=True)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "pinnx", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.10.15" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/experimental_docs/unit-examples-forward/Helmholtz_Dirichlet_2d.py b/docs/experimental_docs/unit-examples-forward/Helmholtz_Dirichlet_2d.py new file mode 100644 index 000000000..ca7ab60a2 --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/Helmholtz_Dirichlet_2d.py @@ -0,0 +1,81 @@ +import brainstate as bst +import brainunit as u +import numpy as np + +import deepxde.experimental as deepxde + +unit_of_u = u.UNITLESS +unit_of_x = u.meter +unit_of_y = u.meter +unit_of_k0 = 1 / unit_of_x +unit_of_f = 1 / u.meter ** 2 + +# General parameters +n = 2 +precision_train = 10 +precision_test = 30 +hard_constraint = True # True or False +weights = 100 # if hard_constraint == False +iterations = 5000 +parameters = [1e-3, 3, 150] + +learning_rate, num_dense_layers, num_dense_nodes = parameters + +geom = deepxde.geometry.Rectangle([0, 0], [1, 1]).to_dict_point(x=unit_of_x, y=unit_of_y) +k0 = 2 * np.pi * n +wave_len = 1 / n + +hx_train = wave_len / precision_train +nx_train = int(1 / hx_train) + +hx_test = wave_len / precision_test +nx_test = int(1 / hx_test) + +def pde(x, y): + hessian = net.hessian(x) + + dy_xx = hessian["y"]["x"]["x"] + dy_yy = hessian["y"]["y"]["y"] + + f = k0 ** 2 * u.math.sin(k0 * x['x'] / unit_of_x) * u.math.sin(k0 * x['y'] / unit_of_y) + return -dy_xx - dy_yy - (k0 * unit_of_k0) ** 2 * y['y'] - f * unit_of_f + + + +if hard_constraint: + bc = [] +else: + bc = deepxde.icbc.DirichletBC(lambda x: {'y': 0 * unit_of_u}) + +net = deepxde.nn.Model( + deepxde.nn.DictToArray(x=unit_of_x, y=unit_of_y), + deepxde.nn.FNN([2] + [num_dense_nodes] * num_dense_layers + [1], + u.math.sin, + bst.init.KaimingUniform()), + deepxde.nn.ArrayToDict(y=unit_of_u), +) + +if hard_constraint: + def transform(x, y): + x = deepxde.utils.array_to_dict(x, ["x", "y"], keep_dim=True) + res = x['x'] * (1 - x['x']) * x['y'] * (1 - x['y']) + return res * y + + + net.approx.apply_output_transform(transform) + +problem = deepxde.problem.PDE( + geom, + pde, + bc, + net, + num_domain=nx_train ** 2, + num_boundary=4 * nx_train, + solution=lambda x: {'y': u.math.sin(k0 * x['x'] / unit_of_x) * u.math.sin(k0 * x['y'] / unit_of_y) * unit_of_u}, + num_test=nx_test ** 2, + loss_weights=None if hard_constraint else [1, weights], +) + +trainer = deepxde.Trainer(problem) +trainer.compile(bst.optim.Adam(learning_rate), metrics=["l2 relative error"]).train(iterations=iterations) +trainer.saveplot(issave=True, isplot=True) diff --git a/docs/experimental_docs/unit-examples-forward/Laplace_disk.ipynb b/docs/experimental_docs/unit-examples-forward/Laplace_disk.ipynb new file mode 100644 index 000000000..ca2547cf5 --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/Laplace_disk.ipynb @@ -0,0 +1,535 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Laplace equation on a disk\n", + "## Problem setup\n", + "We will solve a Laplace equation in a polar coordinate system:\n", + "\n", + "$$\n", + "r\\frac{dy}{dr} + r^2\\frac{dy^2}{dr^2} + \\frac{dy^2}{d\\theta^2} = 0, \\qquad r \\in [0, 1], \\quad \\theta \\in [0, 2\\pi]\n", + "$$\n", + "\n", + "with the Dirichlet boundary condition\n", + "\n", + "$$\n", + "y(1,\\theta) = \\cos(\\theta)\n", + "$$\n", + "\n", + "and the periodic boundary condition\n", + "\n", + "$$\n", + "y(r, \\theta +2\\pi) = y(r, \\theta).\n", + "$$\n", + "\n", + "The reference solution is $y=r\\cos(\\theta)$.\n", + "\n", + "# Dimensional Analysis for the Laplace Equation on a Disk\n", + "\n", + "## Problem Setup\n", + "\n", + "We will solve the Laplace equation in a polar coordinate system:\n", + "\n", + "$$\n", + "r\\frac{dy}{dr} + r^2\\frac{d^2y}{dr^2} + \\frac{d^2y}{d\\theta^2} = 0, \\qquad r \\in [0, 1], \\quad \\theta \\in [0, 2\\pi]\n", + "$$\n", + "\n", + "with the Dirichlet boundary condition:\n", + "\n", + "$$\n", + "y(1,\\theta) = \\cos(\\theta)\n", + "$$\n", + "\n", + "and the periodic boundary condition:\n", + "\n", + "$$\n", + "y(r, \\theta + 2\\pi) = y(r, \\theta).\n", + "$$\n", + "\n", + "The reference solution is:\n", + "\n", + "$$\n", + "y = r\\cos(\\theta).\n", + "$$\n", + "\n", + "---\n", + "\n", + "## Dimensional Analysis\n", + "\n", + "### Step 1: Assign Dimensions to Variables\n", + "\n", + "1. **Radial Coordinate $r$:**\n", + " - The dimension of $r$ is length:\n", + "\n", + " $$\n", + " [r] = L.\n", + " $$\n", + "\n", + "2. **Angular Coordinate $\\theta$:**\n", + " - The dimension of $\\theta$ is dimensionless:\n", + "\n", + " $$\n", + " [\\theta] = 1.\n", + " $$\n", + "\n", + "3. **Solution $y$:**\n", + " - The solution $y$ represents a physical quantity, which we assume to be in volts (V):\n", + "\n", + " $$\n", + " [y] = V.\n", + " $$\n", + "\n", + "---\n", + "\n", + "### Step 2: Analyze the Dimensions of Each Term\n", + "\n", + "1. **First Derivative Term $r\\frac{dy}{dr}$:**\n", + " - The first derivative $\\frac{dy}{dr}$ has dimensions:\n", + "\n", + " $$\n", + " \\left[\\frac{dy}{dr}\\right] = \\frac{[y]}{[r]} = \\frac{V}{L}.\n", + " $$\n", + " - Therefore, the term $r\\frac{dy}{dr}$ has dimensions:\n", + "\n", + " $$\n", + " \\left[r\\frac{dy}{dr}\\right] = [r] \\cdot \\frac{V}{L} = L \\cdot \\frac{V}{L} = V.\n", + " $$\n", + "\n", + "2. **Second Derivative Term $r^2\\frac{d^2y}{dr^2}$:**\n", + " - The second derivative $\\frac{d^2y}{dr^2}$ has dimensions:\n", + "\n", + " $$\n", + " \\left[\\frac{d^2y}{dr^2}\\right] = \\frac{[y]}{[r]^2} = \\frac{V}{L^2}.\n", + " $$\n", + " - Therefore, the term $r^2\\frac{d^2y}{dr^2}$ has dimensions:\n", + "\n", + " $$\n", + " \\left[r^2\\frac{d^2y}{dr^2}\\right] = [r]^2 \\cdot \\frac{V}{L^2} = L^2 \\cdot \\frac{V}{L^2} = V.\n", + " $$\n", + "\n", + "3. **Second Derivative Term $\\frac{d^2y}{d\\theta^2}$:**\n", + " - The second derivative $\\frac{d^2y}{d\\theta^2}$ has dimensions:\n", + "\n", + " $$\n", + " \\left[\\frac{d^2y}{d\\theta^2}\\right] = \\frac{[y]}{[\\theta]^2} = \\frac{V}{1^2} = V.\n", + " $$\n", + "\n", + "---\n", + "\n", + "### Step 3: Verify Dimensional Consistency\n", + "\n", + "The Laplace equation in polar coordinates is:\n", + "\n", + "$$\n", + "r\\frac{dy}{dr} + r^2\\frac{d^2y}{dr^2} + \\frac{d^2y}{d\\theta^2} = 0.\n", + "$$\n", + "\n", + "Each term in the equation has dimensions of $V$:\n", + "\n", + "- $r\\frac{dy}{dr}$: $V$\n", + "- $r^2\\frac{d^2y}{dr^2}$: $V$\n", + "- $\\frac{d^2y}{d\\theta^2}$: $V$\n", + "\n", + "Since all terms have the same dimensions, the equation is dimensionally consistent.\n", + "\n", + "---\n", + "\n", + "### Step 4: Summary of Dimensions\n", + "\n", + "| Variable/Parameter | Physical Meaning | Dimensions |\n", + "|------------------------|-----------------------------------|-----------------------|\n", + "| $r$ | Radial coordinate | $L$ |\n", + "| $\\theta$ | Angular coordinate | $1$ (dimensionless) |\n", + "| $y$ | Solution (e.g., voltage) | $V$ |\n", + "\n", + "---\n", + "\n", + "### Step 5: Initial and Boundary Conditions\n", + "\n", + "1. **Boundary Condition $y(1,\\theta) = \\cos(\\theta)$:**\n", + " - The boundary condition $y(1,\\theta) = \\cos(\\theta)$ is given in volts:\n", + " \n", + " $$\n", + " [y(1,\\theta)] = V.\n", + " $$\n", + " - The term $\\cos(\\theta)$ is dimensionless because $\\theta$ is dimensionless.\n", + "\n", + "2. **Periodic Boundary Condition $y(r, \\theta + 2\\pi) = y(r, \\theta)$:**\n", + " - The periodic boundary condition ensures that the solution is periodic in $\\theta$ with period $2\\pi$.\n", + " - Since $\\theta$ is dimensionless, the condition is dimensionally consistent.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementation\n", + "This description goes through the implementation of a solver for the above described Heat equation step-by-step.\n", + "\n", + "First, import the libraries we need:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import brainstate as bst\n", + "import brainunit as u\n", + "import numpy as np\n", + "\n", + "import deepxde.experimental as deepxde" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We begin by defining a computational geometry. We can use a built-in class `Rectangle` as follows" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "geom = deepxde.geometry.Rectangle(\n", + " xmin=[0, 0],\n", + " xmax=[1, 2 * np.pi],\n", + ").to_dict_point(r=u.meter, theta=u.radian)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we express the PDE residual of the Laplace equation:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def pde(x, y):\n", + " jacobian = net.jacobian(x)\n", + " hessian = net.hessian(x)\n", + "\n", + " dy_r = jacobian[\"y\"][\"r\"]\n", + " dy_rr = hessian[\"y\"][\"r\"][\"r\"]\n", + " dy_thetatheta = hessian[\"y\"][\"theta\"][\"theta\"]\n", + " return x['r'] * dy_r + x['r'] ** 2 * dy_rr + dy_thetatheta" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first argument to `pde` is 2-dimensional vector where the first component(`x[:,0:1]`) is $r$-coordinate and the second componenet (`x[:,1:]`) is the $\\theta$-coordinate. The second argument is the network output, i.e., the solution $y(r, \\theta)$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we consider the Dirichlet boundary condition. We need to implement a function, which should return `True` for points inside the subdomain and `False` for the points outside. In our case, if the points satisfy $r=1$ and are on the whole boundary of the rectangle domain, then function `boundary` returns `True`. Otherwise, it returns `False`. (Note that because of rounding-off errors, it is often wise to use u.math.allclose to test whether two floating point values are equivalent.)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def boundary(x, on_boundary):\n", + " return on_boundary and u.math.allclose(x['r'], 1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The argument `x` to `boundary` is the network input and is a $d$-dim vector, where $d$ is the dimension and $d=2$ in this case. To facilitate the implementation of `boundary`, a boolean `on_boundary` is used as the second argument. If the point $r,\\theta$ (the first argument) is on the entire boundary of the rectangle geometry that created above, then `on_boundary` is `True`, otherwise, `on_boundary` is False." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using a lambda funtion, the `boundary` we defined above can be passed to `DirichletBC` as the second argument. Thus, the Dirichlet boundary condition is" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "uy = u.volt / u.meter\n", + "bc = deepxde.icbc.DirichletBC(\n", + " lambda x: {'y': u.math.cos(x['theta']) * uy},\n", + " lambda x, on_boundary: u.math.logical_and(on_boundary, u.math.allclose(x['r'], 1 * u.meter)),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we rewrite this problem in cartesian coordinates, the variables are in the form of $[r\\sin(\\theta), r\\cos(\\theta)]$. We use them as features to satisfy the certain underlying physical constraints, so that the network is automatically periodic along the $\\theta$ coordinate and the period is $2\\pi$.\n", + "\n", + "Next, we choose the network. Here, we use a fully connected neural network of depth 4 (i.e., 3 hidden layers) and width 20:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# Use [r*sin(theta), r*cos(theta)] as features,\n", + "# so that the network is automatically periodic along the theta coordinate.\n", + "def feature_transform(x):\n", + " x = deepxde.utils.array_to_dict(x, [\"r\", \"theta\"], keep_dim=True)\n", + " return u.math.concatenate([x['r'] * u.math.sin(x['theta']),\n", + " x['r'] * u.math.cos(x['theta'])], axis=-1)\n", + "\n", + "net = deepxde.nn.Model(\n", + " deepxde.nn.DictToArray(r=u.meter, theta=u.radian),\n", + " deepxde.nn.FNN([2] + [20] * 3 + [1], \"tanh\", input_transform=feature_transform),\n", + " deepxde.nn.ArrayToDict(y=uy),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we have specified the geometry, PDE residual, and boundary condition. We then define the `PDE` problem as" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The argument `solution` is the reference solution to compute the error of our solution, and we define it as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "def solution(x):\n", + " r, theta = x['r'], x['theta']\n", + " return {'y': r * u.math.cos(theta) * uy / u.meter}" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "problem = deepxde.problem.PDE(\n", + " geom,\n", + " pde,\n", + " bc,\n", + " net,\n", + " num_domain=2540,\n", + " num_boundary=80,\n", + " solution=solution\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we have the PDE problem and the network. We bulid a `trainer` and choose the optimizer and learning rate:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Compiling trainer...\n", + "'compile' took 0.093740 s\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trainer = deepxde.Trainer(problem)\n", + "trainer.compile(bst.optim.Adam(1e-3), metrics=[\"l2 relative error\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We then train the model for 15000 iterations:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "0 [3.4136772 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [3.4136772 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(1.9016247, dtype=float32)}] \n", + " {'ibc0': {'y': 1.2442183 * volt / meter}}] {'ibc0': {'y': 1.2442183 * volt / meter}}] \n", + "1000 [0.00209501 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [0.00209501 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.03482116, dtype=float32)}] \n", + " {'ibc0': {'y': 9.6204014e-05 * volt / meter}}] {'ibc0': {'y': 9.6204014e-05 * volt / meter}}] \n", + "2000 [0.00059394 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [0.00059394 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.02848322, dtype=float32)}] \n", + " {'ibc0': {'y': 1.8821578e-05 * volt / meter}}] {'ibc0': {'y': 1.8821578e-05 * volt / meter}}] \n", + "3000 [0.0003004 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [0.0003004 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.01964555, dtype=float32)}] \n", + " {'ibc0': {'y': 1.1315266e-05 * volt / meter}}] {'ibc0': {'y': 1.1315266e-05 * volt / meter}}] \n", + "4000 [0.0001739 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [0.0001739 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.0129396, dtype=float32)}] \n", + " {'ibc0': {'y': 8.526638e-06 * volt / meter}}] {'ibc0': {'y': 8.526638e-06 * volt / meter}}] \n", + "5000 [0.00010057 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [0.00010057 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.00673189, dtype=float32)}] \n", + " {'ibc0': {'y': 6.3102784e-06 * volt / meter}}] {'ibc0': {'y': 6.3102784e-06 * volt / meter}}] \n", + "6000 [5.6971734e-05 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [5.6971734e-05 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.00247048, dtype=float32)}] \n", + " {'ibc0': {'y': 4.33217e-06 * volt / meter}}] {'ibc0': {'y': 4.33217e-06 * volt / meter}}] \n", + "7000 [3.255158e-05 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [3.255158e-05 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.0019735, dtype=float32)}] \n", + " {'ibc0': {'y': 2.83005e-06 * volt / meter}}] {'ibc0': {'y': 2.83005e-06 * volt / meter}}] \n", + "8000 [2.4938374e-05 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [2.4938374e-05 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.00377944, dtype=float32)}] \n", + " {'ibc0': {'y': 4.333744e-06 * volt / meter}}] {'ibc0': {'y': 4.333744e-06 * volt / meter}}] \n", + "9000 [1.1950426e-05 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [1.1950426e-05 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.00174527, dtype=float32)}] \n", + " {'ibc0': {'y': 1.2896384e-06 * volt / meter}}] {'ibc0': {'y': 1.2896384e-06 * volt / meter}}] \n", + "10000 [8.125793e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [8.125793e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.00141424, dtype=float32)}] \n", + " {'ibc0': {'y': 9.607884e-07 * volt / meter}}] {'ibc0': {'y': 9.607884e-07 * volt / meter}}] \n", + "11000 [7.288996e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [7.288996e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.00191965, dtype=float32)}] \n", + " {'ibc0': {'y': 1.3772864e-06 * volt / meter}}] {'ibc0': {'y': 1.3772864e-06 * volt / meter}}] \n", + "12000 [5.566375e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [5.566375e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.00146894, dtype=float32)}] \n", + " {'ibc0': {'y': 9.980397e-07 * volt / meter}}] {'ibc0': {'y': 9.980397e-07 * volt / meter}}] \n", + "13000 [4.0166346e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [4.0166346e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.00078307, dtype=float32)}] \n", + " {'ibc0': {'y': 4.9924233e-07 * volt / meter}}] {'ibc0': {'y': 4.9924233e-07 * volt / meter}}] \n", + "14000 [3.4733355e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [3.4733355e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.00065782, dtype=float32)}] \n", + " {'ibc0': {'y': 4.2479667e-07 * volt / meter}}] {'ibc0': {'y': 4.2479667e-07 * volt / meter}}] \n", + "15000 [4.31375e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [4.31375e-06 * 10.0^0 * (meter * (volt / meter) / meter) ** 2, [{'y': Array(0.0016097, dtype=float32)}] \n", + " {'ibc0': {'y': 1.0574405e-06 * volt / meter}}] {'ibc0': {'y': 1.0574405e-06 * volt / meter}}] \n", + "\n", + "Best trainer at step 14000:\n", + " train loss: 3.90e-06\n", + " test loss: 3.90e-06\n", + " test metric: [{'y': Array(0., dtype=float32)}]\n", + "\n", + "'train' took 56.701270 s\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trainer.train(iterations=15000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also save and plot the best trained result and loss history." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving loss history to /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/loss.dat ...\n", + "Saving checkpoint into /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/loss.dat\n", + "Saving training data to /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/train.dat ...\n", + "Saving checkpoint into /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/train.dat\n", + "Saving test data to /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/test.dat ...\n", + "Saving checkpoint into /Users/sichaohe/Documents/GitHub/pinnx/docs/examples-pinn-forward/test.dat\n" + ] + }, + { + "data": { + "image/png": "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5zEWF16MRiM5OTk4OzujKEqrnrusrIzg4GCys7M7xaMHpL2WTdpr2aS9ls/S2qyqKuXl5QQEBGBlde5ZLRYzbNTEysrqvIntUrm4uFjED8qFkvZaNmmvZZP2Wj5LarOrq+sFHScTdoUQQghhViS8CCGEEMKsSHhpATs7O55//nns7Oy0LqVdSHstm7TXskl7LV9nbHMTi5uwK4QQQgjLJj0vQgghhDArEl6EEEIIYVYkvAghhBDCrEh4EUIIIYRZkfBygRITEwkNDUWv1xMXF8fWrVu1Lum8XnvtNWJjY3F2dsbHx4eJEyeSmpra7JiamhoSEhLw9PTEycmJG264gfz8/GbHZGVlcc011+Dg4ICPjw+PP/646RlSTdatW8eAAQOws7Oje/fuLFq0qK2bd16vv/46iqLw8MMPm7ZZYnuPHz/ObbfdhqenJ/b29kRERLB9+3bTflVVmTt3Lv7+/tjb2xMfH8+hQ4eanaO4uJipU6fi4uKCm5sbd999NxUVFc2O2bNnD8OHD0ev1xMcHMwbb7zRLu07lcFg4LnnniMsLAx7e3u6devGyy+/3OxZKObc3g0bNjBhwgQCAgJQFIVly5Y129+ebfv666/p3bs3er2eiIgIVqxY0a7tra+v58knnyQiIgJHR0cCAgK44447yMnJscj2/tX999+Poii88847zbabU3vblCrOa/HixapOp1P//e9/q/v371dnzJihurm5qfn5+VqXdk5jxoxRFy5cqO7bt09NTk5Wr776ajUkJEStqKgwHXP//ferwcHBalJSkrp9+3Z18ODB6mWXXWba39DQoPbr10+Nj49Xd+3apa5YsUL18vJSn376adMxR48eVR0cHNQ5c+aoKSkp6rvvvqtaW1urP/30U7u291Rbt25VQ0ND1cjISPWhhx4ybbe09hYXF6tdunRRp0+frm7ZskU9evSo+vPPP6uHDx82HfP666+rrq6u6rJly9Tdu3er1157rRoWFqZWV1ebjhk7dqwaFRWlbt68Wf3111/V7t27q1OmTDHtLy0tVX19fdWpU6eq+/btU7/88kvV3t5e/fDDD9u1va+++qrq6emp/vDDD2p6err69ddfq05OTuo///lPi2jvihUr1GeeeUb97rvvVEBdunRps/3t1bbffvtNtba2Vt944w01JSVFffbZZ1VbW1t179697dbekpIS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c2tvW5LwcoHeffddNSQkRNXpdOqgQYPUzZs3a13SeQFnfC1cuNB0THV1tfrggw+q7u7uqoODgzpp0iQ1Nze32XkyMjLUcePGqfb29qqXl5f66KOPqvX19c2OWbt2rRodHa3qdDq1a9euza6hpb+GF0ts7//+9z+1X79+qp2dndq7d2/1o48+arbfaDSqzz33nOrr66va2dmpV1xxhZqamtrsmBMnTqhTpkxRnZycVBcXF/XOO+9Uy8vLmx2ze/duddiwYaqdnZ0aGBiovv76623etr8qKytTH3roITUkJETV6/Vq165d1WeeeabZh5k5t3ft2rVn/Ds7bdq0dm/bV199pfbs2VPV6XRq37591R9//LFd25uenn7W32Fr1661uPaeyZnCizm1ty0pqnrKrSmFEEIIITo4mfMihBBCCLMi4UUIIYQQZkXCixBCCCHMioQXIYQQQpgVCS9CCCGEMCsSXoQQQghhViS8CCGEEMKsSHgRQgghhFmR8CKEEEIIsyLhRQjR6goLC9HpdFRWVlJfX4+jo6PpSddnU1VVxdNPP023bt3Q6/V4e3szcuRIli9fbjomNDSUd955p42rF0J0dDZaFyCEsDybNm0iKioKR0dHtmzZgoeHByEhIed8z/3338+WLVt49913CQ8P58SJE/z++++cOHGinaoWQpgL6XkRQrS633//naFDhwKwceNG05/P5fvvv+dvf/sbV199NaGhoQwcOJBZs2Zx1113ATBq1CgyMzN55JFHUBQFRVFM7924cSPDhw/H3t6e4OBgZs+eTWVlpWl/aGgoL7/8MlOmTMHR0ZHAwEASExNN+1VV5YUXXiAkJAQ7OzsCAgKYPXt2a307hBCtTB7MKIRoFVlZWURGRgKNQ0DW1tbY2dlRXV2Noijo9XpuvfVW3nvvvTO+v3fv3kRFRfHJJ5/g7Ox82v7i4mKioqK49957mTFjBgB+fn4cOXKEqKgoXnnlFa655hoKCwuZOXMmUVFRLFy4EGgML8XFxfztb3/j+uuv5+eff+aRRx5h5cqVXHnllXzzzTfcfffdLF68mL59+5KXl8fu3btN1xFCdCwSXoQQraKhoYFjx45RVlZGTEwM27dvx9HRkejoaH788UdCQkJwcnLCy8vrjO/fsGEDU6dOJT8/n6ioKIYNG8aNN97YrNcmNDSUhx9+mIcffti07Z577sHa2poPP/zQtG3jxo2MHDmSyspK9Ho9oaGh9OnTh5UrV5qOueWWWygrK2PFihXMmzePDz/8kH379mFra9v63xwhRKuSYSMhRKuwsbEhNDSUgwcPEhsbS2RkJHl5efj6+jJixAhCQ0PPGlwARowYwdGjR0lKSuLGG29k//79DB8+nJdffvmc1929ezeLFi3CycnJ9BozZgxGo5H09HTTcUOGDGn2viFDhnDgwAEAbrrpJqqrq+natSszZsxg6dKlNDQ0XMJ3QwjRlmTCrhCiVfTt25fMzEzq6+sxGo04OTnR0NBAQ0MDTk5OdOnShf3795/zHLa2tgwfPpzhw4fz5JNP8sorr/DSSy/x5JNPotPpzvieiooK7rvvvjPOUTnfJOEmwcHBpKamsnr1alatWsWDDz7Im2++yfr166UnRogOSMKLEKJVrFixgvr6eq644greeOMNBg4cyC233ML06dMZO3bsRYWA8PBwGhoaqKmpQafTodPpMBgMzY4ZMGAAKSkpdO/e/Zzn2rx582lf9+nTx/S1vb09EyZMYMKECSQkJNC7d2/27t3LgAEDWly3EKJtyZwXIUSrycvLIzQ0lJKSEhRFwc3NjaNHj+Lv73/e944aNYopU6YQExODp6cnKSkpzJkzh8DAQJKSkgC46qqrsLe357333sPOzg4vLy/27NnD4MGDueuuu7jnnntwdHQkJSWFVatWsWDBAqBxrszJkyd55plnmDhxIqtWreKhhx7ixx9/ZMyYMSxatAiDwUBcXBwODg4sXLiQt956i+zsbDw9Pdv0eyaEaDmZ8yKEaDXr1q0jNjYWvV7P1q1bCQoKuqDgAjBmzBg+/fRTrrrqKvr06cOsWbMYM2YMX331lemYl156iYyMDLp164a3tzcAkZGRrF+/nrS0NIYPH07//v2ZO3cuAQEBzc7/6KOPsn37dvr3788rr7zCvHnzGDNmDABubm58/PHHDB06lMjISFavXs3//vc/CS5CdFDS8yKEsHhnWqUkhDBf0vMihBBCCLMi4UUIIYQQZkWGjYQQQghhVqTnRQghhBBmRcKLEEIIIcyKhBchhBBCmBUJL0IIIYQwKxJehBBCCGFWJLwIIYQQwqxIeBFCCCGEWZHwIoQQQgiz8v83RajgIKY1UgAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZ8AAAGOCAYAAABIaA6qAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/GU6VOAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOx9d3wb9f3+c5KXPOS9d2zHsRMnTuLEI4wQQhJGKYXyLRR+QAu0tA0UaKGUUspuKRQoXbRlBAqUlhYoO5BAIIkTZ3mveG95adna0t3vD+dzuTvdaXnECfe8XmmxdPrcSZY/z73X81AMwzCQIUOGDBkyFhCKU30BMmTIkCHjqweZfGTIkCFDxoJDJh8ZMmTIkLHgkMlHhgwZMmQsOGTykSFDhgwZCw6ZfGTIkCFDxoJDJh8ZMmTIkLHgkMlHhgwZMmQsOGTykSFDhgwZCw6ZfGTIkCFDxoJDJh8ZMmTIkLHgkMlHhgwZMmQsOGTykSFDhgwZCw6ZfGTIkCFDxoJDJh8ZMmTIkLHgkMlHhgwZMmQsOGTykSFDhgwZCw6ZfGTIkCFDxoJDJh8ZMmTIkLHgkMlHhgwZMmQsOGTykSFDhgwZCw6ZfGTIkCFDxoJDJh8ZMmTIkLHgkMlHhgwZMmQsOGTykSFDhgwZCw6ZfGTIkCFDxoJDJh8ZMmTIkLHgkMlHhgwZMmQsOGTykSFDhgwZCw6ZfGTIkCFDxoJDJh8ZMmTIkLHgkMlHhgwZMmQsOGTykSFDhgwZCw6ZfGTIkCFDxoJDJh8ZMmTIkLHgkMlHhgwZMmQsOIJO9QXI+GqBYRi4XC7YbDYolUr2n0Ih3wfJkPFVgkw+MhYMDMPA4XDA6XTCZrOxjysUCgQFBSEoKEgmIxkyviKgGIZhTvVFyDjz4XK54HA4QNM0KIqC3W6HQqEAwzBgGAY0TYNhGFAUBYqiZDKSIeMMh0w+MuYVDMPA6XTC6XQCACiKgl6vx9DQENRqNWJjYxEaGso7nhARAUVRAICQkBAEBwcjKCiIfUyGDBmnJ2TykTFvoGmajXYI+vr60NHRgcTERJjNZkxNTSE8PByxsbGIjY1FTEwMQkJC2OMJGR08eBCFhYWIiYmBQqGAUqnkRUcyGcmQcXpBrvnImHMQwnA4HGwqzW63o7GxESaTCWVlZQgPDwdFUXA6ndDr9dDpdOjt7cX09DQiIiJ4ZBQcHAyKotgUHFnbbreDoiiWjIKDg9ljZDKSIWNxQ458ZMwpSFOBy+UCMJMym5ycRGNjI2JjY7F8+XIolUqWOIQkYbfbWTLS6/UwmUyIjIyExWJBdnY2MjIyEBQUxDsfSdMRohNrYJDJSIaMxQWZfGTMGUhE4nK52GaCjo4O9Pf3o6ioCOnp6aAoCjRNS5KPEHa7HTqdDu3t7VAqlbDZbIiKimIjo+joaJaMyFdZJiMZMhY/ZPKRMWuQ2R2n0wmapqFQKGCxWFBfXw+Xy4XS0lJERkayx/tDPgQHDx7E0qVLERERAZ1Ox/4TIyOlUsleFzmfTEYyZCwuyDUfGbMCwzDQ6XSwWq2IjY2FQqHA6OgompqakJqaimXLlrFkMBtQFAWGYRAaGoqUlBSkpKQAACwWC5uma21thd1uZ7voxMiI/LPZbLDb7QDE54xkMpIhY34hk4+MgEEimLGxMeh0OsTExKClpQUajQYrVqxgCUKIudzYVSoVVCoVUlNTwTAMj4yGh4fhdDoRHR2NmJgYxMbGQq1WswQjJCObzcZGRqSlm8wYyWQkQ8bcQiYfGX6DpNm43WwOhwMHDhxAUFAQNmzYAJVK5XENfzdzEvl4OyY8PBzh4eFIS0sDwzAwm81s88Lg4CBcLhdiYmJYMoqKihIlI6vVyq4pk5EMGXMPmXxk+AWapuF0OnndbAaDATqdDkuWLEF+fv6iUSOgKAoRERGIiIhARkYGGIaByWRiyai/vx8Mw7BEFBMTg6ioKLZ1W4yMrFYrnE4nEhISZDKSIWMWkMlHhk8Qm91xOp1oamrC5OQk1Go1li5dOm/n9yXy8WWNyMhIREZGIjMzEwzDYHp6mm1e6OnpAUVRLBnFxsYiIiKCR0bkWNJAoVAo3GpGMhnJkOEdMvnI8AopiZz6+npERUUhPz8fY2Njp/gq/QdFUYiKikJUVBSysrJA0zRLRpOTk+ju7oZCoeCREXldcHAwT5fOZrPBarXKZCRDho+QyUeGR3Bnd0hrdHd3N7q7u1FQUIDs7GyMjIzMOirxhrmIfLxBoVBArVZDrVYjOzsbNE1jamoKOp0O4+Pj6OzsZJUWhoaGEBsbC5VKxaYZCRm5XC7WNoJbMyKSQP60mMuQcaZCJh8ZohCb3bHZbGhsbITFYsH69esRHR3NO34hrmkhoVAoEB0djejoaOTk5ICmaXR2dkKr1WJ0dBTHjx9HSEgILzKSIiOn08mSjpgunUxGMr5qkMlHhhuEEjkKhQITExNobGxEfHw8Vq9ezZO4IWoG84nFsDkrFAqoVCqEh4dj5cqVcLlcMBgM0Ov1GBkZQXt7O0JDQ3lkFBYW5kZGTqcTDoeDR0ZcXbrF0rAhQ8Z8QiYfGTyQ2R0S7TAMg/b2dgwMDKC4uBhpaWluRLAQKTFg4SMfKZD3r1QqERcXh7i4OAAznkV6vZ61jGhra0NYWBiPjEJDQ2UykiEDMvnIOAHh7A5XIoemaVRWVvIkcrhYCPJZDJGPNyiVSsTHxyM+Ph4AeIrdAwMDaGlpEbWPkCIjQHZ5lXHmQiYfGaJptpGREbS0tCA9PR2FhYUeN7yvWuTjK4KCgpCQkICEhAQAgMPh8Mk+QkhGXPsI2eVVxpkCmXy+4nA4HJienmbTQS6XC62trRgbG8PKlSuRlJTkdQ1/yYdhGBgMBoSHh/OM47yd43RHcHAwEhMTkZiYCIBvH9HV1QWz2YzIyEgeGQUFBfHIiHQfOhwOmEwmWCwWpKamsmQku7zKOF0gk89XFCS9o9PpcPjwYZx//vmYmppCfX09QkJCsGHDBoSFhfm0lj/k43A40NjYiImJCdA0LWmPIHXNZxJCQkKQlJTEErzNZmPJqKOjAxaLxePnYzKZoNFokJCQIBoZyS6vMhYzZPL5CoJrb61QKEDTNPr7+3H8+HHk5uYiLy/Prw3LV/Ihg6mRkZHYsGEDW6Anfj02m81NkZrc9S+WDXQ+CTA0NBTJyclITk4GMENGRFGBfD5cMqJpmiUbcm2yy6uM0wUy+XyFwN2cuMRD0zR6enqwdu1atnPLH3gjH4Zh0Nvbi87OTuTn5yM7OxtOpxNBQUFu9ghks+UqUsfGxrLzRl8liNlHEF06Yh+hVCrR3d3tZh8BSJORbB8hYzFAJp+vCMSaCnQ6Herr6wEAlZWVCA0NDWhtT+Rjt9vR2NiIqakprFu3DjExMZLHEnsEoSK1TqfD1NQUpqenodVqebprX6WNU/j59Pb2YmxsDBaLxaN9BMA31rPb7Tz1BZmMZJwKyOTzFYDQ3hoAurq60NPTg9zcXHR2diI4ODjg9Yk1thCE3NRqNTZs2ODXOYSK1PX19YiIiEBISAi0Wi2ru0aIiKgLfFU2TqIvp1KpsHz5cr/sIwCZjGScesjkcwZDSiKnvr4edrsd5eXlCAsLQ2dn56xqGcLIh2EY9PT0oKuri9V/m+0GRlEUwsLCkJGRwYqAGo1G6HQ6ntQNIaK4uLiAI7nTBdzP3JN9hE6nc7OPiI2NRWRkpCgZyS6vMhYCMvmcoRBLs42Pj6OxsRFJSUlYu3YtgoKC2GHGuSIfu92OhoYGmEwmN/232UBIcERtOiYmBrm5uazUjU6nY9UFVCqV20DnmQRibSGGQO0jfHV5JW3dsmK3jEAhk88ZCKJUwJXIaWtrw9DQECuRQ0DScLMp5hNi0Gq1qK+vR0xMDKqqqmaVyvMXQqkbrrpAT08PTCaT6AxNIFhMm62v1xKIfUR4eLhHYz3Z5VXGbCCTzxkEru8Okcgxm828poKIiAjea8hGMdvIh6ZpHD16FIWFhcjMzJzzDcjfQVahuoDdbmfv+js6OmC1Wt1maLidYqcDPEU+3uCLfURQUJCoYrcUGel0OoSHh7PzSDIZyfAEmXzOEHBbqIGZzWV4eBgtLS3IzMzE0qVLRWVYyMYQaORjs9nQ1NQEACgvL4darfb5tf5unrMhyJCQEN4MjdVqZcmopaWF19ZNivOLXbZmLmeOxOwjiGK3J/sIACwZDQ8PIz4+nq21idlHyGQkg0Amn9McYvbWLpcLLS0tmJiYQGlpKSvnIgYyFR/IRjY5OYmGhga2rhMVFRXw+/CGud6wwsLCkJqaitTUVDAMw87QaLVaDAwMgKZpt+L8Yts0ZxP5eAO3k5BbU/NkHwGAHWiVXV5leINMPqcxhE0FFEXBaDSivr4eKpUKVVVVPknkSLVKezpvZ2cnent7sWzZMiQmJmJsbGxeN0Ny3vkARVEIDw9HeHg40tPTRYvzpB5CNtT5fq/+XPtCQKymJmzwIN8jQlzcbkPZ5VWGEDL5nKagaRoTExMYGxtDfn4+AKCvrw8dHR1YsmQJlixZ4vMfsT9mcFarFQ0NDbDZbKioqEBUVBTbljuf0jMLuSGJFedJPWRkZAQWiwXV1dW8GSNfdfDmEqdS6y4oKMjNPuLIkSNQKBR+2UfILq9fXcjkc5qB+wdrNpsxNjaG7OxsVkWgrKyMTYH4Cl/TbhMTE2hoaEBCQgLWrFnDdovNRdOCLzhVmy23HkKiy4yMDGi1Wp5pHJeMFqKte7FEXwBYskhLS0NSUlLA9hFixnrCNJ2MMwMy+ZxGEKbZlEolHA4H9u/fj5iYGL9VBAiIxpsUaJpGZ2cn+vr6UFRUhPT0dN6mtxDks1g2WWDmWrh1Dm5bd19fH5qbm3kbbWxsbMBt3Z6wmMgH4F/PXNhHyC6vZzZk8jlNIGZvPTIyAqvViuLi4lm1N3uKfKxWK+rr6+FwOCTdTM/0yMcbxNq6uRutmDXCXLV1LzbykSIDb/YRVquVR0akXVt2eT1zIZPPIoeYvTUhBJvNhpCQEGRlZc3qHFINB+Pj42hoaEBSUhKKi4slN8zZtmv7eo2nC4QbLbetm6hRc9u61Wp1QJvmYot8iMWDLxDaR1itVjd7DTHClnJ5BWQyOt0gk88iBk3TcDqdPImcsbExNDU1ITk5GQUFBWhoaJj1eYQNBzRNo6OjA/39/SguLkZ6errH1/u7AZJ2cE93ymKvWSyRj7/vV6qtW6fTYXBwEDRNs2QUFxfnc1v3YiMff36fQoSFhXm1j+AqdhOvJymXVwDssDVR95ZdXhcXZPJZhBCb3aFpGm1tbRgeHsaKFSuQkpICo9E4J9EGN/KxWCyor6+H0+mUTLOJvR7wPfIZGhpCc3Mzr3byVbFIEGvr5gqA9vb2un0u4eHhop/LbDb7+YA/kY83CO0juGTkzT4CmPlsNBoNxsfHUVJSAgCyy+sig0w+iwxciRxg5g/GZDKhrq4OSqUSVVVVCA8PB+C9UcBXkMhnbGwMjY2NSElJwbJly/yqS/gSmbhcLrS2tmJ0dBQlJSUICgqCXq/H5OQkurq6EBQUxFOl5rYvL5bIZ66vQSgASjTXtFotT+ZGaB3Bff1iwXxFYmKEzfV6krKPAMBGPLLL6+KDTD6LCFzfHdLdMzQ0hNbWVmRlZaGgoIB3p+vvcKgnDA4OQqfTYfny5UhNTfX79d5mhYQEGhQUBJfLhejoaFZbjAwtDg8Po729HWFhYYiLi2MtoxfTXf58gau5xpW5ITNGRFkgNjYWFovFp8h0obBQkZiv9hGhoaFQKBSYmpri2UeQa5VdXk8tZPJZBBDz3XE6nWhuboZWq8Xq1avZTiouyIY/mztOs9kMk8kEu90uKjzq7/sQg0ajQVNTEzIyMliNORLZEXDlXJYsWcK2L2u1WlaVmrSRk9bc000INBBwPxeArywwPj7OFumF8zOnAnOZdvMHUvYRXV1dMJlMOHbsmKR9BCAb650qyORziiHmu2MwGFjnzg0bNkiaonGLrYH8URBSCAoKQl5e3qyIRyzyoWka7e3tGBoaQklJCdvZ5AuE7cttbW2wWq1wOBxoa2tjC9AkMjodhEDnAlxlAbvdjpCQEERHR/PmZ7hdYgtJ0oulBsVVqAgLC8PSpUt9so+QyWhhIZPPKYRwdgcAent70dnZifz8fOTk5Hj8cnO9ePz5oxc2LwwMDMzujcC9JmOxWFBXVweGYXh1KgJ/aydBQUEIDw/H0qVL2QK0VqvlpVm49SKpIv2ZhqCgIN4wp81mY9NPpGVZrVbzWpbngyBmG4HPB8jfRaD2EbLL6/xCJp9TALHZHbvdjsbGRphMJqxbtw4xMTFe1wlkvsZsNqOurg4AWFIYGhqadSGdSz6zaVzwBLI+twBNcv5kM5mYmOA1L5DI6FRor803xH5noaGhoi3LpJbmdDp5m2xUVNScbJjkWhZD5ENA07Tody9Q+wgpl1cuGckur75DJp8FBkmzHT58GAUFBVCr1aw1QVxcnF8OoP66kI6MjKC5uRnp6ekoLCxkXz8XjQtkjfb2dvT392P58uU8x9TZwtMfMUVRvDtbl8sFo9HI017jWmrHxsaesrrIXMKXSEPYsky6xLRaLfr6+gDArRYSyIbJvTFYLKBp2qffs5R9hLDJg/s5kZsZ2eU1cMjks4Dg2lubTCY4HA4cP34c/f39oppp3uCrrI3L5UJbWxs0Go1o7cUfVWtPaG1tBcMwPs8H+Qtfr1GpVPKK9FyRy56eHjQ1NSEqKoqNik5HF1PA/1qfWJcYiRhJuzv3syN3/L6cg9y8LKbNNdAOSV/sI4iQLCEkUpeVych3yOSzABDO7pA/iNbWVigUioA3a/KF9hS1kBZnhUKBqqoq3owId53ZkM/ExASsVisiIiKwZs2aednIZ/NHKhS5JHURrVaL1tZWOBwOnsLAXKWiFgKzuU5hxEjTNIxGI3Q6HS/9REha6NHDxWJNu83F9YjZR5CbGU/2EYA7GfX392Nqagr5+flfeZdXmXzmGUJ7a4qioNFoYLFYkJiYiNLS0llt1p5SZsPDw2hubvZoo+1tDU9gGAZdXV3o6elBSEgIcnNz5zWCmKsBT25dRDiw2N/fD2AmFUU2XWHzwmLZIOZ64JV0gMXExLDpJ0+bLDd9uVjTbvPxfRR2Ygoja5PJJGofAYAdqSA3jV9ll1eZfOYJ3CE2cgdG0zRaWlowOjrKTmvP9o9DLPLhKgmsWrWKFbj0tIa/G5nNZkNDQwMsFgsqKipQX1/v1xqB6MHNB6RSUUKFAUJEwvmkU4n57i5TKpW8O36x9CVRoiZt+otps1yowWR/7CO4A60Enlxez2QykslnHiA2uzM9PY36+noEBQWhqqoKDQ0NcyqNQzA9PY26ujr2PGJpNiH8jXy0Wi3q6+sRGxuL1atXs4KNZ4KlAjcVlZOTwys+Dw0NwWg0QqlUor29HXFxcad0qJNc70JBbJPlWo0DwNGjR3lq3aeylnaqVDE82UdMTk6yRpDe7CO4ZHT11Vfj+uuvx9VXX73g72e+IJPPHIMrkUO+TAMDA2hvb0dOTg7y8vLYEHuuyIesMzQ0hJaWFmRnZyM/P9/nPzxfIx+GYdDT04Ouri4sXboUWVlZ7OY3l1I/YjhVd3zC4nNnZyeMRiMoimLvaoVzNAs51Hkq74RDQkJYW4Tp6WkcOXIEqampvLZurnXEQg8CLxZJJq59BLlJi46Ohl6v99k+QqPRzIsh4anEmfVuTiGkJHKampqg1+uxZs0aNn0BzGz4JDKaDSiKgsPhQENDAyYmJlBaWsremfqzhjfyIXNI09PTWL9+PaKjo93W8Pec/mIxCIsqlUp2ah6Yuaslw64tLS28DXe+mxcWw+dBwDAMa6MtbOvmDgKT7rC4uLh5VzFfLOTDhcvlgkqlYi02AM/2EU6nE5mZmTCbzW6D2t7w5Zdf4oknnsDRo0cxMjKCt99+G5dddpnH1+zZswd33nknWyu+7777cMMNN/CO+dOf/oQnnngCGo0Gq1atwh/+8AesX7/er2sDZPKZE4il2fR6Perr6xEVFYUNGzaw3S8ESqVyTiIFhmHQ2toKlUqFqqqqgIYpib+OFPR6Perq6qBWqyXnkM7UyMcbQkNDeV493Dka0rzgiz1CIDjVkQ8XwmsRq6URiRuSpuPO1/jT1u0ruNmHhUTjsBG1/UaszlKjJE3Ne06sCcKTfcSDDz6Izz77DAzD4O2330ZMTAzWrVvntp+IwWQyYdWqVfjud7+Lyy+/3OvxPT09uPjii3HLLbfgtddew+7du3HTTTchNTUVW7duBQD861//wp133onnnnsO5eXleOaZZ7B161a0t7d7rS0LIZPPLMGd3SFf9O7ubnR3d6OgoADZ2dmif1CzTbsxDIOhoSFYLBakpKRg5cqVAf+hSaXdSGvo8ePHvcr9zHfNZ7FYKniCcMMVs0cIDg7myQBJtS77gsVEPt5ERbl6a1lZWazEjVar5bV1c8lotqoUCxX5aIw29GstyIpT4Q97evBu4xj73KUlSXj00mXsz94IUWgf8cYbb6C+vh5bt25Fb28vLrvsMpjNZtx222349a9/7fG6LrzwQlx44YU+v4/nnnsOubm5+N3vfgcAKCoqwr59+/D000+z5PPUU0/h5ptvxne+8x32NR988AFefPFF3HPPPT6fC5DJJ2BwZ3eIRA7pALNaraKpKS5mQz5E8XpychIRERFITk6e1R+ZWNRCUoY6nQ5lZWXswKYU/O2YI6Q9m813sUNoj8BtXhgcHERrayvCw8N5czT+5vUXC/n4KyrKlbgRqgoIBzlJY4cvd/tcLAT5vFU3ggc/7ADNABQA4V/Au41juKosjY2AXC6XXzVBhUKB0tJSuFwu/PnPf0Z+fj6am5vZ4dW5xIEDB7B582beY1u3bsXtt98OYCb1fvToUfz85z/nXd/mzZtx4MABv88nk08AELO3npiYQENDAxITE7FmzRqvm0ig5GM0GlFXVweVSoUNGzagrq5u1ukuIXEIz+HLH70/kYlWq0VdXR3sdjvUajVb0Fer1R5nkRZ75OMN3OaFvLw8tnVZq9Wiq6sLFouFLTyTz8PTRrWYPo/Z2imIqQqQDrHe3l5MT0+z7cpkdsbb35i/5MONYFLU7jdFwuc1RhtLPIA78RDUDRgDJh9gZtN3OByIjIyEQqFgnVnnGhqNxk39JDk5GUajkU0Fulwu0WPa2tr8Pp9MPn5AzN6aYRi0t7djYGAAxcXFSE9P92ktf8mHYRi2ay43Nxd5eXk+KRz4AvI+GIbB4OAg2traeOfwZw1v74GodhcUFCA6Opq9221sbARN0+xwp5gy9WLabOcCwtZlq9XK1ouam5vZ5gWubYTw8zhdIx9vEA5ycmdnOjo6YLVaRTvEuNfjC/kQQmkeMeKZz3tBM4CCAq5bn4Fr1qezJMSNcBQU8KuLCpARo2KJxxNKM0/WfQIZfJ2engaARWUcOBeQycdHkKaCxsZG5OTkICIiAmazGfX19QBmFKL98cNRKBRwOBw+HctNgYl1zc2FIrXL5UJjYyMmJibczuHrGp6uw+l0orGxEQaDAevWrUNkZCQcDgciIiLYQivXc4UoUxMimovOwLnCfG34YWFhbs0LpJOut7cXFEXx6kWLiXzm20hOODtDiFqn0/E6xLiddHobcGxwCnnJMwQkjGpeOjCApz/rcYtYaAbYUTOIHTWDuHNTLi5cnsSLcGgGePDDDvx8ax4oCvD053dpSRKv6SCQJojp6Wm2FjSfSElJwejoKO+x0dFRqNVqnqq32DFERd0fyOTjA7izOxMTE0hPT2cVojMyMngK0b7C11ZrYixHutmENZK5iHwcDge0Wi3bzRZox5wU+UxNTaG2tpZ9DyEhIW5KAcKCNLcGMDAwgKmpKSiVSnR0dLAb8OkoBuoruM0LmZmZPA+asbExdHR0AJixP3e5XB511xYCC02EQqLmWkcMDg5i37AL/+xUgjnWBnJVDE5GLQaLA0991uv1PE991oMBrcUtwqEZ4NGPuyRfd9WaVFy6Ktmt2y2QtBtps57v+lVlZSU+/PBD3mOffvopKisrAczcAKxduxa7d+9mW7Zpmsbu3buxfft2v88nk48HiM3ukOFCg8Hgk3SNFLy1WnM7zfLy8pCbmzsvXXPDw8Po6elBaGgo1q1bF/AXXIp8yOBrTk4O8vPzfd6ghPWR/v5+aDQauFwuHD9+HDabzW2eZrHNdMwlhB40LpcLNTU1CAoKYnXXiJ4YKdAv5FDiQriYaow21A0aAADpMWGw2Gk2kgkPD4fRqYTZEYHo2Cy8sbeOjWi430qaAR74oEOyPiOGN+s0fl/rhvxYN+IBAiOf6enpgGaipqen0dnZyf7c09ODuro6xMXFISsrCz//+c8xNDSEV155BQBwyy234I9//CPuvvtufPe738Vnn32Gf//73/jggw/YNe68805cf/31KCsrw/r16/HMM8/AZDKx3W/+QCYfCYjN7kxNTcFms0GpVGLDhg2zagX1NBfjcDjQ1NQEg8HgtdMs0Pkarv5bZmYmpqamZt0xxyUfmqbR2toKjUYT0OCrEEqlEiEhIVi2bKZtlTiZarVa1omVm5Ka65kRgsVSdyIT8GlpaYiPj4fD4WDv/Lk1Ea5txHySw3yn3d6qGxElDRLJAOClxjxhIX6DA1r3bjRSh/KXfIhQqb84cuQIzjvvPPbnO++8EwBw/fXXY8eOHRgZGWFn0QAgNzcXH3zwAe644w78/ve/R0ZGBp5//nm2zRoAvvWtb2F8fBz3338/+7f98ccfuzUh+AKZfEQgZm9NopDg4GDk5eXNegZBKvIhw6mRkZFsisoTAqn5mM1m1NbWsjYLOp0ORqPRrzWE4JKP0ELbF305X9bnQqVSIT09Henp6TwxUJKSCg0N5TmZ+tume7qAfC7BwcFuNRFSLxI6mMbFxSEyMnJOyWIuIh+xbrJ+rQWqEAUe/FA8WiH1F4ZZGFLxFdwmAwLy975Qkc/GjRs97g07duwQfU1tba3Hdbdv3x5Qmk0ImXw4ELO3JlGI0WjE2rVr0dnZOeeabOTcfX196Ojo8DrQ6Wkdb9BoNGhqauK5mRoMhjnrmBsfH0dDQwNSUlJQVFTksXXaX0j9IYmJgZIW5r6+PjQ3NyMyMpJN452u5nFCeKqzhIWF8aRuTCYTGxn19vay9gmEnGcbKXIjH08ty1LPCbvJLlmRhPebxnyKZHw5Zi4hNs/DhbDJgICbRfEHZrM5oMhnsUMmnxMQm93R6XRoaGhgC/EhISFzpsnGJQ2imzY1NYV169YhJiYmoHU8gVhcDw0NYcWKFbzulLmaoZmcnERPT49fLee+wp+NUWgFYLfb2SiAax5HyGiuo4CFgq+/M4qiEBkZicjISF7zAlddgESKJDLyN1IkRCgkEW7LsnAg845NufhOZSY0RhsvpUYz4KkELDZIfeoUgGf/rxgbCxJEnw+UfEjkc6bhK08+YrM7AFiTNKF681xpshHS0Ol0qK+v5xGcP/Cmywa4p8GELZuz1WWz2+2sVHxFRQWioqICXssTAiXIkJAQUfM4rVbLRgHcFN1cpAkXAoF2mImpCwhN4yIiItjPIyYmBhNmF5sC4xb6gRONAMNmME4XHvyS35K8o2YQL9cM4o5NuXjm8x7eQCbpJFuaEjHrlFlZlhpH+meXOvYXFACKAm/2R4p4gJPNBv7+zkwm0xk34wN8xclHaG9NURRsNhvq6+tht9tRXl4OtZofPs+VFQJFUbBarThy5IhHDThv8Fbz4abBli1bJppums2sEBEdpSgKaWlpPhPPqTKTE9NfI1HAyMgI2tvbERYWxpO8OZV+Pd4wF5/LuMmJ/iklspIykZ+fzzYvaLVadHR04PM+G97oUvAIwp9CPwOIztMAgXWSieFovxEXL0/Eh83jfhEZBWD7xmxEhwXh0Y+7eK/1ll5jAHy3EIgIopGXpEZerB1Go1FSyTxQZ1ViRnem4StLPtzZHaIUMDY2hsbGRiQlJWHt2rWirapKpXLWaTe73Y6uri44HA5UVFR41IDzBikypGkanZ2d6Ovrw/Lly5GWlia5RiBpN67iQn5+PiwWy7wTynx0mgmjACLrotVqWbdO4tdDBjsXC2YzW0NqLwd7dXh+/wBvBuby0lTQYdEwhoUgMjsN//q8TnQQ05+W5fn+1BgAHzSPgwKwPCUSzZppn1/3hz19UFDA10qS8F7TGG9o9NIS6doTBWBVSTHWpYWx9bS+vj4A4DV3EKWOQFW2A+12W+z4ypGP2OwOsbceHh5GcXGxx416tpEPcQENDw9HcHDwrIhH6nqsVivq6+vhcDhQWVnp9a7J3/dEhE21Wi3Wrl2LuLg4tLW1nRGWCkJZF+LXQyRvHA4HQkJC0N/fvyCeNJ7giQg9Ff25tRcuSOeYweJgpWY8nj/QC59HMIAk8XiKZGgGeF9APAxmHnv1hlJYHTT+eWQIn7ZN8p6/7c0WVrk6MzOT7bwUKnUQ0dhAvivT09Pzlso+lfhKkY/Y7I7JZEJ9fT3bduxNwiLQyIdhGNZqYenSpYiOjsbRo0cDeh9cCOs1k5OTqK+vR0JCgmT0JraGr3f0xKY7ODiYp7hwploqCP162tvbWRmg7u5udmMhzQsLqTIgFfmI6ZBVLYnjtS1LEQvNwKfJ/4WEP5EMF1xlAwJPkYzUY1YHjcxYFXa3T7ofAL5yNbfzMjs7GzRNs0odY2NjsFgsqK6u5llHePvOmEwm1njuTMJXhnzEZneGh4fR0tKCzMxMLF261KeQ2B9NNgJitWCxWFirhenp6TltXGAYhm2SKCoqQnp6ul+ioL5cC2nTzszMREFBAe/zmguNucUOiqIQEhKCyMhILFu2jLexDA0N8SwS5kplgDvrIiz062zAscFpFKaH8Ir/ws6xBz6YkeLx5bfjrc5xKhAI8QDu74MbyQzrbfjp262858XeO0UBmbEq9ItI7HDBVa7mgmuYp1KpMDIyguzsbF5zR3h4OE+ZQlhjDMTF9HTAGU8+DMPAarXCYrEgLCyMbZUmfjj+Tt/7G/lMTk6ioaEBsbGxWL16NbsZzVXjAnk/R44cgcViEW2S8GUNT8RB0zSOHz+OwcFBlJSUiE4zn6mRjxDca+BuLEuWLOFZJBCVAa5lhL8SQGLpMW6h/4FDDJhDHaAA3LQhExU5seieNIluuj6/Pz+OnSuQTV8sUhEeR7rLhI/Di8AnAYlkVmWofSNaBqgbNCA9JgwKkXMTiA2VCuFyuRAUFMQbAyDfGRJJm0wmVq2boijEx8fPqtvNH8vrjRs34osvvnB7/KKLLmIldm644Qa8/PLLvOe3bt2Kjz/+2O9rO6PJh6TZhoaGMDw8jPXr18NoNPKEOv1VKvCVNLiRyLJly5CRkcGLRMiGP1tBRovFAr1ej+TkZB65+QNPGzupHzmdTlRWVkoWPheCHBYD+XiC0CKBiF12DE1gd9MAEkIZLEmJhSskClNQISYqHFaHe9uyp/QYP5KhTvw/8Pf9A/j7/oEFe69ziY1Zwfi83+GVCBgA39+Qhef29bs9fovI44B7NMONZHwhaQbAXW+3eRx8lRoqFUKs4UD4nbHZbGzzwt/+9jfs2LEDoaEz2nUlJSWoqKjwObXrr+X1W2+9Bbvdzv48OTmJVatW4corr+Qdt23bNrz00kvsz4Gmms9Y8uHaWwcFBcHlcrFeMkuWLMGSJUsCbm32FvlYrVbW0VRq7oV8CQNtvyTeOEQUdNWqVQGTmBShcutHy5cv93id/pIPkcT31b1zsQ+BihX4VSoVPmrX48FP9TODlRSFTbkOfNY94NbSe9u5WYiLDPVJn2xxU7B06k40rQXg836H22OQWGN5WqQ7oZx4XBiZKAC4fas9RDKKE9GTlIwPt/nAZHdiQGtFaabaJ+IBfBMVDQ0NZWfSnn76adxyyy247LLLoNVqcdVVV0Gv1+Paa6/F3//+d6/n89fymhj5EbzxxhsIDw93Ix9yjbPFGUc+wtkdsrGaTCb09fX5ZAntCd6GTImjaUJCgkdH09mQD/EVMhqNyM/Px8jIyKw2Z66ZHPnvnp4edHV1iUZtntbwBRMTE2yTB9eHJT4+XnJGAli8kY/UVD/Ab0dmGGB3t8nt9QyA33/Rh5Pb7umN758lHoX8Yls+Hvm4k/eYVLTx9SVB+F+30+05m8P9FQyA2/7dgq9xmgkUFHBJfjje7TC7HcuNZLit1QwD3hpC0AxQO2DElqJEUadTbwjkb33p0qVQqVS49957sW3bNrS1tbn56YhhLiyvX3jhBVx11VVu2Y49e/YgKSkJsbGx2LRpEx555BG//b+AM4x8yOwOIQeKoqDValmL1w0bNsx6YFCq5sOdq/Gl4M8lH39gMBhQV1fHCo8aDAYMDw/79yYEINdJiJsQG2mO8HUNX5xMuaSWmJjIa2Um6tRkwJOoU3OvcaEhLPgzZheig6XTY9yp/itWp/gRpZx+xCMV4bhoWjQ6YSQ+DbFjN5ctxXs9LTwSoMCgtlV8tkjYTDA8MoLkCCXe7zRLEomn1uphvQ13vd3qdq4ndnXjd7u72Xkof0BqPv6CtFpTFIWioiIUFRV5fc3ExMSsLK8PHTqEpqYmvPDCC7zHt23bhssvvxy5ubno6urCvffeiwsvvBAHDhzwm1jPCPLhSuSQbjaGYdDR0YG+vj5kZWVhaGhoTibVvc3V+CovQ8jH1+YFrr8PV3h0LhoXyLUYjUY0NDQgPDzcb6kff5xM169fj6ioKNjtdjd1aqPRCK1WC41Gg+PHj0OlUiEuLg5BQUHzOkdEwE2fVXdr3dJgFIBzs8PwZX+N16n+/9TOzfT+QkIqNSb2Vp/4xjLU9OrxpuB9vnBgULSe8ujHXW5riRXxxSKZmccpvN7mlLwemgE+aR3HKzVDrH6ct0hG7DGrg8bW4kSY7E6P81BVS+L8ioBcLldA6uqnQl7nhRdeQElJiVtzwlVXXcX+d0lJCVauXIm8vDzs2bMH559/vl/nOO3JR2x2h1skr6ioAAD2rnq2EEY+RL4mKSkJRUVFft3Z+KoTx7XRFqYN54J8SFRx+PDhgOthnshnenoatbW1CAsLY0lN7JopinJTGyAyLyMjI7DZbDh69CjbPaZWqwOKiKTal4XdZWKbHANgT5+7V8uZAqnUmFh0khYThm3FSW7k461mRQhHQQFXlKa4vZ6c8/2mMfz+ymLc9u8WnjEcBXHSosBgx8EhtzU8RTJi1xYWrMChXj2qlsRh5/ZyfNI6jid2dbu9xwGdxW/y8Tc6IFqE/iocJCQkBGx5bTKZ8MYbb+Chhx7yep4lS5YgISEBnZ2dXy3y4UrkEJdRMouSmprKapmZzeY5UaIGTm72NE2jo6MD/f39Aas4+0IcQgtqYWfJbEVBXS4XG4aXlJQEXEiUug6NRoPGxkZkZ2ejoKDAL7IICgpiO4ESExPR2tqKlJQUNwO5uLg4jNhC0DJmQ1Z8GMKDg0Q7yKSiGQUF3H5erpv+2OKsMPkHf+d2DFbxGTYxEr7mpTrcsSnHYwuy2DoXFSfig+Zx0MxMdOgpkmkenhI99xOXLUPTyBRePjjESgOty1Sjpn/KbY3BkTFsLE7HAxcXeGzoIHWga3fU8QZ0txQl4ne7u92+M5mx/gnQBlLzsVqtcLlcfisczMby+s0334TNZsO1117r9TyDg4OYnJwMaAj2tCQfKYmctrY2jIyMuFkGKJVKNjU3W8MrhUIBp9OJQ4cOse3HgYbE3shncHAQra2tyM3NRV5enqSNdqCFeLPZzIqCAphVI4bw2rizQStXrgzI6VC4vkKhcDOQm5ycxMOf9GL/sBPcuglFAQ+ICF+KbXQ0Iy18ebqjKkeNFenR+KuPbdh/2NMn2p4s9hVjADzzeS9urMp0a/P2NJPzQfM4bw3JSIYCntvnft0K6gT5rcvANesyMKCzICxYgWteqhM9dlqvxSs7B5AZHYo/b4tHnU6B5w6Oux1779Z8PLazk1e/e/DDDrx6Qyl+dVGBm2qEv00HgUQ+JtNMg0oge4w3y+vrrrsO6enp+PWvf8173QsvvIDLLrvMrYlgenoaDz74IK644gqkpKSgq6sLd999N/Lz83lup77itCMfKYmcuro6BAUFiUrkkF94oMJ+XOj1ejidTkRGRqKoqGhWpmRS5ON0OtHS0oKJiQmsXr2a1RnzZw1vICKqqampKCwsxKeffjqrCIob+XCVwT3NBnkDNz02pp2G1XLy+ppGplDbb0SUKgz7h10QFuwZRlz4UopgzkTiAYD9vUbs7zX6HQFxj2cY6QiKZgClSDDLYGYmZ9Jkx39qNWx0IhZ1SEUyYoRHnbge0rFGCv+HevWi11eRG4NHavSgGSUUlBO3rHViSZgFFBh2TgonzhetChKt71zzUh1uqsrEzRsykRgZinML4gPqdgtk/zGZTFAoFAHZfHizvO7v73e7nvb2duzbtw+ffPKJ23pKpRINDQ14+eWXodfrkZaWhi1btuDhhx8OaNbntCIf7uwO+dAGBwfR1taG7Oxs5Ofni/5yucX9QJsOyJ08SfcsX7581h1YYsQh1E7zNgTrL/lwGzG4atezHRIlrycWCzExMR5bzcWgMdpQN2gAAIwYbDz/F2Bm47HFjeBov8Ens7EzlVBOgsQNMwiEYMSUAaTW8Cc6AYDn9vVDQQF3bMrBijQ1Jk123PW2e6cVJRLJSB3LPT2JTAqSIpAVp3K7PgrAge6TpEQzwHNHDXj1hlLcF2HEo5/MpNIUAL61hEaQrl+yzvf36pPvsWHIiEcvXSb+IXlAoJHPbMRrPVle79mzx+2xwsJCyX1ApVJh586dAV2HGE4L8uHO7hB7a6KsrNPpfIoOfDFdk4LZbEZ9fT1omkZZWRlqampmrUxArotLHMPDw2hubkZWVpabdpoUhDM6nmC321FfXy86/Drb2hHxJzp8+LDf/kQaow2vHRrCyzXuXVJcMPBPxv/MB//z3bwsHrvaJiVbkcUeOzfZhS803jdEqUjG2/0Kzcyk5l69oRSlGdHi3W2cSIbMSGXGupOJ2GtJZPLAxQX81BiAK0pi8WajTvL4ndvLUT9oBM0wWJYQghCnCTeXavD3OneJIi64QqL+IBDyIS6mi33IOhDMLge1ACBNBdyhUYPBgOrqajidTlRVVXkkHoJAW3VHR0dRXV2N6Oho3oY9l1baRGuutbUVq1atQmFhoc/hOTnOW9Si0+lQXV2N4OBgVFZWuhUwZ1M7crlcGBgYgM1mw9q1a9k2cF/wVt0ItvyhBju8EA/BmU48Yp8aJfG4ELvbJ3HThkz4s08R4iEvoTx8ws/t68d/6zS4qSoTd21egp9vzfPp90E2/OpuLX51UQEUFDmX+3E7agax5Q81bscqKODGqkz2Zy4YnGx9fvWG0plrWx+Cb5bEezz+o+ZR3P1OK+5+pw2XvdCAmnEFbr14LV69odTr51034L9raiANB2eqlw+wiCMfKXtrMqTInXXxBSRa8hWkgWF4eJjXwEAIbK5EQS0WC2pqakBRFKqqqvzO7XKHVcUIizsf5CkiCTTtRpoWXC4Xq+jsK0aNNjz4oRzJcOFr1CIGmgFrDBfoea8rS8ErR0Y9+t6QFBQhRe6xUhI1ZMPfub0cO7eXe02tkWNvPy8HT3824y30QvWApL4azQCvHR7kzfj8VGXmRUPC45/+rJeXkiMEtjJd7aUzjoFS14f2dpNfbreB1nzkyGcBQYYNR0ZGWOKx2+04cuQIBgYGsH79euTm5vr1C/F1pgaY2VAPHjwIvV6PqqoqXufcbFN4XDgcDnR0dCA2Nhbl5eUBFRU9KSU4nU7U19eju7sbZWVlHsk6kMaFiYkJHDhwADExMVi6dKnvaTaDFTU9Ovxtf7/PLboyfAOvSw3AteukjRHF8I+jo7hjU65oxCB1Lu6hRKJG7PVkNoYcR/TVxEAzQP2gEc98zicIMvsjfJmCAl4+OMQZSAV+9+UwGw2JHS/W9Uiuj1yjGL62IglbygpBURR6enqwd+9eHD58GJ2dnZicnBTdG8jNtBz5nMSii3xItEMkxjds2MDqpcXHxwes3OyrFQKZE0pPT5dMf812sJM0L5hMJqSlpfkklyEFKfIhg52hoaGi80FC+BP5cI3xyIzTxMSET69/8+gQ7n+vVSadWeKkXA3F+ZkPBsCrh4f9akSgGUBrtvM2Xk+vFzun2HAoMLPhNw0bcdNrDSf11yQimRlyYEQjloiQIDxwcQEe+LCDvU4ptYIBnQXrsmPwtZIkXpPK+YXx2N0+KTq7oxGJyCkA3z8rE+cUxLO1Hq7bLVGibmtr4+kVEisN8vcZaM3nTMSiIR/h7E5wcDCcTifa29vR39/vt0GaEN7IhwxbjoyMSHrWEPiibC0Fi8WC+vp6uFwuxMfHz1o2g3weXPIZGRlBU1OTxw5AsXV8IVQiajo1NcXzDvKFvDQGq0w8cwQGwHeW0khfUoiJaRuWp0Xhx2+2+PzZSs3hkAhCuIxUm7TU3JSSds6kvD7iE4Qw1UUUCD5pHee1Wd9+Xq5ogwIhiMxYlVdG5ZLJ+0387sjd7ZO4/bxctqOSnDNFHYpDvXpRyZ/1ObGiTQZcJWqGYVgrDTIMzTAMq5FotVr9stM+FdI6C4VFQT5isztOpxNWqxUTExOzGuQk8EQ+ZE7IHyvtQCIfIsWTnJyMoqIiNDc3z1ntiIT17e3tGBoawqpVq0Q9O7yt4QlEbSE8PByVlZU8nSpP5KMxWHGsX4+O8emvHPGQLSbQt+1pWFNro/DyiaFIT1GEVB2JzNZwJWk8zeHERYTgn0eG8GnbSTvpzctEIggA4z2tCA0O9trGTbTUfnJ+HuLCg9n6zjOf9yBaFSQ53Ck110PIigKDH56VKUkmNAOsSIvi1ZTIOauWxImS7T+PDGFddozIWU+CoiiEh4cjPDycHYaenp7G6OgotFotjh49yrNej42N9ThOEYi0zumCU17zoWkaNpsNTqeTnWInkiwAUFFRMSfML0U+w8PDqK6uRnx8PMrLy32yq/U38iFptrq6OhQVFWHFihVQKpVz6mZqsVhw6NAhaLVaVFVV+UU8gPfIZWRkBAcPHkRqairWrl3rJpAo9fo3jw5h41P7cMd/mvDnL3r9uqYzAQzEhTrFah1i98IMZibvxR5/t4/iTeOTdJfYumI1j1UZ0bhmXYbXzi5ybGasCrvbJ3nP7W6fxPmF8eC+y0tKkvD1C85BWGKWl5X50YmwvvPghx0wWBw8ywMCMtcjXOvGyowT0RiFP+8bxFt1I5LHhgUrRM85Pm3DjZWZbtf6adskGof963KjKApRUVFISUmBUqnEOeecg+XLl0OlUmFoaAgHDhzAwYMH0d7ejrGxMTgcfHmj6enpMzbyOWXkQ2Z3bDYbTyKnqakJLS0t7LDTbBUJCISE4XK50NTUxLY3L1u2zOdz+RP5WK1WHDlyBKOjo6isrGSHOsk1zZVSM7FZqKioCMjvXepaaJpGa2srmpubsWrVKkl9NjHyIWm2r0qw42tCmAHw043uG/PJCg5/TSkrAkZwNM3M+N2IXcfmZXyplEtWJCFFHSrq6CkE91ixCGJX2yTvyt9vGkPLqAkrcpK9Ni5szIlAOGzom3S3PhCm6UgXXOOwESnqULdW7NvPy+UpahMyAeB27K8uKoDFTkvODfVrLRBDIC3WwMkZH2K9vmTJEpSVleHss89mpbO4zQsHDhzAu+++C71ePysL7ZycHISFhaG8vByHDh2SPHbHjh2gKIr3TxiRMQyD+++/H6mpqVCpVNi8eTM6OjoCujbgFKXdxNJsZLI/JCQEVVVVUCqVaGlpCdjpUwjiZgqcVBEgcjyBtDf7EvlwnUDFpv1nSz6k8O90OpGbm4vCwsKA1xIjD5vNhrq6OjgcDq8yOcLXawxWfNQ8uijTbP4U4P059oaKdLzESWF5WqOntweA+/darIAvZkUgbDggj+ksdtE1drXxI5b3m8Zwdn6cqKOnEJ6O9WvwU6QN+/MeE97+shYxoZRPdgvctS8vTUXVkjgM6CysNbbY8fWDRrdjU9Sh0BhtkpYOn7RNiH4WpZn+DZYSSA2YcsVzgZPNCzt37sRDDz2EyclJZGVlIS4uDps3b8batWt92g/9tdAGALVajfb2dvZn4U3mb3/7Wzz77LN4+eWXkZubi1/+8pfYunUrWlpavCqxiIFiTpE9pN1uZzfegYEBtLe3IycnB3l5eezm/umnn2LTpk0BeWAI0dLSAoVCgaioKLS0tPilIiDEoUOHWIFLMXC7wTw5gR4/fhx2ux0rVqzw+xocDgcaGhowPT0NmqaxatUqv2ZshDh48CCysrLYyEyv16O2thZxcXFYvny51w5Do9GIw4cPY3nZBvz5i2786+jsDO5OV/iygUod64nouLUfBQX8oCoNf9ov/hm7retBiUBYK/J0vWLH3liViReqB0Rfo6CAndvLAcDjXA8F4GebMjBtMuHPNTrQJx77RqEK7xx3JxTu2lyNNY3Rhq1/dPdZogCWrIR250IbDS6qlsSgulvP/nxpSVJAsjrAzFhCd3e3mz+OJ9A0ja9//euIjY2FQqHA559/jvj4eBw/ftxrw0J5eTnWrVuHP/7xj+xamZmZuPXWW0UttHfs2IHbb78der1edD2GYZCWloaf/OQn+OlPfwpgxtgyOTkZO3bs4Pn8+IpT1nBALJSbmpqg1+uxZs0anooqIQWn0zkn5ENRFMbHxzE0NITS0lL2TiMQeGpesNvtaGhogNls5nWDSa0TSOQjdDM9ePDgnBjKEZkecjPgj0wORVHYN0zjR0/t+8qk2aTA3fy9RX7cYxlIExCDGXmbhMhgRKuCYbV49hTirct4FgYltaJ+rRVZcWGSXXPk2BsrM/B89SA7+Hnx8gS83zQumgYkkQZ3rkcs0vjt54PYub0cIRGjbKrt7XYLKlMVODDiklwbAI9MxIZKSbrOYHHgmc97ec0Ll5emoiApAte8VOd20/DgxYUYn7ahbsCI0ky133I6XAQyYKpQKKBUKnHBBRfghz/8IVwuF/r7+32S0QrEQnt6ehrZ2dmgaRpr1qzBY489huXLlwOYGe7XaDTYvHkze3x0dDTKy8tx4MCB04t8tFotamtrERUVhQ0bNogWsLmpstlgamoKw8PDoCgKGzZsCChE5EIqXabT6VBfX4/o6GhUVlZ6nXoOJO1GbBa4pm9zZShH3EYnJiawdu1avyKpFs00Xu/0ftyZAurE//gqyikGqWOloo/n9vXzzz9H69IM2JkcT11z5Njnq/l1lQ+aJ3BToQvPtwe5beAfNY3hv/Uarx15YkOlDICDGhpPfWMp7ny7w23tvc39eLlO70YmStqJ+z7qcVtfStGgJI2vaMDtqktRh86KdAgC0XUD+K3WSqUSubm5Xl8TiIV2YWEhXnzxRaxcuRIGgwFPPvkkqqqq0NzcjIyMDGg0GnYN4ZrkOX9xyhoOjEYjcnJysGbNGsnIxtfBUCkwDIPBwUEcPHgQarXaa1ujrxDWfBiGQU9PD44cOYKcnByUlpb6JLfhD2m4XC40Njbi+PHjWLNmDc/fZ7aioNz3YDabUVVV5RfxvHl0CNe92gzfS+6LF/40Ddx6bo7Px/uz7r1b83FDRboPBMP41TX328uW4clvFEk+D/Bnb8SOlZrrCVVSfN22E0T3Zp1GtCNPrPtOaqg0OkKFBy7mNwz83/Jo7KjV89Z+8MMO9I0bsSIl3G9Fg8tLU7FzezlevHYlXr2hFBkxKuzpmMArBwf97nATQ6Dks1Ct1pWVlbjuuutQWlqKc889F2+99RYSExPx17/+dd7OecrIJzc316s222zIx+l0oqGhAcePH8fq1auRnJw8Z51l3HSZw+FAbW0t+vr6sG7dOr/15ny5JiL3YzKZUFVV5WbyNNvIZ3x8HDqdDmFhYVi/fr3PBK0xWPFhkwa/fK814HMvNjAAlqdE+kQWf9jTi8rcWJ+EPBkAVbkxPq376MedyI0Pxye3luOuzUs8HEnhO4XA+Wn8pJRY5EM231UZat5GLgYye7O1ONHtWKm1QxQMrlg9s4E/+Y0iyfCLq1Ag7FQj/y1cOzNWxSOH28/Lwb+aDKJksnP/UYz2tuPqfJq3/hWlKZJrE6SoQzGgs+DaHXW48bUG3PrvFjyxuxvffqkOv3hXPGLwFYE0TjEMA5PJ5LeL6WwstAmCg4OxevVqdHbOpDPI6/xdk6TyyT8uTvmcjycEBQX5JQZKYDQaUV1dDZvNxqpezzaK4oJEPkRdm2EYVFVVISYmxu91vJEGUdWOi4uTJIZAFakZhkFXVxfq6uqgVquRnJzsc176+X29M/M7bzZ5ldU/3dCsmfZZYbu6R+fz+z/Yq8f2jdk+rfvghx2oGzRgdaZakigoAJsqSvHwN9fgyW3Sihyk4eCut9uw9Y81AIDHL5MunCsoYNJkh8Zow+WlqV5VnmkGeLpJibfqRpCiDkVseLDH1J8YmTzzeQ/uerttpj7FIY3r1mewr01RhyIzVsVLzQnXvuicdUhNTUVVCoVfVwVjU9pMO/WbtRq29kWOvf28XPRrLdAYbQDAyuqIpRvfbRybVQQUqJFlIAoHXAttAmKhXVlZ6dMaJNNC7LFzc3ORkpLCW9NoNKKmpsbjmsL2bS4WhcKBFPwlDG6xXGg9PdfkYzAYMDAwgLy8PL9FTrnrSJEPTdPo6OhAf38/VqxY4dEjPZDIh9stV15ejp6eHp8J7IV9vXji09OhwMNvRZ5PCLvRLixO5NlFAzPPidlUi4FmTnrceFIu+H8v1+FXFxUgIz4ZwKjbOlszXNg5ePKOm6Snfn9lsbg5HE4SFal9ZMSovF4vA4qtoYgZuwHiZAKA1Xoj74ligCtXJ+M/taPYUTOIVw4NsvUcsXZqsvavLipARlwkJhkbQkNDUVi8Gj+vruF9XhQY3LomAiaE8qR1yPv01CBSN2AMuP4zFzUff+CvhfZDDz2EiooK5OfnQ6/X44knnkBfXx9uuukmADMkcvvtt+ORRx5BQUEB22qdlpaGyy67TPI6HnvsMRw8eBBxcXGIjIyEWq1m/50y8vFls/aHMJxOJ5qamqDT6dw65/xdy9t5JiYmYDabUVZWNqv2ZinSENpQe/vy+VvzITI5ERERqKqqQnBwsM9raAxWPLHrdCAeAHCfHZkvMAC+WZqC/9TO1Dg+bB732jrtS5s1twZjddAw2Z08wU5PZKKggI2lS7FzsMttzVv/3YKLi+PwUav2hBwN8M3VKaxRnC9ri9VnPmkdx5aiRF7XGQWgMjcGB3r0PpEJA+A/taNu1yFFbBSAV28oRWLkjJROJGODUqmUWHtG/uaP+ybc1n/p2+KETBDojA8QmIsySbsFUvPx10Jbp9Ph5ptvhkajQWxsLNauXYvq6moUF59UzLj77rthMpnwve99D3q9HmeddRY+/vhjj2n6v//971CpVCgsLERvby/MZjNMJtOMxp3f72oBoVQqfUq7GQwG1NfXQ6VSSSo4z0YMlIBs2sBMXnU2xEOuSbjh63Q61NXVIS4uzmcban8iHyI6mpOTg/z8fPYmwJfUXcOgAW/VDp9WaTZfL1VsFkaKEMQepwDexk1iLrF1A+lGG9bbEBseDKuDFq113PbvFnythD+Dc/t5uSjLTYCC6hJd98OWSdyzRonQSDWWpcfDQIfizVqNz2sLbc4B4Ild3fjd7m786qIC1rcnLFiBa3fU+UwmUsRWP2jE1uJEUb23jjETrt1Rxz52/fIQfLtQfO2ExAQwmHBb/3BtI/7fslD8o83pdv5LS5Jm1fUWSM3HbDaDYRi/az4E/lhoP/3003j66ac9rkdRFB566CE89NBDPl+DzWbDM888g69//euwWCyseabL5Vrc5OOt1ZprlMZtPRZDoDM1BKTFOScnB0FBQdDpdAGvRcAlRIZh0NfXh46ODixduhRZWVl+NS54Iw5voqPeIp+fvdWMd+pHfLqe0xGXZrnwvz7+5kAIRBih3LMhBr/er3c7VggGwH1b8/DIx/zIQ4y8GMyIdwIUfvp2q9vxd73depLQJF4vnMERE+fkv4ZCUnoWIhgzjrT3g7ZbQCFIcu1XbyjFsN4GBgxKM6IRrQqSNGojZnDrsmMkhT3FoiRPxHbX260w2Z1uagUAeMOlNAPsaLLjgnU2UaKSUstev2Yl0lUuVOaM4/iIDlNWB6YRhrKcOFQUpPpkVS+FQNJuJpMJAE5rbbcXXniBHawVKsmctmk3h8PBDqj6MpMSaNrN5XKhpaUFY2NjWL16NRISEjAwMDBngqA0TbPzNQaDAevWrZvzxgUik0Nsx8W03zwRWMOg4bQnHrJhS0UzQuIhENuIJ83in7UbUVFAcWqU2+OipEbNiHeKrSP2WqmWZ+EMDiGBV28oFR2kHDABz3xOIpogbM6Pwq7OKfFOspZx/OPQEG8j37m9HO8c68Of9rtHTAM6C1LUoZI1ILEoiUjfiBEbg5MRE5nBASBpgUBkeIRrA3AjZJoBbniteSYduGY5NmAm8iDWCHV1dQDAOpfGxcVBpVL5TEaBupgqlUqvXlyLGRdeeCH730K35UXd7SZFGKTLzOVyYcOGDT6lvwIhn+npaRw4cABmsxkbNmxgzaPmIoXHvaYDBw6wxOAv8QCeoxadTofq6mqEhYV5FB2VEgY92KPFew2BDZHNF/y9+eRu1HORMfxbrdGnORIwQPOI+0Yueh0MUDdoQN2gewux2Gt/vMG9u83TLEtJmhpfK+FHu+cXxrMT/+TY3V1TePb/3OdwKDB4uWaIdywR7Tw3Vy36eYQFK3CoVw+AL+wpvD6yDtFnI112Yh153NkcAjHVauAkWQnXBiDaxUeuhRxDbBFKSkpw9tlno7S0FFFRURgbG0NNTQ0OHDiA1tZWjI6OuqlRCxFo5BMRETFn4sqnAtx9ibyPgYEBdHR0LP60m81mY3/mpqby8/P9mqlRKpWs540vv0xSGxHTgJttCo9gYmICTqeTNX0LNKQXi3y4KUlf0nhca3CNwYqXD/bjper+RSmV40/NSUrXbC4aEcjdPJkjEdZLGIiLgkpJzNz1dptkVCM8b0VWJKq6J1A9cvImyJs7p9BQbVfbpChZkTkcbrrq2nXpeOXQsNuxNc1dKE2LwHVFQWythHTocWswJLr5pHUcT+zqdlvntcODeKWGH1WJ+epw3483WR1Pa19emgqLXbx+RiI2LiiKYru0cnJy4HK5oNfrodVq0dfXh+bmZkRFRbFRUXR0NI9sAqn5nAl2Ctx9s6mpCc8++yybOTpllOpv2s1ut6O2tha9vb0oKyvzu72Z/OK9RSw0TaOlpYW1EBCz0p5t5EPO0d0980coZVPgK4QpM9Kj393djbKyMp/02cgabx4dwsan9+HFRUo8/kKKqC5b5R45SH1CYo8zAM7NDAZOTOW/Wavx2NTNHXi8sSpTcnaHETl+VTq/4HzJipkI5sAI/ztI3Dm5v2qGAaq7tZJdZWIRC6mjcH10EiJDRIc0Y4Od6Ovrm7FG4URF7zWOiUZJW4oSRdd5+aB4VCVmhVDdrcXWP9bgxtcasPWPNXirbkRyHklqbY3RJunzwx08lYJSqUR8fDwKCgqwfv16nHXWWcjMzITdbkdrayv27t3LDp9PTU3B6XQGHPmcCWAYBrfffjv279+PoqIilJWVnR5pN5I6AoCqqirExsYGtBbgmXyIkoDBYPBoyDabyMdisaCmpoatVQEIaECUC27kQ96DxWJBZWWlz58VRVE4Pm7FL99tPWXdbP5s/rPF2/X8mRjRtNmJxx+8pED08c/7Hbyrm9nM3VdhMBMZUZjZ/F6oHsAlK5I8EtBvL1vGSr00Dk/xnn+/aQzVfeJ1mbToMN4bIakns8MputHesSnXbXPHiddwU5XPfN6L289zP/acspWITc/Fv7rdmzWE10YiCiGhXLc+Q/J4Moz65DeK8Phly1CQFOFWqyFkQjTafF1b7FqIppu/CAkJQUpKCoqLi1FVVYV169YhISEBBoMBx44dg8ViQV9fH4aHh2GxiHsFCWE2mxEeHj6rG9PFgqmpKezduxcff/wxnnrqKTz66KOnNu3mzT2T+PwcOXLEL4VlqXN5ilhGR0fR2NiItLQ0r8ZygUY+ExMTqK+vZ220yRqz9SwiNZ/x8XHU19cjPT1dNGLzhE+7pvFMtT7ga5gLSH0T/OHCQNJpFIAfnZuNP+zpc3uuIjcGD3zAN8ySSrMBwA/OysBf9g25dchxj+XO7gzrbWwnG3f9tJgwWOw0hvRW0VTSs9Vjouk8Bu7Wc1Lt0oRohE6hUr44WrNd1FV0yODw+pkrANDGMeh0DGiG4a0TFxEkmV4DZiI37syQJzK5vDQVYZZJtE46sGVNHhIjQ/HKoUHJtYmqde2AEatnqVxNQFEUIiIiEBERgczMTNA0jb1790KlUmFkZATt7e0ICwtDXFwc4uLiEBMTIzoDdCak3QhomkZFRQX6+vqQmTnjErtoaz52u50N58vLywMqxAsh1nRALK4HBwexYsUKn7SP/I18iIxNT08PiouLWR8gQrxzQT4GgwEajQbLly/nuaX6Ao3Bit8fEDfP8g9iiZz5g1gb9IOXFOD+9zs8HicEA3HlAQrAgW6922M3laqRmCyeDomLVOH68gx2w1NQwKrkUNRqbLzjaAYwWR3YWpwIk93Jq69w6yXeakDcutPt5+WKthGT98gdVuW2KHMjHE9DpS8fHHI7tiApAqlRQaKfHVfx4ccbkhCpcGDv0UY8VMOAqNFxoyqh4gAxfONGOlLRKXk/b9WN4IHPtGAAvNIwo/4g1m5Nohuunw+3HjSXICntrKwshIeHw+l0svWirq4uWCwWREVFIS4uDrGxsYiOjoZCoTij0m4xMTF49NFH8de//hVmsxkxMTGLM+2m1Wqxf/9+KJVKhIWFzQnxAO4Ri9VqxaFDhzA5OYnKykqfRff8iXyIt8bw8DDKy8t5BnQkMplN/cjhcGBkZAQWiwUVFRV+EY/GYMWHjRr86+jgHKXaFjY9ILxkBnAjHrHjPIH7DhiJc/yt1ojHBLM7BI9+3IkdNTOf5w0V6Xj1hlLUCYiHnGf4eAN27T+CIKsBf7tyKV64pgSv3lDKq5eQaxAL+IXpvGc+70F1t9ZjZ5nVQWNddoxHW+zb/t3CSwt6Sl9d81IdjgyacONKFe/4r5Uk8aKbmOhoLF++HMl5JRDz5ukYGBGNqjzJ6ZD/FxIVOZw7zPrqDaW4a/MSvHpDKUsuQmITdrvNFUijE7nBDAoKQkJCApYuXYqKigpUVlYiPT0dFosFzc3NeOedd7Blyxa89957cLlcAaXl/bHQ/vvf/46zzz6bbSPfvHmz2/E33HCDm07btm3bfL4emqYxPDyM//73v7jqqqvw4x//eHGl3bgOoEuXLoVarWb76+cC3IhlfHwcDQ0NbArMn8iDrONt6MxgMKC2thZqtVrU30ehUMzKDoEoLhBfeH8mod88OjRT3wnozL5jLrrKTmLuIiux6/LnOrlXIvxv8v+v1AwhPSZMdN3rKzJgj1Lizl19YBgzKIzimqUKxKvDRY+/9dwc/GFPr1t0ISaH42muh9sppgpReI2SyFBpekyYW/qKHPvsgXE8c74aO7evFFU0IFFS1ZI4ZMeHi0ZV73VYeWs++GEHlscrkRkTJXo8Mb/jpsqkyFSq203qeLFut9mA3FxKpcHDwsKQmpqK1NRUVp/y7LPPxuuvvw6NRoO0tDRs3rwZF1xwAb71rW95nfvx10J7z549uPrqq1FVVYWwsDA8/vjj2LJlC5qbm3k3y9u2bcNLL73E/uzL/BHZIxsbG/GTn/wEV1xxBTZt2gSbzbZ40m42mw0NDQ2wWCxYv349oqOj2S6RuQKR6+no6EBvby8vBeYPyJdIKl3GFTj11hIeqB3C8PAwmpubkZubi+DgYIyPj3t/0QloDFbc/978Ew8gvaH7RkpCspkj4qGAZ68sxq3/bvHpmqRkbxgA923Lh81JIySIwqMfu2uoibVaUwC2FCXMbNBshEPhtQ4GV68Q36AyYkLd2p+35YTgwx672zkHdBasy44RNUjj1k88iZYSBQLupu3p2DEzjfNODH5KKRpIyeNctz4DO2oG3Y7/+HAbSuMZfLckHC82WnjXQdxWuWQiJdXDTRdyoyGp433pdvMHhHx8ucGlKApZWVn45S9/CYvFArvdjquvvhq7du3Cyy+/jG9/+9te13jqqadw8803syKizz33HD744AO8+OKLohbar732Gu/n559/Hv/973+xe/duXHfddezjoaGhPmeHCAj59PT0QKlU4h//+Af73KIgn8nJSTQ0NCA2NharV69m9cyIvM5sZC24oCgKHR0dYBgGFRUVAWsmkS+RGPm4XC40Nzf77AbqL/kQmZzh4WHWDnxwcNBnUdBerRlak110M11I+Hb6AJtLTvwPw0gQB3PyOEbwsNhmRCIAMdmbRz/u9Kg6wF2Hu9mKNRIwDPB645TbaykAYz1tCFFF4qkL0xASHolIxoKPmt1vOITFdG8yNMTcjStWStYRbtqejk2NOrmVSCkaeJLHEYuqXmgF7jk/G99YpcCK+HF0j08hPFiJx2vHRMlEOO/jidgIQXuqB80VXC4X2/DkD6anp5GQkICNGzdi48aNPr0mUAttLsxmMxwOh9vetWfPHiQlJSE2NhabNm3CI4884ibgLAR5z0uWLMEFF1yAlpYWVqz0lJNPZ2cnenp6UFhYiMzMTB7JeNrk/cXk5CSmpqagVquxbt06nwQ7pcCt1XBTaSaTCbW1tQgODmZDWF/W8pV8rFYr6uvr4XQ6UVlZyaoV+KLt9sK+Xjyxq5PnaXKqMLepOPc1GZz8QSpiEUY9BBW5Maju1rM/X7JiRlAyMdLmm+wN5T5bxGBGt61pZAovHxzCjppBj2QlTOd9rSQJTzWMgWZMoGDCVXk0ViQo8IaIv9nt5+Wy9Q8yhLkuOwYA8Ltd7gKjUkOlUpu22LE/XB+LhHAl75xiQ5/c9BvZ4L0d//hn/di0vRznZ2Vho8uFz1uGwNT2ul3XO8f68PXSTFxemgq1aQhOVSxK82fsG8S63bh+RYQITXYn+ietaBwO3DpBDIHuX2az2e9ut0AstIX42c9+xqb6CLZt24bLL78cubm56Orqwr333osLL7wQBw4c8PjeyFA/RVFob2/H9u3bcdtttyEqKurUkk9TUxMmJiZQXl4Otdr9l82dzQmUfLh1pMjISKSmps6KeADxWo1Go0FTUxMyMjKwdOlSn+9yfCUfonYdHx+P5cuX8z4Pb2s8L/DfOcVBj9+pOF/IipmDepCCmulu4+L9pjGcnR934hzermFGSPTRj7tEW6fvfqeNR5CeyOrJbyxDXEQIWz/hNiD8q1uBlHg1GLibm+VGK/Df2hE89BH/br5qSRxerhkSfc+ZsTME5S0akTrWPDGEj9r1+Ns7NbxzPn7ZMtz1Nn/DI1GHMP3n7fgUdSiUSiVWZCdDQfW6keif9mvw5/0juHFlOMri7MhMCEZyVAgoiuLbO5z4vLl+RZeXpuIPe3rwbuNJBYhLS5Lw6KXShnv+IFAjuYWy0ObiN7/5Dd544w3s2bOHd/N81VVXsf9dUlKClStXIi8vD3v27MH555/vdV2SraEoCj/60Y8QGxt7aslnyZIlWLp0qSQZkE3e6XQiJCTE7/XtdjsaGhpgMpmwfv169Pb2zpmVNul4C6RVW7iOp2vyRSZHag2NwYpj/frTxPhttnM+nonHF2ITi5JoxnfZG4oCzi1IQMPQFG8ju2RFEix2WrRYL0VWqzKiPdZPbFCKzvn093ThyTr+e3rwww48ftky0Wu/rnwmOjjUq+dFSW/VjfBIkRKkpLjCngd7HfjrsWneOR/4sAOv3VAqmsYMC1aIdpm9KnG8UE7Hk0r3i40WZJcxGG3pwq6GfixNjcGGjES8//3VaBm18maqyHnjIoJ5vy9gxrn0qrK0OYmAAr15DmTOZzYW2k8++SR+85vfYNeuXVi5cqXHY5csWYKEhAR0dnZ6JB9CupdffjkyMjLgcrlgNptPvZ9PRESExzZjiqICVqPW6XSor69HdHQ0a5g2l26mSqUSVqsVLS0tcDgcqKysDOguxRP5uFwuNDU1QavVoqysTFKtQKxj7vl9vXjy085THuXMF/xN3c2O2E5GKgoANKRrSePT7hpq7zfNbGRiG6sUWQEzhCDVkfbnQzqckxmCfYN2XvSQFh0Kpq6JdyzNAJ19I27rUADiwoPYOhA3SuK2LJP3VrUkzk1TDQBax23unYPMjAeRWE1FjIhJG7jY8WJRkieduL2jSuwaoEAzNKh6La4vNmJ1tBWjDvdOQpoB9nXqIIbZOJdyESj5BJJ241poE4dRYqEt5e0DAL/97W/x6KOPYufOnSgrK/N6nsHBQUxOTko6LJN025///Gfs3LkTN9xwAzZt2oTo6Gj2mFPeau0NgVhpE/FRoSrCXKlREzQ0NCAxMRFr164NOJUnRT5msxm1tbUICgqSNMjjrsGt+Zw+NtfeIUUyYsOkgHRn2sn1Tg44SsFTd9tNpZFwMgqkpSa6dbcxAL7s0EpurJesSBIlGSFZvdc4xlMikOoy2zdoZ4dGw4IVsNhpSbL66zEDKpKBmtGZ96+ggB9uSHdTtSZRkliUJtWy7K6pcPJVwuYCUo+SinDE0n/CJgnSTr6lKBG/293tts4n/TTnCoBXWp248ntlSDQYQB3pcYsW12WE41/H3K9+Ns6lXMzGQjuQG1p/LbQff/xx3H///Xj99deRk5MDjWZGjSMyMhKRkZGYnp7Ggw8+iCuuuAIpKSno6urC3Xffjfz8fGzdulX0GkjEU1xcjEOHDuEf//gHXn31VRQVFWHDhg0oKSk59Q0H3uCrmylw0uNHyhdnLiIfhmHQ29sLu92OrKwsFBUVzVoUVHhNY2NjaGho8Fkmh0tgDYOGRUc8/kQpYvMyYrA7GfFZHS8n8oV4fr4170QnGz8dRgH4e930zH/XG0XP/9y+ftF0WFiwQjQiOjs/TnSjZzib7ftNY/j5ifQcF4TUBnQWn9qnD40Bz39rGca0eoS7TOgcHQDNKNzWNFicfrUsF8a6byMUTvoTcVN0wIxcjq8pPam0o1S3mlSjxKiJxrq8TDxwcRDv+O+sCEPoZAcqkhU4yMlUzda5lHd+H5X0uSAW2oF05Pprof2Xv/wFdrsd3/zmN3nr/OpXv8IDDzwApVKJhoYGvPzyy9Dr9UhLS8OWLVvw8MMPe7wpZhiG7dQ7dOgQdu3ahb179+LgwYPIzc1d/OTjzc2UwGAwoK6uDhEREaiqqhKtESmVStjtdpFX+wYuuYWHhyM+Pn7WLeDcwVeGYdDZ2Yne3l6/ZHII+SzU4Ki/8Jl4RIrvUnjkY3GCnc17p3ByNkdBARcVJ+Cj1glesZ97HgrSUZIvrdVSG70QNAPEqoJ9rp94IisqOBSXrC8EACzTmfGnliNu537k405cuCwWO9t1Xjf1T1rHsSSIwfb1sfjTIR37uTxw8YxuHKklETIRqhCQD7NqifhIQlacStR4z1M7+cs1g27kL3V8ijoULpcLK1cacLBDg2P9OqSH2LAidQpdXV2sPcJsPHVmE/kEqu3mj4V2b2+vx7VUKhV27tzp9zVw98b169ezjqbV1dV4++23T/+0G3eg0xcr7UAjH6ImEB4ejqqqKhw7dmxO3UwdDgfbHOHvDJJCocCkhcb9Xy4+4vEn6vFEPP4Qky/n55LDFatSUJgSwdusaQb4sGUc1xcFYUer+HeGAfD9DVl4bl+/2+O+tlY/+nEnT/BTGPmRa12VEe2Wtjs7M1SyfiJFVr4W73e26fCTUsCmjEBEVCQK09R45ZA7ST6xqxsUgO3lcfjk1pOOocT2QJiik7J2IAZxwnqSKASvF0ZWl2bTeK9fITm7Izx+3OREv1GBNUV5uKg8FHa7HVqtFlqtFs3NzXA6naxPT1xcnN9K07Op+Zzu2m5jY2OsqgLDMGAYBlVVVaiqqlr8kY+ntJvT6URzczO0Wu28WmkPDQ2hpaUFubm5yMvL86qQ7Q8UCgXMZjOqq6sRGRkpKsPjCRqDFa1DRnQZGNDMqZjgIfe60s+KwdvdvvDYq8tS8dphdytvso7Yxk6UDMSGIl+9oRSftI7j5YNDeLNOIyG3Q0kSD1lneVqk6CavszjwSg1fiFMsUmLgLvgpbPuVqg3tHbDh+yI1HimyumRFku8tzgAGqUS8cXQcDEygoEFFqgI1GkaUPP50SItL1wPrsmMkNdM8qQo0DRtx02sNomTl/nuRlsD5b+0I3u2j2M/79vNyPAqFSgmLpqSkICUlhU1/abVaTE5OoqurC8HBwTwy8taJGwj50DQ9q8jnVIOkGn/wgx8gODgYN954I84//3w2gnQ6nYtTWJQLKcKYmprCgQMHYLPZUFVVNS9W2kStoK2tDaWlpTy30blyM7Varejv70dGRgbWrFnjF/G8eXQI5z29D7f8qxUvtc/6UgKE/4RHAbhtY67PxzIMJInnR+dm45azMkWtn3GiJnLHJneDtSN9ejdy8A8zm/Bt/27BBQXRrNAlidAe/dh9oJMB8L0NWW4rkdoNaXMWqw3VDRo8dogJhTbF1nmvcUyUFNJjwtjXEygo4I3acc7nQ6FGw+DRc2NwZZ7775zUYQBpjTUpD50ZRWv3xgdvhm8aow2HevWsEKjGaMNDH3VAqJitMdrcjiXHexMWpSgKkZGRyMrKQmlpKc4++2wUFRUhJCQE/f392LdvHw4fPozOzk5otVrRPSGQIVOTyQQAAauwnGoQkjn//PPR19eHW2+9Fddccw3++c9/YmRkBEFBQYs/7SZW8yGRSE5Ojl/20/6Qj9lsRl1dHSiKQlVVFVQqvt7TXLiZtrW1wWAwICUlBXl5eX69XmOwCuo78xP1+NvS7AtmNoUen84ndW4i1y/mwcN9LZnREa759Ge9fg20ukdYJze4T48bcHuJCzZlBP5UZxV59ck1zimIw9/297vd+ZvsThzq1UNrdpc+8tQEkBmrwoDO4pMnD8N5nru2WIuzVI0nOSMLN68Iw3//dIh/LWBgnRjAaJgNaeoI0WsVUxXIjFVJkpWUFpxU+3VGjEp0nbkUFlUqlWzEA4CXomtpaWFTdCQyIuMk/txUAifJ53RPu/3whz/ExRdfjI8++gj/+c9/8MADDyAvLw/f/OY3T4+0G9nkXS4XWltbMTo6yuqaBbqWJxDF69TUVEljudlEPlarFXV1dXC5XEhNTfVJHZaA6LN1j5nmob7jnkI7pyAeezsnfU6RzcUV+HScH9cjdih5p8JCtlSajqgo6ybG8PyJjjcCGoArNhtRjA2AOPmQDT0xMtStzkIzJ+V+fK0NKSjg+2tnurGErqNSnjwUTpI297rECMyTwZtQQ40C8P2yaAQHh2BnbTciGQuuK1LhlVbHScJm3FUFuBu8v1pwYu3XUkOq8yksShxMhSk6rVaL7u5uBAUFQaFQQK1Ww263+zwsbzabERwc7NfesFiRnZ2NW265BTfddBPeffdd/OMf/8AvfvELUMxsPZxnAYZhvHaftbe3w+l0IicnB3V1dVAqlSgtLfVJN02IyclJNDU14dxzz5W8Hl+7zZqamhASEoKlS5f6dQ1arRZ1dXVISEjA8uXL0dHRAZqmWbE9T3jz6BDuf69VssYxHxArgM/nuWZ7Hl9rSWLHUQA+ubXcreayKj0KjcNT7PFS1yn2WVEAqpJp7B9VsOe9/8ICLE2OcLM84L5GSBLktaQ2ND4yBKuLQXhsklu9hqwh5VwqjBiqlsTxNnNyLjGDN1I/eatuBA98cJL0LsiPwu6uKfbYH29IQn6EHcf69XihlX9To6CAndvLAZxsMOBGMsL3vHN7OY+oDvXqceNrDW7v+cVrV6Jr1IjHPu1hZ5nEIjhy7LrsmHk1k3O5XDAYDGhvbwdN07DZbIiMjGQjp+joaMl0XH19Pb72ta9Bq9We9jbaNE27qdR0dnaeHmk3vV6PAwcO+K2bJoSnaMVut6O+vp41ZfOWaw3EzZQMv3JFVH2dYyI2CGJtv3MH98hnIe9MJFNsPl4HiVCah6fw130DbiTAPYfUEOkXHZNutZL6IXelaTHyIp8et5OObOAE5M77OyvFfXvIOmJddNy5ngd2T544n1bSm0jYyJCiDuXJ5nizzV6RFoVXbyh1s5gWa5f+tHOK99rf7x/Dzu3lCI0344XWRre1/7CzEe93mL03PoikwaQilrBgBb62PB5h+h6kF670qFEnlQKcS0VrkqJTqVRISkpCQkKCW4ouJiaGJaOIiAh2T5yenj7tU27AyWYLQjxjY2NoamrC8ePHF3fDAU3TGB8fh16vR0lJiWQKzFdIpd30ej2qq6tZNQFfinz+1HycTicaGhrQ09ODsrIynj6bN203jcGKgz1a/PmLbi939HNBE77fYS3kvditG3O8XgOFkz4vz+0bmHmMU9S+aUOmT5/QIx93eo2cGAC/vWwZ7tq8RPK5J79RhMcvW4bU6FBRknqxwSz5GZLakFihncz1cNNsgLjTKbeRQczpk6ToiCqC8FxNw0Zcu6MOT+zqxrU76vBW3UzTh5S7qPDcAzoLax4nXPvd42a3tFlsMC3ZXMCFsGmBrHHtjjr8r3Ec8SoF+56Fx3KFRbf+sQZv1Y0gRR3KHj8fIMKiJEVXXFyMDRs2oKysDHFxcdDpdDhy5Aj279+PlpYWvPbaaxgYGEBkZGRAUY8/LqYA8Oabb2LZsmUICwtDSUkJPvzwQ97zDMPg/vvvR2pqKlQqFTZv3oyODnd1ETEQGbJPPvkEDz30EP7f//t/+MEPfoBHH3301JOP1IdrNptRU1MDq9WK6OhoN4nwQEDIh2QaSTRy+PBh5OTkoLS01GeZHF8jH5PJhIMHD8JqtaKqqspNn80T+ZButut3HMO/jg57OdPChua+Up1wMwkEz+7pdXuMAnBTVSbveoT202CA+7bl4fbzcvD8/gHRtQPJaJBW5i1FiaKb5bDBirvfacVdb7fh7hMND57W4v0M4MaV4YjHNO69INetK6y23yhKZr84ocogXJu7cUtFOESDbTYdaGLvi1sf4q593foMt+NpBmhoace3Cyi3zj1CnNxutctLU/HqDaW890wzMxYMBjv/4i4vTcXO7eV48htFAOMuujrXttlCiLVac7voVq1ahXPOOQfFxcVwuVx46qmn8P3vfx99fX245557sHv3blit0o0sXBAX01/96lc4duwYVq1aha1bt2JsbEz0+Orqalx99dW48cYbUVtbi8suuwyXXXYZmppO6gP+9re/xbPPPovnnnsONTU1iIiIwNatW71ek91ux5133oktW7bgxz/+Md58800kJCTgF7/4BRobGxdnw8HY2BgaGxuRmpoKtVqNwUH3nG0gUCqV7KATaaP2dUZICF8iHyKT4yldKEU+DYOGeVUrWKia0f8rz8DLB33//VEn/se7TA7wfPWA22PCnx8RcRIlIFI6QiUA8hxJCV2yIgnvNY3xJG+qu7W4vDTVbZbm/MJ43qZNUnGefH6IdYLF7gLtsGJ0Uo/mniEk2afxmw0q2IKjMGwJYusvYtd6bkECgpUKt/oF19tHSvftrrdb8cDFM2Kd3jrQPmkdx5YiQQcagAuWRuPTDoPoYOflpakoSIpg03eJkaGiqbBLzl2PMMaKldkaHO3TIT3Eigz7AJ77ZBx/OaJ3q8tY7LTb75VmgAkb5SaAmqIORX94sOjxc22bLYQvcz4KhYJNvx09ehR/+MMf8Morr2BsbAzXX389tFot6uvrUVBQ4HEdf11Mf//732Pbtm246667AAAPP/wwPv30U/zxj3/Ec889B4Zh8Mwzz+C+++7D17/+dQDAK6+8guTkZLzzzjs8qwUhtFotPvroI2zcuBEVFRU477zzkJV1ctRgUZEPTdPo6OhAf38/VqxYgdTUVIyNjc2pEjUAGI1GtmHAm2inp7WkIhZu4wJ5H1IQkk/DoAEvVvfh4+axeSWHharlSBGPFCEwkHjCx9eLQYp4bj8vFzESSgCku41slu8JJPeJFL+wPrSrbVKUBL1ZJwDuA4/3bS3AuZkhOD4whvs/H5V8H2Sj59YviNDoSwcGeE0DYrpvJP22c3s5SzxSRPXErm78bnc3qyw9oLNgvLsFFasycf2GXLf6kNj7+tVFBaLt06kxKrxVp8eDu8ZPPB6EG1WReP7whFu0sjpVhczYMNHfXd8URNUVFso2W4hA5nxCQkKQm5uLF198EQzDoLW11es4RiAupgcOHMCdd97Je2zr1q145513AAA9PT3QaDQ8Y7no6GiUl5fjwIEDHsknJSUF//rXv3j2DFxX11NOPhRFgWEY1qWT2BOQyd65tkEAgMOHDyMrKwsFBQUB15CkIh/iIWQ2m31qXOCSz8/easY79e7DlO5wbwxYDCiMC8JxrdMnUvCX/LgNAwoK+PaqeLxW577R+0JKFIAbKzPYTZkbmSgo4PzCBLbdeiZNlC56xyzmhkp+M0KS8RSZAOIDj4/s7MayG0oREp8BBlq3c12RC6zPjUMwZcewzoy02HCkqEMlO8doxrPum3AeRkqglBDAzu3lWJcdg+oh4INWHZ78wn2WRmqQc+f2crdmBrFjucTDPf+nB2qxIikU31+jxl+PGdnz3rw+CX+r8d1mez5ss4UIROGAq2hNUZRP3bCBuJhqNBrR44myNfl/T8eIoa+vD//+97/ZiIooHnA/h1NOPsDMh1ZfX4+kpCQUFxfzLnCuyIemabS3z8gAFBYW8sK/QCAW+RiNRtTW1iIqKspnmRxCYg2DBh+JB1gMxCPsHgPgM/EEkvLjHk8zkCSem5a58Pc2pdvjpHVZQQE3VmXiheoBfmqMAZ78RhHaR6fw9+qT0RrNAC/XDPnVwn1+YTw+bZtkH7tkRRK7wQk7zUh6SGq49JqX6nDHphzRO/ZQlQr3fKYFAy0o9OK7JWE4pyARD344KHmtNCOt+yachyHdcrUDRlHfHJKu0lpoPHmCtMhzZMOXSt+JDX6KDYlKkfkl55YjxGVGglaLnDALBvVW5CZGYtJu9phaWwjbbCECcTI9naV1AOD48eP485//jLvuuktS1fuUk09nZye6urpQVFSEjAz3QmRQUJDPlgpS4A51BgUFuVktBAJh5ENUF7yJm4qtwzAMjvbrZ31Ns4fvEZVkykwEQqKai5Sf1PkLCouwlR7FzuMG9tEN6cEoSIrEjtqZusHz+wfcXs8A0FsceL7aPU3IALi+PAOv1Ihv6goRYuOCmMkJO80e+KCDJUUK4qTMYEYiRjhzc3WxCq83WzjrUXipyQZVkOeBYJLq81XRwOqgJX1zSLpKYxYXNx3QWSRTXWKDn1JDomLzRmmx4QDCkZCQAHWKDVEjekRRVrg0GjfPJrHU2vP7+1Hdo2d/nkvbbN7nQNNgGGZWkY+vCMTFNCUlxePx5P9HR0d55QMy6C8Fs9mMvr4+bNq0CUqlElFRUaw/kFqtRmRk5KknH5VK5TE9xe1QC6TtcHJyEvX19UhMTERxcTG+/PLLOYmkSORDZHJGRkawevVqJCQk+LXOhNmJ5nEH8gvDZ31Ns8f8RFTFqVFoHnGflXE7uw+NBt5w9/+OC94Fhf1DTuwf0nslPymbBgUFXLMuHVuKEvDtl+rcnruxMgPPVw9KEhvNiJvMMeBEQpBuTCAzNzu3l6N+0AgGM0ruYueJSU6HghK2i8+srqCAn23KQnJUiFt9aEhvlayHeEtXJYa5zz2RtnCx13oiOrHzXF6aiguXJ4nO4gjrSbdVJuC7JRRearKyzRD/l+tCX2sdpuPjcWAUeGLPsNtnN5e22bz3dSI74i/5mM1mVg3aVwTiYlpZWYndu3fj9ttvZx/79NNPUVlZCQDIzc1FSkoKdu/ezZKN0WhETU0NfvCDH0heS2lpKe6//36YTCYYDAZMTU1Bq9Wiv78f09PTMJvNp558iK+3FMgvzd+iHcMw6OnpQVdXF5YtW4aMjAx2qHOurBCcTicOHToEmqZRWVmJ8HD/COTNo0P45XutM7WG2npW5oQgNkwBndWFxZBmmw18IR4AuPXcHNG2ailIdcaJRQ/ir/fuagrM3HkDgMVO49oVEXi92eQ2ROqN2MRM5oRgIN2YQGwKuDURsfXEZHi2n52FdJULNvMUJkb78N7ufuSnJSAuLg494048+km3aO1L2LEmbGZoHDbOWDrQNO45Pxe/2d3HS71du6OOJQ+hPI5Yt1tYsMLj0Kfw9yxWI3r2wAR+vzkaOzeuYteIVymg1WrRMTiOJ/ZoJX8Hc2WbzQXZ2/xNu01PT2PJEvc5Mm/w18X0xz/+Mc4991z87ne/w8UXX4w33ngDR44cwd/+9jcAM/Wm22+/HY888ggKCgqQm5uLX/7yl0hLS2MJTgzZ2dl44IEHPF7rKScfbyBzN06n02fycTgcaGxsxNTUFNavX8/zDZ+rGpLJZILVakV8fLxbncoXCIVBSZqCC52VxulOPP5Aini4d9XcDZcBfMrhidWnKAA/3ZiFJ/a4z/+QDZgCcMemHESrgtjuKQrA9aXROGdFNjJjVaJq09w1pN6L2DVJNSYQ8pOSnxF2rkkpGzy4bxg0MxMB/aDMidzJfjzypQVcFWhS+1qVoXYrxEs1M1CgcO8FM9I/XMkgYbGfu56Yvh2XrDxFN96EQcfMDJbh5OcfHByM5ORk9FlCRRs3CLIjXHA4HH6LgHoCt7vLHwTq5eOvi2lVVRVef/113Hfffbj33ntRUFCAd955BytWrGCPufvuu2EymfC9730Per0eZ511Fj7++OOAJM64OOXk4y2VRlEUKIrymTBI0Z944wiF/GZLPmQw9fjx41AoFFixYoVf6cD5FQadPQJpBggE24qT8EmreyeVGK4oTcF/ajUzaSof1va1xrQ6Nx7UHncZnntWK6CZdkIVHo7MECt+8iF/bueVegOuOXsmEnngA/dJb6n5IQYn53qkfHtIUdxgceDpz2bO+8znPdCa3BsSGHiW4SEWDWIRwl+O6PH4ZcvAoM1tTcPYIEaD49E1Goa85GjJjryTr6Hw6119ePwylcdiP3f2hsz+eCIrqWv3JgzaY3DiUR9brQk2ZqsQatJg375uREVFIT4+HnFxcVCr1bPSVgvUSG56enpBXEwB4Morr8SVV14puR5FUXjooYfw0EMPBXQ9Ujjl5OMNJFXmC2EMDg6itbXVY9F/NuTjdDrR1NQEnU6HkpISNDY2+vXFFAqDLkbMF/EISS0sWOkT8VAA/lun8d4+zYk0fHkPDICWkWnR56wx2Xi5tgc0YwXg3k5KMzMacI/t7HQ7F1es89c7u9w2RjLXozHaRH17bj3hc/TM5728TVms447I8IhZNPiibCBl0zBgUuCRf/eydagfrovG/5Vlotcg3fFHvtNSdSOx6CUjxjNZebp20WFXCrh2pRr/qDf61GpNAdhSlIDrKzLYdJvVamX11wYGZqLi2NhYloz8vdsPZMYHmIl8TuduNyHEavaLnnwAz26mAN9qwVvRP1DyMZlMqK2tZQdTSbOBr40QDYMGtr4DLKxg52KA8P1KtZUL26KvW5+OHTVD3tcP4AMVazBgADzzWY/X349Uc8JvL1uGVRnR6Nda3Lq0bj8vF3WDBvZYqU01KSpENMrhdtxRAG6rTERJmlqyGcCbsoFYfUishvWXIwakK41wuWhQoCSHXcW66IRK2uR9eupu4xKnVLQiNuyaGavC/objPrdaiwmJhoWFIS0tDWlpaWAYBlNTU5icnMTIyAja29uhUqlYIoqJifFKLIFGPoF0uy1GkP2RzHMyDMOm/U45+fiycXsiDK7p24YNG7zemQRCPmIyOcQKwtudjcZgxcsH+/Fidb/kMTJmQFHAAxcVoCApAl92TCI+MgTLU6PwMsdxlMDXuRt2bYjbFIgdJ3aIL514CgoY0llx9zttnM08ByvS1GgaNuJpAamJneuJXd2ibdcKCogLD+J3x3H+doTzQ8JIQ0rZQFgfkoo0tBE5ODs7Arcqh/HHg1pwW3YoMLh3yxLRzR0APmkZF11TqrtNmKIT1oe4a3CHXQEgJVwh+tmFBZ+scwjrT1KgKApqtRpqtRq5ublwOBzQ6XTQarVob2+H3W5HdHQ0S0ZcVWqCQGZ8iC/Q6epiSkCIZ//+/YiPj8eyZcvYz4dhmFNPPr5AzM0UOEkKaWlpPite+0M+DMOgo6MDfX19KCkp4fXKk3N5urPhptlkeAfDAEf7DTyfGDFQkNZkEwNp702PDfWq43b7eTk8bTbyvDcNOArAtkwKz+zpYQv4M/WaXrx6Q6mkc6oYiZJ0F/+6+BEJAPy+egzrlnqeHyLX4UnZgEs8UlHSk7t78NQJcth5awF6J6bhtJqgmdBBYZpE7FQnamsnEB8fj/j4eJRlRePteo0oaZD3nRmrwrrsGLdIRCxFt3N7OT5pHfc47AoAsWEUtpfH4U+HtLz3z21mCBTBwcFISkpCUlISGIaB2Wx2M46Li4tjySg4ODjgyOdMSLuRG/O33noLn332Ga688kps3boVa9euBUVRpwf5CAmDSwretNOE8NUKgevvw5X74V4TAMm27fkWBj0TILbJvdsorr7LxczmTIlGDryBz8pMOGgaLx8cwo6aQcmI4vdXFsPmYMCAQWlGNDrHzW6NABsLEkRrOGTGhwHwUb976zbNAG8dcE8FkffxxGXLMDpld9tUyXOkOUEqIvlQRP6GgfiskJSyQdOwETe91uAxSiJrkEijYkk8gHjYbMn44LP9UKYWAbBAp9Ohu7sb064gPHDQXfiTnJPbxk0iEY3Rho9bxiTleDwNu5JIyWJyYtvSGFQWZ3ltZpgNKIpCREQEIiIikJmZCZqmodfrodVq0dfXh+bmZkRFRSEkJIRN0fsTAZ0JaTeyR1511VVgGAbvvPMO3n//fZx77rnYsmXL6UM+pOZjs9lQX18Pm80mSgq+rOWNfAwGA2praxEdHY2qqipRmwWpLjySZnupul8mHi+YTUQoVnOhMEM4z5+QziGq19yoQFgUpxngthMabeR5IUgjgJiC9QsHBr121f2nzSz6OKmTABDdVMlzezom0DdhESXbVw97s9rgrydMc3HrUoB/sjoA8Hb9KB47pgRz7DhLKl8/ewU+bx4Cg16367jt7DSU5yXB6qChMdpERVWFIOdclx0jmqbjtn9TAH5cFYSSXLXXZoa5BFeVGpjZp7RaLQYHBzE9PY29e/fyGhdUKmkxU5fLBYvFctpHPgTr1q3DunXr0NvbizfeeAPvvPMOvvjii1NPPr66mbpcLuh0OtTV1SE2NhZr1qzx2XuHC6VSCZtN2r+DdMzl5eUhNzfX4/UJB1bfPDr0lY925qNV29d6DYMZwvFEBgxmhln/sKdX9Dix15DuNl8UrAHx+pCw/ZsCg2uWKmEY6QUdqsa169Lwj8PD7HzR7efl4qPmMTz1WY/U2/UL3EiDWx8Sa+EmqTivsjpGG369q5eXZiTRxYqcZCgo9/Rl/8AQ/rB3RmFAQQG/2LIE5yxNlCQe4TnFakpcC3AGM4OmrxZmeG1mmE+EhoYiNTUVNpsNKpUK2dnZmJycxOjoKI4fP46wsDBe4wJ3L5uenunCPN1rPgRkdionJwf33HMPbrnlFvzmN7859WZyvkChUGBychJHjhxBbm4uVq1aFRDxANJWCDRNo7m5Ge3t7Vi9erVP+mzcFJ5waPSrivkgnh9uSMXNHOM4T8f6cv5n94jXXzxBzOFULFJSUMC9W92l7xkAv9iWj2+sSsbmpfF44MJ85Gak4vcH9fi/1zrwyqFhXjPBU5/1zBnxAMCgzirqZEpauIXvQcoIjktintqgAYga1L3bd7JbjmaAR3Z24b9fHPNIPELlaa7zqNQ1kGYGqWtfKBA9yaioKOTk5GDNmjU4++yzkZ+fz5YP9u7di2PHjqG3txfHjx/H1NSMIshsIx+tVotrrrkGarUaMTExuPHGG1likzr+1ltvRWFhIVQqFbKysnDbbbfBYDDwjiNZH+6/N954Q3RNhmEQHBwMk8mEXbt24bHHHsOPfvQjfPnll6c+8vEGp9MJvV4Pm82GsrIyNydQfyGWdrNaraitrQXDMKiqqvIYEgvX0hisaNNpUddv+MoTz1xAGCGsTw3Gn/ePiEcYEKhVn0i5SdWB/LkGXyItBQUsTYpA26iJfWxNprsyAAE3Vbjr+KToMfOF56sHsDQ5wmsLt5isDtcIjlgf+Ov5I1W3YkAhPi4eFPizXBSAx76Wh7KcBMnPU2O0QWu2S0Y4Ys0MCw2xhoOgoCAkJiYiMTERAHiNC9deey0mJyehUCjwxhtvYOvWrQG7OF9zzTUYGRnBp59+CofDge985zv43ve+h9dff130+OHhYQwPD+PJJ59EcXEx+vr6cMstt2B4eBj/+c9/eMe+9NJL2LZtG/uzlFgzRVF48MEHUV1djampKVitVqxZswa//OUvQTFMIBMScwupNNj09DRqa2vhcrkQFxfHMyUKFENDQxgaGsL69esBnBQeTUpKQlFRkV+dKY+9sQf/aHPK3WxzBCHxeDt2e3kcpuhg7Dg8yj4mfC0F4N5t3jvViEact864+UgrLhTu25aPx3Z2um3UO7eXY3za5pMRHLcZQUEBWwtjsbNNB7G2G7I2V62AmyIjx7x6Qyk6xky881xfHILV0RaEh4fz0lOkaM+9Lt7vD8Cd56bi+rPcHT+F7qYLgdbWVoSGhvqs02az2fDCCy/ggQcewPLly1FbW4uVK1fiL3/5CyoqKvw6b3FxMQ4fPoyysjIAwMcff4yLLroIg4ODSEtL82mdN998E9deey1MJhObbaIoCm+//bZHbTcutmzZgvz8fGzevBkXX3wxa965aCOf4eFhNDc3Izs7GwqFAmazeNHWX3BVsnt7e9HZ2Ylly5YhM9N7WoeLhkEDXmn1zb/mqw5fN2x/PksGQE2fEYc0DhDakqrxiHXGUZjRbTs+akZCZDDSYsJQN2jEB03jborLgV7jYsMjH3fi7LxY7O/WSRbsuXI0YtI23IYLmgF2tutw92oKrvhcr80JQoUBcgxpg+ZGSSnqUDgcDrQPjKJ2cBJhfSNQB9GIjY2FK1SNBz88qerA4KQmnWu0AxXF8W7vXUofbr7hb5dbaGgoSkpKkJiYiMOHD2NiYgK7du3ye386cOAAYmJiWOIBgM2bN0OhUKCmpgbf+MY3fFrHYDBArVa7lTl+9KMf4aabbsKSJUtwyy234Dvf+Y5kmeKtt97ipRBpml48rdZk+hUAa1EwPDyMVatWISkpCb29vXPqZup0OlFfXw+9Xo9169b57e8jNxbMYD5IxR8c0jjhi1CRlJLB73b3zvk1LXbs7dLh7LxYfKcyU7Rgz20aEEuTCUEzgJ2hcKEPbdDeNN24w6IA8F7zBB78sJsljJ9tykZONIV9x0dF04dxEcEYpWnUDZtRHBThkz7cYnQxJaKiFEUhMTERV199td/n1Wg0bpYMZA7JkwMpFxMTE3j44Yfxve99j/f4Qw89hE2bNiE8PByffPIJfvjDH2J6ehq33Xab6DrC2hUh40XVcGCxWFBTUwO9Xo+qqir2w/Mmr+MPbDYbzGYz7HY7qqqq/CYeeX7nJOTP4PTE3i4d67XjqWmA1HQ8QUEByRFKyeaE6m4ttv6xBje+1oCtf6zBW3UjsNjd539IlEQgRhiPf9aP0NgUnF++yu26KDD45Egb7j3owo//182eC5BujBCqyM8HZmuhLcQ999wjWvDn/pOyzPYHRqMRF198MYqLi92sEX75y19iw4YNWL16NX72s5/h7rvvxhNPPOH3ORZF5AOctNJOTk52q71IKRz4i9HRUbS2tkKhUKCsrMxv2Qs54plbnM71k9MdHzaNwWKnPTYNiNV4hD/feU4q4oNnbAq8tUH7o+nmiTDE5n1+eFYm/rRv0K3tu1BNIz0m5pS1XQdCPp4UrX/yk5/ghhtu8Pj6JUuWICUlBWNj/NEAp9MJrVYr6WpKMDU1hW3btiEqKgpvv/22V4uJ8vJyPPzww7DZbGw9xxeccvJhGAZdXV0erbTnwgaBKCLk5+ejs7PTb+LRGKy4/z2ZeOYKCgrYsiwBH7dOnOpL+Uri1cPDePXwsFdFAzFvoKvK0tjmhJRgG3p79bzXkRYmb23QUs6ogLigqIICJk12aIw2N6Lr11rAMO7uqEfa+5AVdhzXL1dhR5OdbY9fqLbrQFStPXn5cLvkPKGyshJ6vR5Hjx7F2rVrAQCfffYZaJpGeXm55OuMRiO2bt2K0NBQvPvuuz6peJPZS3+IB1gE5EOKVOXl5VCrxV0EZ0M+RCbHarWisrISCoUCx48f90mNmnjvhAcrcbRfL3e1zSFoBjLxYP6jP2+CqIRg/nF9KeoGxRUNuN5AwsL9HWclY0U4Jfrc7efl+N0GLSUoSt7HXW+38RoGuAQidq6tG9YiOpjGu/9rBYMZMWAGwGeN/ahKoRAXF+fm+TWXCERYdC6kdYqKirBt2zbcfPPNeO655+BwOLB9+3ZcddVVbKfb0NAQzj//fLzyyitYv349jEYjtmzZArPZjFdffRVGoxFGoxHADOkplUq89957GB0dRUVFBcLCwvDpp5/isccew09/+lO/r/GUkw8A5Ofne7S2DgoKCqjmw5XJqaysRFBQEGw2Gyvt7Yl8ZFFQGQuB+f56MYxvBHS0rgGFaXEe01NidZin943iyXNVos8983mvm62EMOLgXpeUoGj9oBF3vd3q1qDAbRhIUYfil9vy8dBHHWBAsS3zANCpc2BXF3+48ot+G85q7EWSsoU1j4uPj0dUVJTfZOEJc5128wevvfYatm/fjvPPPx8KhQJXXHEFnn32WfZ5h8OB9vZ2tpP42LFjqKmpATCzJ3PR09ODnJwcBAcH409/+hPuuOMOMAyD/Px8PPXUU7j55pv9vr5FQT7eIpBAIh8pmRzyRfB0RyI3Fcg4k8CcaEOu6dXhzVr3TicFBVQsz4PSPoVrlirwaruL3cDvOT8byVEzkYFUGm3cIv3cirQoVieOO0MkFiVx1cS5HXCx4cEeGxRIpPT1kkQoRtvQHZSFVw4NY0fNIF45NIgLCsX9vRzqDGwoTWQHPBsaGsAwDKvRFh8f73cqSYi5bjjwB3FxcZIDpQCQk5MD7pjnxo0b4W3sc9u2bbzh0tlgUZCPN3Bnc7wRFU3TaGlpwejoKNasWYP4eH7PP5d8hIU0WRRUxpmKo/16UeIh9Y+inFQAqbiroABXjhnQ0j8GlXMaCksPDhwYmokKQtVukREFIExJSdZomoaNLKmQaKZqSZx7BCViOUEIxtPaXDXue7fkIhiY0cjjrPFJm3h6tzRTzWqwpaam8szjhoeH0d7ezg65xsfHIzo62u+oKNCajz9K/acrFlWrtRS4hOEJpFV7amoKVVVVbsQDzERZYrYKbx4dwsan9uFFmXhkLALkxvvXiXXbxmyPz//zqLhzLIMZDyUCiqKQmxyDi9ctxabKNVi6aj2GFMmoHrCiu7sb31pC8zYNBsAD1WZUd2tFtdzEopm6QYPPOnlSGnNiaz/2SQ96pinRtauWxPAeu7QkiafkQN47MY4rKyvDWWedhZycHDgcDjQ3N2Pv3r1oaGjA4OAgLBbvbdrE6TiQyOdMUbT2hEUR+XiLZsh0LRHpE4M/MjnCNJ4sCipjsaFn0n1zu3h5ItJiwvD3/QNuz/3hiz7Jtbw1NbzbOIYLihIQHhzEk555q26EZ+xHAbj3glw8tdSO2z86SWYMZkjlox+UedVyI3I4YpGMp/qQWHeb6NqUe+u4ggIevLgQ49M21A0YUSqQEJJCcHAwkpOTkZyczLqLTk5OYmxsDB0dHVCpVGx6TsxSm+wxgTQcyOSzSCDlnQOAJ5Mj1aothJB8Pmsfl4lHxqJHVqwKz+0Xt2P3lKr35bt96wlPI25qTOgoywD49a4+PH7ZMgD8SIpmgPe/qMG67Bgkx8cjShmMzNgwUSJIiwkTbbW+vDQVFy5P8igESt6nVCouP4aSbONOUYf6RDpioCgKkZGRiIyMRHZ2NpxOp5uldkxMDEtG4eHhbBOVHPmI47QhH7GmA6fTicbGRhgMBqxfvx7R0dE+rUfSbqTG82K1+B+0DBlzB7HEkn9oH5v2SDJzAZIae/yyZZLeRlKRywWVqxFkn0LH4Dg+PtaNdHUwvr9Gjb8eM/qk5UYg9h7FOuGEJHP3eRmIp0ewQRAlzcc8D1eZmmEYWCwWTE5OspbawcHBiI6OZm+a/bGAkclnAeGLoZxQYocoXoeFhaGqqsqvXn2lUol3mybwxJ46uZVaxgKB+x1nUJpAoc7PMafPjmvn9IqkQDOAweKUtCkXc0T91UUFyE2OwVt1Fjz4+TRoRgEF5cL31wI/W6PEr4+e1OGT0nKTEv+U0mbbub2cR2AhThPa22cUzrmeQ9yf5wMURSE8PBzh4eHIzMyEy+WCXq+HRqMBwzDYu3cvoqOj2agoMjLS4553Jlho+4LTouEA4EvsaDQaHDhwAElJSSgrK/N7SKzPyOC3nw9+JYmHAnD3Bflej5Mxn/CfePxbHXjoEndLAX/w6Med+FpJEo8yKU4K6/LSVOzcXo6Hz4vHXy5MkCSJvx41Ii6rAMKoj2aA/fXHMTExMZOFkCAYMnTqSZuNREpcBem36kbcNOUWCkqlEvHx8UhPT0doaCgqKiqQnJyMqakpHDt2DPv370dLSws0Gg3sdjvvtaS2dKa4mHrCooh8fAGJfNrb2zEwMICVK1f6bbLEptkOWufpKk8NFBRwWS7wVjc/taOggEtXpuJ/9SNs0ufhS4tw5dp0XFySgj6tGS3DRhzu00OtCsI7dRq59nUagYK4VxEDYFgvbRXvCxjMKB+89p1SDOutACisyuAb5aWoQ7E8MYSdDfG3wSAtKggdHR2wWq0YdkVKEoyvrdZ3nJWC5SqFKJE98EEH4iKCsbFAfOZnPkBmfFQqFdLT05Geng6apmEwGKDVatHf34+Wlhao1WrExcWBoihkZGTAbDYjPDx81ucnzqTvvfceO2T6+9//3mNKb+PGjfjiiy94j33/+9/Hc889x/7c39+PH/zgB/j8888RGRmJ66+/Hr/+9a/9dpdeFOTjS9qNoih0dnaCYRhUVFT4nRN98+gQfvle67znzD1j9nl/BQV876wc/HXvzFyEggKuymNw1fosrIrpQU7JeoSHKGFxuJAdF46U6DDccX4e+rRm9mcASIkOQ0p0GMpz4/CdDTNrX708Cl80D2CMicbnbaOYmN3+JWMeQYHBL7YsQbJEOum5fbOvYxJpna3FJ6X5hYZsDMOw0YYqROGWqvOUpqs84adjNpvR3j8KqobvQktRQIjC3QdI2BlHrvXpfRo8ea4KjKhj6kxTxaUlSXj00mWz/mx8gdiMj0KhQGxsLGJjY5GXlwebzQatVovJyUnce++9OHLkCMxmMz799FNkZGT4bPomBn+dTAluvvlmPPTQQ+zPXCJ0uVy4+OKLkZKSgurqaoyMjOC6665DcHAwHnvsMb+ub1E4mTIM4xZ+cmEwGFBTU4PIyEisX7/eb4bVGKzY+NS+0+quXkEBf75qFf7XMIKPmsfYxx762kzkMqK3oLqxEy6DBhvXr0JMTAw+++wzXHDBBX531xBoNBp0dHTA4XAgJSUF0anZ+EfNIHYccLemliGO75+Vhb/OwcbvCd9drYbSrEU3k4jdx3V+vdYfS3GhE6lYTaY4zIDg4GDUT4Xzog3yeq5pm8Zo86jlxjW1I6DA4LslYbi8NBWukCiMW4GsuHD0ay248bUGt2v+ebkKm9avdHNM5eL175QG3PXmDzQaDYaGhlhhT29wOp34/PPPccUVV2DNmjWoq6vDihUr8OCDD/rsGkoQqJPpxo0bUVpaimeeeUb0+Y8++giXXHIJhoeH2czTc889h5/97GcYHx/3qwSy6Gs+AwMDOHToECIiIpCSkuI38QDAsX79otg8FRTw3NWrcOFyvskTBWBDmpI3RPfQ14pw3rJEPPN/K/HFnWfhlRvW4PM7zsKVa9PhdDqh6WlDAq3FhedWICkpib379KSR5w06nQ4mkwl5eXkoKipCclQo7tyUi10/rsBTVxSjKPl0K4LO/299a1EC7/dmd/p3Tn/jYArAsSEz/t6u9Jt4AOCK0lRIJRqKUyLc/Hi8GbJNmJyYtLhESGPGHpvrFpqiDsW67BgemXHrMjjxGn5rBoWXmmzoHJpAX1s9nMOtMIz0IgoWN08fBQWkRgWzkZLUZ1s3YPThk5o9/JXWCQoKYolq9+7dGBsbw7333ousrCy/z+3NydQTXnvtNSQkJGDFihX4+c9/znORPnDgAEpKSnglj61bt8JoNKK5udmva1wUaTcxuFwutLa2YmxsDGvWrMHw8HDAG6ve7Jjjq+NDLNVw93kZaO8exDu9YNNjhFDOW5aIewxW1A7oAQCZ4U4YRvrw2FVr3dJjwMkUGTCj4nDs2DEEBwejsrKSvdMgqctAAlliOTE4OIiwsDC2Y4cUcFOjVUiNVmHb8mQ0DhlwbMCA46PTeLt+NIBPayExuxSnN1yzLg3/PDLM25BfOug+AOoJj1xaiF+82y75vPC7xQCoG/PNwVVsrTdr3QvvFIBLVyZhaWIkripTYsrq4umwAdL1nOEpJ8IcQaJpLquDdkvTETQOG3lzRJ5avGkGiEpbgjUZUexsjX6kF99aQuNfXQrQOFH3XKZmb8IuL01FXEQwO7/ERWnm/Ec9QOAupgAQERGB4OBgfOtb3wro3IE6mX77299GdnY20tLS0NDQgJ/97Gdob2/HW2+9xa4rrLWTn311SGWvx6+j5wnCmo/FYkFtbS0oikJVVRXCwsIwOjoasJtpTIRnMyTRa4Jv980UGPzrpnWwOmmogpUw211QmCdhGOnF1q3LcXtEnCShXBg9Y+o0MTEB3RDNIxkx6HQ61NbWsoZ73MnpQCMfp9OJhoYGTE9PY9myZTzLcoVC4fa7KUmPRkn6zDzVrectwf3vtWJfl96vc54pmLK6fE5h/eCcbPzlS3cVgr2dntun5zJ2E1uLAlCUEoH/NYwBOGk+xh027ddaRE3nFBRgsLoQGyX+nLAhgKTghMoJBJ6aEzJjVTNNRyFR0IcEIXd5NlatcuHC/lG8cUyDD3vseKvViLdbgZ/YOnBt5RJsLEjApSVJeLfx5PsSk9WZLwQqKqpSqSRfd8899+Dxxx/3uEZra6tf5+SCa5ldUlKC1NRUnH/++ejq6kJeXl7A64phUZAPMENADMOwjqYpKSm8DXY2VtprMmMkyURqluFfN62DxeGCKliJj5tH8dKBftZXBMzJaOb/cmksT41kW8Gbmpqg1Wp5Q6+eCAWY2eS9kQZR6S4sLBQNw4kKhD/kw42iysvLMT09DbPZjLq6OnaAzpOZVIo6DH+7ZjU0RivqBgz4y54udEx+dboU3m30LfKjAESFim8mO1vG5/CK3HFZLoV3emhIRUoMgBaNye1x0iFGnSACoekcsWl49qgJFGXC1wQOp2INAQ9+2IGCpAg8+KE78QCemxNS1KGiNaeqJWn4+K0B9v0xAJ76YgRR04PITY7B90vjcdnyZWidsGF1ZvSCEQ8QmKjo9PQ0IiIiJJuwFsLJlAtiPNfZ2Ym8vDykpKTg0KFDvGNGR0/MVvmxLrCIyIdhGHR3d0s6mhIvnkCQEh2Ghy8tctNvI6kwg8WBJz/t5KXHVmacVEtYmRGN6yqy2AgGAPq0ZmTEhKKxZi9ommajNYVCgaqqKr+k2D2RD03TaG9vx/DwsKhKt6/rCKHX63Hs2DFWCw8AoqKiUFFRgYmJCWg0GrS3tyMiIoIlIrVaLfpHkaIOw3L1OG5bZsGr/ZGoGTC7HXN6YfZdicLVfvtpt+Rzs0VlbjS+tToFt7/lnr5bXrgE7/R0ibzKOxhwZmiYk66mw3obz1+HYdwdT6XSdLUDRtFokUsyQh23FHWoZM3p8cuWuZ8HQGJuERLCHZicnIRJr0d+SAhCpswYH7chNjY2oNqxv5gPI7n5djIVoq6uDgBYle3Kyko8+uijGBsbY9N6n376KdRqNYqLi31eF1gk5EPTNGpraz3K5MzGzZRhGJTF2fHQOgbhSVkIUQKjEzpcUFHKRiWXnJh7EabHCIQpsZToMDAMg0bM9NO3tLQgKSkJxcXFfn/hpEjD4XCgrq4ONpsNlZWVXnv/fSWf4eFhNDc3o6CgAFlZWaBpmq3vEP0qouY7MTGBiYkJHDt2DAqFAgkJCUhMTERcXByCgoLAMAza29uh0Wiwdu1anH9+DFsXqh8wnKZupfNbKxKeabYE9M016XBK/N7bRqZmufpJkNZrKX8druMpIJ4+y4pz13sjzQncqIRosRH4O0NUkBqLFHUoT3FgcnISnZ2dsFqtiImJYa0SwsPDfRr38Bcul8tvPyBCPrO9nkCcTLu6uvD666/joosuQnx8PBoaGnDHHXfgnHPOwcqVKwEAW7ZsQXFxMf7f//t/+O1vfwuNRoP77rsPP/rRj04/G21gZtNMTEzE8uXLJVv1Ak27kVSYTqfD1rNniG1oaAgxLoNkUd9XkFRXQ0MDli1bhszMzIC+NGKkMT09jWPHjiEiIgIVFRU+3amR1KUUGIZBZ2cn+vr6UFpaivj4eB7xCK89ODiY9TqhaRp6vR4TExPo6OiAxWJBbGwsbDYbezelUs3YALB1oQrgbqMV/6gZxEsH/CvEfxVAAVifE42aXoPb44DvpPST/7a4dX4RvFU/5vZYVmwoBvU2j/UqsWvguppK1WWkbLCBmeN//GYLL31HIh5v6TB/Z4hS1KFoHDaitt+I1VlqlKTFs1kDs9mMyclJTE5Ooru7GyEhIYiPj0dcXNycRkWn0kgO8N/JNCQkBLt27cIzzzwDk8mEzMxMXHHFFbjvvvvY1yiVSrz//vv4wQ9+gMrKSkREROD666/nzQX5ikUx5wPMfBCe7tpHRkbQ19eHiooKn9c0m82ora1FUFAQSktLWWbWaDTo6elBZWVlwNdL0zRaW1sxMDCAFStW+KSm7ek69+7di61btwIAxsfHUV9fj8zMTCxdutRnQtuzZw9WrVqF2NhYt+dcLhcaGhpgNBqxZs0aREREsJEkIVF/oNVq0djYCJqm4XK5EBERwUZFRFCRC43Riut21GJQf2apSwDA+bkqhISGITU2Ai8eGPT5defmx+KLTvd26a1FCdgpETHORaREAfjZ5hz8Zlev6PPXrQhHYYQZLVrgn51waxgAxGd+ALg9VpAUgWteqnMjDW6KTmzmhxv1cM/FXUM4Q/TlsVYsSYpEWXE+fvFum1ujgdhwKTcqmpycnNOoqKGhAXFxcX7tDTt27MDbb7+Nzz77LKBznk5YFJGPL/A37UYaF1JTU7Fs2TJeKmw2KTwAsNlsqKurg9PpREhIyKwVaBUKBRiGgcvlQn9/Pzo7O7F8+XK/p5ul0m5WqxXHjh2DUqlERUUF7/0HQjwGgwGNjY1ISkpCYWEhXC4XJicnMTExweaIuem54OBgpKjD8MltldhzfBw7DgzgUJ/B80lOI+zusQCwAPBv7kaMeADgEwniUVDAzRuy8Ld9szM8ZADERoSyTQNcUACuPXc5UqLDsHFqCquPD+NIjw5pIRZkOgbR3W1BfHw8vrEqBQVJEXh3fzPOLkrD0qw43mCnt9ZpYYrOV1FRco1iabqieCWiI0PQOGzkEQ8w41l0VVmaW4RFdNjmIyoKpOYzV9I6pwNOK/LxJe3GMAx6enrQ1dWF4uJipKeni64VKPkYDAbU1tayA1z79u2bFZEBJ9ukSafcunXrEBMTE9A6QvIxGAw4duwYEhISUFxcDIZhJNNsvmB0dBTNzc3Iy8tDVlYW6wybkpKClJQUMAwDg8GA8fFxdHV1obGxEbGxsSwZbVw6809jtOK7r9SiV3vmRUJiuGtzLg73GbCnw7sytRSxXLAsAX/ff5J4Ao2CFBSwLjcOX1+ZjHcEs1r3bV2C+PCZv7VPOqfx8CckPRaEH8dEYb3KjMHBQVRrgNc7Zs7/744+XLfe6VdNhqTvAOkBVtLmLTVDJAT5Xtf2iw+R1g0Yvab3xNSpA60VBZp2+yrYKQCLiHx8cTP1tsk7nU40NTVBr9d79PcJlHxIoT4vLw+5ubmsz9BsVAXIdQMzdZ7KykqP7c2eIKz5aDQaNDY2Ij8/H9nZ2R7rO95ATPt6enpQUlIi2XFDURRiYmIQExODgoICmM1mTExMYHx8HB0dHQgPD2eJ6P0fluPLzkk88UkXerTebYkXCwLZ9J/Y1TPr837SOuE2cCoWvXgCBeD6iky0aabwPwHxKCjgnKWJUCqVGDFY8PDHnTxC+P3+MezcXo74DBq37jvMGxB9uWbI75oMgSfVailRUWF9KUUdyn639Rbxrlh/h0tnGxXJ5OMZi4Z8vMEbYZjNZhw7dgwhISFe/X2ImZyvYBgGx48fx8DAAEpLS3kbr79rCWE0GnHs2DEAQGlpacDEQ66F+MZ3dXWhp6cHq1atQkJCwqyIh6ZptLS0sFGZP3Lv4eHhyMrKQlZWFpxOJ5ueq6+vB8MwSEhIwPNXZENjD8XOtgm8UjPk79tecHjb6+eiLuPrmhtyY7CvW+/2uNiGXZkbiwM9Orx0YEC0AYRmZoZecxMiMDnt3pBAM0D3mBEAJRqNXLwkBB9120CDgoIC7jw7FQnhStHWaS6kCGbSZEdmrEqUvLg6cOSxbJrGpMWFF0Te2wXL4mc94+NvVCSTj2ecduTDMIzb5jk+Po6GhgakpaWhsLDQa56VRCtiawnhcDhQX18Pi8UiqqY9m8iHRCZLlixBR0fHrNsrFQoFnE4n6uvrodfrUV5ejsjISI+KBd5gt9tRX1/PdrT5207JRVBQEJKTk5GcnMym5yYmJtDb2wuTyYTN8TGIrUzAHw5OnNZeS/5c+rJoGm0G73UBqTXFiAcA7ttWgIc/4svX7O/2XpN66KMOADNkJxbJ6K0upKpDRMni9otX49tGK6o7NMhSORGHSezdO4iYmBgwYdGwUOFgYk7eXEl1xpFo7q6321hi4ZrGARCtL/327DBQQeKqE1eXuaffZwNfoiK73Y6pqSlERUX5XCsymUyIi4ub02tdrFg05ONtUyR3EFxLWm59x58CvVKpBMMwXslH2O4cHOwu0xNI5ENannt7e7Fq1SokJSWhu7t71uk7AOjq6mINrIKDg2fVWGAymVBbW4uoqCisWLEiYLVsMXDTc/n5+bBYLJiYmMA6xTgeWEtj31gQPumf/eex2NEuQTz+qE8LU2+KEz/PtimBW68h5/jZ/467qR0oKOAXW5bgy+PjePSTbl40sm1lNP5Z04vfVw+ya95SpkZkRCR+9+Uw79id28tRP2jkDbCKuZ4e6tWLRmWaaRfWZ7nPEQlrTPMBsaiooaEBAwMD6Orq8rlWJEc+ixCEcAj5OJ1ONDY2ehxMlQKXyKSipLGxMTQ0NCArKwsFBQWSXxZ/Ix9y3UajERUVFWwKyx91AjEYjUYYDAZERUWxSrYul4ttCPAXWq0W9fX1yMjIQH5+/rwM4XGhUqmQmZmJzMxMrHK5cM7kJK7oH8V/Giawa3Ah9KkXFiSqEHtfFPwgHgA/OX8JntrdzYsEHt3ZMetrZAD83+pUlOfG4if/beERwvtNY/jZGiWy85YiPyUatMuFbX8+7BaNLIkrwbMHTtaqGADPHTECMIoSjNQA64DOwqbrpNJ0iWGMqPePsMY031AqlYiLiwPDMFizZg0YhvG5VvRVcTEFTiPyISkjl8vF3pH7Ut8RA5d8hNEMkfnp7u7GihUrWFkJT9flK2kQLbWgoCCeIrW/6wgxOjqKhoYGqFQqVl9pNh1tg4ODaG9vR1FR0azMrAKFUqlEUlISkpKScNZaBp1DE9hxoA9vt87dtP5cIpAaj6fj/VnrO5WZiFYFi0YCc4F/HRvB+txYUULILSjEWUtnFI1renQScjoG0fqQEDQD7Nx/FIXp8X4PsCoo4Lr1GaDpYSgUCq81poUA+VtWKpUICQnxWiuy2WwICQnB9PT0KXEx7e3tRW5uruhz//73v3HllVcCEM9Q/fOf/8RVV13l9zUuGvLxZZNUKpWYmJjA8ePHkZGRgaVLlwZ0V0+iAWG6jNstV15eDrXae4HS1845T4rUQODpO0KUK1euxMjICDusG2hHW0dHB4aHh7F69epFkXumKAoFGYl49MpE3Gq04gev16N9bHFpx61MV6N+aGE8YrigAGRHOHD/e9KWDL6uQ1HShDUwaRYlhPyUaGiMVvRNWhAuoXq9JClKVE4HIqnCssJsKGxGfLuAwuvHabZx4d4tuaINBju3l+O1w4N4+eAQdtQMggJwT4QO366IdpPnWWhwyYcLqVrRyy+/jGeeeQY0TbN6ips2bQo4Beevi2lmZiZGRvh2G3/729/wxBNP4MILL+Q9/tJLL2Hbtm3sz4GMhQCLSOGApmk4HNK+OwzDYPfu3aBpGitWrJj1Hfnu3buxbt06lmC4agirV6/2OZpqbm5GUFAQCgsLJY/xpkgNAPv27UNhYaFPooHATNTW3NwMrVaL1atXIyoqCj09Pejs7IRKpWIFCGNiYnwiIZfLhcbGRphMJpSWls6ZxMd84Pefdc27W+hixXx00gHA/RcWoG7QKKrU/cNzspEYGYyHP+xkvXMevGTm+/6r99tZQrh0ZTLebRh1IyDu4woK+OW2fDAMjUd2nqwP3bc1D1esnskyUBSF7hEtWgbGEeowYnrahAeOKkVVEq7dUed2Pq776qmC1WpFdXU1zjvvPJ9vAqenp7F27VqUlZWhtbUV/f39uPvuu/Hwww/7de5AXUyFWL36/7d33vFNlW0f/yXde7e0pbulpXSkgz0ERXZpcQH6yFJUHA+iKOojggMBB27xBQc+A1GgDEGGAmXIENqmdNAWSvdI0t2m2TnvH/U+Jm3aZjapnO/n4/s+zTi5E5Jzneu+ftfvSkJycjK++eYb+jYWi4UDBw7oPFlVExaT+fQHqZMoFAqjbQWpZixNTU3gcrka3RAGwhyO1BKJBLm5uaAoSk1YEBwcjMDAQDQ3N9NyZuAvtwEvLy+NqhuxWAwulwtra2uMGTNGo7DCklh9dwQeSgnAZ8fzcbCk9ziAvyu69vTMHOGGX2+29c460DuAvX3sJp6YpPnCKNrHAS4dlfh4uiuch4XCyc4aNS1ivHSgSK3Gc/g6D58vjMMzewrU6jmHr/Pww4pkiGTKP3tyuhVvI/3dkF3VgsQAF4zyd6b73VgsVvcFlK8vQr3CUMZvB5WtPqNGSQHnimr67A8yd/Ah9WRddh+IoejatWsxZcoU3Lp1S22KqLYMNMV0wYIFAx4jOzsbXC4XX3zxRa/7nnnmGTz++OMIDw/HU089heXLl+u1vW8xwac/9Udubi7s7Ozg5ORkkNRXFSJLrqysRGlpKW0MqitWVlaQSqW9bjeVI3VHRweys7Ph4eGBUaNGAfgrxWez2WCz2b3kzKpuA56envDx8YG3tzccHBzQ3t4OLpcLLy8vjduBlohCoQC/ohRzh8uwcioHz/xUjIoWy3JKYAGI8XPCDV7P4Eg0X7rzYJI/fsrpPYlUE+/P8MGB/CYoqb9eb1yIKzZlxOKzrPJezgYUgJ2/V2n0lXt+fzFWcpzwz3lJOJDXgA1HrmvcolNSQGWzSGN9SCRTYkzoX56D+3Pr1LKmDXNGYEGiH5RKJTK59WpZ0T/vCtG4peeNDrBAgVL5PAdD2aYN+szyAdTtdSIjI/V6bX2nmKryzTffYOTIkZgwYYLa7W+99RbuvvtuODo64uTJk3j66afR2dmJf/7znzqv02KCjyaI4iwoKAhRUVG4evWq3gPlesJms1FRUYGOjg6kpqZqNOPUBk1qN30cqbUJPnw+H3l5eQgPD0dYWJhar5Km4K3JbUAgEIDH46GkpAR2dnaQSCQG1c8GG6lUSvvHjR49Gra2tvjluW7PuO8uVeOqBXjGJQa4IK+uQ0PgAfQNPE9OCoaLvXY/14eS/eHk6YWL9QK117tc2YbM03/gYH4fmToFLB49HOmJw/D0ngL6dgrA13lC3JPSQQcMTbBZQHKQWx+jFFTtdMRqx+lWu5ViclT3zgAJPOS+T89W4rkpwfjsXNVfW3ezI/FAUgDgXo13T5b/aeVDYXEkCy21t8EWe9FjP8yBPg2mQP9qN1NPMSWIRCLs3r0b69ev73Wf6m1JSUkQCoV4//33/z7BR7VDX1Vxpo3FjjaIxWKIRCIoFAp6TLe+9BQK6OtI3V/wIdY2t27dQnx8PPz8/NQabrV9DUdHR4SEhCA4OBi3b99GeXk53NzcUF9fDz6fr2YGasyeHmNBXCw09R2pesa9cbgYF7RoqDQVeXW6qfKmRnnCzpqt0cmaBeDF6eEYHeKORd/kaHW8n3Lq+8iQWLglcQGgOUCzADjYsEFRvU8LSgrI0aBcI5A6UHygG96cF62W1bw5L5reagOAyibNdjpVzSJQVG/hg5LqHuh4bFUqPcTRz8UOUqkU93P8MSXSG+WNnWi4mY8poxPQ2tqK8vJyFBYWws3NDV5eXvD29jbZ3B5N6GMqKpVKIZPJ+gw+gzXFdN++fejq6sKSJUsGfOzYsWPx9ttvQyKRDM15PsBf225yuRzXr19HR0dHL8WZoW7UQPcEz9zcXFhZWSEsLMygwEPWRDKQyspK3Lx506iO1EqlEoWFhd0NmH8KJBQKhUFWOcXFxRAIBBg9ejTc3NygVCrR0tICgUCA4uJiSKVSeHl50dtzxtrqNARi6Orv799vUB/mao8d/+DgALcO/zpsmApssMi62awxH2IB+GJRHK5WtmGhloFnIIZ7uwKlmoJP9/bVom9yMDvaVeN2VoiG/hoWgA/vjwUnyI0OMPcnBWBihKeK71r37QMp4xxs2PBxsdN4X5hPt+rL2toaNja2ao3icoUcUmm3WMnd3R2enp504zLprykvL6f7a7y8vODh4WHSCyx9rXUA9KlwG6wppt988w3mz5+v1WtxuVx4eHjodY6wGLUb0F34z83Nhb29PRITE3spzvLz82Fvb4+oqCi9jl9bW4uioiJERUWhqakJ3t7eCAkJMWjNNTU1qKurg4ODAxobG5GUlKSX9JDL5cLNzU1Nay+VSpGbmwuFQkEr8EiA0sexQCaT4fr165DJZH36yFEUBaFQCIFAAIFAgPb2dri6utJffGNMWdQVPp+PgoICREZG9qkW1MRrh4p61TaGGgOp256cpNuIBe3VchRYYPVSmPVUrr05Lxr3JwXQgSXEy0EtyyG3F9a3042w/SnjNKno+rrt/qQA7M2uxZu/lNIO2q/PDEdGQne9g9RAye4E6a9pbGyEVCrt5TpgTOrr61FfX4/k5GStn1NVVYW4uDjIZDKDtwtnz54NHo9HTzFdvnw5UlNTaal1zymmhFu3bmHEiBH45Zdf1OTUAPDzzz+Dx+Nh3LhxsLe3x6+//oq1a9di7dq1ePPNN3Veo8VkPjKZDH/88QcCAwP7vLLVN/NRVZ0lJSXB29sbbW1tRtnCUyqVaGtrg1wuN8iRumfm09HRgZycHLi5uSEuLo5+LfJYXSFSckdHR6Smpvb55WaxWPQo7bCwMEgkEtqV+vbt27Czs1OTcZu6TkTmG8XFxfUqog7Eu+mxWJwaiJzqNoR4OqjVMQabCG9HlDXqrlwaKFD4udjppL/W/kqTpVE4cPg6D7uXJ0Esp+ispqd4gAQG1ds1HUeTMm7DkRL8tno8fls9ns6cAGD6J5fU6kAbjpRghK8THXjIe9t08jYmRXjCx9mGNtMFun8z7u7u8PDwoOufJBCR9gQSiIzxvdZHcECmmBrjN6XrFFPCt99+i+HDh2PGjBm9jmljY4MvvvgCa9asAUVRiIyMxLZt27By5Uq91mhRmU9rays9ilkTpaWlkMlktMpLG4gxpkQiQXJyMn2FU1BQADs7O72zKKDb0ubq1augKArTpk0zKI0vKCiAra0tRowYQdeNQkJCEBERYZAjNdDd4EoG6+lSh+qJQqFAc3MznRUplUq17TljSrSJk3h9fT04HI7ejWyq7M+tw3oDGzIZgBeTrDAxyhfe3t6QWDmqmXwC3QHohxXJWPxtTr9OC+tmRGDrybJet+9awlFTxl0pb8Hy/3B7Pe6l6eF4/7fbvW5/Y3YUFo0eTk/ZJdvi5FRHmszJ/5fL5WhpaaG36ORyOTw8POhgpM8FZVVVFdra2hAfH6/1c65evYrFixejoaFh0HcXzIHFZD5Ad0G8v1hoZWUFsVh7SS3JHlxdXZGUlKR2tW9o/Yg4Uvv5+aG1tdXg/WOynoqKCty8eRNxcXEYNmyYQfUdoHsGEWlwNWTUN1kjyXooikJ7ezsEAgEqKipQWFgId3d3+n5DtjEUCgUKCgrQ0dGBMWPGGG1LhNQiDufV40xpE/JqLdOuBwBWjBuOby9rP5LbELq/Veo1HnJ7T+cDNgsYFxeJxuZmXCgtRItIASWl/ryBxAnkONoo4wAgxEuzl5tzV4PGpO+tYzdxva4d76bH0lkECUDk96T622ez2fRFFNl2bmxsRENDA0pLS+Hk5EQHIldXV60yE31qPnfSFFPAwoJPz2FoPdF2minwV3AICwtDRERErxM3ueLRFVVH6oSEBNjY2KC5eeDplAPBYrEgEAggl8uNIiwgikEyg6i/Bld91+vm5gY3Nzc1V2rVoXEkELm5uWm9fpKpUhSFMWPG6OzbNxDDXO3xxOQwPDE5DPm1xivkG5vvrmgOPKunhuKTrAq9j0vm+ly83QLqz78XRwLuvoH44vc6tcemJ/ohJdi915ZacSuw4RfBn3UWVq8AwALgZavs05lbW2Wcah2p5+NmhtrA1Ra0x1vPlzmYx8Pi1EDEB7p1v+afAYMEhJ5ZUc8G1+DgYISGhkImk6G5uRlNTU3Iz88HRVHw9PSkg1Ff3099gk9nZ6dZaqrmwqKCz0BoI7XuGRz8/Pw0Ps7KygoSieaJh32hyZG6ra3N4FEIUqkUDQ0NUCgUGD9+POzs7AyawUOsd9rb2zF69OhBsWhXdaUmQ+MEAgHdk0MCUX+9F6Qu5ezsbPQRDpqID3TD22nRFrkVp+kajMUCooc562yxwwLwxKRgyBQUvrtUTc/1GeVjgym+Mswen4D0b673et6hPB4ifZzUai1tIpmagzZpYVUNNBSAdUfKMcYXuCoAHTBeuCcccQGuagq4vpRxmupIv60ej+8vVeH7K7U4Vi7DiQoZ3pzHxlOTQ7D9fGWv9edUt9HBpydEiACA3tYm//XMinx8fOim7Y6ODjQ2NtKWWa6urnQgcnFxoX+r+tZ87pRxCsAQCz4DbZWpyrRVxxX0dSxdgkZfjtSGTjIlDalWVlZwd3dXU7TpU3iUSCTgcrlgs9kmyRy0QXVoHBFkkIxILBbD09OT7iki++naSqmNDTn5PfG/PNwSWJZhaU8oCnqJJoh7Qc8ZP4UCGQoFwEneLY3BjgLwQY+aSs+/yePW9xheR6E78Gy91wf8plY4Q4wwWwG8QUEopHC5qQuhXo4Y5mpP/0fQ1IS64UgJ/rMkUW3SLbn984VxGt93cpDmwNOTnoGIoig6EPXMipycnODs7Izw8HBIpVK6TlRdXQ0Wi0UHIplM1m/9WhNEcHCnYFHBR5uBcn1tlana8PQcV9DXsbQNGv05UhsyybSxsRFcLhfBwcGwtrZGa2urQfWdjo4OWncfGxtrciWaNrDZbHh4eMDDwwMjRoygZdwNDQ0oKSmBs7MzHB0dIRAIEBERgdDQ0EFf4zBXexxeNRZZpQKcKBKA1y7B5YrWQV+HKemv/qKPCk8VNgtw62MOj8+wQMwdF0dvy+7LqcXXebf/FHIDL00NxKMTI+gsoaFdjOOFfM0KuUuFGl/D0dYaGYl+arL6jES/PrOeft9LH9tzJCD1zIr8/Pzg7+9PX2Q1NTXRk3nt7e1pF2ttttOY4GPB9BUwiIkmkWlrc9LVNvgM5EjNZrO1moraE+IpN2rUKPj7+6OhoQE3b97E5cuX4evrq3OtRCAQID8/H6GhoQgLC7PYfWMnJyc4OTkhNDQUUqmUVrSx2WxUVVVBJBLR23ODHTxVXRLu/vjSoL72UGJqlCfO3WpW2xJLGkA84ODgABs3H3xz/RYtbKAAfJBVC+fOakQGeOFqsw0++b134AEANoC4YY7Ye7Nd42uoyuqTg9z0Cjya0JQVqQYj1b47V1dXugaak5MDW1tbtLW1oby8HDY2NmoNrpq2npltNwumZ81H1XYmNjYWgYHaz2kfKPho60itabx3fyiVSty4cQM8Hg+pqalwc3ODQqGAl5cX7rrrLlrKnJubS+83D2R5Q3phYmNjtbLPsASII4SqcwNxWbhx4wZkMpmajHswtw+Hudpj7fRwjVtMloqujteGEO3njLM3u0U25DWHudrrZ6sDwDs0BjJlFz6+UKOxlsUG8DjHCQumJAFuDX2+Rnyg8YKOJrTNiohwysPDA4GBgVAqlRoHyHl7e6s1uHZ2djLBx1JRDRikqN7U1KTzGO2ex+oJcaQWi8UDOlKrXhUNhKrTNekSVhUWWFlZqdVKWltb1SxvSJ2EnIyVSiVKS0vB4/GQnJxslF6YwaCnIIJsNZArw+joaHR2dkIgEKC6uhpFRUVwc3NTk3GbOrNbMaHb+eLD325b9AhvFoAZGpyoTcXMkd7d9aM//6bQXXeZGNE9eJAEI9VAOJCtTsxwL1Q2OYJCb4VfRogC4wPtkDqqewpoXwIFc9BXViQUCiEUCmFtbQ2pVAo2mw03Nzc1g19SKyorK0NTUxNOnDiBxsbGfueCacumTZtw9OhRcLlc2NraorW1dcDnUBSFDRs2YOfOnWhtbcXEiROxfft2tT5IXaejDoRFBR9taj4KhQIikYjODCZMmKCXr1BfwUfVkXr8+PEDZjPkyzfQFp5QKER2djacnJwwduxYeiQ4oNkqh81mw9PTE56enhgxYkSvk7GrqytkMhktSda1uGkuSADuT0rNYrHg4uICFxcXhIeHQywW0zLusrKyQXNZWDEhBHPi/FDVLII1lPjHv3srwswNBRgt8KyaEoL/O1/Zb31oTKhHr9dTUgC3ug0bjpT0Ckqq6jhN9jxLx3aPMdHYywNgWqQ7nJ2dcDynDM4oRJhfd8YQ6+2NDgWFK+UtvSx9zAH5DkokEuTn5yMgIADe3t70lrzq9pydnR0CAwPpsdq5ubmQSqW4cOECzp49i6qqKsyZMwezZ8/Wy/5LKpXiwQcfxPjx49UGwfXHe++9h08//RTff/89wsLCsH79esycORNFRUW0KEjX6agDYVEOBwqFot/eG5lMhlOnTsHW1ha+vr4GFdXb2tqQnZ2Nu+++m75NX0fqkydPYsKECX1eAZBhdcOHD0dUVBR9haSvsKC1tZXuhZHL5XRPja+vL1xdXS223kMuGhwdHREfH6+XlFqhUNAy7sbGRiiVSrVheaYYhEeUjlebbLCDO/SH143yd8aNhs5eBqEf3B+L+jaxmpRaEz2l3mwW8K8/1W69HttjO5C4H5wo6h6BQXqNevm3AXgy1RX+w/yx8ehf22yrJ/gi1VOKozea8cMtNv38DXOi8GCKYU3UhtLV1YXs7Gz4+vqqnT96NriqnnJV/ef+8Y9/ICwsDAEBATh69CgUCgUuXLig93p27dqF559/fsDMh6IoBAQE4MUXX8TatWsBdJ8f/fz8sGvXLixatMho01FVsajMZyDIjPGQkBCEh4cbdJJVlUgb6kjdn+KtqqoKJSUlGDlyJAIDAw12LCCBx8/PDyNGjIBSqaSzgpycHK3rRINNe3s7rRiMjo7W+9/OysoKvr6+8PX1pYflNTY2ory8HAUFBfDw8KDfvzGyQeKS4efnh9UTorFoqgTc6jZ8euY2Kpota4Cdttxo6MTKiX8ZkpJ/iRf3F4HNAu6N8cbJG439bjeqZilKCnjn2E2NzaY9L22VFFDbKsauy9UaPd0Or+Tg1JXrGBnkjYiIcNz76WW11/nkIh8/rEjGniOtas/feLQUnlI+ooO6bX8MdavXFZFIpDHwAOq1InKe0NTgWl9fj0mTJuGll17Cyy+/bBTvSW0oLy9HQ0MDpk+fTt/m5uaGsWPH4tKlS1i0aJFRpqP2xKKCT18nJDIGgAQff39/g6/uyRdBoVCgqKiILnzrUzfR1OujKlhQFRYYEngaGhpQVFSk5u7MZrMxbNgwDBs2rNdoBHMW7VUhSrzw8HCEhIQYLTNTHZZHXBaI7xyxRSGBSJ+MsLm5GXl5eQgNDUVoaChYLBaGudpj1ih7zBrlh6xSAc7fasaN+g5wLc6qRzWsqKOkgP+7UNXr0eS+gbbx+urrUW02JU2lPbMoNqv7sZqk1CW1zQD/Ju4a6Y+oqCj8UdGq8XGarHsosNAJe9TX16O4uJj+t/f29tZJNaoPIpEI165dg4+Pz4A7JiQQ9WxwPX/+PHJzc9V2YgbrwpFMN+3ZkO/n50ffZ4zpqD2xqOCjCdI0KZfLMWHCBFy8eNEo00zJPJCrV69CqVQa5EjdM/ORyWTIy8uDWCzGuHHj4ODgYJBjAUVRuH37NqqqqhAfH9/nnA3iUaVatOfz+aiqqkJRUZHRvNd0oaamhpaU9+U2YSyILUpwcDBkMhm9PaeaERKF0UA/bB6Ph8LCQkRHR/epoiTS7KxSgVkdszWj/XdM1333vvp6KAAf3hcLTydbWgzg5mDTS53Wlyy7pboUceHD4eQTiD8qWvsUKPTlCTd2VDiGudpDJpOhsbGR7qMDAG9vb/rf3phbs2KxGNnZ2fD29tYroyeZw+LFi/Hpp5/iqaee0vg4baeYxsTE6PT65sSig097eztycnLg7u6OlJQUWFtbG2WgHADaStzOzg4JCQkGXWWojkMg+76Ojo5aCQsGgmRmra2tSE1N7de1QRXVon1ERATEYjEEAgH4fD5u3rxpcFYwEMTmqLa2FklJSXqPKdcXGxsbtYyQKAdLS0shkUjg6elJv/+eghUSMLUd4zB1hA9G+TujsL7TVG/HiPSdEWnLC/eE9xlAVIfKAd0OEiN8nZBT1Ybk4L+k0D1l2YsjgYTIYOS22WPDD5f7FSj4uNj1K+u2sbGBv7+/WvOn6tYsUU56e3sb5KUmFotx7do1eHp6IiYmRq/jXL16Fffffz/eeecdPPXUU30eQ9sppvpA2jN4PB49NZr8zeFw6McYMh1VExYVfFQ/+Pr6ehQUFCA8PFytvmOM4ENMRwEgJibGaI7Uzc3NyM3NRUBAAKKjo+mUmli360pPk01DJora29vT3mskK+Dz+bS1j2qdyBizTAoLC9HW1qYmpTYXPZWDxGWhrq4OxcXFcHFxoU9GfD4f1dXVOgfMvStHY9OxEvzvat3ADzYDT04KRrSfM7KrWg1eY1yAq1Z9PYBmj7b7kwJoyfTFknrcLq/AXbHD4egdgA3/U5/bc/g6T02g8N3lanx/pZr2ehtIcq3qsBEVFUU7LTQ2NqKsrAy2trb0v70u001JxuPp6YmRI0fqFXhyc3ORkZGB119/Hc8991y/x9B2iqk+hIWFYdiwYTh16hQdbNrb23HlyhWsWrUKgOHTUTVhUWo3iqIgkUhQWlqK6upqJCQk9LryvHjxIj2nXJ/jq5qO5uXlYeLEiQafHK9cuQInJyfU19cjJiYGw4cPN7i+09nZCS6XC1dXV4waNcpk+7+qdSKBQACZTKbWT6TrFgXZclQoFOBwOBYxgrs/pFIpGhsbwefz0djYCIqiMGzYMAQEBMDDw0PnQNzQLga3ug0v7i+ymP6g1GA3TB3hNaCKTRM91WosAE9NDkF8oAscbKzhaMuGSKbsc1x2z5k+bBbw2+rxGOZqj//8XoYtp6poxdrScUH47lJ1rzVsuz8WazOL+jyOvpALRhKMpFIpnRH3J1qQSCS4du0a3N3dERsbq9fvOz8/H3PmzMELL7yA1157zag7D1VVVWhubsbhw4fx/vvv4/z58wCAyMhIWpEbExODzZs300KBrVu3YsuWLWpS6+vXr6tJrQeajqorFpX5yGQyZGdno6urC+PGjdMoXdY389HkSG2MLIqiKIhEIrS3tyM5ORmenp4GB56mpiZcv34dQUFBGsdBGJOedaKOjg4IBAJUVlbSM3qI3c9A6jEipXZwcEBSUpLFKO36w9bWFn5+fhAIBLC3t0dYWBja29tRWFgIuVyucyAmggShVIE3fi6xiAB0raoN16ra9HpuWpQjfi7tUhMWqDpIk2yGDH9TzXQ0uW8rKaCqWYS2tjZsOVVJW+0oKWDXpWqNW3l9CRSqmkUGBZ+e86lIRtyfaEEikSA7Oxtubm56B56ioiKkpaXh2WefNXrgAYA33ngD33//Pf13UlISAODMmTOYOnUqAKCkpARtbX99J15++WUIhUI88cQTaG1txaRJk3D8+HG1ADzQdFRdsajMRyaToaCgAFFRUX3+0LOzs+Hj46PRZ60vVB2pk5KSaNVXVlYWEhMT9a5HyOVy5OXlobm5GSEhIYiMjDSovgMA1dXVKC0txciRI/XSzhsTVfVYS0sL/WP09fVVs48H/pJS+/r6Ijo62iJMTbWBZGpKpRIcDof+bhD7fPL+Ozs71VwWtMmWG9rFOJxXj4/PVJj4XZgGFoAt93hh3ammfh9HshBAfdx1X4/96oFIFNy4iU8Lev8+lo8PwveXq9W26SZGePY6LpsFfL4wDhVNIqQEG99WR1W00NTU/f49PDzQ1tYGd3d3xMfH6/X7Li0txezZs7F06VJs3rzZYnvyBgOLCj4ABpyxw+Vy4ebmhrCwMK2O158j9blz5xAbGwtvb2+d19nV1YWcnBzY2dnBysoKrq6udDeyPoFHdWy0IQHRVJAfI2nutLa2pgMRmTxqbCm1qZFIJMjNzYWtrS0SExP7zdSIYEMgEKC5uRkODg5qLgv9vWfVbGAowQKwbFwQvrvceyusJ7uWcHD2ZpPGbbOe2QwLFFaO8cHXVxs1bqUJOiS9BAo9a0cJga7g1rTTz81I9MO76bF6v9f+oCgKjY2NKCwspJtF9REtlJWVYfbs2XjooYfwwQcfDJkLNFNhccFHKpX2O800Pz8f9vb2ap5DfUEcqUeMGIHg4OBeX5CLFy8iMjJSK0WTKi0tLcjJyYG/vz+io6NRWFiIjo4ODB8+HL6+vjr305AtQZFIBA6HY/GjdJVKJW2A2tDQALlcDjc3NwQFBelVJzIH5OKB7NvrciIgw/JIMAag5rKgyZKpoV2MqmYRiurb8d6vQ8iwFANLsVkAXhnvhi2X2no9lmQoz+wp6OWKoNoHpNHlQEWgAAD5tW3IqWqDq4M1/nW49wDAHx9LNomxqFQqpa2x4uLiIJFI6KyoublZK9FCRUUFZs+ejbS0NHz66ad3fOABhmDwuXHjBlgsVr96dtUGz/5GSF++fBnBwcE6bW/V1taiqKgI0dHRtDdTV1cXeDweBAIBOjo6dKqTiMVi+uqbjOUeCpAx3VVVVRgxYgSdGQiFQqO7DBgbIuEPCAhAVFSUQZkacVkgWVFXV5da0VrT+39w51WzyrJZ6BYSDEYmtnx8EKZEemH5f7i97tu1hINgTwdasQb03rYj2dDvZc0DZo/rZkRg6Tjtt+O1gQQeYgnVM2j0J1ogHoS1tbWYMWMGZsyYge3btzOB508sSnCgDVZWVpDJZH3eT/bwRSLRgI7UugyCI9tiRIbr5eVFCwscHR1pSbhYLAafz6d7SpydnentKWdnZ7UTXVtbG7hcLnx8fBATEzNkvpRKpRJFRUVoaWnBmDFjaGGIqssAn89Xe/8+Pj696kTmoKmpCXl5eQgPDzfK4DpVlwXiWCwQCMDj8ehheSQrIv1U5pRls1lA7DBnFBgY/FZNDsZX56sGyIooxFrz0dnQDhYoWlxA1qGqkKtsEqG5S6pRWEBMSwcKltpOLtUWmUyGnJycPgMP0Ldooa6uDosWLYKdnR3a29sxYcIEfPbZZ0PmNz4YWFzmI5PJ+g0IZWVlEAqFSEhI6HWfqiN1YmLigI7UOTk58PLyGtA5lozn7uzsRHJyMhwdHekenv4UbaROQmS85ErI19cXEokERUVFQ65OIpPJcP36dchkMiQlJfUrpVZ9/01NTbCxsaF/qPrImA2loaEBhYWFgybmUK2TNTU1wcrKig5Enp6eEAhlqGoW4T9XqnGqpP+ivjEI9bQ3mh/do2MC8Z8/anvdrmqvs352JBKcOlFbW4vLfDb23GJB+edj3pgdhYdSh/dSxwHam5aqYuyaD1He2tvbIyEhQa/valFREebNmwd7e3t0dHSAxWJh1qxZePvtt7WuWf+dGXLBp6KiAs3NzUhOTla7nVhp6OJInZeXR9v29wVRytnY2NABTdUeXdugQdJzPp+PhoYGKJVKeHp6Ijg42KIMQPuDbBHa29sjPj5eq+F5BNU6kUAggEKhMLkbtSpk4F5CQoJeAhND6dlPJZVKad89ha0LZm3PNvg1/Fxswe+Qmk3eTRyrSd+PoqMJJSUl8I8YiTaFLeSSLvAaW2ErbYMDJQbl4I7nT3f06iPquSXYV1D6fGEcKptFRp1cCvyV8RAhij6Bp7GxEXPmzMHIkSOxe/du2kbn6NGjWL16tc515r8jQy741NTUoL6+HqNHjwag7kit6zTTgoIC2NnZ9SleUFXKxcTE0LM5AOj1hSRTTBsbGxEREQGhUAg+n08bgPr6+lpswb6jowO5ubnw9vY2eIuQoii0t7fTJ2JSJyJ1MmM6EpPaVE1NDZKSknQeOmgKVLdnBAJBt0y93QHfFUjNtqYFiX44kMcz6Biq2Ud1dTVu3ryJGtsgvHemppeAQCgUYv/VCmw5x+91nPWzIvHO8Vu9HLJZKlmVqhDBmMhkMuTm5tIXm/p8z1taWjBv3jyEhITgp59+Mpuhr6VjccFHLpf32/hZX1+PyspKjBs3jrZxaWxsRFJSks6O1P2JF+rq6lBYWIgRI0YgKChIq222/iC1KLlcDg6HQ59gKYqiDUBJP4mpTsT6QppeVd2djQmpkwgEArS2tvZbJ9MFEuxJpmxum5++IOqp4ioeDha24FTd0KwLEHFAbW0drhVXIn7kCDyxt7RPAYGmJlw2gOUxwDfFvY+/7X5101JjI5fL6X7AgaT3fdHW1oa0tDT4+fkhMzPT4h0+zMmQCz6kkD169Gjk5uZCqVQiOTlZr5N0SUkJ5HI5Ro0aRd9GURRu3ryJqqoqJCYmwtvbm3bR1rdxVCgUgsvlwsnJacAhaqoF+9bWVtp3zNfX1yATRH2pra1FcXExYmNj1UwHTQWxuyF1ElIn8vX11WlqqUKhQH5+Prq6uvT+fpgDhUIBbmkldv1ejtN1LLUivTnp2avTF/fHuiKzqJ22L9X0FE12OeQ1Ns6NRoKPNRZ8V9hrm+3nJzgI8zNN/5sxAk9HRwfS09Ph6uqKw4cPD5nvnLkYcsGHXIUTlZG+EzGB3uIFVQsecqVsaMbT3NyM69evIzAwEJGRkTodQ9V3rKmpCfb29vSJ2NQzSlTHOCQmJsLT09Nkr9UXpE5GsiLVqaXe3t591pzIqG4A4HA4FrmN2Retra3Izc1FaGgo7DyG4e2jRThb1j7wE00ImwWsnR4+YH+SptqMpmP1JSDYdn8sZo3qHrvRs6l0eZw9El2EcHR0pL8Dbm5uRhGtyOVy5Obmgs1mg8Ph6HU+EQqFuO+++2BtbY0jR45YbJZtSVhc8BlolPbt27dRWlqKqKgog6eZlpeXo7W1FUlJSRCLxbTDM4fD0VtYoArJGmJiYnSqRWmCjI8m23NkPo2vr69RnKhVUZVSJyUl9TkefDAhdSLy/lX7aVS3J4kowsHBwaALE3NAZOBRUVEICgqib39gx1UUNQxuX5A2zaWqsFnAtADgVG8BnAaHg256ZjY9jUJJYy7ZZuvZ3EtRFD2nR99aqUKhoOc96Rt4RCIRHnjgAcjlcvzyyy9ajz250xkywYcUjsvLy0FRFGbMmGHwa1VVVUEgECAiIgK5ubnw8fHByJEjDRYWEPfsmpoak2QNZD4NORETJ2pfX1+DlWPEr04mk6nVpiyNrq4u+v23tbXBxcUFbm5u4PF48PLy0tm1wNzw+Xzk5+f3KQPPKhXgRJEAh64bJgowFUnewFsLEpH+dZ5GD7aeDgf9CQiIK3aIV9+1HdUR6kS04u7uTmdFjo6OA14wKhQK5Obmdq9fTyNcsViMRYsWob29HSdOnLAIQctQYUgEH9XtsNjYWGRnZ2PmzJkGbzvV1tbi9u3bEIvFiIqKQnBwMO3dpO82G/E56+joQFJSksnTb2KASU7EQqGQzgh8fX11KniSrIEM2NNFSm1OpFIpqqqqUFFRAQD09iTxXbP0IFRXV4cbN24gPj5+QAnuJ6fLeo3AthR+fCwZP1yrxUEV1VxGoh/SE/w1OhxoEhD0Nf8H6D8okTk9xASX9NT19R1QKBTgcrl0zVifwCORSPCPf/wDPB4Pv/76q8X5MVo6Fnd26XnCV3WkHj9+PH2/QqEw6ORIURT4fD6EQiGSk5O7+y0MHIUgFovB5XJhZWWFMWPGDIrEksViwdXVFa6uroiMjKQzgoaGBpSUlMDV1VVNsNAXRErt5eXVy4DV0mlvb6dtfgIDA+k6UX5+PpRKJX0S6st3zZwQSXJ/NlCqrL47AhVNXThxo3EQVqcbR/N5ONwjMzt8nYfFqYF9Tj0Fut0NCKpOBkqq+++JEZ5q6jgWgLfS1KXWDg4O9LBEskXd2NhIfwdITxUZoU4Cj74Zj0wmw7Jly1BTU4PTp08zgUcPLC7zUSqVtH2OJkdqpVKJkydPYtq0aXrLGIkSqrm5GVZWVpg8ebLBwgJy8vb09LSYLR+JRKLRidnX11dtdDYRcYSEhCAsLGzIuC0Af2UNo0aN6jVgsC/fNSJjN6cMlqIoVFRUoKKiQq82gfzaNuy6VI1jRQLTLNCI7FrCwcG8+l4ZUUqwu1qW098wuRf2F/W6/fTzAw+TI7VCkhV1dnbCysoK1tbWSEhI0GuEvFwux4oVK1BcXIzTp08zDaN6YrHBpz9H6hMnTmDSpEl6bWmpCgtCQkJQUlKCCRMmANBfWECussPCwkzSB2MMSLGWWP0QTyorKytUVVUhNjbW7PODdKWiogK3b99GYmKiVlmDamMnqRORQDSYMnYi56+vr0dycrJBBeqGdjH+c6UGuy5VW8Tgup6Qms+zPxb0Eh1ocjLoeRubBSwbNxzfXqrpdew3Zkdh0ejhWq9FqVQiJycHYrEYjo6O9PYcqRNpY/mkUCjw5JNPIjc3F2fOnNFrojJDNxYXfORyOQoKCvp1pD516hRGjx4NV1dXnY7d1tZG+7mNGjUKQqEQly5dol2ofX19dSqwUxSFqqoqlJWVYdSoUfDz89NpPeaCWN2UlZWhvb2dDkREsGBpW1M9ISfvuro6JCcn6/w9ALrrRCQQNTU1qfnuGUvCqwmKonDjxg00NTUZtfE1q1SAp/cUGOVYhqJJ3abtSUZ1mNxAPD0lBM9O7dsaSxWlUom8vDxIpVIkJyfDxsZGTcrf2NgIuVxOb895e3v32jZXKBR47rnn8PvvvyMrK8tgBeudjsUFHzIrp79ivT4TSBsaGpCfn4+IiAiEhobSwgJyElJt6iSBqL8Tg1KpRHFxMQQCATgczpBSuZDO/6amJnA4HLr+JRAIIBKJ1CTMltahTWTgRCJvjJM3qRGQYARArZ/IWHJtpVJJi1FSUlKMriR87VCR2taWOehL3daTvrIcMkxu0Tc5AwYsbef3KJVKXL9+HRKJhA48PSHCHbI919HRAVdXV9jY2EAqlSIlJQUvvvgiTp06hTNnzgxoRswwMBYXfCiKgkQi6XcL5Pz584iJiYGPj49Wx7t9+zZu376NhIQEevKmpvqOaiBqamqCo6MjHYhUxwEQZ2epVAoOh2ORM2v6gjh0SyQSJCUl9ToBkq0pPp+P9vZ2emKjr6+v2YfcKRQK5OXl0ScRUwRGUiciwVgsFhslGCsUCrUToKnEKOasBbFZwNKxQYgLdMGLGmo0qo7XmgbHvXBPOFZMCMGV8haN6jhVtHWxViqV9KDGlJQUrdsQiOXRkSNH8Nprr4HFYsHKygqffPIJHn74YYttQRhKWGTwkUr7N1i8ePEiwsPDB9xvJbLnlpYWJCcnw9nZWWthgVwuVxuHYGNjQxfqb9++Tc/4sPQtKlWIGk/Vobs/iGCBz+ejubkZTk5OdCAa7Nk8UqkUXC4XbDYbiYmJg+ZaQMxfiQEoUQ/qUieSy+Vq6qrBWPtg14JGB7viWtVftjpA7yZS4njtYMNGl1SJEC8H/FLAw4e/3QaFv4LSxAjPXkPlWABeure7qVxbF2t9A0/PY7zyyiv44YcfMHv2bJw/fx6NjY1YvHgxvv76a52Px/AXFhd8gO6TXn/88ccfCAwM7HfPVSKRICcnBywWCxwOB7a2trRtj67CArI3XFNTg8bGRrDZbPj7+5vEXcBUdHZ20mo8faTUMplMTbCgr+eaPohEIuTm5tJjjM3lWtBTPdizn0jTd0oqlaq5JA/22s1VC+rZRLp0bBAeHTdcbSJpf04HfU0u1SXjKSgogFAoREpKil6ZJkVRePvtt7Fr1y6cOXOGbkAvKChARUUF0tLSdD4mw18MyeCTnZ0NHx8fBAdrHplLxiR7eHjQpqHkbeqraKuvr0dRURGioqLg7OwMPp8PPp9Pz6Uh4xAs0c6lubkZeXl5CA4ONtiSCPhLsEAyAtJLQwQLxvwMyIBAMu3VUpSEmupEqv1EVlZWtLKSGMqa6yLFXLWgN2ZHoaZVjO/+zL608X4DuqXZ1S0irP+5ROP9A9V6jBV4tm7diu3bt+P06dOIj4/X+RgM/WORwUcqlaK/ZXG5XLi5uWmcBsjj8XD9+nWEh4cjLCzM4P4dVYPN+Ph4tUFkqn5jfD4fYrGYnsvj4+NjEYaWJGiaanqnai9Nz89Ak2JIF1paWsDlco0WNE0FRVFobW2lPwOJRAI3Nzd0dnbC09MTcXFxZs+O82vbsP1cBbJuNg/q6+rjEadJmq3KuhkRWDpO84UnyUw6OjqQmpqqd+D56KOPsG3bNpw6dQpJSUk6H4NhYIZk8NE0BI6iKJSXl6OsrAzx8fHw8/Mz2LFAoVCgsLAQbW1tWhlskrk8fD7f7HN5yOdRWVmJhIQErfpgjPGaqoKFjo4OuLu701mRLsIMPp+PgoICjBgxAsOHa9/LYW4oioJAIEBBQQGsrKwgk8ng6uqq1k9kTkgtSFMzpznoq9+nv5NSX5kPRVEoLCxEe3s7UlJS9BKHUBSFzz//HFu2bMGJEycwZswYnY9hTGpra7Fu3TocO3YMXV1diIyMxHfffYfU1FSzrssYDMng03MIHEmzSe+Ei4uLwRmPRCJBXl4eANA1I10QiUR0IGpra6NPQIOhGiMycDJkz1wuu2KxmA5ELS0tWg+JI27gcXFxQ6Z3itDW1obc3FwEBQUhPDxcrZ+I1IlIIDL1WIz+MF8tiKL/L5sFbJgzAjHDnLWSVgN913woiqIl+KmpqXoHnh07dmDjxo04duwY3XxuLoir/LRp07Bq1Sr4+Pjg5s2biIiIQEREhFnXZgwsMvgMNEq7tLQUMpkMo0aNgkQiQW5uLiiKQlJSkkHCAgIpzru7uyM2NtbgGoZUKqUDEVGNkUBkyKROTQwkpTYXMplMbTYRUQ8SwQKLxVKznDHXDCFDILW18PBwjX0gxGWCNDWyWCy6n8jYtTJtGKxaEMlkuoNNFEZ5WaGwkgdReyu6ZEqw7J3xyTVhv8eYFeuD5eOD+sx4SODRt3+Koijs2rULr776Ko4cOYIpU6bofAxj88orr+D333/H+fPnzb0UkzAkg09ZWRk6OzsRHh6O7OxsuLu7Iy4uDiwWy+AZPMSM0FR1hp4nYVtbW/okbOiVMAnENjY2SEhIsIiakyaIepAIFoDupk6ZTIa2tjakpKQMuZkoxGIpOjpaq853MhaDZEUSiYTurvfx8RkUU1qguxZ06gYfVTW1OF5lmlMBC8CH98ci0N2ellj3VLMNtNXWX8ZDRqWnpqbqHXj+97//4cUXX8ShQ4dw991363wMUxAbG4uZM2eipqYGZ8+eRWBgIJ5++mmsXLnS3EszCkMy+FRUVKC+vh6dnZ0ICwtDeHg4vc3GYrH0Lu5WV1ejtLR00EZG91RMsVgsOhBp4zOlCsnWPDw8LMbYVBsoikJLSwtu3LgBkUiklg1YimhjIBoaGlBYWKjR3FQbSK2MBOOOjg66udfUdSKxWIzs7Gy4ubnhN74DdphoXIOqbY4m1Vvv2o+qPq6bnrUeiqJQXFyMpqYmgwLP3r178eyzz2Lfvn2YNWuWzscwFeT9vPDCC3jwwQdx9epVrF69Gl999RWWLl1q5tUZjkUGn/5GaVMUhby8PDQ0NCAxMRHDhg0zWFhAURRKSkrQ0NAADoejs8OwMVAqlWhpaaFrJAqFQmv5srGl1IMJGV4nl8vB4XDUXCaIaIN8DpayhahKTU0NSktLkZCQoKaENARSKyN1IkdHRzoQGbNOJBKJkJ2dTV+wsFgsrNmbb9C4Bu0Ci2aINPvbPsQQS+OdsGxCCP17IIEnJSVFb5eRAwcO4IknnsCPP/6IefPm6XUMU2Fra4vU1FRcvHiRvu2f//wnrl69ikuXLplxZcZhSAUfpVKJwsJC8Hg82NnZYdKkSQYHHjKoTiQSISkpySKsclQl3DweDxKJRK2XSDUbIFJqY4zqHmykUilycnL6dFwQiURqvnvOzs5qvnvmDrIVFRUoLy8Hh8Mx2TwX1TqR6vh0Hx8feHp66l0n6urqQnZ2Nry9vXv1T+XXtuHtX0pRUG/46G42C3ggyR8/5dRr9fj+tt/en+EDD2U7urq6YGdnR1+w6PvZHzlyBMuXL8d///tfLFiwQK9jmJKQkBDce++9ak4K27dvxzvvvIPaWg3zyocYQyb4kE5xhUKB4OBgVFRUYNy4caAoSu/6jkgkApfLha2trcXWSMiWDI/Ho+eREK8xsViM6upqo151DxZdXV3IycmBm5sbRo0aNeA2oVQqVauV2dnZGa1WpitkpHtNTY3ertr6oFon4vP5kEqlagao2taJhEIhsrOz4efnhxEjRvT52RlLEddXQOnpft0fpOZD5NQCgQBOTk5ob2+Hs7Mz/TloO5/n+PHjePTRR/Htt99i4cKFOr2fweLhhx9GdXW1muBgzZo1uHLlilo2NFSxyODTc5R2Z2cnsrOz4erqiri4OHR2duLq1asYNmwYvS2la42jra0NXC4Xvr6+iI6OHjI1EpFIBB6Ph8rKSkilUjg7O9NWP+Y2/tSWjo4O5OTkwM/PD9HR0ToHDk21MmNkA9pAtmj5fD7tF2gOKIpCZ2en2halm5sbLePu67tAfksBAQGIjIwc8LNXHWttTFgAXp8dhbeP3RzwcV8sisPUET70KI2GhgakpqbC0dGRFvCQ0RhWVlZ0IOrru3D69GksWrQIX331FR555BGzZ9B9cfXqVUyYMAFvvvkmHnroIfzxxx9YuXIlduzYgUceecTcyzMYiw8+AoEAeXl5CAkJQUREBJRKZa+OcrlcrpO9C4/HQ2FhISIiInoNqrN0iDuySCTCqFGj0NHR0UvC7efnZxHbUpog9anQ0FCjDN7rmQ3IZDK1bMCY2azqOAdD6gymoK86kerU2o6ODmRnZ2P48OGIiIjQ+rNvaBdj/c/F+L2sxahrfmN2FN45frPPgXLEaPT+pABQFIVbt26hvr6eDjw9Ua2bCgQCSKVSWkHIYrEQEBCAc+fO4cEHH8Qnn3yC5cuXW+RvRJUjR47g1Vdfxc2bNxEWFoYXXniBUbuZEoVCAZlMhsrKSty8eROjRo2Cv78/FApFr222nvURshVB6iOqdQTS9V9RUYH4+HitRjJYEhKJBFwuF1ZWVr2cnVUl3I2NjbCzs4Ofn5/ZmxlV4fF4KCgoMFl9imQDpKdKKBSqjUMwRLBAHJKJX5ilzTlShTiyk34iNpsNd3d3NDU1ITg4GJGRkXodN7+2Dd9dqsaJIoFRnLJZABKHu4Jb007fdlekB2L9XeHjbIup0d4Y5mpPB566ujqkpqZqpf5TddsoKCjAww8/jODgYNTV1WHdunXYuHHjkNnt+LtikcGHNEryeDwkJyfDzc1NY+DpierJh8fj0YPR/Pz84OXlhZs3b6K5udmsXf/6IhQKkZOTA3d39wFrJGRbikh3VSeV6irhNhbV1dW4efPmoAb9rq4uOiMiLhPkc9BFvqxQKMDlciGXy+lG5qGCUqmkFXlWVlagKEqtn0ifzDC/tk1rRwJDyEj0w6b5I1FWVoba2lqtA48mfv75ZyxduhRRUVEoKyvDsGHDMH/+fLz11luDVrNjUMcigw/5sZAOfX2tckjvRENDAzo7O2FlZYXw8HAEBAQMqRMIMdgMCgrSabsE+GsrgmQDFEXRmeFgdNWrGrMmJSWZRcYOqA8KVLW5Ud2W0oRMJkNubi7YbDY4HM6Qmt8EAK2trcjNzUVERASCgoLoizMiXtHXe89UtaCefDjLD44iAVJSUvSur+Xm5mLevHl4/fXX8cILL0AsFuPUqVM4efIkPvroI4t0or8TsMjgo1AoIBaLjeJYIBQKkZubC0dHR3h4eNBDwdzd3emTjyX2jxBIA2N0dLTBBpuqUzqJWkrVgdrYaj/Sfd7Y2GjW4nxPNMmXNTX3kplQ9vb2SEhIGHInqZaWFuTm5iIqKgpBQUG97idSdoFAgJaWFnpYoLaqsYZ2MV4+UIRrlW2megu4PxxYlzFa7+9Ofn4+5syZg7Vr1+KVV16xiO1nhm4sMvjI5XKIxWL6b323iUhxe/jw4WrKHrFYTJ+AW1tbadNPPz8/iykiUxSFyspK3L592yRbVX3VR8hJ2NDMkEyRFQqFFtM/pQlNzb3e3t5wc3NDVVWV1lJwS6OpqQl5eXla2/2oDgskqjFVBWF/7/+T02X4PxM5I3y3eCTGRunuGgEARUVFmD17Np555hls2LCBCTwWhkUGn6VLl6KsrAwZGRmYP38+AgMDdf7i1NTUoKSkZMDidk/TT9LISBRj5oDIeXk8HpKSkgZlT7qrq4v+HNrb22nZrq7bMUD3iSwvLw9KpVIvR3BzQcQrdXV1qK2tpesjRL5sySIDVRobG3H9+nXExMToNcOpZ0CWy+UDKggb2sXIKm3EW7/0L53Whbmxnnj/gUS9nltSUoLZs2dj+fLlePfdd80WeDZu3Ig333xT7bbo6GgUFxebZT2WhEUGn5qaGuzbtw+ZmZm4ePEiUlNTkZ6ejvT0dISEhPT7RSK9AHV1dUhISNDJGVkmk9E/uKamJjg4ONCByNju032hUCiQn5+Prq4us2UMmkYhaOssMNS3qkgPUkBAAAICAujPob29fVDHYuiLQCDA9evX9faZ6wlFUejo6KA/B6FQSFse+fj49Pp+GloLemasJxoEzZg7NgbjovTzVywrK8OsWbOwaNEivP/++2bNWjdu3Ih9+/bht99+o2+ztrYeck3hpsAigw+BoijU19fjwIEDyMzMxLlz55CQkEAHop5NcuTELRQKweFwDMpciFyVSJdtbW3h5+c3YIHaEIiLgyYptbnoGZD7K9QTRd5QMzclkOJ8aGhorym5EolETbDg6OhIZ0Sm+j7oCpGym3IOUl91Il9fX7i4uIDFYhnUF/RULLBsRore2X5FRQVmz56NtLQ0fPrpp2b/Dm7cuBEHDx4El8s16zosEYsOPqpQFIXGxkY6EJ0+fRoxMTF0ILK3t8dTTz2Fl156CVOnTjXqiVuTdJlkRGQWjaEQYQRxcTD3j0YTCoVCLSCTz8HX1xdsNhtcLhcBAQGIioqyiJOxLpAaSV/FeVV6XphYW1vTgchcUnYiTElISBg0KbuquwD5HFQl/YX1HVizrxB1bRKtj7l6ynA8OTVq4AdqoKamBjNnzsSMGTOwfft2i/gNbdy4Ee+//z7c3Nxgb2+P8ePHY/PmzQgO1jwG/E5iyAQfVYgN/+HDh7F//36cOHECSqUSERER2LFjB1JSUkz2xVMqlfQsGj6fT1u7+Pn56X3iaW1tBZfLRWBgoFaWJ5aA6ufA4/Egl8vh6uqK8PBwk1vcGBs+n4/8/Hy9RmmQz4FkRUqlkt6S8vb2HpTPoa6uDsXFxWb1+FP9HAQCARQKBd1PtKegEzsv1Wh1nL5GZA9EfX09Zs2ahcmTJ2Pnzp0W8/07duwYOjs7ER0djfr6erz55puora1FQUHBkOs1NDZDMviocvDgQTz66KOYO3cuJBIJTp48CX9/f6SnpyMjIwNJSUkmDUStra30CZiiKDWbH21el1j9aHPFbYk0NDSgoKAAoaGhUCgUahY3mlwmLA1y4o6Li4Ovr69BxyJSdhKIxGIxrSA01YC42tpalJSUIDExEV5eXkY/vj4Q4QYJREKhEJ8W2eBWa98zuoC+B8YNBI/Hw+zZs5Gamorvv//eYgKPJlpbWxESEoJt27bhscceM/dyzMqQDj55eXmYNGkS/v3vf9OW6J2dnfjll1+wf/9+HDt2DJ6enpg/fz4yMjIwevRok30xyYmHx+PRJ2ASiPq6Aq6srERZWdmQtPoBgKqqKty6dUvtipsUqElm2NXVpaYYsyTlG1m/qU7cpMmZz+fTA+L0VRBqgrhGJCUlmWykgzEgdaIT12vxv6Iu8MV/XZTFuisxPX44Jo4YplfG09jYiDlz5iA2Nha7d++26AsdwujRozF9+nRs3rzZ3EsxK0M6+ADdP8C+Moauri6cOHEC+/fvx9GjR+Hk5IS0tDRkZGRg/PjxJvuikhMwCURisZjOBHx8fGBlZYXS0lJ6eJ2bm+4/OnNCvLZqa2uRlJTU7/p7noAtobmXePxVVlYOmutCT+NPYgLr6+url5KyqqoKZWVlZnWN0AeZTIYLRdU4f6MWAbZiRHnZ0h6Eum5bNzc3Y+7cuQgPD8ePP/5oURc2fdHZ2Yng4GBs3LgR//znP829HLMy5IOPtojFYvz222/IzMzEoUOHYG1tjbS0NCxYsACTJk0ymbJMdR4PkaqS1xqsHh5jolQqcePGDTQ3NyM5OVknRSFp7iVKKRcXFzUJ92BApPj19fVITk42y757z0K9jY0NfWHi7u4+4AmYDLEjvodDDdWtQoqi6O+EUqlU6yfq7+KwtbUVaWlpGDZsGDIzMy22B2vt2rVIS0tDSEgI6urqsGHDBnC5XBQVFQ3J3Q5jcscEH1VkMhnOnDmD/fv34+DBg1AoFJg7dy4yMjIwdepUk32RpVIpsrOzIZfLYWNjQ4+JJidgS/0BEcg4B7FYTPvu6QsZDsfj8dDc3Ez3VKlKdo0NsftpamrSOXCaCoVCoVaoJ3VDHx8fjd57xCdvMIfYGRNSY+NwOGo9eKp1IrJd25cjeXt7OzIyMuDq6orDhw9btD3WokWLcO7cOTQ1NcHHxweTJk3Cpk2bEBERYe6lmZ07MvioIpfLceHCBezduxcHDx6EUCjE3LlzkZ6ejnvuucdoTZ5kcqerqytGjRoFKysriEQiekuqra0Nbm5u9BaEpdnRyGQyuleBw+EYNVMkXms8Hk8tE/D19TWalF2pVKKgoAAdHR1ISUmxyBNWT+89Mj6dZAJVVVWora01W8ZmKPX19bhx40avwKMJ4kguEAjQ2tqK9vZ2XLp0CXPmzMGmTZtgb2+PI0eOWGyzL8PA3PHBRxWFQoFLly5h3759OHDgAFpaWjBr1iykp6djxowZel8pEyl1fz0wEomEPumobkn5+fmZ/QcmFouRk5MDR0dHxMfHm1RNpFQqadNPPp8PAHQgGshjrC9IxiaRSJCcnDwkagNku1a1XsZisRASEoLhw4db3MXJQJDAo4+4QyqV4uLFi9iyZQt+//132NjYYMWKFXjggQcwefJki2jGZtAdJvj0gVKpxNWrV+lAVF9fjxkzZiA9PR2zZ8/W+sqTz+ejoKBAJym1qv1/U1OTWSeUdnZ2Ijc3F15eXoiJiRnUxj0ysZacgFUVhF5eXloJRuRyObhcLiiKMnrGNhhQFEWLUwIDA9HW1qaz5ZG5aWhoQFFRkUGqQrFYjIULF6K9vR1r167Fr7/+ip9//hk2NjaoqKiwiIZSBt1ggo8WKJVKcLlcOhBVVFTgnnvuQXp6OubOndvnpFAi5TWkh6TnhFJ7e3va5sdUtRECydh0HbtsCnpKuEUikdo4CE3ZDLErsrGxQWJiokX3f2iCoigUFxejsbERKSkpdAZMLI+IYMHOzk5NsGBJgYhY/iQmJurdACuRSPDII49AIBDg5MmTtKxcqVSirKwMUVH6OSIwmBcm+OgIRVEoLCykjU+Li4sxbdo0ZGRkYO7cufDy8oJSqcR3332HyMjIAaXIukDsbUhtxNbWlr76NfaobIFAgPz8fERGRlqkFUjPLSki3CDFabJV6OTkhPj4+CF3ZUxRFIqKitDS0oKUlJQ+t9mI9RMJRsRxg4xCMGfAJYHHEMsfmUyGJUuWoLKyEqdOnbKYRloGw2GCjwGQLZH9+/dj//79uH79OiZMmICGhgZ0dHTg999/N5nBI1FJ8Xg8Nb854qtlSCCqq6vDjRs3jOaMbGpIEyOZz+Tk5ES7CwzVwFNYWIi2tjadxBHEcYN8FsRpor9RCKaCWBYZEnjkcjlWrFiB4uJinDlz5o6XJv/dYIKPkaAoCteuXcODDz6IxsZGiEQiTJw4EfPnz0d6ejoCAgJMth1C5q+QQERRlN5F+oqKCty+fdui7Fp0gUzvtLGxgUQioV2XB3MshiEQVV5nZydSUlL0lt9rGhbYMzs0FWSsQ3x8vN7bzQqFAk8++SRyc3Nx5swZi7oI2rJlC1599VWsXr0aH3/8sbmXM2Rhgo+RKC8vx8yZM5GQkIB///vfaGxsxP79+5GZmYlLly5h9OjRtM1PcHCwyU6CxHSVnHQUCoVakb6vbRjV5suh2PwKAG1tbcjNzUVQUBDCw8N7uXCbQsJtTJRKJT3LKSUlxaiqPCLrJ9Jloqb08fEx6nhzEngMGeugUCjw3HPP4ffff0dWVpZWk1gHi6tXr+Khhx6Cq6srpk2bxgQfA2CCj5Goq6vDN998g3/9619qmQZFUairq6NHQZw/fx4JCQnIyMhAenq6SQv5pHGPuCtIpVKNhp9KpRJFRUVobW1FcnKy2aXd+kBGpoeHhyMkJKTX/WSbkpyASW3EEAm3MVEqlcjLyxsUOThRUwoEArUZTT4+PgbVDhsbG5GXl2dQ4FEqlXj++edx+vRpnDlzRuO/pbno7OxEcnIyvvzyS7zzzjvgcDhM8DEAJvgMIsRK5ODBg8jMzMSZM2cQExNDB6KYmBiTBqLOzk46EBG1mLe3NxoaGiCXy5GUlGTxLguaIOKI6Ohora6SVd3ISXaoGpQHu0ivUCiQl5cHmUyG5OTkQa3N9JxVxWaz9QrKZHR3bGys3ltkSqUSL7/8Mo4cOYKsrCyEh4frdRxTsXTpUnh6euKjjz7C1KlTmeBjIEzwMRNke+zQoUPYv38/fvvtN4SHh9OjIEaNGmXSq/HOzk7U19ejqqoKSqUSnp6etIR7KDRhEsgQNX2vtkl2SAKRWCxWc+E2dSBQKBTgcrlQKpVISkoyqyuzalAWCASQy+Vqcva+1kYG8RkaeF5//XXs3bsXWVlZFief3rNnDzZt2oSrV6/C3t6eCT5GgAk+FkJbWxt+/vlnejheYGAgHYg4HI7RA5FIJEJOTg6cnZ0RERFB10ba29vh7u5O2/xYog0NoaamBqWlpUYbotbTVcDU3ntyuRy5ublgsVjgcDgWNQ5Ata+KzORRnU1EPovm5mZwuVyMHDlS50F8qq/11ltv4fvvv0dWVhZiYmKM+VYMprq6Gqmpqfj111+RkJAAAEzwMQJM8LFAOjo61GYSeXt70w7co0ePNjgQdXR0IDc3Fz4+Pr22+ojzNI/HQ1tbG1xdXemMyJIsXYizM4fDMdksm57ee66urnQgMrQuJpPJkJubCysrK3A4HItvgO3q6qIDEfksnJ2dUV9fj5EjRyIgIECv41IUhS1btuCrr77CmTNnEBcXZ+SVG87BgwexYMECtX8jhUIBFosFNpsNiURi8f9+lggTfCycrq4uHD9+nJ5J5OzsTKvmxo8fr/OXvqWlBVwuFyEhIQgLC+u3xiSRSCAQCMDj8WhLFxKIzOUITVEUysrKUFNTM6jOzuSz4PP5Bs/jkclkyMnJGbLOCxKJBJWVlaisrASLxYKjoyOdEbm6umr9WVAUhY8++ggfffQRTp06BQ6HY9qF60lHRwcqKyvVblu+fDliYmKwbt06iwyYQwEm+AwhxGIxfv31V3omka2tLZ0RTZw4ccD6BPGZGzFiBIYPH67TaxNLFx6Pp+Y3p+8wNH2gKAolJSXg8/lITk42qkRYF3paHhF7G22cJqRSKXJycmBvb4+EhASzq+z0gfRSRUdHw8/PjxYsNDY20s3OAw2HoygKn3/+ObZu3YoTJ05g9OjRg/wuDIPZdjMcJvgMUaRSqdpMIqVSiXnz5tEziXqKBkh9xBCfOYJcLlez+SFSXV9fX52ufHVBVQ7en93MYKNJLabqNKF68iXznIg7+FAMPK2trcjJydF4AUOanclnoaoiVDWCpSgKO3bswJtvvoljx45h/Pjx5ngrBsEEH8Nhgs/fALlcjvPnz9MziUQiET2TaNq0aXjrrbcgl8vxr3/9y+j1EXLyJYHI2traJLN4rl+/DpFIhOTkZIuVg6uefPl8PpRKJS1bdnZ2BpfLhYuLi8mVjKaitbUVubm5iIyMHNChXVVFKBAIIBKJcODAAXqI2tatW3HkyBFMmTJlMJbOYIEwwedvhkKhwMWLF2kH7oaGBrBYLLz22mt45plnTNpASmbxqDZy9pUFaAuRIsvl8kHvgTEE1cFwPB4PYrEY9vb2iIiIGBQJt7Fpa2tDTk6OVoFHE0KhEO+99x52796Nuro6xMfHY9myZcjIyLC4fh6GwcHsl19ffPEFQkNDYW9vj7Fjx+KPP/7o9/F79+5FTEwM7O3tER8fj19++WWQVjo0sLKywuTJk/Hee+9h3Lhx8Pf3x6OPPop///vfCA0NxSOPPIK9e/eio6PD6K9NGhRHjRqFKVOm0IXYgoICnDt3DoWFhRAIBFAqlVodTyaTITs7GxRFISUlZUidsFksFtzd3ekTta+vLwICAlBZWYmzZ88iJycHNTU1kEgkZl7pwJDAExERoVfgAQBHR0fExsaira0N//3vf/H000/jxIkTiImJwY4dO4y8YoahgFkznx9//BFLlizBV199hbFjx+Ljjz/G3r17UVJSorEucfHiRUyZMgWbN2/GvHnzsHv3bmzduhU5OTmM4qQHy5cvR1FREY4cOQIfHx8olUrk5ubSoyCqqqowffp0pKenY86cOUYfyaBKz6Fwcrkc3t7e8PPz69NvTiKRqBXmh5oiDOhWKmZnZ8PHxwfR0dH050tky6Svys3Njc4QLaWWRWhvb0d2dnaftkXakpmZiSeffBI//vgj5s2bR9/e1tYGiqLg7u5uhNUyDCXMGnzGjh2L0aNH4/PPPwfQvW0TFBSE5557Dq+88kqvxy9cuBBCoRBHjhyhbxs3bhw4HA6++uqrQVv3UKC8vLxP00iKolBQUEAHotLSUrWZRJ6enib3myPbURKJhA5EpIueNMC6uroO2fqIUChEdnY2/Pz8MGLEiD4/TzI+XSAQoLm52aImlJLAExYWhtDQUL2Pc+TIESxfvhz//e9/sWDBAuMtkGFIY7bgI5VK4ejoiH379iEjI4O+fenSpWhtbcWhQ4d6PSc4OBgvvPACnn/+efq2DRs24ODBg8jLyxuEVf/9IPJlMpMoPz8fU6ZMQXp6OtLS0uDr62tyvzkSiEQiEdzc3NDZ2QkfHx/ExsZanPO0NnR2diI7OxsBAQGIjIzU+j1omlprahVhX3R0dCA7OxuhoaEGBZ7jx4/j0UcfxXfffYeHHnrIeAvUg+3bt2P79u2oqKgAAIwaNQpvvPEGZs+ebdZ13amY7ZKysbERCoWilx+Xn58fGhoaND6noaFBp8czDAyLxUJMTAz+9a9/ITs7Gzdu3MCMGTPwv//9D1FRUZg9eza2b9+O2tpaGPs6hcViwcXFBREREZgwYQLi4+PR1tYGFouF+vp65ObmoqamBlKp1Kiva0o6Ojpw7do1DB8+XKfAAwA2Njbw9/dHYmIipk6diqioKHr78fz58yguLkZzc7PWNTN9IYEnJCTEoMBz6tQpLFmyBDt27MCDDz5ovAXqyfDhw7FlyxZkZ2fj2rVruPvuu5Geno7CwkJzL+2OxHLMpBjMDovFQmRkJNatW4eXX34ZVVVV9EyidevWYfTo0UhPT0d6errRZxK1traisLAQ4eHhCAsLg0gkAo/HQ11dHYqLi+Hu7k5nAZbqN9fe3o6cnBzaPcIQVCfTqkq48/PzQVGUmvO0MethJGsLDg426D2cO3cODz/8MD777DM8/PDDFpHBpqWlqf29adMmbN++HZcvX8aoUaPMtKo7F7MFH2Jdz+Px1G7n8Xh9OuMOGzZMp8cz6A+LxUJISAheeOEFrFmzBnV1dcjMzERmZibWr1+PxMREehREeHi4QScX4oocFRVFq6kcHBzoLR/iN8fn81FaWmpUjzVjQRRhhtZHNMFms+Hl5QUvLy/ExMTQEu6SkhJ6RpOPjw98fHwMMiclgYcM49OXixcv4qGHHsKHH36IZcuWWUTg6YlCocDevXshFAqHZJPr3wGzCw7GjBmDzz77DEC34CA4OBjPPvtsn4KDrq4u/Pzzz/RtEyZMQEJCAiM4GCQoigKPx6NnEmVlZWHkyJF0IFJVdWkDuZqPjY3VyhVZKpXSgahngd5cdjuk+TIiIgLBwcGD9rqaRmV7eXnRWZEuozGEQiGuXbuGwMBAREZG6r2mP/74AxkZGXjnnXfwzDPPWFzgyc/Px/jx4yEWi+Hs7Izdu3djzpw5Jn1NhUJBZ6dKpbLXsElL+4wGC7NLrZcuXYr/+7//w5gxY/Dxxx/jp59+QnFxMfz8/LBkyRIEBgZi8+bNALqvqO666y5s2bIFc+fOxZ49e/Duu+8yUmszQVEUmpub1WYSRUZG0qMgYmNj+1WqkS01fS1/iN8cn89HU1MTHBwc4OvrCz8/v0HzmyMjBfTxyzM2miTcZDRGfxJu1cBjyGTdnJwcpKWlYf369VizZo1FnlSlUimqqqrQ1taGffv24euvv8bZs2cRGxtrktcjgYeiKKxduxbl5eXw9fXF4sWLMWnSJPo+S/ysTI3ZHQ4+//xzvP/++2hoaACHw8Gnn36KsWPHAuj2TwoNDcWuXbvox+/duxevv/46KioqEBUVhffee8/kVy4MA0M6+slMopMnT2L48OF0IEpMTFQLRGVlZaisrASHw4Gnp6fBr0/85ohSzNbWlg5EplKKke1CbSeoDiZisZgOzKqO5D3l90QS7u/vr7NAQpXr169j7ty5eOmll7Bu3bohczKdPn06IiIi8H//939GP7ZqUBkzZgzYbDbmzZuH3377DWKxGMuXL8eKFStgY2NzRwYgswcfhr8nHR0dOHr0KPbv34/jx4/D29sb8+fPR3p6OjIzM3Ht2jUcOHAAbm5uRn/tnmafqsV7Dw8Po/zIydhoQ4aoDRZSqZQOzCRDJOMPiouL4e/vj6ioKL0/l6KiIsyePRvPPvss3njjjSF1Er377rsRHBysdoFrbFasWIGqqir89ttvAIDHHnsMP/zwA0aOHIkVK1Zg5cqVsLW1veMCEKN2YzAJLi4uWLRoERYtWgShUEjPJJo1axYUCgUyMjJQUFCAcePGGd29oKdSrLm5GXw+H9evXweLxYKPjw/8/Pz09psjoykMGRs9mNja2iIgIAABAQGQy+VoampCXV0dKioqYGVlRavp9AnMJSUlmDdvHp544gmLDzyvvvoqZs+ejeDgYHR0dGD37t3IysrCiRMnjPo6qjUeoVAIf39/usdp1apVOHv2LM6dO4dXX30VH374IcRiMVatWmUx4pnBYui1jpsQXXzmdu7cicmTJ8PDwwMeHh6YPn36gL50dypOTk7IyMiAs7Mz/P398eWXX8LFxQULFy7EiBEj8PzzzyMrKwsymczor81ms+Ht7Y3Y2FhMmTKFHmVQUFCAs2fPoqCggLb/1wYej4f8/HzExcUNicDTE2tra7i6uqKzsxNBQUGIj4+HUqlEfn4+zp49q5P/3q1btzBv3jz84x//wNtvv23RgQfovmhYsmQJoqOjcc899+Dq1as4ceIE7r33XqO9RmtrKx149u7dCycnJyxfvhx33303fv75Z1y4cAH/+c9/kJqaimXLlqGpqQnff/89CgoKjLaGoQKz7fYnuvrMPfLII5g4cSImTJgAe3t7bN26FQcOHEBhYaHF7f9bAl9//TU+/PBD/Prrr3RhXiqV4vTp0/RMIgD0TKK77rpLJ7WWrpAaFY/HA5/Ph0wmU7P50ZSN1dfX48aNG4iPj4ePj4/J1mZKRCIRrl271stvrqf/Hvk8fH19adsjVSoqKjBr1ixkZGTg448/HpIWSMZm06ZNKC4uxmeffYb09HSIxWJcunSJ/mw+/PBD7N+/HydOnICLiwu+/vpr3L59G1OmTMGsWbPMvPrBhwk+f6Krz1xPFAoFPDw88Pnnn2PJkiWmXu6QQ6FQoL29vc95QnK5HOfOnaNnEonFYsybNw/p6em4++67TdpYSlEUOjo61MYfqJ54bWxsaGVeYmIivLy8TLYWUyIWi3Ht2jW6X6ivTEX18+Dz+RCJRPD09ER9fT2SkpIgl8sxc+ZMzJo1C19++SUTeP7kzJkzuOeeexAUFARnZ2dcuXIFzs7O9DbcBx98gN27d2P16tUICAjA0qVLsXnzZixdutTcSzcLTPCBfj5zPeno6ICvry/27t2r5trLoDsKhQK///479u3bh4MHD6KtrY2+yr733ntNujdOURSEQiGdEQmFQjg6OqKrqwsJCQkGT4E1FyTweHp6YuTIkTptkQmFQvD5fKxatQoXL16EtbU1OBwOfvzxR4Ocrv9OkAAzadIkXLx4EQ888AC++OILtQy5sbERDz/8MMrKytDV1YWFCxfe0ZNQmeCD7n6TwMBAXLx4Ua3b+eWXX8bZs2dx5cqVAY9B5pMUFhZarP3LUESpVOLKlSv0cDw+n48ZM2YgIyMDM2fOhIuLi0lfv6ysDBUVFbC3t4dIJIKHhwctZrDUiao9EYvFyM7OhoeHh86BRxUej4d77rkHHh4ecHFxwYULF5CSkoJXXnnljnWrJgo10jy6a9cu+Pr64v7778f8+fOxdetWhIaG0sGJBPLW1lYkJSWZe/lmhVG7GYEtW7Zgz549yMrKYgKPkWGz2Rg/fjzGjx+P999/Hzk5Odi3bx/eeecdPPnkk5g+fToyMjIwZ84co/fzVFZWoqqqCikpKXB3d4dIJAKfz0dDQwNKSkoseg4PQSKRIDs7G+7u7gYFHoFAgLS0NIwePRr/+9//YG1tDYFAgJ9//tkkcvmhgKqqjcfjwdnZGQsWLICbmxtOnz6N6dOnQy6XY9u2bQgJCUFTUxN27dqFF1980cwrtwyYzAeGbbt98MEHeOedd/Dbb78hNTV1EFbLAHRnRKoziW7evEm7FBtjJlF5eTkqKiqQnJys8eRK5vCQJk4XFxe1OTyWAAk8ZC6Svp9Hc3Mz5syZg4iICPz0009DaqKsqVC1yVm3bh0uXbqE1tZWeHl5YcuWLRg7dizy8vJw1113YezYsViwYAG2bduG+Ph47N+/38yrtwyY4PMnuvrMAcB7772HTZs24cSJExg3btxgLpdBBYqiUFxcTAeigoIC3HXXXfRMIh8fH51OvGVlZaiurkZKSopW23pSqVTN5sfJyYl2VzDXQDipVIpr164ZHHhaW1uRlpYGf39/ZGZmmlSBqA2bN29GZmYmiouL4eDggAkTJmDr1q2Ijo42y3qeffZZHDp0CDt27ICTkxNWrlwJiUSCa9euwdvbGyUlJViyZAns7e0RFRWFr7/+2izrtESY4PMnuvrMbd26FW+88QZ2796NiRMn0sdxdnY2m8ElQ3cgKisrowNRbm4uJkyYgPT0dMyfPx/+/v79qrzKyspQW1uLlJQUvf4d+xoI5+fnBxcXl0EJRCTwuLi4IC4uTu/XbG9vR0ZGBtzc3HDo0CGL2FKeNWsWFi1ahNGjR0Mul+O1115DQUEBioqKBj3jLCsrw+LFi/H5559jzJgx+Pbbb/Hyyy9jx44duO+++9DV1QVHR0d0dnaiq6tryIpVTAUTfFTQxWcuNDQUlZWVvY6xYcMGbNy4cRBXzdAXFEWhsrIS+/fvx4EDB3D58mWMGTOGnkkUFBREn5iVSiVKSkrA5/ORmppqlBOZQqFAY2MjeDweGhsbYWNjQwciNzc3kwQiqVSK7OxsODk5IS4uTm8ZdGdnJ+677z7Y2tri6NGjFlvTEggE8PX1xdmzZzFlypRBfe3c3Fzce++9qK6uxsGDB/Hkk0/im2++wYMPPoiWlhZ8+eWXWLZsGdP31wdM8GG4I6AoCrW1tfRMot9//x0cDgcZGRlIS0vD5s2bIZFIsGPHDpNIuRUKBW3zw+fz1SyA3N3djdIrQwKPo6Mj7eSgD11dXXjggQdAURSOHj1q0Zn8rVu3EBUVRbtOmApV3zXyvxsbG7FixQrExMTgq6++wo4dO7Bo0SIAwLVr17Bx40a89tprmDBhgsnWNZRhgg/DHQeZSXTgwAHs378fp0+fhpWVFVauXImVK1dixIgRJt0eI15qPB4PAoEAFEXRgcjT01OvoCGTyZCdnQ0HBweDAo9YLMbChQtpPz5XV1e9jjMYKJVKzJ8/H62trbhw4YLJXkdV1SaVSqFUKmFvbw+lUol//OMf2LNnD9atW0dvyTc1NWH+/PkICgrCnj17TLauoQ4TfCyYL774gt4GTExMxGeffYYxY8YM+Lw9e/Zg8eLFSE9Pp21rGHqjUCjw+OOPIysrC6tWrUJWVhZOnTqFqKgozJ8/HwsWLMDIkSNN2sFPbG1IU6tCoaCHwXl5eWllukoCj729PRISEvRer0QiwSOPPILGxkacPHkS7u7ueh1nsFi1ahWOHTuGCxcumGSWEpfLBYfDof/+7LPPcOTIEcjlcixatAgrV64EAEybNg3V1dUYN24cPD09cfnyZdja2po0IP4dYHwxLJQff/wRL7zwAjZs2ICcnBwkJiZi5syZ4PP5/T6voqICa9euxeTJkwdppUOXDz/8EJcuXcKFCxfw8ssv4+jRo+DxeHj55ZdRWFiIKVOmICUlBRs2bACXy9XKbFNXWCwWPDw8EBMTg8mTJyM5ORm2trYoLS3F2bNncf36dTQ0NEAul2t8vkwmQ05ODuzs7AwKPFKpFEuXLkV9fT2OHz9u8YHn2WefxZEjR3DmzBmTBJ5DV6WmhwAAEnJJREFUhw4hOTkZP/zwAwBgx44dWL9+PeLi4hAYGIgnn3wSa9euBdBtq7NkyRK6xpeens4EHi1gMh8LRR+vOYVCgSlTpmDFihU4f/48WltbmcynHzo7OyEUCuHn56fx/vb2drWZRL6+vnRGlJKSYvKMqLOzk86IRCIRvLy84OvrCx8fH9jY2EAulyMnJwc2Nja9hvXpgkwmw2OPPYaSkhKcPn3aok1TKYrCc889hwMHDiArKwtRUVEmeZ3W1lZs3boVH374IXbt2oWOjg54eXnhgQcegEKhwI8//kjP4iHtGQy6wTgcWCCkcPzqq6/St7HZbEyfPh2XLl3q83lvvfUWfH198dhjj+H8+fODsdQhzUCyeFdXVyxevBiLFy+GUCjEsWPHkJmZifnz58PNzQ3z589HRkYGxo4da/SZRCwWCy4uLnBxcUFkZCQ6OzvB5/NRVVWFoqIieHh4QCQSGbzVJpfL8dRTT6GoqAhnzpyx6MADAM888wx2796NQ4cOwcXFBQ0NDQAANzc3gxV5qqICd3d3bN68GVZWVnj00Ufh6+uLTz/9FED3vKiHH34YNjY2tCkoE4B0h9l2s0AaGxuhUCh6XZH7+fnRP7aeXLhwAd988w127tw5GEu843BycsIDDzyA3bt3o6GhAZ999hk6Ojrw0EMPITo6GmvWrMHZs2f73B4zFGdnZ4SHh2PcuHEYO3Ysurq6IJPJ0NraitzcXFRVVUEsFut0TIVCgeeeew7Z2dn47bff+swALYnt27ejra0NU6dOhb+/P/3fjz/+aPCxWSwWKisrcfr0aQDAl19+CZFIhK1bt6KpqQlFRUVqj3/wwQexZ88efPHFF7TYgEF7mMznb0BHRwceffRR7Ny5E97e3uZezt8eBwcHuldIKpXit99+Q2ZmJpYsWQIWi4W5c+diwYIFmDJlitEdAeRyOYqLi+Ho6AgOhwOZTEaPgigtLYWrqyvdS9RfJqBUKrFmzRpcuHABZ86cQUBAgFHXaSpMVSWgKApdXV144oknYGNjg+PHj+ODDz7AgQMHMGvWLCiVSrzyyitwd3fH6tWr6efNnz8f586dY6y19ICp+VggunrNcblcJCUlqW39kOI4m81GSUkJIiIiBmXtdzIymYyeSXTo0CFIJBLMnTsXGRkZmDZtmsEOAQqFArm5uWCxWOBwOL22+iQSCQQCAXg8HlpaWuDs7Aw/P79efnNKpZIWWGRlZSEsLMygdf2dyMvLwyOPPIKioiK8+uqr2LRpE4DusRJfffUVXnrpJbz33nu02IBBf5jgY6Ho4jUnFotx69Yttdtef/11dHR04JNPPsGIESPM7sl1p6FQKHDhwgV6JlF7eztmz56NjIwMTJ8+XedGVhJ4APS60NCETCajA1FTUxNYLBaOHTuG+++/H4cOHUJmZibOnDljsoL9UEUoFGLq1Kno7OxEdHQ0VqxYgfnz59P3ff3111i3bh3WrFnDbLUZCBN8LBRdveZ6smzZMkbtZiEolUpcvnyZDkQCgUBtJtFADgIKhYKWeicnJ+ssbpDL5cjPz8f69etx7tw5AMDy5cvxxBNPIDU11SzGp5ZMZ2cnKisr8eyzz8Le3h5PPPEEPa+Ioihs2bIF33//PXJyckw62PDvDiM4sFAWLlyIDz74AG+88QY4HA64XC6OHz9OF4WrqqpQX19v5lUyaAObzcaECROwbds23Lp1C6dPn0ZUVBTefvtthIaGYtGiRfjhhx/Q1tbWq6ahUCiQl5cHpVKpVcajCTJ5dMKECfDw8MAHH3wAkUiE6dOnIzQ0FIWFhcZ6q38LnJ2dMWrUKGzbtg1SqRQ7d+7ETz/9BAB47rnnUFVVxQQeI8BkPgwMZkKpVOL69evYv38/MjMzcevWLdxzzz2YP38+5s2bB1tbWzzxxBN45JFHMHPmTFhb66cPoigK27Ztw8cff4zTp08jMTERQHeN6NSpU5g2bZrFGoeam4KCArzyyiuorKyEra0tKisrcfHiRYwYMcLcSxvyMMGHgcECoCgKN27coEdBFBYWwtnZGQ4ODjh27BgiIyP12h6jKAqfffYZ3nvvPZw8eZJRZelBeXk5Tp06hfr6eixZsgQhISHmXtLfA4qBQUs+//xzKiQkhLKzs6PGjBlDXblypd/Ht7S0UE8//TQ1bNgwytbWloqKiqKOHj06SKsduojFYuruu++mAgMDKQ6HQ1lbW1NTpkyhtm3bRt26dYvq7OykhELhgP91dnZSH374IeXm5kZdunTJ3G+LoiiKOnv2LDVv3jzK39+fAkAdOHDA3EtiMBNMzYdBK3T1mpNKpbj33ntRUVGBffv2oaSkBDt37mRmmwyAQqHAwoUL0dbWhoKCAuTk5KC0tBRpaWnYt28foqOjMWPGDHz++eeorq7us++Foih899132LhxI37++WeLmbQrFAqRmJiIL774wtxLYTA35o5+DEODMWPGUM888wz9t0KhoAICAqjNmzdrfPz27dup8PBwSiqVDtYS/zZ8/fXXVFNTU6/blUolVVVVRX388cfUlClTKCsrK2r06NHUpk2bqIKCAjoj6uzspLZv3045OztTZ86cGfw3oCVgMp87GqbmwzAguja9AsCcOXPg6ekJR0dHHDp0CD4+Pnj44Yexbt06o/ug3YlQFIWGhgYcOHAAmZmZOHv2LOLi4pCeng47Ozts2rQJmZmZmDFjhrmX2icsFgsHDhxQ+04x3Dkw9joMA9Kf11xxcbHG59y+fRunT5/GI488gl9++QW3bt3C008/DZlMhg0bNgzGsv/WsFgs+Pv74+mnn8aqVavQ1NSEQ4cOYffu3Th9+jT++9//WnTgYWBggg+DSVAqlfD19cWOHTtgZWWFlJQU1NbW4v3332eCj5FhsVjw9vbGY489hhUrVqC2ttYkM24YGIwJE3wYBsTb2xtWVlbg8Xhqt/N4PAwbNkzjc/z9/WFjY6O2xTZy5Eg0NDRAKpUydj8mgsViMYGHYUjAqN0YBsTW1hYpKSk4deoUfZtSqcSpU6cwfvx4jc+ZOHEibt26pTb9s7S0FP7+/kzgYWBgYIIPg3a88MIL2LlzJ77//nvcuHEDq1atglAoxPLlywEAS5YsURt+t2rVKjQ3N2P16tUoLS3F0aNH8e677+KZZ54x11tgsAA6OzvB5XLB5XIBdDdwcrlcVFVVmXdhDIMOs+1mwRAhoiUYPy5cuBACgQBvvPEGGhoawOFwennNqU7TDAoKwokTJ7BmzRokJCQgMDAQq1evxrp168z1FhgsgGvXrmHatGn03y+88AKAbuXkrl27zLQqBnPASK0tFJFIpOa3ZUmBiIGBgcFQmOBjoSxYsAApKSmYPXs2AgIC4O/vr3a/UqkEm83GjRs38O9//xsjR47EokWLmHoKAwPDkICp+Vgg9fX1uHXrFnbu3IkPPvgAHA6HHtlMIBnQ+fPn8f3332PZsmV4+OGH0djYaK5lm4UvvvgCoaGhsLe3x9ixY/HHH3/0+/iPP/4Y0dHRcHBwQFBQENasWQOxWDxIq2VgYCAwNR8LhDjoZmRkYO3atfjoo49QWFgIW1tbUBQFFotFB5+cnByMHj0ae/bsAYvFgo2NjZlXP3gQv7mvvvoKY8eOxccff4yZM2eipKQEvr6+vR6/e/duvPLKK/j2228xYcIElJaWYtmyZWCxWNi2bZsZ3gEDw50Ls+1mgSxZsgT19fX49ttvERQURG+xEUgAunHjBp577jkkJCT0efIkj/07MnbsWIwePRqff/45gO6tyKCgIDz33HO9Ro0DwLPPPosbN26oScZffPFFXLlyBRcuXBi0dTMwMDDbbhZHXV0dSkpKMG7cOAQFBQGAWuAB/hIfXL58GZ2dnbjrrrsAdDsiA4BAIKAfSwKPar/N3wGpVIrs7GxMnz6dvo3NZmP69Om4dOmSxudMmDAB2dnZ9Nbc7du38csvv2DOnDmDsmYGBoa/YLbdLIwzZ86AoiikpKQA0Jy5kGB09epVuLq6YuLEiQD+CjRTpkzBfffdh5kzZ8LGxgbjx49XC2AKhQIsFqtXUBtK6OM3R2pikyZNAkVRkMvleOqpp/Daa68NxpIZGBhUGLpnn78pmZmZ8PHxAYfD0Xg/yXpKS0tx69YtxMbGwtvbGxRFgc1mo6qqCtXV1fjpp5/w3//+F/fddx8SEhJQWVlJH8PKyqpX4Kmrq8MzzzyDF198EbW1tSZ7f+YkKysL7777Lr788kvk5OQgMzMTR48exdtvv23upQ05dBV6MDD0hAk+FoRUKkV0dDSSk5MRGhoKoHdfj+qWW3t7O6ZMmQLgr221w4cPg81m4/XXX8f27dtx+fJlSKVS7N69G5mZmZg9ezYWLlxId5gTPD09sWjRInzyySdIS0vDjRs3TPtmDUQfv7n169fj0UcfxeOPP474+HgsWLAA7777LjZv3vy325Y0JboOFmRg0AQTfCwIW1tbvPvuu/1eiatuuTk7O/facvvhhx9w//33Y8mSJbCysoK7uzsiIiLw6aefIisrC2vXroVIJMLq1ashkUjo49rb22Py5MmYPHkyxo0b1+cJ3FLQx2+uq6urV8ZHjE8Z3Y32bNu2DStXrsTy5csRGxuLr776Co6Ojvj222/NvTSGIQQTfCwIiqL6vQInJ8iysjKUlpYiJiaGrnmw2WzU1NTgjz/+wMKFC+lgJBQKkZeXh2eeeQaffPIJ7rnnHqxYsQJVVVU4e/YsgL+ypuLiYpSWlmLs2LHw8PAw5Vs1Crr6zaWlpWH79u3Ys2cPysvL8euvv2L9+vVIS0tjBtxpiT5CDwYGTTCCAwtCtX9HE0R88Mcff6C9vR0TJkwA0C0gsLKywi+//AIfHx+MGTOGfs7169dRX1+PFStW0Md2cXEBi8WCs7MzAEAul8PW1hYHDhyAn58f4uLiTPgujYeufnOvv/46WCwWXn/9ddTW1sLHxwdpaWnYtGmTud7CkEMfoQcDgyaY4DOEICfSs2fPwtbWFg8//DCAv7bcPvvsM0yZMgWenp4AuoPK4cOHkZCQgICAAACATCZDTk4ORCIRHbxIY+rRo0eRkpKCsLCwQX1fhvDss8/i2Wef1XhfVlaW2t/W1tbYsGEDM8yOgcECYLbdhhgdHR2wt7fH+fPnMXfuXNTU1IDNZkMul2PWrFl4/PHH6cc2Njbi+PHjePTRR+nbBAIBTp06RfcGSaVSsFgslJeXo6KiAuPHj6eDFwNDT/QRejAwaIIJPkMMFxcXfPjhh8jJycHChQvpWoW1tTXef/99tb34W7duoaKiAhkZGfRt5eXlyMvLwyOPPALgrzpSZmYmvLy8EB8fP3hvhmHIoY/Qg4FBE0zwGYJYWVmBw+FgyZIltNu1UqnsJVaYNGkSbt26hfDwcADd23CnT59Ga2sr0tLSAPy15Xb48GGkpKTQj2XQj3PnziEtLQ0BAQFgsVg4ePDggM/JyspCcnIy7OzsEBkZafFzbQYSejAwaAMTfP4msNlsjY4FqsGEzWYjMjISTzzxBIDuLTc2m43q6mqUlZVh/Pjx8PLyGrQ1/x0RCoVITEzEF198odXjy8vLMXfuXEybNg1cLhfPP/88Hn/8cZw4ccLEK9WfhQsX4oMPPsAbb7wBDocDLperJvRgYNAGxliUAZ988gm+/vpr7Ny5E+PGjTP3cv42sFgsHDhwQG3bsyfr1q3D0aNHUVBQQN+2aNEitLa24vjx44OwSgYG88BkPncYcrmc/t+XL19Gc3Mzfv31V6SmpiIiIsKMK7szuXTpklqdDgBmzpzJ9Mww/O1hgs8dhrV1t7peKpXi0qVL8Pf3xy+//IKEhAT4+PiYeXV3Hg0NDRp7Ztrb2yESicy0KgYG08P0+dyh2NraYs2aNVi4cCGKiooQEhJi7iUxMDDcQTDB5w4nICCAbkBlGHyGDRumsWfG1dUVDg4OZloVA4PpYbbdGBjMyPjx49V6ZgDg119/ZXpmGP72MMGHgcGIdHZ2gsvl0iMrysvLweVyUVVVBQB49dVXsWTJEvrxTz31FG7fvo2XX34ZxcXF+PLLL/HTTz9hzZo15lg+A8OgwUitGRiMSFZWFqZNm9br9qVLl2LXrl1YtmwZKioq1HznsrKysGbNGhQVFWH48OFYv349li1bNniLZmAwA0zwYWBgYGAYdJhtNwYGBgaGQYcJPgwMDAwMgw4TfBgYGBgYBh0m+DAwMDAwDDpM8GFgYGBgGHSY4MPAwMDAMOgwwYeBgYGBYdBhgg8DAwMDw6DDBB8GBgYGhkGHCT4MDAwMDIMOE3wYGBgYGAad/wfhUwqWnbE4XgAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "trainer.saveplot(issave=True, isplot=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "pinnx", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/experimental_docs/unit-examples-forward/Laplace_disk.py b/docs/experimental_docs/unit-examples-forward/Laplace_disk.py new file mode 100644 index 000000000..73acafb9e --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/Laplace_disk.py @@ -0,0 +1,64 @@ +import brainstate as bst +import brainunit as u +import numpy as np + +import deepxde.experimental as deepxde + +# geom = experimental.geometry.Rectangle(xmin=[0, 0], xmax=[1, 2 * np.pi]) +# geom = geom.to_dict_point("r", "theta") + +geom = deepxde.geometry.Rectangle( + xmin=[0, 0], + xmax=[1, 2 * np.pi], +).to_dict_point(r=u.meter, theta=u.radian) + +uy = u.volt / u.meter +bc = deepxde.icbc.DirichletBC( + lambda x: {'y': u.math.cos(x['theta']) * uy}, + lambda x, on_boundary: u.math.logical_and(on_boundary, u.math.allclose(x['r'], 1 * u.meter)), +) + + +def solution(x): + r, theta = x['r'], x['theta'] + # TODO: Why add more divide u.meter? + return {'y': r * u.math.cos(theta) * uy / u.meter} + + +def pde(x, y): + jacobian = net.jacobian(x) + hessian = net.hessian(x) + + dy_r = jacobian["y"]["r"] + dy_rr = hessian["y"]["r"]["r"] + dy_thetatheta = hessian["y"]["theta"]["theta"] + return x['r'] * dy_r + x['r'] ** 2 * dy_rr + dy_thetatheta + + +# Use [r*sin(theta), r*cos(theta)] as features, +# so that the network is automatically periodic along the theta coordinate. +def feature_transform(x): + x = deepxde.utils.array_to_dict(x, ["r", "theta"], keep_dim=True) + return u.math.concatenate([x['r'] * u.math.sin(x['theta']), + x['r'] * u.math.cos(x['theta'])], axis=-1) + + +net = deepxde.nn.Model( + deepxde.nn.DictToArray(r=u.meter, theta=u.radian), + deepxde.nn.FNN([2] + [20] * 3 + [1], "tanh", input_transform=feature_transform), + deepxde.nn.ArrayToDict(y=uy), +) + +problem = deepxde.problem.PDE( + geom, + pde, + bc, + net, + num_domain=2540, + num_boundary=80, + solution=solution +) + +trainer = deepxde.Trainer(problem) +trainer.compile(bst.optim.Adam(1e-3), metrics=["l2 relative error"]).train(iterations=15000) +trainer.saveplot(issave=True, isplot=True) diff --git a/docs/experimental_docs/unit-examples-forward/burgers.ipynb b/docs/experimental_docs/unit-examples-forward/burgers.ipynb new file mode 100644 index 000000000..d50c1c462 --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/burgers.ipynb @@ -0,0 +1,637 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e0159dcbb63a3365", + "metadata": {}, + "source": [ + "# Burgers equation\n", + "\n", + "\n", + "## Problem setup\n", + "\n", + "\n", + "We will solve a Burgers equation:\n", + "\n", + "$$\n", + "\\frac{\\partial u}{\\partial t} + u\\frac{\\partial u}{\\partial x} = \\nu\\frac{\\partial^2u}{\\partial x^2}, \\qquad x \\in [-1, 1], \\quad t \\in [0, 1]\n", + "$$\n", + "\n", + "\n", + "with the Dirichlet boundary conditions and initial conditions\n", + "\n", + "$$\n", + "u(-1,t)=u(1,t)=0, \\quad u(x,0) = - \\sin(\\pi x).\n", + "$$\n", + "\n", + "## Dimensional Analysis\n", + "\n", + "### Step 1: Assign Dimensions to Variables\n", + "\n", + "1. **Spatial Coordinate $x$:**\n", + " - The dimension of $x$ is length:\n", + "\n", + " $$\n", + " [x] = L.\n", + " $$\n", + "\n", + "2. **Time $t$:**\n", + " - The dimension of time is:\n", + "\n", + " $$\n", + " [t] = T.\n", + " $$\n", + "\n", + "3. **Velocity $u$:**\n", + " - Velocity has dimensions of length per unit time:\n", + "\n", + " $$\n", + " [u] = L / T.\n", + " $$\n", + "\n", + "4. **Viscosity $\\nu$:**\n", + " - The term $\\nu \\frac{\\partial^2 u}{\\partial x^2}$ involves the second spatial derivative of velocity, which must have the same dimensions as the time derivative $\\frac{\\partial u}{\\partial t}$.\n", + "\n", + "---\n", + "\n", + "### Step 2: Analyze the Dimensions of Each Term\n", + "\n", + "1. **Time Derivative Term:**\n", + " - The time derivative $\\frac{\\partial u}{\\partial t}$ has dimensions:\n", + "\n", + " $$\n", + " \\left[\\frac{\\partial u}{\\partial t}\\right] = \\frac{[u]}{[t]} = \\frac{L / T}{T} = \\frac{L}{T^2}.\n", + " $$\n", + "\n", + "2. **Advection Term:**\n", + " - The advection term $u \\frac{\\partial u}{\\partial x}$ involves the spatial derivative of velocity:\n", + "\n", + " $$\n", + " \\left[u \\frac{\\partial u}{\\partial x}\\right] = [u] \\cdot \\frac{[u]}{[x]} = \\frac{L}{T} \\cdot \\frac{L / T}{L} = \\frac{L}{T^2}.\n", + " $$\n", + "\n", + "3. **Diffusion Term:**\n", + " - The diffusion term $\\nu \\frac{\\partial^2 u}{\\partial x^2}$ involves the second spatial derivative of velocity:\n", + "\n", + " $$\n", + " \\left[\\frac{\\partial^2 u}{\\partial x^2}\\right] = \\frac{[u]}{[x]^2} = \\frac{L / T}{L^2} = \\frac{1}{L T}.\n", + " \n", + " $$\n", + " - Therefore, the diffusion term has dimensions:\n", + "\n", + " $$\n", + " \\left[\\nu \\frac{\\partial^2 u}{\\partial x^2}\\right] = [\\nu] \\cdot \\frac{1}{L T} = \\frac{L}{T^2}.\n", + " $$\n", + "\n", + "---\n", + "\n", + "### Step 3: Determine the Dimensions of $\\nu$\n", + "\n", + "- The diffusion term $\\nu \\frac{\\partial^2 u}{\\partial x^2}$ must have the same dimensions as the time derivative $\\frac{\\partial u}{\\partial t}$:\n", + "\n", + " $$\n", + " [\\nu] \\cdot \\frac{1}{L T} = \\frac{L}{T^2} \\implies [\\nu] = \\frac{L^2}{T}.\n", + " $$\n", + "- Therefore, the viscosity $\\nu$ has dimensions of kinematic viscosity:\n", + "\n", + " $$\n", + " [\\nu] = \\frac{L^2}{T}.\n", + " $$\n", + "\n", + "---\n", + "\n", + "### Step 4: Summary of Dimensions\n", + "\n", + "| Variable/Parameter | Physical Meaning | Dimensions |\n", + "|------------------------|-----------------------------------|-----------------------|\n", + "| $x$ | Spatial coordinate | $L$ |\n", + "| $t$ | Time | $T$ |\n", + "| $u$ | Velocity | $L / T$ |\n", + "| $\\nu$ | Kinematic viscosity | $L^2 / T$ |\n", + "\n", + "---\n", + "\n", + "### Step 5: Initial and Boundary Conditions\n", + "\n", + "1. **Boundary Conditions:**\n", + " - The boundary conditions $u(-1,t) = u(1,t) = 0$ are given in meters per second:\n", + "\n", + " $$\n", + " [u(-1,t)] = [u(1,t)] = L / T.\n", + " $$\n", + "\n", + "2. **Initial Condition:**\n", + " - The initial condition $u(x,0) = -\\sin(\\pi x)$ is given in meters per second:\n", + " \n", + " $$\n", + " [u(x,0)] = L / T.\n", + " $$\n", + " - The term $\\sin(\\pi x)$ is dimensionless because $x$ is in meters, and $\\pi$ is a dimensionless constant." + ] + }, + { + "cell_type": "markdown", + "id": "5f173a598aa4fb4", + "metadata": {}, + "source": [ + "## Implementation" + ] + }, + { + "cell_type": "markdown", + "id": "a491f73861dcaa4", + "metadata": {}, + "source": [ + "This description goes through the implementation of a solver for the above described Burgers equation step-by-step.\n", + "\n", + "First, import the libraries we need:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a6e9a11ec74e35dd", + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-26T08:31:14.883767Z", + "start_time": "2024-11-26T08:31:12.197302Z" + } + }, + "outputs": [], + "source": [ + "import brainstate as bst\n", + "import brainunit as u\n", + "import numpy as np\n", + "import deepxde.experimental as deepxde" + ] + }, + { + "cell_type": "markdown", + "id": "95245422ff39b28d", + "metadata": {}, + "source": [ + "We begin by defining a computational geometry and time domain. We can use a built-in class ``Interval`` and ``TimeDomain`` and we combine both the domains using ``GeometryXTime`` as follows:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2b87ed2d174e56cf", + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-26T08:31:14.937721Z", + "start_time": "2024-11-26T08:31:14.888260Z" + } + }, + "outputs": [], + "source": [ + "geometry = deepxde.geometry.GeometryXTime(\n", + " geometry=deepxde.geometry.Interval(-1., 1.),\n", + " timedomain=deepxde.geometry.TimeDomain(0., 0.99)\n", + ").to_dict_point(x=u.meter, t=u.second)" + ] + }, + { + "cell_type": "markdown", + "id": "271c9ad81e74bf98", + "metadata": {}, + "source": [ + "Next, we express the PDE residual of the Burgers equation:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "89d86ee9fcaa2e22", + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-26T08:31:15.009040Z", + "start_time": "2024-11-26T08:31:14.993264Z" + } + }, + "outputs": [], + "source": [ + "v = 0.01 / u.math.pi * u.meter ** 2 / u.second\n", + "\n", + "\n", + "def pde(x, y):\n", + " jacobian = approximator.jacobian(x)\n", + " hessian = approximator.hessian(x)\n", + " dy_x = jacobian['y']['x']\n", + " dy_t = jacobian['y']['t']\n", + " dy_xx = hessian['y']['x']['x']\n", + " residual = dy_t + y['y'] * dy_x - v * dy_xx\n", + " return residual" + ] + }, + { + "cell_type": "markdown", + "id": "5d8df2efba443bb4", + "metadata": {}, + "source": [ + "Next, we consider the boundary/initial condition. ``on_boundary`` is chosen here to use the whole boundary of the computational domain in considered as the boundary condition. We include the ``geomtime`` space, time geometry created above and ``on_boundary`` as the BCs in the ``DirichletBC`` function of DeepXDE. We also define ``IC`` which is the inital condition for the burgers equation and we use the computational domain, initial function, and ``on_initial`` to specify the IC.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ce3ebafdc08158a0", + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-26T08:31:15.018480Z", + "start_time": "2024-11-26T08:31:15.015094Z" + } + }, + "outputs": [], + "source": [ + "uy = u.meter / u.second\n", + "\n", + "bc = deepxde.icbc.DirichletBC(lambda x: {'y': 0. * uy})\n", + "ic = deepxde.icbc.IC(lambda x: {'y': -u.math.sin(u.math.pi * x['x'] / u.meter) * uy})" + ] + }, + { + "cell_type": "markdown", + "id": "a0d5bb9643b9573b", + "metadata": {}, + "source": [ + "Next, we choose the network. Here, we use a fully connected neural network of depth 4 (i.e., 3 hidden layers) and width 20:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "6c6eefc678fcc466", + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-26T08:31:15.418417Z", + "start_time": "2024-11-26T08:31:15.025837Z" + } + }, + "outputs": [], + "source": [ + "approximator = deepxde.nn.Model(\n", + " deepxde.nn.DictToArray(x=u.meter, t=u.second),\n", + " deepxde.nn.FNN(\n", + " [geometry.dim] + [20] * 3 + [1],\n", + " \"tanh\",\n", + " bst.init.KaimingUniform()\n", + " ),\n", + " deepxde.nn.ArrayToDict(y=uy)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "48a114491365b25a", + "metadata": {}, + "source": [ + "Now, we have specified the geometry, PDE residual, and boundary/initial condition. We then define the ``TimePDE`` problem as\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "aa60a6f8cad0dace", + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-26T08:31:16.586859Z", + "start_time": "2024-11-26T08:31:15.430286Z" + } + }, + "outputs": [], + "source": [ + "problem = deepxde.problem.TimePDE(\n", + " geometry,\n", + " pde,\n", + " [bc, ic],\n", + " approximator,\n", + " num_domain=2540,\n", + " num_boundary=80,\n", + " num_initial=160,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "de04e3c7d5dce9cb", + "metadata": {}, + "source": [ + "The number 2540 is the number of training residual points sampled inside the domain, and the number 80 is the number of training points sampled on the boundary. We also include 160 initial residual points for the initial conditions.\n", + "\n", + "Now, we have the PDE problem and the network. We build a ``Trainer`` and choose the optimizer and learning rate:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "29fa25c853bbc6f6", + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-26T08:33:20.343840Z", + "start_time": "2024-11-26T08:31:16.598749Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Compiling trainer...\n", + "'compile' took 0.047883 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric\n", + "0 [0.15803409 * 10.0^0 * ((meter / second) / second) ** 2, [0.15803409 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 0.25960198 * meter / second}}, {'ibc0': {'y': 0.25960198 * meter / second}}, \n", + " {'ibc1': {'y': 1.1659584 * meter / second}}] {'ibc1': {'y': 1.1659584 * meter / second}}] \n", + "1000 [0.04754296 * 10.0^0 * ((meter / second) / second) ** 2, [0.04754296 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 0.00308682 * meter / second}}, {'ibc0': {'y': 0.00308682 * meter / second}}, \n", + " {'ibc1': {'y': 0.06809452 * meter / second}}] {'ibc1': {'y': 0.06809452 * meter / second}}] \n", + "2000 [0.04182805 * 10.0^0 * ((meter / second) / second) ** 2, [0.04182805 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 0.00125541 * meter / second}}, {'ibc0': {'y': 0.00125541 * meter / second}}, \n", + " {'ibc1': {'y': 0.05376936 * meter / second}}] {'ibc1': {'y': 0.05376936 * meter / second}}] \n", + "3000 [0.03440975 * 10.0^0 * ((meter / second) / second) ** 2, [0.03440975 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 0.00054205 * meter / second}}, {'ibc0': {'y': 0.00054205 * meter / second}}, \n", + " {'ibc1': {'y': 0.04500021 * meter / second}}] {'ibc1': {'y': 0.04500021 * meter / second}}] \n", + "4000 [0.0215442 * 10.0^0 * ((meter / second) / second) ** 2, [0.0215442 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 0.00029352 * meter / second}}, {'ibc0': {'y': 0.00029352 * meter / second}}, \n", + " {'ibc1': {'y': 0.03042006 * meter / second}}] {'ibc1': {'y': 0.03042006 * meter / second}}] \n", + "5000 [0.01140877 * 10.0^0 * ((meter / second) / second) ** 2, [0.01140877 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 0.00016095 * meter / second}}, {'ibc0': {'y': 0.00016095 * meter / second}}, \n", + " {'ibc1': {'y': 0.02001206 * meter / second}}] {'ibc1': {'y': 0.02001206 * meter / second}}] \n", + "6000 [0.00863622 * 10.0^0 * ((meter / second) / second) ** 2, [0.00863622 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 9.4245006e-05 * meter / second}}, {'ibc0': {'y': 9.4245006e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.01318286 * meter / second}}] {'ibc1': {'y': 0.01318286 * meter / second}}] \n", + "7000 [0.00690631 * 10.0^0 * ((meter / second) / second) ** 2, [0.00690631 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 5.483637e-05 * meter / second}}, {'ibc0': {'y': 5.483637e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00822749 * meter / second}}] {'ibc1': {'y': 0.00822749 * meter / second}}] \n", + "8000 [0.00483667 * 10.0^0 * ((meter / second) / second) ** 2, [0.00483667 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 2.3079416e-05 * meter / second}}, {'ibc0': {'y': 2.3079416e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00571595 * meter / second}}] {'ibc1': {'y': 0.00571595 * meter / second}}] \n", + "9000 [0.00386771 * 10.0^0 * ((meter / second) / second) ** 2, [0.00386771 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.4185833e-05 * meter / second}}, {'ibc0': {'y': 1.4185833e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00445467 * meter / second}}] {'ibc1': {'y': 0.00445467 * meter / second}}] \n", + "10000 [0.00332004 * 10.0^0 * ((meter / second) / second) ** 2, [0.00332004 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.3227182e-05 * meter / second}}, {'ibc0': {'y': 1.3227182e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00389429 * meter / second}}] {'ibc1': {'y': 0.00389429 * meter / second}}] \n", + "11000 [0.00295054 * 10.0^0 * ((meter / second) / second) ** 2, [0.00295054 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.3391712e-05 * meter / second}}, {'ibc0': {'y': 1.3391712e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00342724 * meter / second}}] {'ibc1': {'y': 0.00342724 * meter / second}}] \n", + "12000 [0.00252938 * 10.0^0 * ((meter / second) / second) ** 2, [0.00252938 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 1.0651251e-05 * meter / second}}, {'ibc0': {'y': 1.0651251e-05 * meter / second}}, \n", + " {'ibc1': {'y': 0.00329811 * meter / second}}] {'ibc1': {'y': 0.00329811 * meter / second}}] \n", + "13000 [0.00229796 * 10.0^0 * ((meter / second) / second) ** 2, [0.00229796 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 8.255725e-06 * meter / second}}, {'ibc0': {'y': 8.255725e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00314907 * meter / second}}] {'ibc1': {'y': 0.00314907 * meter / second}}] \n", + "14000 [0.00211558 * 10.0^0 * ((meter / second) / second) ** 2, [0.00211558 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 6.807138e-06 * meter / second}}, {'ibc0': {'y': 6.807138e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.002992 * meter / second}}] {'ibc1': {'y': 0.002992 * meter / second}}] \n", + "15000 [0.002326 * 10.0^0 * ((meter / second) / second) ** 2, [0.002326 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 7.791486e-06 * meter / second}}, {'ibc0': {'y': 7.791486e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00277965 * meter / second}}] {'ibc1': {'y': 0.00277965 * meter / second}}] \n", + "\n", + "Best trainer at step 15000:\n", + " train loss: 5.11e-03\n", + " test loss: 5.11e-03\n", + " test metric: []\n", + "\n", + "'train' took 123.689102 s\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trainer = deepxde.Trainer(problem)\n", + "trainer.compile(bst.optim.Adam(1e-3)).train(iterations=15000)" + ] + }, + { + "cell_type": "markdown", + "id": "1cff205141601ec3", + "metadata": {}, + "source": [ + "After we train the network using Adam, we continue to train the network using L-BFGS to achieve a smaller loss:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "5013a7d8bcac6ee9", + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-26T08:33:36.821381Z", + "start_time": "2024-11-26T08:33:20.374591Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Compiling trainer...\n", + "'compile' took 0.105205 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric\n", + "15000 [0.002326 * 10.0^0 * ((meter / second) / second) ** 2, [0.002326 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 7.791486e-06 * meter / second}}, {'ibc0': {'y': 7.791486e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00277965 * meter / second}}] {'ibc1': {'y': 0.00277965 * meter / second}}] \n", + "15200 [0.00468681 * 10.0^0 * ((meter / second) / second) ** 2, [0.00468681 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 6.556747e-06 * meter / second}}, {'ibc0': {'y': 6.556747e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00299743 * meter / second}}] {'ibc1': {'y': 0.00299743 * meter / second}}] \n", + "15400 [0.00374917 * 10.0^0 * ((meter / second) / second) ** 2, [0.00374917 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 5.420788e-06 * meter / second}}, {'ibc0': {'y': 5.420788e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00296684 * meter / second}}] {'ibc1': {'y': 0.00296684 * meter / second}}] \n", + "15600 [0.00311677 * 10.0^0 * ((meter / second) / second) ** 2, [0.00311677 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.825496e-06 * meter / second}}, {'ibc0': {'y': 4.825496e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00294279 * meter / second}}] {'ibc1': {'y': 0.00294279 * meter / second}}] \n", + "15800 [0.00269283 * 10.0^0 * ((meter / second) / second) ** 2, [0.00269283 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.55498e-06 * meter / second}}, {'ibc0': {'y': 4.55498e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00292296 * meter / second}}] {'ibc1': {'y': 0.00292296 * meter / second}}] \n", + "16000 [0.00241696 * 10.0^0 * ((meter / second) / second) ** 2, [0.00241696 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.4667136e-06 * meter / second}}, {'ibc0': {'y': 4.4667136e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00290674 * meter / second}}] {'ibc1': {'y': 0.00290674 * meter / second}}] \n", + "16200 [0.00223442 * 10.0^0 * ((meter / second) / second) ** 2, [0.00223442 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.4755107e-06 * meter / second}}, {'ibc0': {'y': 4.4755107e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00289299 * meter / second}}] {'ibc1': {'y': 0.00289299 * meter / second}}] \n", + "16400 [0.00212654 * 10.0^0 * ((meter / second) / second) ** 2, [0.00212654 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.4758385e-06 * meter / second}}, {'ibc0': {'y': 4.4758385e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00288151 * meter / second}}] {'ibc1': {'y': 0.00288151 * meter / second}}] \n", + "16600 [0.00205081 * 10.0^0 * ((meter / second) / second) ** 2, [0.00205081 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.5549314e-06 * meter / second}}, {'ibc0': {'y': 4.5549314e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00287196 * meter / second}}] {'ibc1': {'y': 0.00287196 * meter / second}}] \n", + "16800 [0.00199925 * 10.0^0 * ((meter / second) / second) ** 2, [0.00199925 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.6707073e-06 * meter / second}}, {'ibc0': {'y': 4.6707073e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00286368 * meter / second}}] {'ibc1': {'y': 0.00286368 * meter / second}}] \n", + "17000 [0.00197535 * 10.0^0 * ((meter / second) / second) ** 2, [0.00197535 * 10.0^0 * ((meter / second) / second) ** 2, [] \n", + " {'ibc0': {'y': 4.7530243e-06 * meter / second}}, {'ibc0': {'y': 4.7530243e-06 * meter / second}}, \n", + " {'ibc1': {'y': 0.00285702 * meter / second}}] {'ibc1': {'y': 0.00285702 * meter / second}}] \n", + "\n", + "Best trainer at step 17000:\n", + " train loss: 4.84e-03\n", + " test loss: 4.84e-03\n", + " test metric: []\n", + "\n", + "'train' took 16.232710 s\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trainer.compile(bst.optim.LBFGS(1e-3)).train(2000, display_every=200)" + ] + }, + { + "cell_type": "markdown", + "id": "9dc20d3bc5b2e106", + "metadata": {}, + "source": [ + "Let's visualize and save the data." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "5c9f7a8ec63d3638", + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-26T08:33:37.376974Z", + "start_time": "2024-11-26T08:33:36.834728Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saving loss history to D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\loss.dat ...\n", + "Saving checkpoint into D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\loss.dat\n", + "Saving training data to D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\train.dat ...\n", + "Saving checkpoint into D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\train.dat\n", + "Saving test data to D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\test.dat ...\n", + "Saving checkpoint into D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\test.dat\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAGwCAYAAABPSaTdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAABRa0lEQVR4nO3deXhU5cH+8e9M9j1kIQtJCDsJhAAhhBQRl2gAX1TclbeCVayKK2pR6ytqbfUnaq0Ql2oFW1tFraCtuAAKuECAQNjCTkgC2Viyh6xzfn9QRlMQCCQ5k8n9ua65LnLmzJx7TsbM7ZnnnMdiGIaBiIiIiJOymh1AREREpD2p7IiIiIhTU9kRERERp6ayIyIiIk5NZUdEREScmsqOiIiIODWVHREREXFqrmYHaGs2m43CwkL8/PywWCxmxxEREZEzYBgGVVVVREZGYrW27bEYpys7hYWFREdHmx1DREREzkJBQQFRUVFt+pxOV3b8/PyAYzvL39/f5DQiIiJyJiorK4mOjrZ/jrclpys7x7+68vf3V9kRERHpZNpjCIrTDFDOyMggPj6e5ORks6OIiIiIA7E420SglZWVBAQEUFFRoSM7IiIinUR7fn47zZEdERERkZNxujE7IiLivJqbm2lsbDQ7hpwFNzc3XFxcTNm2yo6IiDg8wzAoLi6mvLzc7ChyDgIDAwkPD+/w6+Cp7IiIiMM7XnS6d++Ot7e3LhrbyRiGQW1tLaWlpQBERER06PZVdkRExKE1Nzfbi05wcLDZceQseXl5AVBaWkr37t079CstDVAWERGHdnyMjre3t8lJ5Fwd/x129LgrlR0REekU9NVV52fW71BlR0RERJyayo6IiIg4NZUdERGRTiQ2NpaXX37Z9OfoTFR2zlBzUxMFuzZyuGS/2VFERKQTsFgsp7w9+eSTZ/W8a9eu5fbbb2/bsE5Op56foY0vX8Xw6hWs7v8wwTc9bnYcERFxcEVFRfZ/L1iwgCeeeIIdO3bYl/n6+tr/bRgGzc3NuLqe/mM5NDS0bYN2ATqyc4YaAvsCYC3danISERExDIPahiZTbmc6f3Z4eLj9FhAQgMVisf+8fft2/Pz8+Pzzz0lKSsLDw4PvvvuOPXv2cMUVVxAWFoavry/JycksXbq0xfP+91dQFouFt956i0mTJuHt7U2/fv349NNPW7U/8/PzueKKK/D19cXf35/rrruOkpIS+/0bN27kwgsvxM/PD39/f5KSkli3bh0AeXl5TJw4kW7duuHj48OgQYNYvHhxq7bf3nRk5wy5RSbAfgis3m12FBGRLu9oYzPxT3xpyrZznk7H271tPj4feeQRXnjhBXr37k23bt0oKChgwoQJ/P73v8fDw4O//vWvTJw4kR07dhATE/Ozz/PUU0/x/PPPM3v2bObMmcPkyZPJy8sjKCjotBlsNpu96KxYsYKmpiamT5/O9ddfz/LlywGYPHkyw4YN47XXXsPFxYXs7Gzc3NwAmD59Og0NDaxcuRIfHx9ycnJaHLVyBCo7Zyi07zBYA1GNediam7GaNJmZiIg4j6effppLLrnE/nNQUBCJiYn2n3/3u9+xcOFCPv30U+6+++6ffZ6pU6dy4403AvCHP/yBV155hTVr1jBu3LjTZli2bBmbN28mNzeX6OhoAP76178yaNAg1q5dS3JyMvn5+Tz88MMMHDgQgH79+tkfn5+fz9VXX01CQgIAvXv3bsUe6BgqO2eoR+/B1BlueFvq2Z+7jai+g82OJCLSZXm5uZDzdLpp224rI0aMaPFzdXU1Tz75JJ999hlFRUU0NTVx9OhR8vPzT/k8Q4YMsf/bx8cHf39/+zxUp7Nt2zaio6PtRQcgPj6ewMBAtm3bRnJyMjNmzOC2227jb3/7G2lpaVx77bX06dMHgHvvvZc777yTr776irS0NK6++uoWeRyBxuycIRdXV/a7HjuEWLpng8lpRES6NovFgre7qym3trwKsI+PT4ufH3roIRYuXMgf/vAHvv32W7Kzs0lISKChoeGUz3P8K6Wf7h+bzdZmOZ988km2bt3KZZddxtdff018fDwLFy4E4LbbbmPv3r388pe/ZPPmzYwYMYI5c+a02bbbgspOK5T5HjtsV39gs8lJRETEGX3//fdMnTqVSZMmkZCQQHh4OPv27WvXbcbFxVFQUEBBQYF9WU5ODuXl5cTHx9uX9e/fnwceeICvvvqKq666innz5tnvi46O5o477uDjjz/mwQcf5M0332zXzK2lstMKzaFxAHgc3mZyEhERcUb9+vXj448/Jjs7m40bN3LTTTe16RGak0lLSyMhIYHJkyezfv161qxZw80338zYsWMZMWIER48e5e6772b58uXk5eXx/fffs3btWuLijn0m3n///Xz55Zfk5uayfv16vvnmG/t9jkJlpxV8Yo4NGgut1RlZIiLS9l566SW6devGL37xCyZOnEh6ejrDhw9v121aLBY++eQTunXrxvnnn09aWhq9e/dmwYIFALi4uHD48GFuvvlm+vfvz3XXXcf48eN56qmnAGhubmb69OnExcUxbtw4+vfvz6uvvtqumVvLYpzpBQM6icrKSgICAqioqMDf379Nn/tQcT4hryfQbFhonLkfT2/HOrVORMQZ1dXVkZubS69evfD09DQ7jpyDU/0u2/PzW0d2WiG4exRl+ONiMSjYqUHKIiIinYHKTitYrFYK3XsBUJabbW4YEREROSMqO61UFdAfAFuxpo0QERHpDFR2WskaPggAn/LtJicRERGRM6Gy00qBvYYBEFGfa3ISERERORMqO60U1X8YNsNCCOUcKT1gdhwRERE5DZWdVvL2DaDQGgZA4c4sk9OIiIjI6ajsnIWDXscmP6vO32RyEhERETkdlZ2zUB90bIp7a6nOyBIREce0b98+LBYL2dnZZkcxncrOWXCLTAAgsFrTRoiIyMlZLJZT3p588slzeu5Fixa1WVZn52p2gM4otO8wWANRjXnYmpuxuriYHUlERBxMUVGR/d8LFizgiSeeYMeOHfZlvr6acqij6MjOWejRezB1hhvelnoKczUDuoiInCg8PNx+CwgIwGKxtFj2/vvvExcXh6enJwMHDmwxeWZDQwN33303EREReHp60rNnT5599lkAYmNjAZg0aRIWi8X+85lYsWIFI0eOxMPDg4iICB555BGamprs93/00UckJCTg5eVFcHAwaWlp1NTUALB8+XJGjhyJj48PgYGBjB49mry8vHPfUR3AIY/s/Pvf/+bBBx/EZrMxc+ZMbrvtNrMjteDi6kquawx9m/dQumcDUX0Hmx1JRKRrMQxorDVn227eYLGc01P8/e9/54knnmDu3LkMGzaMDRs2MG3aNHx8fJgyZQqvvPIKn376KR988AExMTEUFBRQUFAAwNq1a+nevTvz5s1j3LhxuJzhtwsHDhxgwoQJTJ06lb/+9a9s376dadOm4enpyZNPPklRURE33ngjzz//PJMmTaKqqopvv/0WwzBoamriyiuvZNq0abz33ns0NDSwZs0aLOe4HzqKw5WdpqYmZsyYwTfffENAQABJSUlMmjSJ4OBgs6O1UObbDyr2UH9gs9lRRES6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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "trainer.saveplot(issave=True, isplot=True)" + ] + }, + { + "cell_type": "markdown", + "id": "295c375c641f4943", + "metadata": {}, + "source": [ + "We can also test the model with the data:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40109d87837d8e20", + "metadata": {}, + "outputs": [], + "source": [ + "def gen_testdata():\n", + " data = np.load(\"../dataset/Burgers.npz\")\n", + " t, x, exact = data[\"t\"], data[\"x\"], data[\"usol\"].T\n", + " xx, tt = np.meshgrid(x, t)\n", + " X = {'x': np.ravel(xx) * u.meter, 't': np.ravel(tt) * u.second}\n", + " y = exact.flatten()[:, None]\n", + " return X, y * uy" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "600622e0fc0ccf2e", + "metadata": { + "ExecuteTime": { + "end_time": "2024-11-26T08:33:39.149077Z", + "start_time": "2024-11-26T08:33:37.402855Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mean residual: 0.02894243 * (meter / second) / second\n", + "L2 relative error: 224.70277\n" + ] + } + ], + "source": [ + "X, y_true = gen_testdata()\n", + "y_pred = trainer.predict(X)\n", + "f = pde(X, y_pred)\n", + "print(\"Mean residual:\", u.math.mean(u.math.absolute(f)))\n", + "print(\"L2 relative error:\", deepxde.metrics.l2_relative_error(y_true, y_pred['y']))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/experimental_docs/unit-examples-forward/diffusion_1d.ipynb b/docs/experimental_docs/unit-examples-forward/diffusion_1d.ipynb new file mode 100644 index 000000000..45f0d5ef2 --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/diffusion_1d.ipynb @@ -0,0 +1,433 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# One-dimensional Diffusion Equation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Problem setup\n", + "We will solve a diffusion equation:\n", + "\n", + "$$\n", + "\\frac{\\partial y}{\\partial t} = C \\frac{\\partial^2y}{\\partial x^2} - e^{-t}(\\sin(\\pi x) - \\pi^2\\sin(\\pi x)), \\qquad x \\in [-1, 1], \\quad t \\in [0, 1]\n", + "$$\n", + "\n", + "with the initial condition\n", + "\n", + "$$\n", + "y(x, 0) = \\sin(\\pi x)\n", + "$$\n", + "\n", + "and the Dirichlet boundary condition\n", + "\n", + "$$\n", + "y(-1, t) = y(1, t) = 0.\n", + "$$\n", + "\n", + "The reference solution is $y = e^{-t} \\sin(\\pi x)$.\n", + "\n", + "\n", + "\n", + "## Dimensional Analysis\n", + "\n", + "\n", + "Below is the dimensional analysis of the given **diffusion equation** with an unknown parameter $C$:\n", + "\n", + "\n", + "### Step 1: Assign Dimensions to Variables\n", + "\n", + "1. **Spatial Coordinate $x$:**\n", + " - Spatial coordinate has the dimension of length:\n", + "\n", + " $$\n", + " [x] = L.\n", + " $$\n", + "\n", + "2. **Time $t$:**\n", + " - Time has the dimension:\n", + "\n", + " $$\n", + " [t] = T.\n", + " $$\n", + "\n", + "3. **Function $y(x, t)$:**\n", + " - $y$ is the solution of the diffusion equation and depends on the context. For this case, $y$ has no explicit physical quantity associated with it, but we assume it to be **dimensionless** since the reference solution is given as $ y = e^{-t} \\sin(\\pi x) $, where both $e^{-t}$ and $\\sin(\\pi x)$ are dimensionless.\n", + "\n", + " $$\n", + " [y] = 1 \\quad \\text{(dimensionless)}.\n", + " $$\n", + "\n", + "4. **Parameter $C$:**\n", + " - The term $C \\frac{\\partial^2 y}{\\partial x^2}$ must have the same dimension as $\\frac{\\partial y}{\\partial t}$ for consistency.\n", + "\n", + " - First, consider the time derivative:\n", + "\n", + " $$\n", + " \\left[\\frac{\\partial y}{\\partial t}\\right] = \\frac{[y]}{[t]} = \\frac{1}{T}.\n", + " $$\n", + "\n", + " - Next, consider the second spatial derivative:\n", + "\n", + " $$\n", + " \\left[\\frac{\\partial^2 y}{\\partial x^2}\\right] = \\frac{[y]}{[x]^2} = \\frac{1}{L^2}.\n", + " $$\n", + " - Multiplying by $C$, the dimensions of $C$ must satisfy:\n", + "\n", + " $$\n", + " [C] \\cdot \\frac{1}{L^2} = \\frac{1}{T} \\implies [C] = \\frac{L^2}{T}.\n", + " $$\n", + "\n", + "5. **Source Term:** $e^{-t} \\left(\\sin(\\pi x) - \\pi^2 \\sin(\\pi x)\\right)$\n", + " - The exponential term $e^{-t}$ and the sine functions are dimensionless. Therefore, the source term is dimensionally consistent with:\n", + " \n", + " $$\n", + " \\text{Source Term} = \\frac{1}{T}.\n", + " $$\n", + "\n", + "---\n", + "\n", + "### Step 2: Initial and Boundary Conditions\n", + "\n", + "- **Initial Condition:** $y(x, 0) = \\sin(\\pi x)$.\n", + " - $\\sin(\\pi x)$ is dimensionless, consistent with $ [y] = 1 $.\n", + "\n", + "- **Boundary Condition:** $y(-1, t) = y(1, t) = 0$.\n", + " - The boundary values are dimensionless.\n", + "\n", + "---\n", + "\n", + "### Step 3: Summary of Dimensions\n", + "\n", + "| Variable/Parameter | Physical Meaning | Dimensions |\n", + "|------------------------|-----------------------------------|-----------------------|\n", + "| $x$ | Spatial coordinate | $L$ |\n", + "| $t$ | Time | $T$ |\n", + "| $y$ | Solution (dimensionless) | $1$ |\n", + "| $C$ | Diffusion coefficient | $L^2 / T$ |\n", + "| Source term | Forcing function | $1 / T$ |\n", + "\n", + "---\n", + "\n", + "In conclusion,\n", + "\n", + "- The unknown parameter $C$ has dimensions of $L^2 / T$, which is consistent with the physical meaning of a diffusion coefficient.\n", + "- The function $y$ and the boundary/initial conditions are dimensionless, ensuring the consistency of the problem setup.\n", + "\n", + "\n", + "## Implementation\n", + "\n", + "Import the required libraries:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:42:57.489721Z", + "start_time": "2024-12-17T13:42:53.900651Z" + } + }, + "outputs": [], + "source": [ + "import brainstate as bst\n", + "import brainunit as u\n", + "\n", + "import deepxde.experimental as deepxde" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the physical units for the problem:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:42:57.520303Z", + "start_time": "2024-12-17T13:42:57.494778Z" + } + }, + "outputs": [], + "source": [ + "unit_of_x = u.meter\n", + "unit_of_t = u.second\n", + "unit_of_f = 1 / u.second\n", + "\n", + "c = 1. * u.meter ** 2 / u.second" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the geometry and time domain:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:42:57.577592Z", + "start_time": "2024-12-17T13:42:57.573968Z" + } + }, + "outputs": [], + "source": [ + "geom = deepxde.geometry.Interval(-1, 1)\n", + "timedomain = deepxde.geometry.TimeDomain(0, 1)\n", + "geomtime = deepxde.geometry.GeometryXTime(geom, timedomain)\n", + "geomtime = geomtime.to_dict_point(x=unit_of_x, t=unit_of_t)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the initial condition and boundary condition functions:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:42:57.589705Z", + "start_time": "2024-12-17T13:42:57.586185Z" + } + }, + "outputs": [], + "source": [ + "def func(x):\n", + " y = u.math.sin(u.math.pi * x['x'] / unit_of_x) * u.math.exp(-x['t'] / unit_of_t)\n", + " return {'y': y}\n", + "\n", + "\n", + "bc = deepxde.icbc.DirichletBC(func)\n", + "ic = deepxde.icbc.IC(func)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the neural network model:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:42:58.048271Z", + "start_time": "2024-12-17T13:42:57.611872Z" + } + }, + "outputs": [], + "source": [ + "net = deepxde.nn.Model(\n", + " deepxde.nn.DictToArray(x=unit_of_x, t=unit_of_t),\n", + " deepxde.nn.FNN([2] + [32] * 3 + [1], \"tanh\"),\n", + " deepxde.nn.ArrayToDict(y=None),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the PDE function:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:42:58.061945Z", + "start_time": "2024-12-17T13:42:58.057100Z" + } + }, + "outputs": [], + "source": [ + "def pde(x, y):\n", + " jacobian = net.jacobian(x, x='t')\n", + " hessian = net.hessian(x, xi='x', xj='x')\n", + " dy_t = jacobian[\"y\"][\"t\"]\n", + " dy_xx = hessian[\"y\"][\"x\"][\"x\"]\n", + " source = (\n", + " u.math.exp(-x['t'] / unit_of_t) * (\n", + " u.math.sin(u.math.pi * x['x'] / unit_of_x) -\n", + " u.math.pi ** 2 * u.math.sin(u.math.pi * x['x'] / unit_of_x)\n", + " )\n", + " )\n", + " return dy_t - c * dy_xx + source * unit_of_f\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the problem and train the model:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:42:59.334380Z", + "start_time": "2024-12-17T13:42:58.070890Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Warning: 10000 points required, but 10082 points sampled.\n" + ] + } + ], + "source": [ + "problem = deepxde.problem.TimePDE(\n", + " geomtime,\n", + " pde,\n", + " [bc, ic],\n", + " net,\n", + " num_domain=40,\n", + " num_boundary=20,\n", + " num_initial=10,\n", + " solution=func,\n", + " num_test=10000,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Train the model:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "ExecuteTime": { + "end_time": "2024-12-17T13:43:08.334394Z", + "start_time": "2024-12-17T13:42:59.359833Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Compiling trainer...\n", + "'compile' took 0.066982 s\n", + "\n", + "Training trainer...\n", + "\n", + "Step Train loss Test loss Test metric \n", + "0 [39.28094 * becquerel2, [42.6258 * becquerel2, [{'y': Array(2.4083016, dtype=float32)}] \n", + " {'ibc0': {'y': Array(0.82120794, dtype=float32)}}, {'ibc0': {'y': Array(0.82120794, dtype=float32)}}, \n", + " {'ibc1': {'y': Array(1.7334808, dtype=float32)}}] {'ibc1': {'y': Array(1.7334808, dtype=float32)}}] \n", + "1000 [0.00716378 * becquerel2, [0.03221128 * becquerel2, [{'y': Array(0.08175115, dtype=float32)}] \n", + " {'ibc0': {'y': Array(0.00580588, dtype=float32)}}, {'ibc0': {'y': Array(0.00580588, dtype=float32)}}, \n", + " {'ibc1': {'y': Array(0.00591157, dtype=float32)}}] {'ibc1': {'y': Array(0.00591157, dtype=float32)}}] \n", + "2000 [0.00374766 * becquerel2, [0.01406009 * becquerel2, [{'y': Array(0.0635821, dtype=float32)}] \n", + " {'ibc0': {'y': Array(0.0034853, dtype=float32)}}, {'ibc0': {'y': Array(0.0034853, dtype=float32)}}, \n", + " {'ibc1': {'y': Array(0.00233263, dtype=float32)}}] {'ibc1': {'y': Array(0.00233263, dtype=float32)}}] \n", + "3000 [0.00207005 * becquerel2, [0.00866511 * becquerel2, [{'y': Array(0.04210193, dtype=float32)}] \n", + " {'ibc0': {'y': Array(0.00153627, dtype=float32)}}, {'ibc0': {'y': Array(0.00153627, dtype=float32)}}, \n", + " {'ibc1': {'y': Array(0.00113975, dtype=float32)}}] {'ibc1': {'y': Array(0.00113975, dtype=float32)}}] \n", + "4000 [0.00109155 * becquerel2, [0.00996974 * becquerel2, [{'y': Array(0.02316353, dtype=float32)}] \n", + " {'ibc0': {'y': Array(0.00045823, dtype=float32)}}, {'ibc0': {'y': Array(0.00045823, dtype=float32)}}, \n", + " {'ibc1': {'y': Array(0.00050199, dtype=float32)}}] {'ibc1': {'y': Array(0.00050199, dtype=float32)}}] \n", + "5000 [0.00051956 * becquerel2, [0.01253793 * becquerel2, [{'y': Array(0.01541764, dtype=float32)}] \n", + " {'ibc0': {'y': Array(0.00017889, dtype=float32)}}, {'ibc0': {'y': Array(0.00017889, dtype=float32)}}, \n", + " {'ibc1': {'y': Array(0.00024536, dtype=float32)}}] {'ibc1': {'y': Array(0.00024536, dtype=float32)}}] \n", + "6000 [0.00026679 * becquerel2, [0.01434105 * becquerel2, [{'y': Array(0.01198213, dtype=float32)}] \n", + " {'ibc0': {'y': Array(0.00010253, dtype=float32)}}, {'ibc0': {'y': Array(0.00010253, dtype=float32)}}, \n", + " {'ibc1': {'y': Array(0.00014029, dtype=float32)}}] {'ibc1': {'y': Array(0.00014029, dtype=float32)}}] \n", + "7000 [0.00015957 * becquerel2, [0.01430503 * becquerel2, [{'y': Array(0.00955142, dtype=float32)}] \n", + " {'ibc0': {'y': Array(6.4639426e-05, dtype=float32)}}, {'ibc0': {'y': Array(6.4639426e-05, dtype=float32)}}, \n", + " {'ibc1': {'y': Array(8.0856196e-05, dtype=float32)}}] {'ibc1': {'y': Array(8.0856196e-05, dtype=float32)}}] \n", + "8000 [0.00011952 * becquerel2, [0.01355371 * becquerel2, [{'y': Array(0.00870061, dtype=float32)}] \n", + " {'ibc0': {'y': Array(5.1637177e-05, dtype=float32)}}, {'ibc0': {'y': Array(5.1637177e-05, dtype=float32)}}, \n", + " {'ibc1': {'y': Array(4.7870093e-05, dtype=float32)}}] {'ibc1': {'y': Array(4.7870093e-05, dtype=float32)}}] \n", + "9000 [2.9594898e-05 * becquerel2, [0.01290757 * becquerel2, [{'y': Array(0.00810704, dtype=float32)}] \n", + " {'ibc0': {'y': Array(3.8509617e-05, dtype=float32)}}, {'ibc0': {'y': Array(3.8509617e-05, dtype=float32)}}, \n", + " {'ibc1': {'y': Array(2.8898923e-05, dtype=float32)}}] {'ibc1': {'y': Array(2.8898923e-05, dtype=float32)}}] \n", + "10000 [1.6880738e-05 * becquerel2, [0.01241341 * becquerel2, [{'y': Array(0.00777998, dtype=float32)}] \n", + " {'ibc0': {'y': Array(3.39993e-05, dtype=float32)}}, {'ibc0': {'y': Array(3.39993e-05, dtype=float32)}}, \n", + " {'ibc1': {'y': Array(2.0438354e-05, dtype=float32)}}] {'ibc1': {'y': Array(2.0438354e-05, dtype=float32)}}] \n", + "\n", + "Best trainer at step 10000:\n", + " train loss: 7.13e-05\n", + " test loss: 1.25e-02\n", + " test metric: [{'y': Array(0.01, dtype=float32)}]\n", + "\n", + "'train' took 8.116077 s\n", + "\n", + "Saving loss history to D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\loss.dat ...\n", + "Saving checkpoint into D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\loss.dat\n", + "Saving training data to D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\train.dat ...\n", + "Saving checkpoint into D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\train.dat\n", + "Saving test data to D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\test.dat ...\n", + "Saving checkpoint into D:\\codes\\projects\\pinnx\\docs\\examples-pinn-forward\\test.dat\n" + ] + }, + { + "data": { + "image/png": "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T4yIE1J4EZF28cUXX1BfX8+ll17KM888w6hRo/jFL37BHXfcwRVXXHFOIWDIkCE0NDRQU1ODu7s77u7uWCyWFuuMHDmS7du3069fvzNu64cffjjp9eDBg+2vvby8mDx5MpMnT2bmzJkMGjSIrVu3MnLkyDbXLSIdS2NeRKTdFBYWEh8fT0lJCSaTicDAQPbt20dkZORZ3zthwgRuvvlmUlJS6NWrF9u3b2f27NlER0ezdOlSAC6//HK8vLx4+eWX8fDwICQkhB9//JELLriAO++8k7vvvhsfHx+2b9/OkiVLmDt3LmAbK3Ps2DF+//vfc+2117JkyRIeeughPv/8cyZOnMiCBQuwWCykpaXh7e3N/Pnzee6558jLy6NXr14desxEpO005kVE2s2yZctITU3F09OTdevWERMT06rgAjBx4kTefPNNLr/8cgYPHswDDzzAxIkTee+99+zr/PGPfyQnJ4e+ffsSGhoKwIgRI1i+fDm7du1i3LhxJCcnM2fOHKKiolps/ze/+Q0bNmwgOTmZp556iueff56JEycCEBgYyLx58xg7diwjRozgm2++4dNPP1VwEXFS6nkRkW7vVFcpiUjXpZ4XERER6VIUXkRERKRL0WkjERER6VLU8yIiIiJdisKLiIiIdCkKLyIiItKlKLyIiIhIl6LwIiIiIl2KwouIiIh0KQovIiIi0qUovIiIiEiX8v8DQhErOahxLmQAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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7rkHH330EU477TROr0MIfoj4nGBYrVYcPHiQdeCWyWTg8Xj47W9/i5tuusmvA6QURbGZN7Ozs+DxeHZ+c1wuuEwrssViQW1tbdCF2AGLy6XNzc2Ii4tDWVnZmjsvME0ls7Oz7F6eq845tVqNlpYWr4XnH//4B+644w78+9//xumnn+7tj0IIYtZcfJ577jnWFbaqqgrPPPMMNm3atOzz3377bdx3330YGRlBYWEhHn30UZx77rmreMbBgdFoxM9//nMcPnwYZ511Fvbv34/p6WmcddZZ2LVrF7Zv3+7XNEzHzBuapu1sftwRIqYjjM/nu2xFDlT0ej2am5shEolQUlKy5sLjCLOXZ9s5x/ydmM45Rnjy8/M5O5zTNI233noLt9xyC959911s27bNxz8JIdhY02SwN998E7fffjseeOABtLS0oKqqCtu2bcPMzIzT5x88eBCXXnoprr32WrS2tmLXrl3YtWsXOjo6VvnMA58bb7wRo6OjOHbsGP7+97+jv78fX331FYqLi/HII48gJycHP/vZz/D6669DpVLB1/cgfD4fiYmJKCkpwWmnnYaqqioIBAL09PTgq6++Qnt7O+s95wyj0Yhjx44hNDTUbvgymNDpdDh27BjEYnFACg8AhIaGIj09HdXV1diyZQsKCwvZHKSvv/4aUqkUx44dQ25urlfRGu+//z5uvvlmvPHGG6sqPM899xxycnIQERGB+vp6HDlyZMXnv/322yguLkZERAQqKirwySefrNKZnnysaeVTX1+PjRs34tlnnwWweLeclZWFW265xWkS4M9+9jNotVp89NFH7GMNDQ2orq7G888/v2rnHQwMDw8va91P0zQ6OjrYTKK+vj67TCKRSOR3vzlmlshoNEIsFiMlJYX1m2MGYIVC4Zrsj/gCrVaL5uZmpKSkBJS7trtQFIWJiQn09vayXXJM51xiYqJHNwMfffQRrr76avzjH//AhRde6K9TXsKbb76JK664As8//zzq6+vx1FNP4e2330Zvb6/TppuDBw/itNNOwx//+Eecd955eP311/Hoo4+ipaWFzB/5gTUTHy4Z6NnZ2bj99ttx6623so898MAD+OCDDyCVSlfhrE88mPZlJpOovb0dp512Gnbu3Inzzz8fycnJfvebY4RIr9cjLi4OGo0GSUlJKC0tDbqLNvBDrEN6ejoKCgqC8mdY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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "trainer = deepxde.Trainer(problem)\n", + "trainer.compile(bst.optim.Adam(0.001), metrics=[\"l2 relative error\"]).train(iterations=10000)\n", + "trainer.saveplot(issave=True, isplot=True)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/experimental_docs/unit-examples-forward/diffusion_1d.py b/docs/experimental_docs/unit-examples-forward/diffusion_1d.py new file mode 100644 index 000000000..d41538b6e --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/diffusion_1d.py @@ -0,0 +1,61 @@ +import brainstate as bst +import brainunit as u + +import deepxde.experimental as deepxde + +unit_of_x = u.meter +unit_of_t = u.second +unit_of_f = 1 / u.second + +c = 1. * u.meter ** 2 / u.second + +geom = deepxde.geometry.Interval(-1, 1) +timedomain = deepxde.geometry.TimeDomain(0, 1) +geomtime = deepxde.geometry.GeometryXTime(geom, timedomain) +geomtime = geomtime.to_dict_point(x=unit_of_x, t=unit_of_t) + + +def func(x): + y = u.math.sin(u.math.pi * x['x'] / unit_of_x) * u.math.exp(-x['t'] / unit_of_t) + return {'y': y} + + +bc = deepxde.icbc.DirichletBC(func) +ic = deepxde.icbc.IC(func) + +net = deepxde.nn.Model( + deepxde.nn.DictToArray(x=unit_of_x, t=unit_of_t), + deepxde.nn.FNN([2] + [32] * 3 + [1], "tanh"), + deepxde.nn.ArrayToDict(y=None), +) + + +def pde(x, y): + jacobian = net.jacobian(x, x='t') + hessian = net.hessian(x, xi='x', xj='x') + dy_t = jacobian["y"]["t"] + dy_xx = hessian["y"]["x"]["x"] + source = ( + u.math.exp(-x['t'] / unit_of_t) * ( + u.math.sin(u.math.pi * x['x'] / unit_of_x) - + u.math.pi ** 2 * u.math.sin(u.math.pi * x['x'] / unit_of_x) + ) + ) + return dy_t - c * dy_xx + source * unit_of_f + + +problem = deepxde.problem.TimePDE( + geomtime, + pde, + [bc, ic], + net, + num_domain=40, + num_boundary=20, + num_initial=10, + solution=func, + num_test=10000, +) + +trainer = deepxde.Trainer(problem) +trainer.compile(bst.optim.Adam(0.001), metrics=["l2 relative error"]).train(iterations=10000) +trainer.saveplot(issave=True, isplot=True) diff --git a/docs/experimental_docs/unit-examples-forward/heat.ipynb b/docs/experimental_docs/unit-examples-forward/heat.ipynb new file mode 100644 index 000000000..3728f756a --- /dev/null +++ b/docs/experimental_docs/unit-examples-forward/heat.ipynb @@ -0,0 +1,550 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Heat equation\n", + "\n", + "## Problem setup\n", + "We will solve a heat equation:\n", + "\n", + "$$\n", + "\\frac{\\partial u}{\\partial t}=\\alpha \\frac{\\partial^2u}{\\partial x^2}, \\qquad x \\in [0, 1], \\quad t \\in [0, 1]\n", + "$$\n", + "\n", + "where $alpha=0.4$ is the thermal diffusivity constant.\n", + "\n", + "With Dirichlet boundary conditions:\n", + "\n", + "$$\n", + "u(0,t) = u(1,t)=0,\n", + "$$\n", + "\n", + "and periodic(sinusoidal) inital condition:\n", + "\n", + "$$\n", + "u(x,0) = \\sin (\\frac{n\\pi x}{L}),\\qquad 0