From f56baaba3cc169ed30c0f44788c52f91be2a3c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas=20de=20Sousa=20Almeida?= Date: Wed, 31 Jul 2024 16:49:10 -0300 Subject: [PATCH 01/10] special functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Lucas de Sousa Almeida --- simulai/residuals/_pytorch_residuals.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/simulai/residuals/_pytorch_residuals.py b/simulai/residuals/_pytorch_residuals.py index b6a38f3..0300666 100644 --- a/simulai/residuals/_pytorch_residuals.py +++ b/simulai/residuals/_pytorch_residuals.py @@ -52,6 +52,7 @@ def __init__( device: str = "cpu", engine: str = "torch", auxiliary_expressions: list = None, + special_expressions: list = None, ) -> None: if engine == "torch": super(SymbolicOperator, self).__init__() @@ -129,6 +130,7 @@ def __init__( self.f_expressions = list() self.g_expressions = dict() + self.h_expressions = list() self.feed_vars = None @@ -164,6 +166,7 @@ def __init__( self.f_expressions.append(f_expr) + # auxiliary expressions (usually boundary conditions) if self.auxiliary_expressions is not None: for key, expr in self.auxiliary_expressions.items(): if not callable(expr): @@ -173,6 +176,16 @@ def __init__( self.g_expressions[key] = g_expr + # special expressions (usually employed for certain kinds of loss functions) + if self.special_expressions is not None: + for key, expr in self.special_expressions.items(): + if not callable(expr): + h_expr = sympy.lambdify(self.all_vars, expr, subs) + else: + h_expr = expr + + self.h_expressions.append(h_expr) + # Method for executing the expressions evaluation if self.processing == "serial": self.process_expression = self._process_expression_serial From 62055436437d34cd773107bdb2d9b59f2a38da6f Mon Sep 17 00:00:00 2001 From: Joao Lucas de Sousa Almeida Date: Wed, 31 Jul 2024 21:52:52 -0300 Subject: [PATCH 02/10] Parsing special expressions Signed-off-by: Joao Lucas de Sousa Almeida --- simulai/residuals/_pytorch_residuals.py | 49 +++++++++++++++---------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/simulai/residuals/_pytorch_residuals.py b/simulai/residuals/_pytorch_residuals.py index 0300666..c310316 100644 --- a/simulai/residuals/_pytorch_residuals.py +++ b/simulai/residuals/_pytorch_residuals.py @@ -185,13 +185,16 @@ def __init__( h_expr = expr self.h_expressions.append(h_expr) + + self.process_special_expression = self._factory_process_expression_serial(expressions=self.h_expressions) # Method for executing the expressions evaluation if self.processing == "serial": - self.process_expression = self._process_expression_serial + self.process_expression = self._factory_process_expression_serial(expressions=self.f_expressions) else: raise Exception(f"Processing case {self.processing} not supported.") + def _construct_protected_functions(self): """This function creates a dictionary of protected functions from the engine object attribute. @@ -376,33 +379,39 @@ def _forward_dict(self, input_data: dict = None) -> torch.Tensor: """ return self.function.forward(**input_data) + + def _factory_process_expression_serial(self, expressions:list=None): + def _process_expression_serial(feed_vars: dict = None) -> List[torch.Tensor]: + """Process the expression list serially using the given feed variables. - def _process_expression_serial(self, feed_vars: dict = None) -> List[torch.Tensor]: - """Process the expression list serially using the given feed variables. + Args: + feed_vars (dict, optional): The feed variables. (Default value = None) - Args: - feed_vars (dict, optional): The feed variables. (Default value = None) + Returns: + List[torch.Tensor]: A list of tensors after evaluating the expressions serially. - Returns: - List[torch.Tensor]: A list of tensors after evaluating the expressions serially. + """ + return [f(**feed_vars).to(self.device) for f in expressions] - """ - return [f(**feed_vars).to(self.device) for f in self.f_expressions] + return _process_expression_serial - def _process_expression_individual( - self, index: int = None, feed_vars: dict = None - ) -> torch.Tensor: - """Evaluates a single expression specified by index from the f_expressions list with given feed variables. + def _factory_process_expression_individual(self, expressions:list=None): + def _process_expression_individual( + index: int = None, feed_vars: dict = None + ) -> torch.Tensor: + """Evaluates a single expression specified by index from the f_expressions list with given feed variables. - Args: - index (int, optional): Index of the expression to be evaluated, by default None - feed_vars (dict, optional): Dictionary of feed variables, by default None + Args: + index (int, optional): Index of the expression to be evaluated, by default None + feed_vars (dict, optional): Dictionary of feed variables, by default None - Returns: - torch.Tensor: Result of evaluating the specified expression with given feed variables + Returns: + torch.Tensor: Result of evaluating the specified expression with given feed variables - """ - return self.f_expressions[index](**feed_vars).to(self.device) + """ + return self.expressions[index](**feed_vars).to(self.device) + + return _process_expression_individual def __call__( self, inputs_data: Union[np.ndarray, dict] = None From bee254cd0c9f557f949cbd67eec950615929e638 Mon Sep 17 00:00:00 2001 From: Joao Lucas de Sousa Almeida Date: Wed, 31 Jul 2024 21:54:38 -0300 Subject: [PATCH 03/10] Reformatting Signed-off-by: Joao Lucas de Sousa Almeida --- simulai/residuals/_pytorch_residuals.py | 34 +++++++++++++++++-------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/simulai/residuals/_pytorch_residuals.py b/simulai/residuals/_pytorch_residuals.py index c310316..a4bf0f7 100644 --- a/simulai/residuals/_pytorch_residuals.py +++ b/simulai/residuals/_pytorch_residuals.py @@ -73,7 +73,16 @@ def __init__( self.processing = processing self.periodic_bc_protected_key = "periodic" - self.protected_funcs = ["cos", "sin", "sqrt", "exp", "tanh", "cosh", "sech", "sinh"] + self.protected_funcs = [ + "cos", + "sin", + "sqrt", + "exp", + "tanh", + "cosh", + "sech", + "sinh", + ] self.protected_operators = ["L", "Div", "Identity", "Kronecker"] self.protected_funcs_subs = self._construct_protected_functions() @@ -185,16 +194,19 @@ def __init__( h_expr = expr self.h_expressions.append(h_expr) - - self.process_special_expression = self._factory_process_expression_serial(expressions=self.h_expressions) + + self.process_special_expression = self._factory_process_expression_serial( + expressions=self.h_expressions + ) # Method for executing the expressions evaluation if self.processing == "serial": - self.process_expression = self._factory_process_expression_serial(expressions=self.f_expressions) + self.process_expression = self._factory_process_expression_serial( + expressions=self.f_expressions + ) else: raise Exception(f"Processing case {self.processing} not supported.") - def _construct_protected_functions(self): """This function creates a dictionary of protected functions from the engine object attribute. @@ -379,8 +391,8 @@ def _forward_dict(self, input_data: dict = None) -> torch.Tensor: """ return self.function.forward(**input_data) - - def _factory_process_expression_serial(self, expressions:list=None): + + def _factory_process_expression_serial(self, expressions: list = None): def _process_expression_serial(feed_vars: dict = None) -> List[torch.Tensor]: """Process the expression list serially using the given feed variables. @@ -395,7 +407,7 @@ def _process_expression_serial(feed_vars: dict = None) -> List[torch.Tensor]: return _process_expression_serial - def _factory_process_expression_individual(self, expressions:list=None): + def _factory_process_expression_individual(self, expressions: list = None): def _process_expression_individual( index: int = None, feed_vars: dict = None ) -> torch.Tensor: @@ -653,20 +665,20 @@ def sech(self, x): cosh = getattr(self.engine, "cosh") - return 1/cosh(x) + return 1 / cosh(x) def csch(self, x): sinh = getattr(self.engine, "sinh") - return 1/sinh(x) + return 1 / sinh(x) def coth(self, x): cosh = getattr(self.engine, "cosh") sinh = getattr(self.engine, "sinh") - return cosh(x)/sinh(x) + return cosh(x) / sinh(x) def diff(feature: torch.Tensor, param: torch.Tensor) -> torch.Tensor: From fd6d31f4f6b752551a445d92cf3b81c65d3239c3 Mon Sep 17 00:00:00 2001 From: Joao Lucas de Sousa Almeida Date: Thu, 1 Aug 2024 07:17:05 -0300 Subject: [PATCH 04/10] Extending the supported operators to include Grad Signed-off-by: Joao Lucas de Sousa Almeida --- simulai/residuals/_pytorch_residuals.py | 6 ++--- simulai/tokens.py | 32 ++++++++++++++++++++++++ tests/residuals/test_symbolicoperator.py | 31 +++++++++++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/simulai/residuals/_pytorch_residuals.py b/simulai/residuals/_pytorch_residuals.py index a4bf0f7..1c3661b 100644 --- a/simulai/residuals/_pytorch_residuals.py +++ b/simulai/residuals/_pytorch_residuals.py @@ -83,7 +83,7 @@ def __init__( "sech", "sinh", ] - self.protected_operators = ["L", "Div", "Identity", "Kronecker"] + self.protected_operators = ["L", "Div", "Grad", "Identity", "Kronecker"] self.protected_funcs_subs = self._construct_protected_functions() self.protected_operators_subs = self._construct_implict_operators() @@ -186,8 +186,8 @@ def __init__( self.g_expressions[key] = g_expr # special expressions (usually employed for certain kinds of loss functions) - if self.special_expressions is not None: - for key, expr in self.special_expressions.items(): + if special_expressions is not None: + for expr in self.special_expressions: if not callable(expr): h_expr = sympy.lambdify(self.all_vars, expr, subs) else: diff --git a/simulai/tokens.py b/simulai/tokens.py index 3641ced..ca6e1cf 100644 --- a/simulai/tokens.py +++ b/simulai/tokens.py @@ -93,6 +93,38 @@ def Div(u: sympy.Symbol, vars: tuple) -> callable: return l +def Grad(u: sympy.Symbol, vars: tuple) -> callable: + """ + Generate a callable object to compute the gradient operator. + + The gradient operator is a first-order differential operator that measures the + magnitude and direction of a flow of a vector field from its source and + convergence to a point. + + Parameters + ---------- + u : sympy.Symbol + The vector field to compute the divergence of. + vars : tuple + A tuple of variables to compute the divergence with respect to. + + Returns + ------- + callable + A callable object that computes the divergence of a vector field with respect + to the given variables. + + Examples + -------- + >>> x, y, z = sympy.symbols('x y z') + >>> u = sympy.Matrix([x**2, y**2, z**2]) + >>> Grad(u, (x, y, z)) + 2*x + 2*y + 2*z + """ + g = [D(u, var) for var in vars] + + return g + def Gp( g0: Union[torch.tensor, float], r: Union[torch.tensor, float], n: int diff --git a/tests/residuals/test_symbolicoperator.py b/tests/residuals/test_symbolicoperator.py index 4a410d7..ce3a0d0 100644 --- a/tests/residuals/test_symbolicoperator.py +++ b/tests/residuals/test_symbolicoperator.py @@ -226,6 +226,37 @@ def test_symbolic_operator_diff_operators(self): assert all([isinstance(item, torch.Tensor) for item in residual(data)]) + def test_symbolic_operator_grad_operator(self): + + f = "Grad(u, (x, y))" + + input_labels = ["x", "y"] + output_labels = ["u"] + + L_x = 1 + L_y = 1 + N_x = 100 + N_y = 100 + dx = L_x / N_x + dy = L_y / N_y + + grid = np.mgrid[0:L_x:dx, 0:L_y:dy] + + data = np.hstack([grid[1].flatten()[:, None], grid[0].flatten()[:, None]]) + + net = model(n_inputs=len(input_labels), n_outputs=len(output_labels)) + + residual = SymbolicOperator( + expressions=[f], + input_vars=input_labels, + constants={"alpha": 5}, + output_vars=output_labels, + function=net, + engine="torch", + ) + + assert all([isinstance(item, torch.Tensor) for item in residual(data)]) + def test_symbolic_operator_1d_pde(self): # Allen-Cahn equation f_0 = "D(u, t) - mu*D(D(u, x), x) + alpha*(u**3) + beta*u" From 22d65e8f79673b5ff675ccf3088aa449d1752318 Mon Sep 17 00:00:00 2001 From: Joao Lucas de Sousa Almeida Date: Thu, 1 Aug 2024 07:40:10 -0300 Subject: [PATCH 05/10] Supporting lists of expressions (representing vector operators, as gradient) during compilation Signed-off-by: Joao Lucas de Sousa Almeida --- simulai/residuals/_pytorch_residuals.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/simulai/residuals/_pytorch_residuals.py b/simulai/residuals/_pytorch_residuals.py index 1c3661b..0647336 100644 --- a/simulai/residuals/_pytorch_residuals.py +++ b/simulai/residuals/_pytorch_residuals.py @@ -207,6 +207,16 @@ def __init__( else: raise Exception(f"Processing case {self.processing} not supported.") + def _subs_expr(self, expr=None, constants=None): + + if isinstance(expr, list): + for j, e in enumerate(expr): + expr[j] = e.subs(constants) + else: + expr = expr.subs(constants) + + return expr + def _construct_protected_functions(self): """This function creates a dictionary of protected functions from the engine object attribute. @@ -331,9 +341,10 @@ def _parse_expression(self, expr=Union[sympy.Expr, str]) -> sympy.Expr: ) if self.constants is not None: - expr_ = expr_.subs(self.constants) + expr_ = self._subs_expr(expr=expr_, constants=self.constants) if self.trainable_parameters is not None: - expr_ = expr_.subs(self.trainable_parameters) + expr_ = self._subs_expr(expr=expr_, constants=self.trainable_parameters) + except ValueError: if self.constants is not None: _expr = expr From 53ee3d5093a78970b9d22b78887d48f33e46124c Mon Sep 17 00:00:00 2001 From: Joao Lucas de Sousa Almeida Date: Thu, 1 Aug 2024 21:49:20 -0300 Subject: [PATCH 06/10] Minor adjusts Signed-off-by: Joao Lucas de Sousa Almeida --- simulai/residuals/_pytorch_residuals.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/simulai/residuals/_pytorch_residuals.py b/simulai/residuals/_pytorch_residuals.py index 0647336..d7b3543 100644 --- a/simulai/residuals/_pytorch_residuals.py +++ b/simulai/residuals/_pytorch_residuals.py @@ -113,6 +113,8 @@ def __init__( else: self.auxiliary_expressions = auxiliary_expressions + self.special_expressions = special_expressions + self.input_vars = [self._parse_variable(var=var) for var in input_vars] self.output_vars = [self._parse_variable(var=var) for var in output_vars] From 107863108bdc58058646a8a3822a1c0ed6da4b56 Mon Sep 17 00:00:00 2001 From: Joao Lucas de Sousa Almeida Date: Fri, 2 Aug 2024 15:31:35 -0300 Subject: [PATCH 07/10] Minor changes in the tests Signed-off-by: Joao Lucas de Sousa Almeida --- tests/residuals/test_symbolicoperator.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/residuals/test_symbolicoperator.py b/tests/residuals/test_symbolicoperator.py index ce3a0d0..eb5bad4 100644 --- a/tests/residuals/test_symbolicoperator.py +++ b/tests/residuals/test_symbolicoperator.py @@ -228,7 +228,8 @@ def test_symbolic_operator_diff_operators(self): def test_symbolic_operator_grad_operator(self): - f = "Grad(u, (x, y))" + f = "D(u, t) - alpha*D(u, (x, y))" + s = "Grad(D(u, t) - alpha*D(u, (x, y)), (x, y))" input_labels = ["x", "y"] output_labels = ["u"] @@ -248,14 +249,18 @@ def test_symbolic_operator_grad_operator(self): residual = SymbolicOperator( expressions=[f], + special_expressions=[s], input_vars=input_labels, constants={"alpha": 5}, output_vars=output_labels, function=net, engine="torch", ) + u = net(input_data=data) - assert all([isinstance(item, torch.Tensor) for item in residual(data)]) + feed_vars = {'x': data[:, 0], 'y': data[:,1], 'u': u} + + print(residual.process_special_expression(feed_vars)) def test_symbolic_operator_1d_pde(self): # Allen-Cahn equation From 5841823673d581dbc7337f1381a150f6ab82fa6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas=20de=20Sousa=20Almeida?= Date: Fri, 2 Aug 2024 17:27:33 -0300 Subject: [PATCH 08/10] Prepare dataset to be used together with symbolic expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Lucas de Sousa Almeida --- simulai/residuals/_pytorch_residuals.py | 36 ++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/simulai/residuals/_pytorch_residuals.py b/simulai/residuals/_pytorch_residuals.py index d7b3543..7aa1a78 100644 --- a/simulai/residuals/_pytorch_residuals.py +++ b/simulai/residuals/_pytorch_residuals.py @@ -139,9 +139,9 @@ def __init__( self.output = None - self.f_expressions = list() - self.g_expressions = dict() - self.h_expressions = list() + self.f_expressions = list() # Main expressions, as PDEs and ODEs + self.g_expressions = dict() # Auxiliary expressions, as boundary conditions + self.h_expressions = list() # Others auxiliary expressions, as those used to evaluate special loss functions self.feed_vars = None @@ -165,9 +165,11 @@ def __init__( else: gradient_function = gradient + # Diff symbol is related to automatic differentiation subs = {self.diff_symbol.name: gradient_function} subs.update(self.external_functions) subs.update(self.protected_funcs_subs) + subs.update(self.protected_operators_subs) for expr in self.expressions: if not callable(expr): @@ -438,9 +440,7 @@ def _process_expression_individual( return _process_expression_individual - def __call__( - self, inputs_data: Union[np.ndarray, dict] = None - ) -> List[torch.Tensor]: + def _create_input_for_eval(self, inputs_data: Union[np.ndarray, dict]=None) -> List[torch.Tensor]: """Evaluate the symbolic expression. This function takes either a numpy array or a dictionary of numpy arrays as input. @@ -457,6 +457,7 @@ def __call__( does: not match with the inputs_key attribute """ + constructor = MakeTensor( input_names=self.input_names, output_names=self.output_names ) @@ -490,6 +491,29 @@ def __call__( for inputs_list" ) + return outputs, inputs + + def __call__( + self, inputs_data: Union[np.ndarray, dict] = None + ) -> List[torch.Tensor]: + """Evaluate the symbolic expression. + + This function takes either a numpy array or a dictionary of numpy arrays as input. + + Args: + inputs_data (Union[np.ndarray, dict], optional): Union (Default value = None) + + Returns: + List[torch.Tensor]: List[torch.Tensor]: A list of tensors containing the evaluated expressions. + + Raises: + + Raises: + does: not match with the inputs_key attribute + + """ + outputs, inputs = self._create_input_for_eval(inputs_data=inputs_data) + feed_vars = {**outputs, **inputs} # It returns a list of tensors containing the expressions From c7eef6534ba754e1d360dcb82e1aec26663c094c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas=20de=20Sousa=20Almeida?= Date: Fri, 2 Aug 2024 17:28:34 -0300 Subject: [PATCH 09/10] Final test for special expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Lucas de Sousa Almeida --- tests/residuals/test_symbolicoperator.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/residuals/test_symbolicoperator.py b/tests/residuals/test_symbolicoperator.py index eb5bad4..48d6d42 100644 --- a/tests/residuals/test_symbolicoperator.py +++ b/tests/residuals/test_symbolicoperator.py @@ -228,8 +228,9 @@ def test_symbolic_operator_diff_operators(self): def test_symbolic_operator_grad_operator(self): - f = "D(u, t) - alpha*D(u, (x, y))" - s = "Grad(D(u, t) - alpha*D(u, (x, y)), (x, y))" + f = "D(u, x) - D(u, y)" + s_1 = "D(D(u, x) - D(u, y), x)" + s_2 = "D(D(u, x) - D(u, y), y)" input_labels = ["x", "y"] output_labels = ["u"] @@ -249,18 +250,17 @@ def test_symbolic_operator_grad_operator(self): residual = SymbolicOperator( expressions=[f], - special_expressions=[s], + special_expressions=[s_1, s_2], input_vars=input_labels, - constants={"alpha": 5}, output_vars=output_labels, function=net, engine="torch", ) u = net(input_data=data) + outputs, inputs = residual._create_input_for_eval(inputs_data=data) + feed_vars = {**outputs, **inputs} - feed_vars = {'x': data[:, 0], 'y': data[:,1], 'u': u} - - print(residual.process_special_expression(feed_vars)) + all(isinstance(item, torch.Tensor) for item in residual.process_special_expression(feed_vars)) def test_symbolic_operator_1d_pde(self): # Allen-Cahn equation From 9d95d324cf246678a2ab07fe14bdbda5f5408465 Mon Sep 17 00:00:00 2001 From: Joao Lucas de Sousa Almeida Date: Wed, 7 Aug 2024 20:45:38 -0300 Subject: [PATCH 10/10] Commentaries are welcome Signed-off-by: Joao Lucas de Sousa Almeida --- simulai/residuals/_pytorch_residuals.py | 93 ++++++++++--------------- 1 file changed, 36 insertions(+), 57 deletions(-) diff --git a/simulai/residuals/_pytorch_residuals.py b/simulai/residuals/_pytorch_residuals.py index 7aa1a78..36c1176 100644 --- a/simulai/residuals/_pytorch_residuals.py +++ b/simulai/residuals/_pytorch_residuals.py @@ -29,9 +29,8 @@ class SymbolicOperator(torch.nn.Module): - """The SymbolicOperatorClass is a class that constructs tensor operators using symbolic expressions written in PyTorch. - - + """The SymbolicOperatorClass is a class that constructs tensor operators + using symbolic expressions written in PyTorch. Returns: object: An instance of the SymbolicOperatorClass. """ @@ -59,8 +58,11 @@ def __init__( else: pass + # The engine used to build the expressions. + # Usually PyTorch. self.engine = importlib.import_module(engine) + # Basic attributes self.constants = constants if trainable_parameters is not None: @@ -73,6 +75,7 @@ def __init__( self.processing = processing self.periodic_bc_protected_key = "periodic" + # Special funcions that must be replaced before the compilation self.protected_funcs = [ "cos", "sin", @@ -83,8 +86,12 @@ def __init__( "sech", "sinh", ] + + # Special operators that must be replaced before the compilation self.protected_operators = ["L", "Div", "Grad", "Identity", "Kronecker"] + # Replacing special functions and operatorswith corresponding classes + # and objects self.protected_funcs_subs = self._construct_protected_functions() self.protected_operators_subs = self._construct_implict_operators() @@ -139,9 +146,11 @@ def __init__( self.output = None - self.f_expressions = list() # Main expressions, as PDEs and ODEs - self.g_expressions = dict() # Auxiliary expressions, as boundary conditions - self.h_expressions = list() # Others auxiliary expressions, as those used to evaluate special loss functions + self.f_expressions = list() # Main expressions, as PDEs and ODEs + self.g_expressions = dict() # Auxiliary expressions, as boundary conditions + self.h_expressions = ( + list() + ) # Others auxiliary expressions, as those used to evaluate special loss functions self.feed_vars = None @@ -171,6 +180,7 @@ def __init__( subs.update(self.protected_funcs_subs) subs.update(self.protected_operators_subs) + # Compiling expressions to tensor operators for expr in self.expressions: if not callable(expr): f_expr = sympy.lambdify(self.all_vars, expr, subs) @@ -219,12 +229,10 @@ def _subs_expr(self, expr=None, constants=None): else: expr = expr.subs(constants) - return expr + return expr def _construct_protected_functions(self): """This function creates a dictionary of protected functions from the engine object attribute. - - Returns: dict: A dictionary of function names and their corresponding function objects. """ @@ -245,8 +253,6 @@ def _construct_protected_functions(self): def _construct_implict_operators(self): """This function creates a dictionary of protected operators from the operators engine module. - - Returns: dict: A dictionary of operator names and their corresponding function objects. """ @@ -326,18 +332,15 @@ def _collect_data_from_inputs_list(self, inputs_list: dict = None) -> list: def _parse_expression(self, expr=Union[sympy.Expr, str]) -> sympy.Expr: """Parses the input expression and returns a SymPy expression. - Args: - expr (Union[sympy.Expr, str], optional, optional): The expression to parse, by default None. It can either be a SymPy expression or a string. - + expr (Union[sympy.Expr, str], optional, optional): The expression to parse, by default None. + It can either be a SymPy expression or a string. Returns: sympy.Expr: The parsed SymPy expression. - Raises: Exception: If the `constants` attribute is not defined, and the input expression is a string. - - """ + if isinstance(expr, str): try: expr_ = sympify( @@ -347,7 +350,9 @@ def _parse_expression(self, expr=Union[sympy.Expr, str]) -> sympy.Expr: if self.constants is not None: expr_ = self._subs_expr(expr=expr_, constants=self.constants) if self.trainable_parameters is not None: - expr_ = self._subs_expr(expr=expr_, constants=self.trainable_parameters) + expr_ = self._subs_expr( + expr=expr_, constants=self.trainable_parameters + ) except ValueError: if self.constants is not None: @@ -370,14 +375,13 @@ def _parse_expression(self, expr=Union[sympy.Expr, str]) -> sympy.Expr: def _parse_variable(self, var=Union[sympy.Symbol, str]) -> sympy.Symbol: """Parse the input variable and return a SymPy Symbol. - Args: - var (Union[sympy.Symbol, str], optional, optional): The input variable, either a SymPy Symbol or a string. (Default value = Union[sympy.Symbol, str]) - + var (Union[sympy.Symbol, str], optional, optional): The input variable, either a SymPy Symbol or a string. + (Default value = Union[sympy.Symbol, str]) Returns: sympy.Symbol: A SymPy Symbol representing the input variable. - """ + if isinstance(var, str): return sympy.Symbol(var) else: @@ -385,39 +389,33 @@ def _parse_variable(self, var=Union[sympy.Symbol, str]) -> sympy.Symbol: def _forward_tensor(self, input_data: torch.Tensor = None) -> torch.Tensor: """Forward the input tensor through the function. - Args: input_data (torch.Tensor, optional): The input tensor. (Default value = None) - Returns: torch.Tensor: The output tensor after forward pass. - """ + return self.function.forward(input_data=input_data) def _forward_dict(self, input_data: dict = None) -> torch.Tensor: """Forward the input dictionary through the function. - Args: input_data (dict, optional): The input dictionary. (Default value = None) - Returns: torch.Tensor: The output tensor after forward pass. - """ + return self.function.forward(**input_data) def _factory_process_expression_serial(self, expressions: list = None): def _process_expression_serial(feed_vars: dict = None) -> List[torch.Tensor]: """Process the expression list serially using the given feed variables. - Args: feed_vars (dict, optional): The feed variables. (Default value = None) - Returns: List[torch.Tensor]: A list of tensors after evaluating the expressions serially. - """ + return [f(**feed_vars).to(self.device) for f in expressions] return _process_expression_serial @@ -427,35 +425,28 @@ def _process_expression_individual( index: int = None, feed_vars: dict = None ) -> torch.Tensor: """Evaluates a single expression specified by index from the f_expressions list with given feed variables. - Args: index (int, optional): Index of the expression to be evaluated, by default None feed_vars (dict, optional): Dictionary of feed variables, by default None - Returns: torch.Tensor: Result of evaluating the specified expression with given feed variables - """ + return self.expressions[index](**feed_vars).to(self.device) return _process_expression_individual - def _create_input_for_eval(self, inputs_data: Union[np.ndarray, dict]=None) -> List[torch.Tensor]: + def _create_input_for_eval( + self, inputs_data: Union[np.ndarray, dict] = None + ) -> List[torch.Tensor]: """Evaluate the symbolic expression. - This function takes either a numpy array or a dictionary of numpy arrays as input. - Args: inputs_data (Union[np.ndarray, dict], optional): Union (Default value = None) - Returns: List[torch.Tensor]: List[torch.Tensor]: A list of tensors containing the evaluated expressions. - - Raises: - Raises: does: not match with the inputs_key attribute - """ constructor = MakeTensor( @@ -497,21 +488,15 @@ def __call__( self, inputs_data: Union[np.ndarray, dict] = None ) -> List[torch.Tensor]: """Evaluate the symbolic expression. - This function takes either a numpy array or a dictionary of numpy arrays as input. - Args: inputs_data (Union[np.ndarray, dict], optional): Union (Default value = None) - Returns: List[torch.Tensor]: List[torch.Tensor]: A list of tensors containing the evaluated expressions. - - Raises: - Raises: does: not match with the inputs_key attribute - """ + outputs, inputs = self._create_input_for_eval(inputs_data=inputs_data) feed_vars = {**outputs, **inputs} @@ -522,11 +507,9 @@ def __call__( def eval_expression(self, key, inputs_list): """This function evaluates an expression stored in the class attribute 'g_expressions' using the inputs in 'inputs_list'. If the expression has a periodic boundary condition, the function evaluates the expression at the lower and upper boundaries and returns the difference. If the inputs are provided as a list, they are split into individual tensors and stored in a dictionary with the keys as the input names. If the inputs are provided as an np.ndarray, they are converted to tensors and split along the second axis. If the inputs are provided as a dict, they are extracted using the 'inputs_key' attribute. The inputs, along with the outputs obtained from running the function, are then passed as arguments to the expression using the 'g(**feed_vars)' syntax. - Args: key (str): the key used to retrieve the expression from the 'g_expressions' attribute inputs_list (list): either a list of arrays, an np.ndarray, or a dict containing the inputs to the function - Returns: the result of evaluating the expression using the inputs.: @@ -629,8 +612,8 @@ def eval_expression(self, key, inputs_list): assert ( self.inputs_key is not None ), "If inputs_list is dict, \ - it is necessary to provide\ - a key." + it is necessary to provide\ + a key." inputs = { key: value @@ -652,11 +635,9 @@ def eval_expression(self, key, inputs_list): @staticmethod def gradient(feature, param): """Calculates the gradient of the given feature with respect to the given parameter. - Args: feature (torch.Tensor): Tensor with the input feature. param (torch.Tensor): Tensor with the parameter to calculate the gradient with respect to. - Returns: torch.Tensor: Tensor with the gradient of the feature with respect to the given parameter. Example: @@ -679,10 +660,8 @@ def gradient(feature, param): def jac(self, inputs): """Calculates the Jacobian of the forward function of the model with respect to its inputs. - Args: inputs (torch.Tensor): Tensor with the input data to the forward function. - Returns: torch.Tensor: Tensor with the Jacobian of the forward function with respect to its inputs. Example: