diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 0000000..8b8b860 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: e9259293e3959cef89fe618c7445ba31 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/_autosummary/fastvpinns.html b/_autosummary/fastvpinns.html new file mode 100644 index 0000000..c0bae4a --- /dev/null +++ b/_autosummary/fastvpinns.html @@ -0,0 +1,125 @@ + + +
+ + + +
+# This program will be used to setup the FE2D and quadrature rule for a given cell based on the
+# given mesh and the degree of the basis functions
+
+# Author: Thivin Anandh D
+# Date: 30/Aug/2023
+# Implementation History :
+# the grad_x_orig and grad_y_orig will actually store the magnitute with which we need to multiply this grad_x_ref and grad_y_ref
+# to obtain the actual values of the gradient in the original cell
+# this is done to improve efficiency
+
+
+# Importing the required libraries
+# from .basis_function_2d import *
+
+# import Quadrature rules
+from .quadratureformulas_quad2d import *
+from .fe2d_setup_main import *
+
+
+
+[docs]
+class FE2D_Cell:
+ """
+ This class is used to Store the FE Values, Such as Coordinates, Basis Functions, Quadrature Rules, etc. for a given cell.
+ """
+
+ def __init__(
+ self,
+ cell_coordinates: np.ndarray,
+ cell_type: str,
+ fe_order: int,
+ fe_type: str,
+ quad_order: int,
+ quad_type: str,
+ fe_transformation_type: str,
+ forcing_function,
+ ):
+ self.cell_coordinates = cell_coordinates
+ self.cell_type = cell_type
+ self.fe_order = fe_order
+ self.fe_type = fe_type
+ self.quad_order = quad_order
+ self.quad_type = quad_type
+ self.fe_transformation = fe_transformation_type
+ self.forcing_function = forcing_function
+
+ # Basis function Class
+ self.basis_function = None
+
+ # Quadrature Values
+ self.quad_xi = None
+ self.quad_eta = None
+ self.quad_weight = None
+ self.jacobian = None
+ self.mult = None
+
+ # FE Values
+ self.basis_at_quad = None
+ self.basis_gradx_at_quad = None
+ self.basis_grady_at_quad = None
+ self.basis_gradxy_at_quad = None
+ self.basis_gradxx_at_quad = None
+ self.basis_gradyy_at_quad = None
+
+ # Quadrature Coordinates
+ self.quad_actual_coordinates = None
+
+ # Forcing function values at the quadrature points
+ self.forcing_at_quad = None
+
+ # FE Transformation Class
+ self.fetransformation = None
+
+ # get instance of the FE_setup class
+ self.fe_setup = FE2DSetupMain(
+ cell_type=self.cell_type,
+ fe_order=self.fe_order,
+ fe_type=self.fe_type,
+ quad_order=self.quad_order,
+ quad_type=self.quad_type,
+ )
+
+ # Call the function to assign the basis function
+ self.assign_basis_function()
+
+ # Assign the quadrature points and weights
+ self.assign_quadrature()
+
+ # Assign the FE Transformation
+ self.assign_fe_transformation()
+
+ # calculate mult -> quadrature weights * Jacobian
+ self.assign_quad_weights_and_jacobian()
+
+ # Calculate the basis function values at the quadrature points
+ self.assign_basis_values_at_quadrature_points()
+
+ # calculate the actual coordinates of the quadrature points
+ self.assign_quadrature_coordinates()
+
+ # Calculate the forcing function values at the actual quadrature points
+ # NOTE : The function is just for printing the shape of the force matrix, the
+ # actual calculation is performed on the fespace class
+ self.assign_forcing_term(self.forcing_function)
+
+ # # print the values
+ # print("============================================================================")
+ # print("Cell Co-ord : ", self.cell_coordinates)
+ # print("Basis function values at the quadrature points: \n", self.basis_at_quad / self.mult)
+ # print("Basis function gradx at the quadrature points: \n", self.basis_gradx_at_quad)
+ # print("Basis function grady at the quadrature points: \n", self.basis_grady_at_quad)
+ # print("Forcing function values at the quadrature points: \n", self.forcing_at_quad)
+
+ # grad_x = np.array([5,6,7,8])
+ # grad_y = np.array([1,2,3,4])
+
+ # pde = np.matmul(self.basis_gradx_at_quad, grad_x.reshape(-1,1)) + np.matmul(self.basis_grady_at_quad, grad_y.reshape(-1,1))
+ # print("PDE values at the quadrature points: \n", pde)
+
+
+[docs]
+ def assign_basis_function(self) -> BasisFunction2D:
+ """
+ Assigns the basis function class based on the cell type and the FE order.
+
+ :return: An instance of the BasisFunction2D class.
+ """
+ self.basis_function = self.fe_setup.assign_basis_function()
+
+
+
+[docs]
+ def assign_quadrature(self) -> None:
+ """
+ Assigns the quadrature points and weights based on the cell type and the quadrature order.
+
+ :return: None
+ """
+ self.quad_weight, self.quad_xi, self.quad_eta = self.fe_setup.assign_quadrature_rules()
+
+
+
+[docs]
+ def assign_fe_transformation(self) -> None:
+ """
+ Assigns the FE Transformation class based on the cell type and the FE order.
+
+ This method assigns the appropriate FE Transformation class based on the cell type and the FE order.
+ It sets the cell coordinates for the FE Transformation and obtains the Jacobian of the transformation.
+
+ :return: None
+ """
+ self.fetransformation = self.fe_setup.assign_fe_transformation(
+ self.fe_transformation, self.cell_coordinates
+ )
+ # Sets cell co-ordinates for the FE Transformation
+ self.fetransformation.set_cell()
+
+ # obtains the Jacobian of the transformation
+ self.jacobian = self.fetransformation.get_jacobian(self.quad_xi, self.quad_eta).reshape(
+ -1, 1
+ )
+
+
+
+[docs]
+ def assign_basis_values_at_quadrature_points(self) -> None:
+ """
+ Assigns the basis function values at the quadrature points.
+
+ This method calculates the values of the basis functions and their gradients at the quadrature points.
+ The basis function values are stored in `self.basis_at_quad`, while the gradients are stored in
+ `self.basis_gradx_at_quad`, `self.basis_grady_at_quad`, `self.basis_gradxy_at_quad`,
+ `self.basis_gradxx_at_quad`, and `self.basis_gradyy_at_quad`.
+
+ The basis function values are of size N_basis_functions x N_quad_points.
+
+ Returns:
+ None
+ """
+ self.basis_at_quad = []
+ self.basis_gradx_at_quad = []
+ self.basis_grady_at_quad = []
+ self.basis_gradxy_at_quad = []
+ self.basis_gradxx_at_quad = []
+ self.basis_gradyy_at_quad = []
+
+ self.basis_at_quad = self.basis_function.value(self.quad_xi, self.quad_eta)
+
+ # For Gradients we need to perform a transformation to the original cell
+ grad_x_ref = self.basis_function.gradx(self.quad_xi, self.quad_eta)
+ grad_y_ref = self.basis_function.grady(self.quad_xi, self.quad_eta)
+
+ grad_x_orig, grad_y_orig = self.fetransformation.get_orig_from_ref_derivative(
+ grad_x_ref, grad_y_ref, self.quad_xi, self.quad_eta
+ )
+
+ self.basis_gradx_at_quad = grad_x_orig
+ self.basis_grady_at_quad = grad_y_orig
+
+ self.basis_gradx_at_quad_ref = grad_x_ref
+ self.basis_grady_at_quad_ref = grad_y_ref
+
+ # get the double derivatives of the basis functions ( ref co-ordinates )
+ grad_xx_ref = self.basis_function.gradxx(self.quad_xi, self.quad_eta)
+ grad_xy_ref = self.basis_function.gradxy(self.quad_xi, self.quad_eta)
+ grad_yy_ref = self.basis_function.gradyy(self.quad_xi, self.quad_eta)
+
+ # get the double derivatives of the basis functions ( orig co-ordinates )
+ grad_xx_orig, grad_xy_orig, grad_yy_orig = (
+ self.fetransformation.get_orig_from_ref_second_derivative(
+ grad_xx_ref, grad_xy_ref, grad_yy_ref, self.quad_xi, self.quad_eta
+ )
+ )
+
+ # = the value
+ self.basis_gradxy_at_quad = grad_xy_orig
+ self.basis_gradxx_at_quad = grad_xx_orig
+ self.basis_gradyy_at_quad = grad_yy_orig
+
+ # Multiply each row with the quadrature weights
+ # Basis at Quad - n_test * N_quad
+ self.basis_at_quad = self.basis_at_quad * self.mult
+ self.basis_gradx_at_quad = self.basis_gradx_at_quad * self.mult
+ self.basis_grady_at_quad = self.basis_grady_at_quad * self.mult
+ self.basis_gradxy_at_quad = self.basis_gradxy_at_quad * self.mult
+ self.basis_gradxx_at_quad = self.basis_gradxx_at_quad * self.mult
+ self.basis_gradyy_at_quad = self.basis_gradyy_at_quad * self.mult
+
+
+
+[docs]
+ def assign_quad_weights_and_jacobian(self) -> None:
+ """
+ Assigns the quadrature weights and the Jacobian of the transformation.
+
+ This method calculates and assigns the quadrature weights and the Jacobian of the transformation
+ for the current cell. The quadrature weights are multiplied by the flattened Jacobian array
+ and stored in the `mult` attribute of the class.
+
+ :return: None
+ """
+ self.mult = self.quad_weight * self.jacobian.flatten()
+
+
+
+[docs]
+ def assign_quadrature_coordinates(self) -> None:
+ """
+ Assigns the actual coordinates of the quadrature points.
+
+ This method calculates the actual coordinates of the quadrature points based on the given Xi and Eta values.
+ The Xi and Eta values are obtained from the `quad_xi` and `quad_eta` attributes of the class.
+ The calculated coordinates are stored in the `quad_actual_coordinates` attribute as a NumPy array.
+
+ :return: None
+ """
+ actual_co_ord = []
+ for xi, eta in zip(self.quad_xi, self.quad_eta):
+ actual_co_ord.append(self.fetransformation.get_original_from_ref(xi, eta))
+
+ self.quad_actual_coordinates = np.array(actual_co_ord)
+
+
+
+[docs]
+ def assign_forcing_term(self, forcing_function) -> None:
+ """
+ Assigns the forcing function values at the quadrature points.
+
+ This function computes the values of the forcing function at the quadrature points
+ and assigns them to the `forcing_at_quad` attribute of the FE2D_Cell object.
+
+ Parameters:
+ forcing_function (callable): The forcing function that takes the coordinates (x, y)
+ as input and returns the value of the forcing function at those coordinates.
+
+ Returns:
+ None
+
+ Notes:
+ - The final shape of `forcing_at_quad` will be N_shape_functions x 1.
+ - This function is for backward compatibility with old code and currently assigns
+ the values as zeros. The actual calculation is performed in the fespace class.
+ """
+ # get number of shape functions
+ n_shape_functions = self.basis_function.num_shape_functions
+
+ # Loop over all the basis functions and compute the integral
+ f_integral = np.zeros((n_shape_functions, 1), dtype=np.float64)
+
+ # The above code is for backward compatibility with old code. this function will just assign the values as zeros
+ # the actual calculation is performed in the fespace class
+
+ # for i in range(n_shape_functions):
+ # val = 0
+ # for q in range(self.basis_at_quad.shape[1]):
+ # x = self.quad_actual_coordinates[q, 0]
+ # y = self.quad_actual_coordinates[q, 1]
+ # # print("f_values[q] = ",f_values[q])
+
+ # # the JAcobian and the quadrature weights are pre multiplied to the basis functions
+ # val += ( self.basis_at_quad[i, q] ) * self.forcing_function(x, y)
+ # # print("val = ", val)
+
+ # f_integral[i] = val
+
+ self.forcing_at_quad = f_integral
+
+
+
+"""
+file: basis_2d_QN_Jacobi.py
+description: This file contains the class Basis2DQNJacobi which is used
+ to define the basis functions for a Jacobi Polynomial.
+ Test functions and derivatives are inferred from the work by Ehsan Kharazmi et.al
+ (hp-VPINNs: Variational Physics-Informed Neural Networks With Domain Decomposition)
+ available at https://github.com/ehsankharazmi/hp-VPINNs/
+authors: Thivin Anandh D
+changelog: 30/Aug/2023 - Initial version
+known_issues: None
+dependencies: Requires scipy and numpy.
+"""
+
+# import the jacobi polynomials
+from scipy.special import jacobi
+
+import numpy as np
+from .basis_function_2d import BasisFunction2D
+
+
+
+[docs]
+class Basis2DQNJacobi(BasisFunction2D):
+ """
+ This class defines the basis functions for a 2D Q1 element.
+ """
+
+ def __init__(self, num_shape_functions: int):
+ super().__init__(num_shape_functions)
+
+
+[docs]
+ def jacobi_wrapper(self, n, a, b, x):
+ """
+ Evaluate the Jacobi polynomial of degree n with parameters a and b at the given points x.
+
+ :param n: Degree of the Jacobi polynomial.
+ :type n: int
+ :param a: First parameter of the Jacobi polynomial.
+ :type a: float
+ :param b: Second parameter of the Jacobi polynomial.
+ :type b: float
+ :param x: Points at which to evaluate the Jacobi polynomial.
+ :type x: array_like
+
+ :return: Values of the Jacobi polynomial at the given points x.
+ :rtype: array_like
+ """
+ x = np.array(x, dtype=np.float64)
+ return jacobi(n, a, b)(x)
+
+
+ # Derivative of the Jacobi polynomials
+
+[docs]
+ def djacobi(self, n, a, b, x, k: int):
+ """
+ Evaluate the k-th derivative of the Jacobi polynomial of degree n with parameters a and b at the given points x.
+
+ :param n: Degree of the Jacobi polynomial.
+ :type n: int
+ :param a: First parameter of the Jacobi polynomial.
+ :type a: float
+ :param b: Second parameter of the Jacobi polynomial.
+ :type b: float
+ :param x: Points at which to evaluate the Jacobi polynomial.
+ :type x: array_like
+ :param k: Order of the derivative.
+ :type k: int
+
+ :return: Values of the k-th derivative of the Jacobi polynomial at the given points x.
+ :rtype: array_like
+
+ :raises ValueError: If the derivative order is not 1 or 2.
+
+ :raises ImportError: If the required module 'jacobi' is not found.
+
+ :raises Exception: If an unknown error occurs during the computation.
+ """
+ x = np.array(x, dtype=np.float64)
+ if k == 1:
+ return jacobi(n, a, b).deriv()(x)
+ if k == 2:
+ return jacobi(n, a, b).deriv(2)(x)
+ else:
+ print(f"Invalid derivative order {k} in {__name__}.")
+ raise ValueError("Derivative order should be 1 or 2.")
+
+
+ ## Helper Function
+
+[docs]
+ def test_fcnx(self, n_test, x):
+ """
+ Compute the x-component of the test functions for a given number of test functions and x-coordinates.
+
+ :param n_test: Number of test functions.
+ :type n_test: int
+ :param x: x-coordinates at which to evaluate the test functions.
+ :type x: array_like
+
+ :return: Values of the x-component of the test functions.
+ :rtype: array_like
+ """
+ test_total = []
+ for n in range(1, n_test + 1):
+ test = self.jacobi_wrapper(n - 1, 0, 0, x)
+ test_total.append(test)
+ return np.asarray(test_total, np.float64)
+
+
+
+[docs]
+ def test_fcny(self, n_test, y):
+ """
+ Compute the y-component of the test functions for a given number of test functions and y-coordinates.
+
+ Parameters:
+ n_test (int): Number of test functions.
+ y (array_like): y-coordinates at which to evaluate the test functions.
+
+ Returns:
+ array_like: Values of the y-component of the test functions.
+ """
+ test_total = []
+ for n in range(1, n_test + 1):
+ test = self.jacobi_wrapper(n - 1, 0, 0, y)
+ test_total.append(test)
+ return np.asarray(test_total, np.float64)
+
+
+
+[docs]
+ def dtest_fcn(self, n_test, x):
+ """
+ Compute the x-derivatives of the test functions for a given number of test functions and x-coordinates.
+
+ :param n_test: Number of test functions.
+ :type n_test: int
+ :param x: x-coordinates at which to evaluate the test functions.
+ :type x: array_like
+
+ :return: Values of the x-derivatives of the test functions.
+ :rtype: array_like
+ """
+ d1test_total = []
+ for n in range(1, n_test + 1):
+ d1test = self.djacobi(n - 1, 0, 0, x, 1)
+ d1test_total.append(d1test)
+ return np.asarray(d1test_total)
+
+
+
+[docs]
+ def ddtest_fcn(self, n_test, x):
+ """
+ Compute the x-derivatives of the test functions for a given number of test functions and x-coordinates.
+
+ :param n_test: Number of test functions.
+ :type n_test: int
+ :param x: x-coordinates at which to evaluate the test functions.
+ :type x: array_like
+
+ :return: Values of the x-derivatives of the test functions.
+ :rtype: array_like
+ """
+ d1test_total = []
+ for n in range(1, n_test + 1):
+ d1test = self.djacobi(n - 1, 0, 0, x, 2)
+ d1test_total.append(d1test)
+ return np.asarray(d1test_total)
+
+
+
+[docs]
+ def value(self, xi, eta):
+ """
+ This method returns the values of the basis functions at the given (xi, eta) coordinates.
+
+ :param xi: x-coordinates at which to evaluate the basis functions.
+ :type xi: array_like
+ :param eta: y-coordinates at which to evaluate the basis functions.
+ :type eta: array_like
+ :return: Values of the basis functions.
+ :rtype: array_like
+ """
+ num_shape_func_in_1d = int(np.sqrt(self.num_shape_functions))
+ test_x = self.test_fcnx(num_shape_func_in_1d, xi)
+ test_y = self.test_fcny(num_shape_func_in_1d, eta)
+ values = np.zeros((self.num_shape_functions, len(xi)), dtype=np.float64)
+
+ for i in range(num_shape_func_in_1d):
+ values[num_shape_func_in_1d * i : num_shape_func_in_1d * (i + 1), :] = (
+ test_x[i, :] * test_y
+ )
+
+ return values
+
+
+
+[docs]
+ def gradx(self, xi, eta):
+ """
+ This method returns the x-derivatives of the basis functions at the given (xi, eta) coordinates.
+
+ :param xi: x-coordinates at which to evaluate the basis functions.
+ :type xi: array_like
+ :param eta: y-coordinates at which to evaluate the basis functions.
+ :type eta: array_like
+ :return: Values of the x-derivatives of the basis functions.
+ :rtype: array_like
+ """
+ num_shape_func_in_1d = int(np.sqrt(self.num_shape_functions))
+ grad_test_x = self.dtest_fcn(num_shape_func_in_1d, xi)
+ test_y = self.test_fcny(num_shape_func_in_1d, eta)
+ values = np.zeros((self.num_shape_functions, len(xi)), dtype=np.float64)
+
+ for i in range(num_shape_func_in_1d):
+ values[num_shape_func_in_1d * i : num_shape_func_in_1d * (i + 1), :] = (
+ grad_test_x[i, :] * test_y
+ )
+
+ return values
+
+
+
+[docs]
+ def grady(self, xi, eta):
+ """
+ This method returns the y-derivatives of the basis functions at the given (xi, eta) coordinates.
+
+ :param xi: x-coordinates at which to evaluate the basis functions.
+ :type xi: array_like
+
+ :param eta: y-coordinates at which to evaluate the basis functions.
+ :type eta: array_like
+
+ :return: Values of the y-derivatives of the basis functions.
+ :rtype: array_like
+ """
+ num_shape_func_in_1d = int(np.sqrt(self.num_shape_functions))
+ test_x = self.test_fcnx(num_shape_func_in_1d, xi)
+ grad_test_y = self.dtest_fcn(num_shape_func_in_1d, eta)
+ values = np.zeros((self.num_shape_functions, len(xi)), dtype=np.float64)
+
+ for i in range(num_shape_func_in_1d):
+ values[num_shape_func_in_1d * i : num_shape_func_in_1d * (i + 1), :] = (
+ test_x[i, :] * grad_test_y
+ )
+
+ return values
+
+
+
+[docs]
+ def gradxx(self, xi, eta):
+ """
+ This method returns the xx-derivatives of the basis functions at the given (xi, eta) coordinates.
+
+ :param xi: x-coordinates at which to evaluate the basis functions.
+ :type xi: array_like
+ :param eta: y-coordinates at which to evaluate the basis functions.
+ :type eta: array_like
+
+ :return: Values of the xx-derivatives of the basis functions.
+ :rtype: array_like
+ """
+ num_shape_func_in_1d = int(np.sqrt(self.num_shape_functions))
+ grad_grad_x = self.ddtest_fcn(num_shape_func_in_1d, xi)
+ test_y = self.test_fcny(num_shape_func_in_1d, eta)
+ values = np.zeros((self.num_shape_functions, len(xi)), dtype=np.float64)
+
+ for i in range(num_shape_func_in_1d):
+ values[num_shape_func_in_1d * i : num_shape_func_in_1d * (i + 1), :] = (
+ grad_grad_x[i, :] * test_y
+ )
+
+ return values
+
+
+
+[docs]
+ def gradxy(self, xi, eta):
+ """
+ This method returns the xy-derivatives of the basis functions at the given (xi, eta) coordinates.
+
+ :param xi: x-coordinates at which to evaluate the basis functions.
+ :type xi: array_like
+ :param eta: y-coordinates at which to evaluate the basis functions.
+ :type eta: array_like
+ :return: Values of the xy-derivatives of the basis functions.
+ :rtype: array_like
+ """
+ num_shape_func_in_1d = int(np.sqrt(self.num_shape_functions))
+ grad_test_x = self.dtest_fcn(num_shape_func_in_1d, xi)
+ grad_test_y = self.dtest_fcn(num_shape_func_in_1d, eta)
+ values = np.zeros((self.num_shape_functions, len(xi)), dtype=np.float64)
+
+ for i in range(num_shape_func_in_1d):
+ values[num_shape_func_in_1d * i : num_shape_func_in_1d * (i + 1), :] = (
+ grad_test_x[i, :] * grad_test_y
+ )
+
+ return values
+
+
+
+[docs]
+ def gradyy(self, xi, eta):
+ """
+ This method returns the yy-derivatives of the basis functions at the given (xi, eta) coordinates.
+
+ :param xi: x-coordinates at which to evaluate the basis functions.
+ :type xi: array_like
+ :param eta: y-coordinates at which to evaluate the basis functions.
+ :type eta: array_like
+
+ :return: Values of the yy-derivatives of the basis functions.
+ :rtype: array_like
+ """
+ num_shape_func_in_1d = int(np.sqrt(self.num_shape_functions))
+ test_x = self.test_fcnx(num_shape_func_in_1d, xi)
+ grad_grad_y = self.ddtest_fcn(num_shape_func_in_1d, eta)
+ values = np.zeros((self.num_shape_functions, len(xi)), dtype=np.float64)
+
+ for i in range(num_shape_func_in_1d):
+ values[num_shape_func_in_1d * i : num_shape_func_in_1d * (i + 1), :] = (
+ test_x[i, :] * grad_grad_y
+ )
+
+ return values
+
+
+
+"""
+file: basis_function_2d.py
+description: This file contains a wrapper class for all the finite element basis functions
+ used in the FE2D code. The 2D basis functions have methods to return the value
+ of the basis function and its derivatives at the reference point (xi, eta).
+authors: Thivin Anandh D
+changelog: 30/Aug/2023 - First version
+known_issues: None
+dependencies: None specified.
+"""
+
+from abc import abstractmethod
+
+
+
+[docs]
+class BasisFunction2D:
+ """
+ Represents a basis function in 2D.
+
+ Args:
+ num_shape_functions (int): The number of shape functions.
+
+ Methods:
+ value(xi, eta): Evaluates the basis function at the given xi and eta coordinates.
+ gradx(xi, eta): Computes the partial derivative of the basis function with respect to xi.
+ grady(xi, eta): Computes the partial derivative of the basis function with respect to eta.
+ gradxx(xi, eta): Computes the second partial derivative of the basis function with respect to xi.
+ gradxy(xi, eta): Computes the mixed partial derivative of the basis function with respect to xi and eta.
+ gradyy(xi, eta): Computes the second partial derivative of the basis function with respect to eta.
+ """
+
+ def __init__(self, num_shape_functions):
+ self.num_shape_functions = num_shape_functions
+
+
+[docs]
+ @abstractmethod
+ def value(self, xi, eta):
+ """
+ Evaluates the basis function at the given xi and eta coordinates.
+
+ :param float xi: The xi coordinate.
+ :param float eta: The eta coordinate.
+ :return: The value of the basis function at the given coordinates.
+ :rtype: float
+ """
+ pass
+
+
+
+[docs]
+ @abstractmethod
+ def gradx(self, xi, eta):
+ """
+ Computes the partial derivative of the basis function with respect to xi.
+
+ :param float xi: The xi coordinate.
+ :param float eta: The eta coordinate.
+ :return: The partial derivative of the basis function with respect to xi.
+ :rtype: float
+ """
+ pass
+
+
+
+[docs]
+ @abstractmethod
+ def grady(self, xi, eta):
+ """
+ Computes the partial derivative of the basis function with respect to eta.
+
+ :param float xi: The xi coordinate.
+ :param float eta: The eta coordinate.
+ :return: The partial derivative of the basis function with respect to eta.
+ :rtype: float
+ """
+ pass
+
+
+
+[docs]
+ @abstractmethod
+ def gradxx(self, xi, eta):
+ """
+ Computes the second partial derivative of the basis function with respect to xi.
+
+ :param float xi: The xi coordinate.
+ :param float eta: The eta coordinate.
+ :return: The second partial derivative of the basis function with respect to xi.
+ :rtype: float
+ """
+ pass
+
+
+
+[docs]
+ @abstractmethod
+ def gradxy(self, xi, eta):
+ """
+ Computes the mixed partial derivative of the basis function with respect to xi and eta.
+
+ :param float xi: The xi coordinate.
+ :param float eta: The eta coordinate.
+ :return: The mixed partial derivative of the basis function with respect to xi and eta.
+ :rtype: float
+ """
+ pass
+
+
+
+[docs]
+ @abstractmethod
+ def gradyy(self, xi, eta):
+ """
+ Computes the second partial derivative of the basis function with respect to eta.
+
+ :param float xi: The xi coordinate.
+ :param float eta: The eta coordinate.
+ :return: The second partial derivative of the basis function with respect to eta.
+ :rtype: float
+ """
+ pass
+
+
+
+
+# ---------------- Legendre -------------------------- #
+from .basis_2d_QN_Legendre import * # Normal Legendre from Jacobi -> J(n) = J(n-1) - J(n+1)
+from .basis_2d_QN_Legendre_Special import * # L(n) = L(n-1) - L(n+1)
+
+# ---------------- Jacobi -------------------------- #
+from .basis_2d_QN_Jacobi import * # Normal Jacobi
+
+# ---------------- Chebyshev -------------------------- #
+from .basis_2d_QN_Chebyshev_2 import * # Normal Chebyshev
+
+# This program will be used to setup the FE2D and quadrature rule for a given cell based on the
+# given mesh and the degree of the basis functions
+
+# Author: Thivin Anandh D
+# Date: 30/Aug/2023
+
+# Importing the required libraries
+from .basis_function_2d import *
+
+# import Quadrature rules
+from .quadratureformulas_quad2d import *
+
+
+# import base class for FE transformation
+from .fe_transformation_2d import *
+
+
+
+[docs]
+class FE2DSetupMain:
+ """
+ This class is used to setup the FE2D and quadrature rule for a given cell based on the given mesh and the degree of the basis functions.
+ """
+
+ def __init__(
+ self, cell_type: str, fe_order: int, fe_type: str, quad_order: int, quad_type: str
+ ):
+ self.cell_type = cell_type
+ self.fe_order = fe_order
+ self.fe_type = fe_type
+ self.quad_order = quad_order
+ self.quad_type = quad_type
+
+ self.assign_basis_function()
+
+
+[docs]
+ def assign_basis_function(self) -> BasisFunction2D:
+ """
+ Assigns the basis function based on the cell type and the fe_order.
+
+ :return: An instance of the BasisFunction2D class representing the assigned basis function.
+ :rtype: BasisFunction2D
+ :raises ValueError: If the fe_order is invalid.
+ """
+ # check for FE order lower bound and higher bound
+ if self.fe_order <= 1 or self.fe_order >= 1e3:
+ print(f"Invalid FE order {self.fe_order} in {self.__class__.__name__} from {__name__}.")
+ raise ValueError("FE order should be greater than 1 and less than 1e4.")
+
+ if self.cell_type == "quadrilateral":
+ self.n_nodes = 4
+
+ # --- LEGENDRE --- #
+ if self.fe_type == "legendre" or self.fe_type == "jacobi":
+ # jacobi is added for backward compatibility with prev pushes
+ # generally, jacobi is referred to as Legendre basis on previous iterations
+ return Basis2DQNLegendre(self.fe_order**2)
+
+ elif self.fe_type == "legendre_special":
+ return Basis2DQNLegendreSpecial(self.fe_order**2)
+
+ # ----- CHEBYSHEV ---- #
+ elif self.fe_type == "chebyshev_2":
+ return Basis2DQNChebyshev2(self.fe_order**2)
+
+ # ----- PLain jacobi ---- #
+ elif self.fe_type == "jacobi_plain":
+ return Basis2DQNJacobi(self.fe_order**2)
+
+ else:
+ print(
+ f"Invalid FE order {self.fe_order} in {self.__class__.__name__} from {__name__}."
+ )
+ raise ValueError(
+ 'FE order should be one of the : "legendre" , "jacobi", "legendre_special", "chebyshev_2", "jacobi_plain"'
+ )
+
+ print(f"Invalid cell type {self.cell_type} in {self.__class__.__name__} from {__name__}.")
+
+
+
+[docs]
+ def assign_quadrature_rules(self):
+ """
+ Assigns the quadrature rule based on the quad_order.
+
+ :return: A tuple containing the weights, xi, and eta values of the quadrature rule.
+ :rtype: tuple
+ :raises ValueError: If the quad_order is invalid or the cell_type is invalid.
+ """
+ if self.cell_type == "quadrilateral":
+ if self.quad_order < 3:
+ raise ValueError("Quad order should be greater than 2.")
+ elif self.quad_order >= 2 and self.quad_order <= 9999:
+ weights, xi, eta = Quadratureformulas_Quad2D(
+ self.quad_order, self.quad_type
+ ).get_quad_values()
+ return weights, xi, eta
+ else:
+ print(
+ f"Invalid quad order {self.quad_order} in {self.__class__.__name__} from {__name__}."
+ )
+ raise ValueError("Quad order should be between 1 and 9999.")
+
+ raise ValueError(
+ f"Invalid cell type {self.cell_type} in {self.__class__.__name__} from {__name__}."
+ )
+
+
+
+[docs]
+ def assign_fe_transformation(
+ self, fe_transformation_type, cell_coordinates
+ ) -> FETransforamtion2D:
+ """
+ Assigns the FE transformation based on the cell type.
+
+ :param fe_transformation_type: The type of FE transformation.
+ :type fe_transformation_type: str
+ :param cell_coordinates: The coordinates of the cell.
+ :type cell_coordinates: list
+ :return: The FE transformation object.
+ :rtype: FETransforamtion2D
+ :raises ValueError: If the cell type or FE transformation type is invalid.
+ """
+ if self.cell_type == "quadrilateral":
+ if fe_transformation_type == "affine":
+ return QuadAffin(cell_coordinates)
+ elif fe_transformation_type == "bilinear":
+ return QuadBilinear(cell_coordinates)
+ else:
+ raise ValueError(
+ f"Invalid FE transformation type {fe_transformation_type} in {self.__class__.__name__} from {__name__}."
+ )
+
+ else:
+ raise ValueError(
+ f"Invalid cell type {self.cell_type} in {self.__class__.__name__} from {__name__}."
+ )
+
+
+
+# This class provides an interface for the transformation methods used in the
+# 2D finite element analysis. The transformation is essential for mapping
+# element geometry in the reference domain to the actual physical domain.
+# The primary functionalities encapsulated within this class include:
+# 1. set_cell() - To set the physical coordinates of the cell. These coordinates
+# are essential for subsequent computations involving Jacobians and transformations.
+# 2. get_original_from_ref(xi, eta) - Given reference coordinates (xi, eta), this
+# method returns the corresponding coordinates in the physical domain.
+# 3. get_jacobian(xi, eta) - For a given point in the reference domain, represented
+# by (xi, eta), this method calculates and returns the Jacobian of
+# the transformation, which provides information about the local stretching,
+# rotation, and skewing of the element.
+#
+# Further implementations of this class for specific element types (like quad and triangular elements)
+# can incorporate more detailed and element-specific transformation techniques.
+#
+# Author: Thivin Anandh D
+# Date: 20-Sep-2023
+# History: First version - 20-Sep-2023 - Thivin Anandh
+
+
+from abc import abstractmethod
+
+
+
+[docs]
+class FETransforamtion2D:
+ """
+ This class represents a 2D finite element transformation.
+ """
+
+ def __init__(self):
+ pass
+
+
+[docs]
+ @abstractmethod
+ def set_cell(self):
+ """
+ Set the cell co-ordinates, which will be used to calculate the Jacobian and actual values.
+
+ :return: None
+ """
+ pass
+
+
+ @abstractmethod
+ def get_original_from_ref(self, xi, eta):
+ """
+ This method returns the original coordinates from the reference coordinates.
+
+ :param xi: The xi coordinate in the reference space.
+ :type xi: float
+ :param eta: The eta coordinate in the reference space.
+ :type eta: float
+ :return: The original coordinates (x, y) corresponding to the given reference coordinates.
+ :rtype: tuple
+ """
+ pass
+
+
+[docs]
+ def get_original_from_ref(self, xi, eta):
+ """
+ This method returns the original co-ordinates from the reference co-ordinates.
+
+ :param xi: The xi value of the reference co-ordinates.
+ :type xi: float
+ :param eta: The eta value of the reference co-ordinates.
+ :type eta: float
+ :return: The original co-ordinates corresponding to the given reference co-ordinates.
+ :rtype: tuple
+ """
+ pass
+
+
+
+[docs]
+ @abstractmethod
+ def get_jacobian(self, xi, eta):
+ """
+ This method returns the Jacobian of the transformation.
+
+ :param xi: The xi coordinate.
+ :type xi: float
+ :param eta: The eta coordinate.
+ :type eta: float
+ :return: The Jacobian matrix.
+ :rtype: numpy.ndarray
+ """
+ pass
+
+
+
+
+## Mandatory, Import all the basis functions here (Quad element Transformations)
+from .quad_affine import *
+from .quad_bilinear import *
+
+"""
+file: fespace2d.py
+description: This file contains the main class that holds the information of all the
+ Finite Element (FE) spaces of all the cells within the given mesh.
+authors: Thivin Anandh D
+changelog: 30/Aug/2023 - Initial version
+known_issues: None
+dependencies: None specified.
+"""
+
+import numpy as np
+import meshio
+from .FE2D_Cell import FE2D_Cell
+
+# from rich.progress import Progress, TextColumn, BarColumn, TimeElapsedColumn
+from tqdm import tqdm
+
+# import plotting
+import matplotlib.pyplot as plt
+
+# import path
+from pathlib import Path
+
+# import tensorflow
+import tensorflow as tf
+
+from ..utils.print_utils import print_table
+
+from pyDOE import lhs
+import pandas as pd
+
+from matplotlib import rc
+from cycler import cycler
+
+
+plt.rcParams["xtick.labelsize"] = 20
+plt.rcParams["axes.titlesize"] = 20
+plt.rcParams["axes.labelsize"] = 20
+
+plt.rcParams["legend.fontsize"] = 20
+plt.rcParams["ytick.labelsize"] = 20
+plt.rcParams["axes.prop_cycle"] = cycler(
+ color=[
+ "darkblue",
+ "#d62728",
+ "#2ca02c",
+ "#ff7f0e",
+ "#bcbd22",
+ "#8c564b",
+ "#17becf",
+ "#9467bd",
+ "#e377c2",
+ "#7f7f7f",
+ ]
+)
+
+
+
+[docs]
+class Fespace2D:
+ """
+ Represents a finite element space in 2D.
+
+ :param mesh: The mesh object.
+ :type mesh: Mesh
+ :param cells: The array of cell indices.
+ :type cells: ndarray
+ :param boundary_points: The dictionary of boundary points.
+ :type boundary_points: dict
+ :param cell_type: The type of cell.
+ :type cell_type: str
+ :param fe_order: The order of the finite element.
+ :type fe_order: int
+ :param fe_type: The type of finite element.
+ :type fe_type: str
+ :param quad_order: The order of the quadrature.
+ :type quad_order: int
+ :param quad_type: The type of quadrature.
+ :type quad_type: str
+ :param fe_transformation_type: The type of finite element transformation.
+ :type fe_transformation_type: str
+ :param bound_function_dict: The dictionary of boundary functions.
+ :type bound_function_dict: dict
+ :param bound_condition_dict: The dictionary of boundary conditions.
+ :type bound_condition_dict: dict
+ :param forcing_function: The forcing function.
+ :type forcing_function: function
+ :param output_path: The output path.
+ :type output_path: str
+ :param generate_mesh_plot: Whether to generate a plot of the mesh. Defaults to False.
+ :type generate_mesh_plot: bool, optional
+ """
+
+ def __init__(
+ self,
+ mesh,
+ cells,
+ boundary_points,
+ cell_type: str,
+ fe_order: int,
+ fe_type: str,
+ quad_order: int,
+ quad_type: str,
+ fe_transformation_type: str,
+ bound_function_dict: dict,
+ bound_condition_dict: dict,
+ forcing_function,
+ output_path: str,
+ generate_mesh_plot: bool = False,
+ ) -> None:
+ """
+ The constructor of the Fespace2D class.
+ """
+ self.mesh = mesh
+ self.boundary_points = boundary_points
+ self.cells = cells
+ self.cell_type = cell_type
+ self.fe_order = fe_order
+ self.fe_type = fe_type
+ self.quad_order = quad_order
+ self.quad_type = quad_type
+
+ self.fe_transformation_type = fe_transformation_type
+
+ if cell_type == "triangle":
+ raise ValueError(
+ "Triangle Mesh is not supported yet"
+ ) # added by thivin - to remove support for triangular mesh
+
+ self.output_path = output_path
+ self.bound_function_dict = bound_function_dict
+ self.bound_condition_dict = bound_condition_dict
+ self.forcing_function = forcing_function
+ self.generate_mesh_plot = generate_mesh_plot
+
+ # to be calculated in the plot function
+ self.total_dofs = 0
+ self.total_boundary_dofs = 0
+
+ # to be calculated on get_boundary_data_dirichlet function
+ self.total_dirichlet_dofs = 0
+
+ # get the number of cells
+ self.n_cells = self.cells.shape[0]
+
+ self.fe_cell = []
+
+ # Function which assigns the fe_cell for each cell
+ self.set_finite_elements()
+
+ # generate the plot of the mesh
+ if self.generate_mesh_plot:
+ self.generate_plot(self.output_path)
+ # self.generate_plot(self.output_path)
+
+ # Obtain boundary Data
+ self.dirichlet_boundary_data = self.generate_dirichlet_boundary_data()
+
+ title = [
+ "Number of Cells",
+ "Number of Quadrature Points",
+ "Number of Dirichlet Boundary Points",
+ "Quadrature Order",
+ "FE Order",
+ "FE Type",
+ "FE Transformation Type",
+ ]
+ values = [
+ self.n_cells,
+ self.total_dofs,
+ self.total_dirichlet_dofs,
+ self.quad_order,
+ self.fe_order,
+ self.fe_type,
+ self.fe_transformation_type,
+ ]
+ # print the table
+ print_table("FE Space Information", ["Property", "Value"], title, values)
+
+
+[docs]
+ def set_finite_elements(self) -> None:
+ """
+ Assigns the finite elements to each cell.
+
+ This method initializes the finite element objects for each cell in the mesh.
+ It creates an instance of the `FE2D_Cell` class for each cell, passing the necessary parameters.
+ The finite element objects store information about the basis functions, gradients, Jacobians,
+ quadrature points, weights, actual coordinates, and forcing functions associated with each cell.
+
+ After initializing the finite element objects, this method prints the shape details of various matrices
+ and updates the total number of degrees of freedom (dofs) for the entire mesh.
+
+ :return: None
+ """
+ progress_bar = tqdm(
+ total=self.n_cells,
+ desc="Fe2D_cell Setup",
+ unit="cells_assembled",
+ bar_format="{l_bar}{bar:40}{r_bar}{bar:-10b}",
+ colour="blue",
+ ncols=100,
+ )
+
+ dof = 0
+ for i in range(self.n_cells):
+ self.fe_cell.append(
+ FE2D_Cell(
+ self.cells[i],
+ self.cell_type,
+ self.fe_order,
+ self.fe_type,
+ self.quad_order,
+ self.quad_type,
+ self.fe_transformation_type,
+ self.forcing_function,
+ )
+ )
+
+ # obtain the shape of the basis function (n_test, N_quad)
+ dof += self.fe_cell[i].basis_at_quad.shape[1]
+
+ progress_bar.update(1)
+ # print the Shape details of all the matrices from cell 0 using print_table function
+ title = [
+ "Shape function Matrix Shape",
+ "Shape function Gradient Matrix Shape",
+ "Jacobian Matrix Shape",
+ "Quadrature Points Shape",
+ "Quadrature Weights Shape",
+ "Quadrature Actual Coordinates Shape",
+ "Forcing Function Shape",
+ ]
+ values = [
+ self.fe_cell[0].basis_at_quad.shape,
+ self.fe_cell[0].basis_gradx_at_quad.shape,
+ self.fe_cell[0].jacobian.shape,
+ self.fe_cell[0].quad_xi.shape,
+ self.fe_cell[0].quad_weight.shape,
+ self.fe_cell[0].quad_actual_coordinates.shape,
+ self.fe_cell[0].forcing_at_quad.shape,
+ ]
+ print_table("FE Matrix Shapes", ["Matrix", "Shape"], title, values)
+
+ # update the total number of dofs
+ self.total_dofs = dof
+
+
+
+[docs]
+ def generate_plot(self, output_path) -> None:
+ """
+ Generate a plot of the mesh.
+
+ :param output_path: The path to save the generated plot.
+ :type output_path: str
+ """
+ total_quad = 0
+ marker_list = [
+ "o",
+ ".",
+ ",",
+ "x",
+ "+",
+ "P",
+ "s",
+ "D",
+ "d",
+ "^",
+ "v",
+ "<",
+ ">",
+ "p",
+ "h",
+ "H",
+ ]
+
+ print(f"[INFO] : Generating the plot of the mesh")
+ # Plot the mesh
+ plt.figure(figsize=(6.4, 4.8), dpi=300)
+
+ # label flag ( to add the label only once)
+ label_set = False
+
+ # plot every cell as a quadrilateral
+ # loop over all the cells
+ for i in range(self.n_cells):
+ # get the coordinates of the cell
+ x = self.fe_cell[i].cell_coordinates[:, 0]
+ y = self.fe_cell[i].cell_coordinates[:, 1]
+
+ # add the first point to the end of the array
+ x = np.append(x, x[0])
+ y = np.append(y, y[0])
+
+ plt.plot(x, y, "k-", linewidth=0.5)
+
+ # plot the quadrature points
+ x_quad = self.fe_cell[i].quad_actual_coordinates[:, 0]
+ y_quad = self.fe_cell[i].quad_actual_coordinates[:, 1]
+
+ total_quad += x_quad.shape[0]
+
+ if not label_set:
+ plt.scatter(x_quad, y_quad, marker="x", color="b", s=2, label="Quad Pts")
+ label_set = True
+ else:
+ plt.scatter(x_quad, y_quad, marker="x", color="b", s=2)
+
+ self.total_dofs = total_quad
+
+ bound_dof = 0
+ # plot the boundary points
+ # loop over all the boundary tags
+ for i, (bound_id, bound_pts) in enumerate(self.boundary_points.items()):
+ # get the coordinates of the boundary points
+ x = bound_pts[:, 0]
+ y = bound_pts[:, 1]
+
+ # add the first point to the end of the array
+ x = np.append(x, x[0])
+ y = np.append(y, y[0])
+
+ bound_dof += x.shape[0]
+
+ plt.scatter(x, y, marker=marker_list[i + 1], s=2, label=f"Bd-id : {bound_id}")
+
+ self.total_boundary_dofs = bound_dof
+
+ plt.legend(bbox_to_anchor=(0.85, 1.02))
+ plt.axis("equal")
+ plt.axis("off")
+ plt.tight_layout()
+
+ plt.savefig(str(Path(output_path) / "mesh.png"), bbox_inches="tight")
+ plt.savefig(str(Path(output_path) / "mesh.svg"), bbox_inches="tight")
+
+ # print the total number of quadrature points
+ print(f"Plots generated")
+ print(f"[INFO] : Total number of cells = {self.n_cells}")
+ print(f"[INFO] : Total number of quadrature points = {self.total_dofs}")
+ print(f"[INFO] : Total number of boundary points = {self.total_boundary_dofs}")
+
+
+
+[docs]
+ def generate_dirichlet_boundary_data(self) -> np.ndarray:
+ """
+ Generate Dirichlet boundary data.
+
+ This function returns the boundary points and their corresponding values.
+
+ :return: A tuple containing two arrays:
+ - The first array contains the boundary points as numpy arrays.
+ - The second array contains the values of the boundary points as numpy arrays.
+ :rtype: Tuple[np.ndarray, np.ndarray]
+ """
+ x = []
+ y = []
+ for bound_id, bound_pts in self.boundary_points.items():
+ # get the coordinates of the boundary points
+ for pt in bound_pts:
+ pt_new = np.array([pt[0], pt[1]], dtype=np.float64)
+ x.append(pt_new)
+ val = np.array(
+ self.bound_function_dict[bound_id](pt[0], pt[1]), dtype=np.float64
+ ).reshape(-1, 1)
+ y.append(val)
+
+ print(f"[INFO] : Total number of Dirichlet boundary points = {len(x)}")
+ self.total_dirichlet_dofs = len(x)
+ print(f"[INFO] : Shape of Dirichlet-X = {np.array(x).shape}")
+ print(f"[INFO] : Shape of Y = {np.array(y).shape}")
+
+ return x, y
+
+
+
+[docs]
+ def generate_dirichlet_boundary_data_vector(self, component) -> np.ndarray:
+ """
+ Generate the boundary data vector for the Dirichlet boundary condition.
+
+ This function returns the boundary points and their corresponding values for a specific component.
+
+ :param component: The component for which the boundary data vector is generated.
+ :type component: int
+
+ :return: The boundary points and their values as numpy arrays.
+ :rtype: tuple(numpy.ndarray, numpy.ndarray)
+ """
+ x = []
+ y = []
+ for bound_id, bound_pts in self.boundary_points.items():
+ # get the coordinates of the boundary points
+ for pt in bound_pts:
+ pt_new = np.array([pt[0], pt[1]], dtype=np.float64)
+ x.append(pt_new)
+ val = np.array(
+ self.bound_function_dict[bound_id](pt[0], pt[1])[component], dtype=np.float64
+ ).reshape(-1, 1)
+ y.append(val)
+
+ return x, y
+
+
+
+[docs]
+ def get_shape_function_val(self, cell_index) -> np.ndarray:
+ """
+ Get the actual values of the shape functions on a given cell.
+
+ :param cell_index: The index of the cell.
+ :type cell_index: int
+
+ :return: An array containing the actual values of the shape functions.
+ :rtype: np.ndarray
+
+ :raises ValueError: If the cell_index is greater than the number of cells.
+ """
+ if cell_index >= len(self.fe_cell) or cell_index < 0:
+ raise ValueError(
+ f"cell_index should be less than {self.n_cells} and greater than or equal to 0"
+ )
+
+ return self.fe_cell[cell_index].basis_at_quad.copy()
+
+
+
+[docs]
+ def get_shape_function_grad_x(self, cell_index) -> np.ndarray:
+ """
+ Get the gradient of the shape function with respect to the x-coordinate.
+
+ :param cell_index: The index of the cell.
+ :type cell_index: int
+
+ :return: An array containing the gradient of the shape function with respect to the x-coordinate.
+ :rtype: np.ndarray
+
+ :raises ValueError: If the cell_index is greater than the number of cells.
+
+ This function returns the actual values of the gradient of the shape function on a given cell.
+ """
+ if cell_index >= len(self.fe_cell) or cell_index < 0:
+ raise ValueError(
+ f"cell_index should be less than {self.n_cells} and greater than or equal to 0"
+ )
+
+ return self.fe_cell[cell_index].basis_gradx_at_quad.copy()
+
+
+
+[docs]
+ def get_shape_function_grad_x_ref(self, cell_index) -> np.ndarray:
+ """
+ Get the gradient of the shape function with respect to the x-coordinate on the reference element.
+
+ :param cell_index: The index of the cell.
+ :type cell_index: int
+
+ :return: An array containing the gradient of the shape function with respect to the x-coordinate.
+ :rtype: np.ndarray
+
+ :raises ValueError: If the cell_index is greater than the number of cells.
+ """
+ if cell_index >= len(self.fe_cell) or cell_index < 0:
+ raise ValueError(
+ f"cell_index should be less than {self.n_cells} and greater than or equal to 0"
+ )
+
+ return self.fe_cell[cell_index].basis_gradx_at_quad_ref.copy()
+
+
+
+[docs]
+ def get_shape_function_grad_y(self, cell_index) -> np.ndarray:
+ """
+ Get the gradient of the shape function with respect to y at the given cell index.
+
+ :param cell_index: The index of the cell.
+ :type cell_index: int
+
+ :return: The gradient of the shape function with respect to y.
+ :rtype: np.ndarray
+
+ :raises ValueError: If the cell_index is greater than the total number of cells.
+ """
+ if cell_index >= len(self.fe_cell) or cell_index < 0:
+ raise ValueError(
+ f"cell_index should be less than {self.n_cells} and greater than or equal to 0"
+ )
+
+ return self.fe_cell[cell_index].basis_grady_at_quad.copy()
+
+
+
+[docs]
+ def get_shape_function_grad_y_ref(self, cell_index):
+ """
+ Get the gradient of the shape function with respect to y at the reference element.
+
+ :param cell_index: The index of the cell.
+ :type cell_index: int
+ :return: The gradient of the shape function with respect to y at the reference element.
+ :rtype: np.ndarray
+ :raises ValueError: If cell_index is greater than the number of cells.
+
+ This function returns the gradient of the shape function with respect to y at the reference element
+ for a given cell. The shape function gradient values are stored in the `basis_grady_at_quad_ref` array
+ of the corresponding finite element cell. The `cell_index` parameter specifies the index of the cell
+ for which the shape function gradient is required. If the `cell_index` is greater than the total number
+ of cells, a `ValueError` is raised.
+
+ .. note::
+ The returned gradient values are copied from the `basis_grady_at_quad_ref` array to ensure immutability.
+ """
+ if cell_index >= len(self.fe_cell) or cell_index < 0:
+ raise ValueError(
+ f"cell_index should be less than {self.n_cells} and greater than or equal to 0"
+ )
+
+ return self.fe_cell[cell_index].basis_grady_at_quad_ref.copy()
+
+
+
+[docs]
+ def get_quadrature_actual_coordinates(self, cell_index) -> np.ndarray:
+ """
+ Get the actual coordinates of the quadrature points for a given cell.
+
+ :param cell_index: The index of the cell.
+ :type cell_index: int
+
+ :return: An array containing the actual coordinates of the quadrature points.
+ :rtype: np.ndarray
+
+ :raises ValueError: If the cell_index is greater than the number of cells.
+
+ :example:
+ >>> fespace = FESpace2D()
+ >>> fespace.get_quadrature_actual_coordinates(0)
+ array([[0.1, 0.2],
+ [0.3, 0.4],
+ [0.5, 0.6]])
+ """
+ if cell_index >= len(self.fe_cell) or cell_index < 0:
+ raise ValueError(
+ f"cell_index should be less than {self.n_cells} and greater than or equal to 0"
+ )
+
+ return self.fe_cell[cell_index].quad_actual_coordinates.copy()
+
+
+
+[docs]
+ def get_quadrature_weights(self, cell_index) -> np.ndarray:
+ """
+ Return the quadrature weights for a given cell.
+
+ Parameters
+ ----------
+ cell_index : int
+ The index of the cell for which the quadrature weights are needed.
+
+ Returns
+ -------
+ np.ndarray
+ The quadrature weights for the given cell of dimension (N_Quad_Points, 1).
+
+ Raises
+ ------
+ ValueError
+ If cell_index is greater than the number of cells.
+
+ Notes
+ -----
+ This function returns the quadrature weights associated with a specific cell.
+ The quadrature weights are stored in the `mult` attribute of the `fe_cell` object.
+
+ Example
+ -------
+ >>> fespace = FESpace2D()
+ >>> weights = fespace.get_quadrature_weights(0)
+ >>> print(weights)
+ [0.1, 0.2, 0.3, 0.4]
+
+ :param cell_index: The index of the cell for which the quadrature weights are needed.
+ :type cell_index: int
+ :return: The quadrature weights for the given cell.
+ :rtype: np.ndarray
+ :raises ValueError: If cell_index is greater than the number of cells.
+ """
+ if cell_index >= len(self.fe_cell) or cell_index < 0:
+ raise ValueError(
+ f"cell_index should be less than {self.n_cells} and greater than or equal to 0"
+ )
+
+ return self.fe_cell[cell_index].mult.copy()
+
+
+
+[docs]
+ def get_forcing_function_values(self, cell_index) -> np.ndarray:
+ """
+ Get the forcing function values at the quadrature points.
+
+ :param cell_index: The index of the cell.
+ :type cell_index: int
+
+ :return: The forcing function values at the quadrature points.
+ :rtype: np.ndarray
+
+ :raises ValueError: If cell_index is greater than the number of cells.
+
+ This function computes the forcing function values at the quadrature points for a given cell.
+ It loops over all the basis functions and computes the integral using the actual coordinates
+ and the basis functions at the quadrature points. The resulting values are stored in the
+ `forcing_at_quad` attribute of the corresponding `fe_cell` object.
+
+ Note: The forcing function is evaluated using the `forcing_function` method of the `fe_cell`
+ object.
+
+ Example usage:
+ >>> fespace = FESpace2D()
+ >>> cell_index = 0
+ >>> forcing_values = fespace.get_forcing_function_values(cell_index)
+ """
+ if cell_index >= len(self.fe_cell) or cell_index < 0:
+ raise ValueError(
+ f"cell_index should be less than {self.n_cells} and greater than or equal to 0"
+ )
+
+ # Changed by Thivin: To assemble the forcing function at the quadrature points here in the fespace
+ # so that it can be used to handle multiple dimensions on a vector valud problem
+
+ # get number of shape functions
+ n_shape_functions = self.fe_cell[cell_index].basis_function.num_shape_functions
+
+ # Loop over all the basis functions and compute the integral
+ f_integral = np.zeros((n_shape_functions, 1), dtype=np.float64)
+
+ for i in range(n_shape_functions):
+ val = 0
+ for q in range(self.fe_cell[cell_index].basis_at_quad.shape[1]):
+ x = self.fe_cell[cell_index].quad_actual_coordinates[q, 0]
+ y = self.fe_cell[cell_index].quad_actual_coordinates[q, 1]
+ # print("f_values[q] = ",f_values[q])
+
+ # the JAcobian and the quadrature weights are pre multiplied to the basis functions
+ val += (self.fe_cell[cell_index].basis_at_quad[i, q]) * self.fe_cell[
+ cell_index
+ ].forcing_function(x, y)
+ # print("val = ", val)
+
+ f_integral[i] = val
+
+ self.fe_cell[cell_index].forcing_at_quad = f_integral
+
+ return self.fe_cell[cell_index].forcing_at_quad.copy()
+
+
+
+[docs]
+ def get_forcing_function_values_vector(self, cell_index, component) -> np.ndarray:
+ """
+ This function will return the forcing function values at the quadrature points
+ based on the Component of the RHS Needed, for vector valued problems
+
+ :param cell_index: The index of the cell
+ :type cell_index: int
+ :param component: The component of the RHS needed
+ :type component: int
+ :return: The forcing function values at the quadrature points
+ :rtype: np.ndarray
+ :raises ValueError: If cell_index is greater than the number of cells
+ """
+ if cell_index >= len(self.fe_cell) or cell_index < 0:
+ raise ValueError(
+ f"cell_index should be less than {self.n_cells} and greater than or equal to 0"
+ )
+
+ # get the coordinates
+ x = self.fe_cell[cell_index].quad_actual_coordinates[:, 0]
+ y = self.fe_cell[cell_index].quad_actual_coordinates[:, 1]
+
+ # compute the forcing function values
+ f_values = self.fe_cell[cell_index].forcing_function(x, y)[component]
+
+ # compute the integral
+ f_integral = np.sum(self.fe_cell[cell_index].basis_at_quad * f_values, axis=1)
+
+ self.fe_cell[cell_index].forcing_at_quad = f_integral.reshape(-1, 1)
+
+ return self.fe_cell[cell_index].forcing_at_quad.copy()
+
+
+
+[docs]
+ def get_sensor_data(self, exact_solution, num_points):
+ """
+ Obtain sensor data (actual solution) at random points.
+
+ This method is used in the inverse problem to obtain the sensor data at random points within the domain.
+ Currently, it only works for problems with an analytical solution.
+ Methodologies to obtain sensor data for problems from a file are not implemented yet.
+ It is also not implemented for external or complex meshes.
+
+ :param exact_solution: A function that computes the exact solution at a given point.
+ :type exact_solution: function
+ :param num_points: The number of random points to generate.
+ :type num_points: int
+ :return: A tuple containing the generated points and the exact solution at those points.
+ :rtype: tuple(numpy.ndarray, numpy.ndarray)
+ """
+ # generate random points within the bounds of the domain
+ # get the bounds of the domain
+ x_min = np.min(self.mesh.points[:, 0])
+ x_max = np.max(self.mesh.points[:, 0])
+ y_min = np.min(self.mesh.points[:, 1])
+ y_max = np.max(self.mesh.points[:, 1])
+ # sample n random points within the bounds of the domain
+ # Generate points in the unit square
+
+ num_internal_points = int(num_points * 0.9)
+
+ points = lhs(2, samples=num_internal_points)
+ points[:, 0] = x_min + (x_max - x_min) * points[:, 0]
+ points[:, 1] = y_min + (y_max - y_min) * points[:, 1]
+ # get the exact solution at the points
+ exact_sol = exact_solution(points[:, 0], points[:, 1])
+
+ # print the shape of the points and the exact solution
+ print(f"[INFO] : Number of sensor points = {points.shape[0]}")
+ print(f"[INFO] : Shape of sensor points = {points.shape}")
+
+ # plot the points
+ plt.figure(figsize=(6.4, 4.8), dpi=300)
+ plt.scatter(points[:, 0], points[:, 1], marker="x", color="r", s=2)
+ plt.axis("equal")
+ plt.title("Sensor Points")
+ plt.tight_layout()
+ plt.savefig("sensor_points.png", bbox_inches="tight")
+
+ return points, exact_sol
+
+
+
+[docs]
+ def get_sensor_data_external(self, exact_sol, num_points, file_name):
+ """
+ This method is used to obtain the sensor data from an external file.
+
+ :param exact_sol: The exact solution values.
+ :type exact_sol: array-like
+ :param num_points: The number of points to sample from the data.
+ :type num_points: int
+ :param file_name: The path to the file containing the sensor data.
+ :type file_name: str
+
+ :return: A tuple containing two arrays:
+ - points (ndarray): The sampled points from the data.
+ - exact_sol (ndarray): The corresponding exact solution values.
+ :rtype: tuple
+ """
+ # use pandas to read the file
+ df = pd.read_csv(file_name)
+
+ x = df.iloc[:, 0].values
+ y = df.iloc[:, 1].values
+ exact_sol = df.iloc[:, 2].values
+
+ # now sample num_points from the data
+ indices = np.random.randint(0, x.shape[0], num_points)
+
+ x = x[indices]
+ y = y[indices]
+ exact_sol = exact_sol[indices]
+
+ # stack them together
+ points = np.stack((x, y), axis=1)
+
+ return points, exact_sol
+
+
+
+"""
+file: quad_affine.py
+description: This file defines the Quad Affine transformation of the reference element.
+ The implementation is referenced from the ParMooN project (File: QuadAffine.C).
+authors: Thivin Anandh D
+changelog: 30/Aug/2023 - Initial version
+known_issues: None
+dependencies: None specified.
+"""
+
+import numpy as np
+from .fe_transformation_2d import FETransforamtion2D
+
+
+
+[docs]
+class QuadAffin(FETransforamtion2D):
+ """
+ Defines the Quad Affine transformation of the reference element.
+
+ :param co_ordinates: The coordinates of the reference element.
+ :type co_ordinates: numpy.ndarray
+ """
+
+ def __init__(self, co_ordinates) -> None:
+ """
+ Constructor for the QuadAffin class.
+
+ :param co_ordinates: The coordinates of the reference element.
+ :type co_ordinates: numpy.ndarray
+ """
+ self.co_ordinates = co_ordinates
+ self.set_cell()
+ self.get_jacobian(
+ 0, 0
+ ) # 0,0 is just a dummy value # this sets the jacobian and the inverse of the jacobian
+
+
+[docs]
+ def set_cell(self):
+ """
+ Set the cell co-ordinates, which will be used to calculate the Jacobian and actual values.
+
+ :param None:
+ There are no parameters for this method.
+
+ :returns None:
+ This method does not return anything.
+ """
+
+ self.x0 = self.co_ordinates[0][0]
+ self.x1 = self.co_ordinates[1][0]
+ self.x2 = self.co_ordinates[2][0]
+ self.x3 = self.co_ordinates[3][0]
+
+ # get the y-co-ordinates of the cell
+ self.y0 = self.co_ordinates[0][1]
+ self.y1 = self.co_ordinates[1][1]
+ self.y2 = self.co_ordinates[2][1]
+ self.y3 = self.co_ordinates[3][1]
+
+ self.xc0 = (self.x1 + self.x3) * 0.5
+ self.xc1 = (self.x1 - self.x0) * 0.5
+ self.xc2 = (self.x3 - self.x0) * 0.5
+
+ self.yc0 = (self.y1 + self.y3) * 0.5
+ self.yc1 = (self.y1 - self.y0) * 0.5
+ self.yc2 = (self.y3 - self.y0) * 0.5
+
+
+
+[docs]
+ def get_original_from_ref(self, xi, eta):
+ """
+ Returns the original co-ordinates from the reference co-ordinates.
+
+ :param float xi: The xi coordinate.
+ :param float eta: The eta coordinate.
+ :return: numpy.ndarray
+ The original co-ordinates.
+ """
+ x = self.xc0 + self.xc1 * xi + self.xc2 * eta
+ y = self.yc0 + self.yc1 * xi + self.yc2 * eta
+
+ return np.array([x, y])
+
+
+
+[docs]
+ def get_jacobian(self, xi, eta):
+ """
+ Returns the Jacobian of the transformation.
+
+ :param xi: The xi coordinate.
+ :type xi: float
+ :param eta: The eta coordinate.
+ :type eta: float
+
+ :return: The Jacobian of the transformation.
+ :rtype: float
+ """
+ self.detjk = self.xc1 * self.yc2 - self.xc2 * self.yc1
+ self.rec_detjk = 1 / self.detjk
+
+ return abs(self.detjk)
+
+
+
+[docs]
+ def get_orig_from_ref_derivative(self, ref_gradx, ref_grady, xi, eta):
+ """
+ Returns the derivatives of the original co-ordinates with respect to the reference co-ordinates.
+
+ :param ref_gradx: The reference gradient in the x-direction.
+ :type ref_gradx: numpy.ndarray
+ :param ref_grady: The reference gradient in the y-direction.
+ :type ref_grady: numpy.ndarray
+ :param xi: The xi coordinate.
+ :type xi: float
+ :param eta: The eta coordinate.
+ :type eta: float
+
+ :return: The derivatives of the original co-ordinates with respect to the reference co-ordinates.
+ :rtype: tuple
+ """
+ gradx_orig = np.zeros(ref_gradx.shape)
+ grady_orig = np.zeros(ref_grady.shape)
+
+ for i in range(ref_gradx.shape[0]):
+ gradx_orig[i] = (self.yc2 * ref_gradx[i] - self.yc1 * ref_grady[i]) * self.rec_detjk
+ grady_orig[i] = (-self.xc2 * ref_gradx[i] + self.xc1 * ref_grady[i]) * self.rec_detjk
+
+ return gradx_orig, grady_orig
+
+
+
+[docs]
+ def get_orig_from_ref_second_derivative(self, grad_xx_ref, grad_xy_ref, grad_yy_ref, xi, eta):
+ """
+ Returns the second derivatives (xx, xy, yy) of the original co-ordinates with respect to the reference co-ordinates.
+
+ :param grad_xx_ref: The reference second derivative in the xx-direction.
+ :type grad_xx_ref: numpy.ndarray
+ :param grad_xy_ref: The reference second derivative in the xy-direction.
+ :type grad_xy_ref: numpy.ndarray
+ :param grad_yy_ref: The reference second derivative in the yy-direction.
+ :type grad_yy_ref: numpy.ndarray
+ :param xi: The xi coordinate.
+ :type xi: float
+ :param eta: The eta coordinate.
+ :type eta: float
+
+ :return: The second derivatives (xx, xy, yy) of the original co-ordinates with respect to the reference co-ordinates.
+ :rtype: tuple
+ """
+ GeoData = np.zeros((3, 3))
+ Eye = np.identity(3)
+
+ # Populate GeoData (assuming xc1, xc2, yc1, yc2 are defined)
+ GeoData[0, 0] = self.xc1 * self.xc1
+ GeoData[0, 1] = 2 * self.xc1 * self.yc1
+ GeoData[0, 2] = self.yc1 * self.yc1
+ GeoData[1, 0] = self.xc1 * self.xc2
+ GeoData[1, 1] = self.yc1 * self.xc2 + self.xc1 * self.yc2
+ GeoData[1, 2] = self.yc1 * self.yc2
+ GeoData[2, 0] = self.xc2 * self.xc2
+ GeoData[2, 1] = 2 * self.xc2 * self.yc2
+ GeoData[2, 2] = self.yc2 * self.yc2
+
+ # solve the linear system
+ solution = np.linalg.solve(GeoData, Eye)
+
+ # generate empty arrays for the original second derivatives
+ grad_xx_orig = np.zeros(grad_xx_ref.shape)
+ grad_xy_orig = np.zeros(grad_xy_ref.shape)
+ grad_yy_orig = np.zeros(grad_yy_ref.shape)
+
+ for j in range(grad_xx_ref.shape[0]):
+ r20 = grad_xx_ref[j]
+ r11 = grad_xy_ref[j]
+ r02 = grad_yy_ref[j]
+
+ grad_xx_orig[j] = solution[0, 0] * r20 + solution[0, 1] * r11 + solution[0, 2] * r02
+ grad_xy_orig[j] = solution[1, 0] * r20 + solution[1, 1] * r11 + solution[1, 2] * r02
+ grad_yy_orig[j] = solution[2, 0] * r20 + solution[2, 1] * r11 + solution[2, 2] * r02
+
+ return grad_xx_orig, grad_xy_orig, grad_yy_orig
+
+
+
+"""
+file: quad_bilinear.py
+description: This file defines the Quad Affine transformation of the reference element.
+ The implementation is referenced from the ParMooN project (File: QuadBilineare.C).
+authors: Thivin Anandh D
+changelog: 30/Aug/2023 - Initial version
+known_issues: Second derivative Calculations are not implemented as of now.
+dependencies: None specified.
+"""
+
+import numpy as np
+from .fe_transformation_2d import FETransforamtion2D
+
+
+
+[docs]
+class QuadBilinear(FETransforamtion2D):
+ """
+ Defines the Quad Bilinear transformation of the reference element.
+
+ :param co_ordinates: The coordinates of the reference element.
+ :type co_ordinates: numpy.ndarray
+ """
+
+ def __init__(self, co_ordinates) -> None:
+ """
+ Constructor for the QuadBilinear class.
+
+ :param co_ordinates: The coordinates of the reference element.
+ :type co_ordinates: numpy.ndarray
+ """
+ self.co_ordinates = co_ordinates
+ self.set_cell()
+ self.detjk = None # Jacobian of the transformation
+
+
+[docs]
+ def set_cell(self):
+ """
+ Set the cell co-ordinates, which will be used as intermediate values to calculate the Jacobian and actual values.
+
+ :param None:
+ :type None:
+
+ :returns: None
+ :rtype: None
+ """
+ self.x0 = self.co_ordinates[0][0]
+ self.x1 = self.co_ordinates[1][0]
+ self.x2 = self.co_ordinates[2][0]
+ self.x3 = self.co_ordinates[3][0]
+
+ # get the y-co-ordinates of the cell
+ self.y0 = self.co_ordinates[0][1]
+ self.y1 = self.co_ordinates[1][1]
+ self.y2 = self.co_ordinates[2][1]
+ self.y3 = self.co_ordinates[3][1]
+
+ self.xc0 = (self.x0 + self.x1 + self.x2 + self.x3) * 0.25
+ self.xc1 = (-self.x0 + self.x1 + self.x2 - self.x3) * 0.25
+ self.xc2 = (-self.x0 - self.x1 + self.x2 + self.x3) * 0.25
+ self.xc3 = (self.x0 - self.x1 + self.x2 - self.x3) * 0.25
+
+ self.yc0 = (self.y0 + self.y1 + self.y2 + self.y3) * 0.25
+ self.yc1 = (-self.y0 + self.y1 + self.y2 - self.y3) * 0.25
+ self.yc2 = (-self.y0 - self.y1 + self.y2 + self.y3) * 0.25
+ self.yc3 = (self.y0 - self.y1 + self.y2 - self.y3) * 0.25
+
+
+
+[docs]
+ def get_original_from_ref(self, xi, eta):
+ """
+ This method returns the original co-ordinates from the reference co-ordinates.
+
+ :param xi: The xi coordinate in the reference element.
+ :type xi: float
+ :param eta: The eta coordinate in the reference element.
+ :type eta: float
+
+ :returns: The original co-ordinates [x, y] corresponding to the given reference co-ordinates.
+ :rtype: numpy.ndarray
+ """
+ x = self.xc0 + self.xc1 * xi + self.xc2 * eta + self.xc3 * xi * eta
+ y = self.yc0 + self.yc1 * xi + self.yc2 * eta + self.yc3 * xi * eta
+
+ return np.array([x, y], dtype=np.float64)
+
+
+
+[docs]
+ def get_jacobian(self, xi, eta):
+ """
+ This method returns the Jacobian of the transformation.
+
+ :param xi: The xi coordinate in the reference element.
+ :type xi: float
+ :param eta: The eta coordinate in the reference element.
+ :type eta: float
+
+ :returns: The Jacobian of the transformation at the given reference co-ordinates.
+ :rtype: float
+ """
+ self.detjk = abs(
+ (self.xc1 + self.xc3 * eta) * (self.yc2 + self.yc3 * xi)
+ - (self.xc2 + self.xc3 * xi) * (self.yc1 + self.yc3 * eta)
+ )
+ return self.detjk
+
+
+
+[docs]
+ def get_orig_from_ref_derivative(self, ref_gradx, ref_grady, xi, eta):
+ """
+ This method returns the derivatives of the original co-ordinates with respect to the reference co-ordinates.
+
+ :param ref_gradx: The gradient of the xi coordinate in the reference element.
+ :type ref_gradx: numpy.ndarray
+ :param ref_grady: The gradient of the eta coordinate in the reference element.
+ :type ref_grady: numpy.ndarray
+ :param xi: The xi coordinate in the reference element.
+ :type xi: float
+ :param eta: The eta coordinate in the reference element.
+ :type eta: float
+
+ :returns: The derivatives of the original co-ordinates [x, y] with respect to the reference co-ordinates.
+ :rtype: numpy.ndarray
+ """
+ n_test = ref_gradx.shape[0]
+ gradx_orig = np.zeros(ref_gradx.shape, dtype=np.float64)
+ grady_orig = np.zeros(ref_grady.shape, dtype=np.float64)
+
+ for j in range(n_test):
+ Xi = xi
+ Eta = eta
+ rec_detjk = 1 / (
+ (self.xc1 + self.xc3 * Eta) * (self.yc2 + self.yc3 * Xi)
+ - (self.xc2 + self.xc3 * Xi) * (self.yc1 + self.yc3 * Eta)
+ )
+ gradx_orig[j] = (
+ (self.yc2 + self.yc3 * Xi) * ref_gradx[j]
+ - (self.yc1 + self.yc3 * Eta) * ref_grady[j]
+ ) * rec_detjk
+ grady_orig[j] = (
+ -(self.xc2 + self.xc3 * Xi) * ref_gradx[j]
+ + (self.xc1 + self.xc3 * Eta) * ref_grady[j]
+ ) * rec_detjk
+
+ return gradx_orig, grady_orig
+
+
+
+[docs]
+ def get_orig_from_ref_second_derivative(self, grad_xx_ref, grad_xy_ref, grad_yy_ref, xi, eta):
+ """
+ This method returns the second derivatives of the original co-ordinates with respect to the reference co-ordinates.
+
+ :param grad_xx_ref: The second derivative of the xi coordinate in the reference element.
+ :type grad_xx_ref: numpy.ndarray
+ :param grad_xy_ref: The mixed second derivative of the xi and eta coordinates in the reference element.
+ :type grad_xy_ref: numpy.ndarray
+ :param grad_yy_ref: The second derivative of the eta coordinate in the reference element.
+ :type grad_yy_ref: numpy.ndarray
+ :param xi: The xi coordinate in the reference element.
+ :type xi: float
+ :param eta: The eta coordinate in the reference element.
+ :type eta: float
+
+ :returns: The second derivatives of the original co-ordinates [xx, xy, yy] with respect to the reference co-ordinates.
+ :rtype: numpy.ndarray
+ """
+ # print(" Error : Second Derivative not implemented -- Ignore this error, if second derivative is not required ")
+ return grad_xx_ref, grad_xy_ref, grad_yy_ref
+
+
+
+"""
+file: quadratureformulas_quad2d.py
+description: This file defines the Quadrature Formulas for the 2D Quadrilateral elements.
+ It supports both Gauss-Legendre and Gauss-Jacobi quadrature types.
+ The quadrature points and weights are calculated based on the specified quadrature order and type.
+authors: Not specified
+changelog: Not specified
+known_issues: None
+dependencies: numpy, scipy
+"""
+
+import numpy as np
+from scipy.special import roots_legendre, roots_jacobi, jacobi, gamma
+from scipy.special import legendre
+from scipy.special import eval_legendre, legendre
+
+
+
+[docs]
+class Quadratureformulas_Quad2D:
+ """
+ Defines the Quadrature Formulas for the 2D Quadrilateral elements.
+
+ :param quad_order: The order of the quadrature.
+ :type quad_order: int
+ :param quad_type: The type of the quadrature.
+ :type quad_type: str
+ """
+
+ def __init__(self, quad_order: int, quad_type: str):
+ """
+ Constructor for the Quadratureformulas_Quad2D class.
+
+ :param quad_order: The order of the quadrature.
+ :type quad_order: int
+ :param quad_type: The type of the quadrature.
+ :type quad_type: str
+ """
+ self.quad_order = quad_order
+ self.quad_type = quad_type
+ self.num_quad_points = quad_order * quad_order
+
+ # Calculate the Gauss-Legendre quadrature points and weights for 1D
+ # nodes_1d, weights_1d = roots_jacobi(self.quad_order, 1, 1)
+
+ quad_type = self.quad_type
+
+ if quad_type == "gauss-legendre":
+ # Commented out by THIVIN - to Just use legendre quadrature points as it is
+ # if quad_order == 2:
+ # nodes_1d = np.array([-1, 1])
+ # weights_1d = np.array([1, 1])
+ # else:
+ nodes_1d, weights_1d = np.polynomial.legendre.leggauss(quad_order) # Interior points
+ # nodes_1d = np.concatenate(([-1, 1], nodes_1d))
+ # weights_1d = np.concatenate(([1, 1], weights_1d))
+
+ # Generate the tensor outer product of the nodes
+ xi_quad, eta_quad = np.meshgrid(nodes_1d, nodes_1d)
+ xi_quad = xi_quad.flatten()
+ eta_quad = eta_quad.flatten()
+
+ # Multiply the weights accordingly for 2D
+ quad_weights = (weights_1d[:, np.newaxis] * weights_1d).flatten()
+
+ # Assign the values
+ self.xi_quad = xi_quad
+ self.eta_quad = eta_quad
+ self.quad_weights = quad_weights
+
+ elif quad_type == "gauss-jacobi":
+
+ def GaussJacobiWeights(Q: int, a, b):
+ [X, W] = roots_jacobi(Q, a, b)
+ return [X, W]
+
+ def jacobi_wrapper(n, a, b, x):
+
+ x = np.array(x, dtype=np.float64)
+
+ return jacobi(n, a, b)(x)
+
+ # Weight coefficients
+ def GaussLobattoJacobiWeights(Q: int, a, b):
+ W = []
+ X = roots_jacobi(Q - 2, a + 1, b + 1)[0]
+ if a == 0 and b == 0:
+ W = 2 / ((Q - 1) * (Q) * (jacobi_wrapper(Q - 1, 0, 0, X) ** 2))
+ Wl = 2 / ((Q - 1) * (Q) * (jacobi_wrapper(Q - 1, 0, 0, -1) ** 2))
+ Wr = 2 / ((Q - 1) * (Q) * (jacobi_wrapper(Q - 1, 0, 0, 1) ** 2))
+ else:
+ W = (
+ 2 ** (a + b + 1)
+ * gamma(a + Q)
+ * gamma(b + Q)
+ / (
+ (Q - 1)
+ * gamma(Q)
+ * gamma(a + b + Q + 1)
+ * (jacobi_wrapper(Q - 1, a, b, X) ** 2)
+ )
+ )
+ Wl = (
+ (b + 1)
+ * 2 ** (a + b + 1)
+ * gamma(a + Q)
+ * gamma(b + Q)
+ / (
+ (Q - 1)
+ * gamma(Q)
+ * gamma(a + b + Q + 1)
+ * (jacobi_wrapper(Q - 1, a, b, -1) ** 2)
+ )
+ )
+ Wr = (
+ (a + 1)
+ * 2 ** (a + b + 1)
+ * gamma(a + Q)
+ * gamma(b + Q)
+ / (
+ (Q - 1)
+ * gamma(Q)
+ * gamma(a + b + Q + 1)
+ * (jacobi_wrapper(Q - 1, a, b, 1) ** 2)
+ )
+ )
+ W = np.append(W, Wr)
+ W = np.append(Wl, W)
+ X = np.append(X, 1)
+ X = np.append(-1, X)
+ return [X, W]
+
+ # get quadrature points and weights in 1D
+ x, w = GaussLobattoJacobiWeights(self.quad_order, 0, 0)
+
+ # Generate the tensor outer product of the nodes
+ xi_quad, eta_quad = np.meshgrid(x, x)
+ xi_quad = xi_quad.flatten()
+ eta_quad = eta_quad.flatten()
+
+ # Multiply the weights accordingly for 2D
+ quad_weights = (w[:, np.newaxis] * w).flatten()
+
+ # Assign the values
+ self.xi_quad = xi_quad
+ self.eta_quad = eta_quad
+ self.quad_weights = quad_weights
+
+ else:
+ print("Supported quadrature types are: gauss-legendre, gauss-jacobi")
+ print(
+ f"Invalid quadrature type {quad_type} in {self.__class__.__name__} from {__name__}."
+ )
+ raise ValueError("Quadrature type not supported.")
+
+
+[docs]
+ def get_quad_values(self):
+ """
+ Returns the quadrature weights, xi and eta values.
+
+ :return: A tuple containing the quadrature weights, xi values, and eta values.
+ :rtype: tuple
+ """
+ return self.quad_weights, self.xi_quad, self.eta_quad
+
+
+
+[docs]
+ def get_num_quad_points(self):
+ """
+ Returns the number of quadrature points.
+
+ :return: The number of quadrature points.
+ :rtype: int
+ """
+ return self.num_quad_points
+
+
+
+"""
+This module, `geometry_2d.py`, contains the `Geometry_2D` class which defines functions to read mesh from Gmsh and
+generate internal mesh for 2D problems. It supports different types of meshes and mesh generation methods.
+The class also allows for specifying the number of test points in the x and y directions, and the output folder for storing results.
+
+Author: Thivin Anandh D
+Date: 21-Sep-2023
+"""
+
+from pathlib import Path
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib.colors as mcolors
+import meshio
+from pyDOE import lhs
+
+import gmsh
+
+
+
+[docs]
+class Geometry_2D:
+ """
+ Defines functions to read mesh from Gmsh and internal mesh for 2D problems.
+
+ :param mesh_type: The type of mesh to be used.
+ :type mesh_type: str
+ :param mesh_generation_method: The method used to generate the mesh.
+ :type mesh_generation_method: str
+ :param n_test_points_x: The number of test points in the x-direction.
+ :type n_test_points_x: int
+ :param n_test_points_y: The number of test points in the y-direction.
+ :type n_test_points_y: int
+ :param output_folder: The path to the output folder.
+ :type output_folder: str
+ """
+
+ def __init__(
+ self,
+ mesh_type: str,
+ mesh_generation_method: str,
+ n_test_points_x: int,
+ n_test_points_y: int,
+ output_folder: str,
+ ):
+ """
+ Constructor for Geometry_2D class.
+ :param mesh_type: The type of mesh to be used.
+ :type mesh_type: str
+ :param mesh_generation_method: The method used to generate the mesh.
+ :type mesh_generation_method: str
+ :param n_test_points_x: The number of test points in the x-direction.
+ :type n_test_points_x: int
+ :param n_test_points_y: The number of test points in the y-direction.
+ :type n_test_points_y: int
+ :param output_folder: The path to the output folder.
+ :type output_folder: str
+ """
+ self.mesh_type = mesh_type
+ self.mesh_generation_method = mesh_generation_method
+ self.n_test_points_x = n_test_points_x
+ self.n_test_points_y = n_test_points_y
+ self.output_folder = output_folder
+
+ if self.mesh_generation_method not in ["internal", "external"]:
+ print(
+ f"Invalid mesh generation method {self.mesh_generation_method} in {self.__class__.__name__} from {__name__}."
+ )
+ raise ValueError("Mesh generation method should be either internal or external.")
+
+ if self.mesh_type not in ["quadrilateral"]:
+ print(
+ f"Invalid mesh type {self.mesh_type} in {self.__class__.__name__} from {__name__}."
+ )
+ raise ValueError("Mesh type should be quadrilateral only.")
+
+ # To be filled - only when mesh is internal
+ self.n_cells_x = None
+ self.n_cells_y = None
+ self.x_limits = None
+ self.y_limits = None
+
+ # to be filled by external
+ self.mesh_file_name = None
+ self.mesh = None
+ self.bd_dict = None
+ self.cell_points = None
+ self.test_points = None
+
+
+[docs]
+ def read_mesh(
+ self,
+ mesh_file: str,
+ boundary_point_refinement_level: int,
+ bd_sampling_method: str,
+ refinement_level: int,
+ ):
+ """
+ Reads mesh from a Gmsh .msh file and extracts cell information.
+
+ :param mesh_file: The path to the mesh file.
+ :type mesh_file: str
+ :param boundary_point_refinement_level: The number of boundary points to be generated.
+ :type boundary_point_refinement_level: int
+ :param bd_sampling_method: The method used to generate the boundary points.
+ :type bd_sampling_method: str
+ :param refinement_level: The number of times the mesh should be refined.
+ :type refinement_level: int
+ :return: The cell points and the dictionary of boundary points.
+ :rtype: tuple
+ """
+
+ self.mesh_file_name = mesh_file
+
+ # bd_sampling_method = "uniform" # "uniform" or "lhs"
+
+ file_extension = Path(mesh_file).suffix
+
+ if file_extension != ".mesh":
+ raise ValueError("Mesh file should be in .mesh format.")
+
+ # Read mesh using meshio
+ self.mesh = meshio.read(mesh_file)
+
+ if self.mesh_type == "quadrilateral":
+ # Extract cell information
+ cells = self.mesh.cells_dict["quad"]
+
+ num_cells = cells.shape[0]
+ print(f"[INFO] : Number of cells = {num_cells}")
+ cell_points = self.mesh.points[cells][
+ :, :, 0:2
+ ] # remove the z coordinate, which is 0 for all points
+
+ # loop over all cells and rearrange the points in anticlockwise direction
+ for i in range(num_cells):
+ cell = cell_points[i]
+ # get the centroid of the cell
+ centroid = np.mean(cell, axis=0)
+ # get the angle of each point with respect to the centroid
+ angles = np.arctan2(cell[:, 1] - centroid[1], cell[:, 0] - centroid[0])
+ # sort the points based on the angles
+ cell_points[i] = cell[np.argsort(angles)]
+
+ # Extract number of points within each cell
+ print(f"[INFO] : Number of points per cell = {cell_points.shape}")
+
+ # Collect the Boundary point id's within the domain
+ boundary_edges = self.mesh.cells_dict["line"]
+
+ # Using the point id, collect the coordinates of the boundary points
+ boundary_coordinates = self.mesh.points[boundary_edges]
+
+ # Number of Existing Boundary points
+ print(
+ f"[INFO] : Number of Bound points before refinement = {np.unique(boundary_coordinates.reshape(-1,3)).shape[0] * 0.5 + 1}"
+ )
+
+ # now Get the physical tag of the boundary edges
+ boundary_tags = self.mesh.cell_data["medit:ref"][0]
+
+ # Generate a Dictionary of boundary tags and boundary coordinates
+ # Keys will be the boundary tags and values will be the list of coordinates
+ boundary_dict = {}
+
+ # refine the boundary points based on the number of boundary points needed
+ for i in range(boundary_coordinates.shape[0]):
+ p1 = boundary_coordinates[i, 0, :]
+ p2 = boundary_coordinates[i, 1, :]
+
+ if bd_sampling_method == "uniform":
+ # take the current point and next point and then perform a uniform sampling
+ new_points = np.linspace(p1, p2, pow(2, boundary_point_refinement_level) + 1)
+ elif bd_sampling_method == "lhs":
+ # take the current point and next point and then perform a uniform sampling
+ new_points = lhs(2, pow(2, boundary_point_refinement_level) + 1)
+ new_points[:, 0] = new_points[:, 0] * (p2[0] - p1[0]) + p1[0]
+ new_points[:, 1] = new_points[:, 1] * (p2[1] - p1[1]) + p1[1]
+ else:
+ print(
+ f"Invalid sampling method {bd_sampling_method} in {self.__class__.__name__} from {__name__}."
+ )
+ raise ValueError("Sampling method should be either uniform or lhs.")
+
+ # get the boundary tag
+ tag = boundary_tags[i]
+
+ if tag not in boundary_dict:
+ boundary_dict[tag] = new_points
+ else:
+ current_val = new_points
+ prev_val = boundary_dict[tag]
+ final = np.vstack([prev_val, current_val])
+ boundary_dict[tag] = final
+
+ # get unique
+ for tag in boundary_dict.keys():
+ val = boundary_dict[tag]
+ val = np.unique(val, axis=0)
+ boundary_dict[tag] = val
+
+ self.bd_dict = boundary_dict
+ # print the new boundary points on each boundary tag (key) in a tabular format
+
+ total_bound_points = 0
+ print(f"| {'Boundary ID':<12} | {'Number of Points':<16} |")
+ print(f"| {'-'*12:<12}---{'-'*16:<16} |")
+ for k, v in self.bd_dict.items():
+ print(f"| {k:<12} | {v.shape[0]:<16} |")
+ total_bound_points += v.shape[0]
+
+ print(f"[INFO] : No of bound pts after refinement: {total_bound_points}")
+
+ # Assign to class values
+ self.cell_points = cell_points
+
+ # generate testvtk
+ self.generate_vtk_for_test()
+
+ return cell_points, self.bd_dict
+
+
+
+[docs]
+ def generate_quad_mesh_internal(
+ self,
+ x_limits: tuple,
+ y_limits: tuple,
+ n_cells_x: int,
+ n_cells_y: int,
+ num_boundary_points: int,
+ ):
+ """
+ Generate and save a quadrilateral mesh with physical curves.
+
+ :param x_limits: The lower and upper limits in the x-direction (x_min, x_max).
+ :type x_limits: tuple
+ :param y_limits: The lower and upper limits in the y-direction (y_min, y_max).
+ :type y_limits: tuple
+ :param n_cells_x: The number of cells in the x-direction.
+ :type n_cells_x: int
+ :param n_cells_y: The number of cells in the y-direction.
+ :type n_cells_y: int
+ :param num_boundary_points: The number of boundary points.
+ :type num_boundary_points: int
+ :return: The cell points and the dictionary of boundary points.
+ :rtype: tuple[numpy.ndarray, dict]
+ """
+
+ self.n_cells_x = n_cells_x
+ self.n_cells_y = n_cells_y
+ self.x_limits = x_limits
+ self.y_limits = y_limits
+
+ # generate linspace of points in x and y direction
+ x = np.linspace(x_limits[0], x_limits[1], n_cells_x + 1)
+ y = np.linspace(y_limits[0], y_limits[1], n_cells_y + 1)
+
+ # Generate quad cells from the points
+ # the output should be a list of 4 points for each cell , each being a list of 2 points [x,y]
+ cells = []
+
+ for i in range(n_cells_x):
+ for j in range(n_cells_y):
+ # get the four points of the cell
+ p1 = [x[i], y[j]]
+ p2 = [x[i + 1], y[j]]
+ p3 = [x[i + 1], y[j + 1]]
+ p4 = [x[i], y[j + 1]]
+
+ # append the points to the cells
+ cells.append([p1, p2, p3, p4])
+
+ # convert to numpy array
+ cells = np.array(cells, dtype=np.float64)
+
+ # use arctan2 to sort the points in anticlockwise direction
+ # loop over all cells and rearrange the points in anticlockwise direction
+ for i in range(cells.shape[0]):
+ cell = cells[i]
+ # get the centroid of the cell
+ centroid = np.mean(cell, axis=0)
+ # get the angle of each point with respect to the centroid
+ angles = np.arctan2(cell[:, 1] - centroid[1], cell[:, 0] - centroid[0])
+ # sort the points based on the angles
+ cells[i] = cell[np.argsort(angles)]
+
+ # generate a meshio mesh object using the cells
+ self.mesh = meshio.Mesh(points=cells.reshape(-1, 2), cells=[("quad", cells.reshape(-1, 4))])
+
+ # lets generate the boundary points, this function will return a dictionary of boundary points
+ # the keys will be the boundary tags and values will be the list of boundary points
+ bd_points = {}
+
+ num_bound_per_side = int(num_boundary_points / 4)
+
+ def _temp_bd_func(start, end, num_pts):
+ """
+ This function returns the boundary points between the start and end points
+ using lhs sampling.
+
+ :param start: The starting point of the boundary.
+ :type start: float
+ :param end: The ending point of the boundary.
+ :type end: float
+ :param num_pts: The number of points to generate.
+ :type num_pts: int
+ :return: The boundary points as a 1D numpy array.
+ :rtype: numpy.ndarray
+ """
+ # generate the boundary points using lhs as a np.float64 array
+ bd_pts = lhs(1, num_pts).astype(np.float64)
+ # scale the points
+ bd_pts = bd_pts * (end - start) + start
+
+ return bd_pts.reshape(-1)
+
+ # bottom boundary
+ y_bottom = (np.ones(num_bound_per_side, dtype=np.float64) * y_limits[0]).reshape(-1)
+ x_bottom = _temp_bd_func(x_limits[0], x_limits[1], num_bound_per_side)
+ bd_points[1000] = np.vstack([x_bottom, y_bottom]).T
+
+ # right boundary
+ x_right = (np.ones(num_bound_per_side, dtype=np.float64) * x_limits[1]).reshape(-1)
+ y_right = _temp_bd_func(y_limits[0], y_limits[1], num_bound_per_side)
+ bd_points[1001] = np.vstack([x_right, y_right]).T
+
+ # top boundary
+ y_top = (np.ones(num_bound_per_side, dtype=np.float64) * y_limits[1]).reshape(-1)
+ x_top = _temp_bd_func(x_limits[0], x_limits[1], num_bound_per_side)
+ bd_points[1002] = np.vstack([x_top, y_top]).T
+
+ # left boundary
+ x_left = (np.ones(num_bound_per_side, dtype=np.float64) * x_limits[0]).reshape(-1)
+ y_left = _temp_bd_func(y_limits[0], y_limits[1], num_bound_per_side)
+ bd_points[1003] = np.vstack([x_left, y_left]).T
+
+ self.cell_points = cells
+ self.bd_dict = bd_points
+
+ # generate vtk
+ self.generate_vtk_for_test()
+
+ return self.cell_points, self.bd_dict
+
+
+
+[docs]
+ def generate_vtk_for_test(self):
+ """
+ Generates a VTK from Mesh file (External) or using gmsh (for Internal).
+
+ :return: None
+ """
+
+ if self.mesh_generation_method == "internal":
+ # initialise the mesh
+ gmsh.initialize()
+
+ # Now, lets generate the mesh with the points.
+ x_range = self.x_limits[1] - self.x_limits[0]
+ y_range = self.y_limits[1] - self.y_limits[0]
+
+ mesh_size_x = x_range / self.n_test_points_x
+ mesh_size_y = y_range / self.n_test_points_y
+
+ # generate a gmsh with the given parameters
+ Xmin = self.x_limits[0]
+ Xmax = self.x_limits[1]
+ Ymin = self.y_limits[0]
+ Ymax = self.y_limits[1]
+
+ point1 = gmsh.model.geo.add_point(Xmin, Ymin, 0, mesh_size_x)
+ point2 = gmsh.model.geo.add_point(Xmax, Ymin, 0, mesh_size_x)
+ point3 = gmsh.model.geo.add_point(Xmax, Ymax, 0, mesh_size_y)
+ point4 = gmsh.model.geo.add_point(Xmin, Ymax, 0, mesh_size_y)
+
+ line1 = gmsh.model.geo.add_line(point1, point2, 1000) ## Bottom
+ line2 = gmsh.model.geo.add_line(point2, point3, 1001) ## Right
+ line3 = gmsh.model.geo.add_line(point3, point4, 1002) ## Top
+ line4 = gmsh.model.geo.add_line(point4, point1, 1003) ## Left
+
+ face1 = gmsh.model.geo.add_curve_loop([line1, line2, line3, line4])
+
+ gmsh.model.geo.add_plane_surface([face1])
+
+ # Create the relevant Gmsh data structures
+ # from Gmsh model.
+ gmsh.model.geo.synchronize()
+
+ # Generate mesh:
+ gmsh.model.mesh.generate()
+
+ mesh_file_name = Path(self.output_folder) / "internal.msh"
+ vtk_file_name = Path(self.output_folder) / "internal.vtk"
+
+ gmsh.write(str(mesh_file_name))
+ print("[INFO] : Internal mesh file generated at ", str(mesh_file_name))
+
+ # close the gmsh
+ gmsh.finalize()
+
+ # read the mesh using meshio
+ mesh = meshio.gmsh.read(str(mesh_file_name))
+ meshio.vtk.write(str(vtk_file_name), mesh, binary=False, fmt_version="4.2")
+
+ print("[INFO] : VTK file for internal mesh file generated at ", str(mesh_file_name))
+
+ elif self.mesh_generation_method == "external":
+
+ vtk_file_name = Path(self.output_folder) / "external.vtk"
+
+ # Use the internal mesh to generate the vtk file
+ mesh = meshio.read(str(self.mesh_file_name))
+ meshio.vtk.write(str(vtk_file_name), mesh, binary=False, fmt_version="4.2")
+
+ print("[INFO] : VTK file for external mesh file generated at ", str(vtk_file_name))
+
+
+
+[docs]
+ def get_test_points(self):
+ """
+ This function is used to extract the test points from the given mesh
+
+ Parameters:
+ None
+
+ Returns:
+ test_points (numpy.ndarray): The test points for the given domain
+ """
+
+ if self.mesh_generation_method == "internal":
+ # vtk_file_name = Path(self.output_folder) / "internal.vtk"
+ # code over written to plot from np.linspace instead of vtk file
+ # generate linspace of points in x and y direction based on x and y limits
+ x = np.linspace(self.x_limits[0], self.x_limits[1], self.n_test_points_x)
+ y = np.linspace(self.y_limits[0], self.y_limits[1], self.n_test_points_y)
+ # generate meshgrid
+ x_grid, y_grid = np.meshgrid(x, y)
+ # stack the points
+ self.test_points = np.vstack([x_grid.flatten(), y_grid.flatten()]).T
+
+ return self.test_points
+
+ elif self.mesh_generation_method == "external":
+ vtk_file_name = Path(self.output_folder) / "external.vtk"
+
+ mesh = meshio.read(str(vtk_file_name))
+ points = mesh.points
+ return points[:, 0:2] # return only first two columns
+
+
+
+[docs]
+ def write_vtk(self, solution, output_path, filename, data_names):
+ """
+ Writes the data to a VTK file.
+
+ :param solution: The solution vector.
+ :type solution: numpy.ndarray
+ :param output_path: The path to the output folder.
+ :type output_path: str
+ :param filename: The name of the output file.
+ :type filename: str
+ :param data_names: The list of data names in the VTK file to be written as scalars.
+ :type data_names: list
+ :return: None
+ """
+ # read the existing vtk into file
+ if self.mesh_generation_method == "internal":
+ vtk_file_name = Path(self.output_folder) / "internal.vtk"
+ elif self.mesh_generation_method == "external":
+ vtk_file_name = Path(self.output_folder) / "external.vtk"
+
+ data = []
+ with open(vtk_file_name, "r", encoding="utf-8") as File:
+ for line in File:
+ data.append(line)
+
+ # get the output file name
+ output_file_name = Path(output_path) / filename
+
+ if solution.shape[1] != len(data_names):
+ print("[Error] : File : geometry_2d.py, Function: write_vtk")
+ print(
+ "Num Columns in solution = ",
+ solution.shape[1],
+ " Num of data names = ",
+ len(data_names),
+ )
+ raise ValueError("Number of data names and solution columns are not equal")
+
+ # write the data to the output file
+ with open(str(output_file_name), "w", encoding="utf-8") as FN:
+ for line in data:
+ FN.write(line)
+ if "POINT_DATA" in line.strip():
+ break
+
+ for i in range(solution.shape[1]):
+ FN.write("SCALARS " + data_names[i] + " float\n")
+ FN.write("LOOKUP_TABLE default\n")
+ np.savetxt(FN, solution[:, i])
+ FN.write("\n")
+
+
+ # save the vtk file as image
+ # self.save_vtk_as_image(str(output_file_name), data_names)
+
+
+[docs]
+ def plot_adaptive_mesh(
+ self, cells_list, area_averaged_cell_loss_list, epoch, filename="cell_residual"
+ ):
+ """
+ Plots the residuals in each cell of the mesh.
+
+ :param cells_list: The list of cells.
+ :type cells_list: list
+ :param area_averaged_cell_loss_list: The list of area averaged cell residual (or the normal residual).
+ :type area_averaged_cell_loss_list: list
+ :param epoch: The epoch number (for file name).
+ :type epoch: int
+ :param filename: The name of the output file, defaults to "cell_residual".
+ :type filename: str, optional
+ :return: None
+ """
+
+ plt.figure(figsize=(6.4, 4.8), dpi=300)
+
+ # normalise colors
+ norm = mcolors.Normalize(
+ vmin=np.min(area_averaged_cell_loss_list), vmax=np.max(area_averaged_cell_loss_list)
+ )
+
+ # Create a colormap
+ colormap = plt.cm.jet
+
+ for index, cell in enumerate(cells_list):
+ x = cell[:, 0]
+ y = cell[:, 1]
+
+ x = np.append(x, x[0])
+ y = np.append(y, y[0])
+
+ curr_cell_loss = float(area_averaged_cell_loss_list[index])
+
+ color = colormap(norm(curr_cell_loss))
+
+ plt.fill(x, y, color=color, alpha=0.9)
+
+ plt.plot(x, y, "k")
+
+ # # compute x_min, x_max, y_min, y_max
+ # x_min = np.min(x)
+ # x_max = np.max(x)
+ # y_min = np.min(y)
+ # y_max = np.max(y)
+
+ # # compute centroid of the cells
+ # centroid = np.array([np.mean(x), np.mean(y)])
+
+ # plot the loss text within the cell
+ # plt.text(centroid[0], centroid[1], f"{curr_cell_loss:.3e}", fontsize=16, horizontalalignment='center', verticalalignment='center')
+
+ sm = plt.cm.ScalarMappable(cmap=colormap, norm=norm)
+ sm.set_array([])
+ plt.colorbar(sm)
+
+ # output filename
+ output_filename = Path(f"{self.output_folder}/{filename}_{epoch}.png")
+ plt.title(f"Cell Residual")
+ plt.savefig(str(output_filename), dpi=300)
+
+
+
+# This class is to handle data for 2D problems, convert them into tensors using custom tf functions
+# and make them available for the model to train
+# @Author : Thivin Anandh D
+# @Date : 22/Sep/2023
+# @History : 22/Sep/2023 - Initial implementation with basic data handling
+
+from ..FE_2D.fespace2d import *
+from ..Geometry.geometry_2d import *
+import tensorflow as tf
+
+
+
+[docs]
+class DataHandler2D:
+ """
+ This class is to handle data for 2D problems, convert them into tensors using custom tf functions.
+ It is responsible for all type conversions and data handling.
+
+ .. note:: All inputs to these functions are generally numpy arrays with dtype np.float64.
+ So we can either maintain the same dtype or convert them to tf.float32 ( for faster computation ).
+
+ :param fespace: The FESpace2D object.
+ :type fespace: FESpace2D
+ :param domain: The Domain2D object.
+ :type domain: Domain2D
+ :param shape_val_mat_list: List of shape function values for each cell.
+ :type shape_val_mat_list: list
+ :param grad_x_mat_list: List of shape function derivatives with respect to x for each cell.
+ :type grad_x_mat_list: list
+ :param grad_y_mat_list: List of shape function derivatives with respect to y for each cell.
+ :type grad_y_mat_list: list
+ :param x_pde_list: List of actual coordinates of the quadrature points for each cell.
+ :type x_pde_list: list
+ :param forcing_function_list: List of forcing function values for each cell.
+ :type forcing_function_list: list
+ :param dtype: The tensorflow dtype to be used for all the tensors.
+ :type dtype: tf.DType
+ """
+
+ def __init__(self, fespace, domain, dtype):
+ """
+ Constructor for the DataHandler2D class
+
+ :param fespace: The FESpace2D object.
+ :type fespace: FESpace2D
+ :param domain: The Domain2D object.
+ :type domain: Domain2D
+ :param shape_val_mat_list: List of shape function values for each cell.
+ :type shape_val_mat_list: list
+ :param grad_x_mat_list: List of shape function derivatives with respect to x for each cell.
+ :type grad_x_mat_list: list
+ :param grad_y_mat_list: List of shape function derivatives with respect to y for each cell.
+ :type grad_y_mat_list: list
+ :param x_pde_list: List of actual coordinates of the quadrature points for each cell.
+ :type x_pde_list: list
+ :param forcing_function_list: List of forcing function values for each cell.
+ :type forcing_function_list: list
+ :param dtype: The tensorflow dtype to be used for all the tensors.
+ :type dtype: tf.DType
+ """
+
+ self.fespace = fespace
+ self.domain = domain
+ self.shape_val_mat_list = []
+ self.grad_x_mat_list = []
+ self.grad_y_mat_list = []
+ self.x_pde_list = []
+ self.forcing_function_list = []
+ self.dtype = dtype
+
+ # check if the given dtype is a valid tensorflow dtype
+ if not isinstance(self.dtype, tf.DType):
+ raise TypeError("The given dtype is not a valid tensorflow dtype")
+
+ for cell_index in range(self.fespace.n_cells):
+ shape_val_mat = tf.constant(
+ self.fespace.get_shape_function_val(cell_index), dtype=self.dtype
+ )
+ grad_x_mat = tf.constant(
+ self.fespace.get_shape_function_grad_x(cell_index), dtype=self.dtype
+ )
+ grad_y_mat = tf.constant(
+ self.fespace.get_shape_function_grad_y(cell_index), dtype=self.dtype
+ )
+ x_pde = tf.constant(
+ self.fespace.get_quadrature_actual_coordinates(cell_index), dtype=self.dtype
+ )
+ forcing_function = tf.constant(
+ self.fespace.get_forcing_function_values(cell_index), dtype=self.dtype
+ )
+ self.shape_val_mat_list.append(shape_val_mat)
+ self.grad_x_mat_list.append(grad_x_mat)
+ self.grad_y_mat_list.append(grad_y_mat)
+ self.x_pde_list.append(x_pde)
+ self.forcing_function_list.append(forcing_function)
+
+ # now convert all the shapes into 3D tensors for easy multiplication
+ # input tensor - x_pde_list
+ self.x_pde_list = tf.reshape(self.x_pde_list, [-1, 2])
+
+ self.forcing_function_list = tf.concat(self.forcing_function_list, axis=1)
+
+ self.shape_val_mat_list = tf.stack(self.shape_val_mat_list, axis=0)
+ self.grad_x_mat_list = tf.stack(self.grad_x_mat_list, axis=0)
+ self.grad_y_mat_list = tf.stack(self.grad_y_mat_list, axis=0)
+
+ # test points
+ self.test_points = None
+
+
+[docs]
+ def get_dirichlet_input(self):
+ """
+ This function will return the input for the Dirichlet boundary data
+
+ :return:
+ - input_dirichlet (tf.Tensor): The input for the Dirichlet boundary data
+ - actual_dirichlet (tf.Tensor): The actual Dirichlet boundary data
+ """
+ input_dirichlet, actual_dirichlet = self.fespace.generate_dirichlet_boundary_data()
+
+ # convert to tensors
+ input_dirichlet = tf.constant(input_dirichlet, dtype=self.dtype)
+ actual_dirichlet = tf.constant(actual_dirichlet, dtype=self.dtype)
+ actual_dirichlet = tf.reshape(actual_dirichlet, [-1, 1])
+
+ return input_dirichlet, actual_dirichlet
+
+
+
+[docs]
+ def get_test_points(self):
+ """
+ Get the test points for the given domain.
+
+ :return: The test points for the given domain.
+ :rtype: tf.Tensor
+ """
+ self.test_points = self.domain.get_test_points()
+ self.test_points = tf.constant(self.test_points, dtype=self.dtype)
+ return self.test_points
+
+
+
+[docs]
+ def get_bilinear_params_dict_as_tensors(self, function):
+ """
+ Accepts a function from example file and converts all the values into tensors of the given dtype
+
+ Parameters:
+ - function (function): The function from the example file which returns the bilinear parameters dictionary
+
+ Returns:
+ - bilinear_params_dict (dict): The bilinear parameters dictionary with all the values converted to tensors
+
+ :param function: The function from the example file which returns the bilinear parameters dictionary
+ :type function: function
+ :return: The bilinear parameters dictionary with all the values converted to tensors
+ :rtype: dict
+ """
+ # get the dictionary of bilinear parameters
+ bilinear_params_dict = function()
+
+ # loop over all keys and convert the values to tensors
+ for key in bilinear_params_dict.keys():
+ bilinear_params_dict[key] = tf.constant(bilinear_params_dict[key], dtype=self.dtype)
+
+ return bilinear_params_dict
+
+
+ # to be used only in inverse problems
+
+[docs]
+ def get_sensor_data(self, exact_sol, num_sensor_points, mesh_type, file_name=None):
+ """
+ Accepts a function from example file and converts all the values into tensors of the given dtype
+
+ :param exact_sol: The function from the example file which returns the exact solution
+ :type exact_sol: function
+ :param num_sensor_points: The number of sensor points to be generated
+ :type num_sensor_points: int
+ :param mesh_type: The type of mesh to be used for sensor data generation
+ :type mesh_type: str
+ :param file_name: The name of the file to be used for external mesh generation, defaults to None
+ :type file_name: str, optional
+ :return: The sensor points and sensor values as tensors
+ :rtype: tuple[tf.Tensor, tf.Tensor]
+ """
+ print(f"mesh_type = {mesh_type}")
+ if mesh_type == "internal":
+ # Call the method to get the sensor data
+ points, sensor_values = self.fespace.get_sensor_data(exact_sol, num_sensor_points)
+ elif mesh_type == "external":
+ # Call the method to get the sensor data
+ points, sensor_values = self.fespace.get_sensor_data_external(
+ exact_sol, num_sensor_points, file_name
+ )
+ # convert the points and sensor values into tensors
+ points = tf.constant(points, dtype=self.dtype)
+ sensor_values = tf.constant(sensor_values, dtype=self.dtype)
+
+ sensor_values = tf.reshape(sensor_values, [-1, 1])
+ points = tf.reshape(points, [-1, 2])
+
+ return points, sensor_values
+
+
+ # get inverse param dict as tensors
+
+[docs]
+ def get_inverse_params(self, inverse_params_dict_function):
+ """
+ Accepts a function from example file and converts all the values into tensors of the given dtype
+
+ :param inverse_params_dict_function: The function from the example file which returns the inverse parameters dictionary
+ :type inverse_params_dict_function: function
+ :return: The inverse parameters dictionary with all the values converted to tensors
+ :rtype: dict
+ """
+ # loop over all keys and convert the values to tensors
+
+ inverse_params_dict = inverse_params_dict_function()
+
+ for key in inverse_params_dict.keys():
+ inverse_params_dict[key] = tf.constant(inverse_params_dict[key], dtype=self.dtype)
+
+ return inverse_params_dict
+
+
+
+"""
+file: model.py
+description: This file hosts the Neural Network (NN) model and the training loop for variational Physics-Informed Neural Networks (PINNs).
+ The focus is on the model architecture and the training loop, and not on the loss functions.
+authors: Thivin Anandh D
+changelog: 22/Sep/2023 - Initial implementation with basic model architecture and training loop
+known_issues: None
+dependencies: None specified.
+"""
+
+import tensorflow as tf
+from tensorflow.keras import layers
+from tensorflow.keras import initializers
+import copy
+
+
+# Custom Model
+
+[docs]
+class DenseModel(tf.keras.Model):
+ """
+ Defines the Dense Model for the Neural Network for solving Variational PINNs.
+
+ :param layer_dims: List of dimensions of the dense layers.
+ :type layer_dims: list
+ :param learning_rate_dict: The dictionary containing the learning rate parameters.
+ :type learning_rate_dict: dict
+ :param params_dict: The dictionary containing the parameters.
+ :type params_dict: dict
+ :param loss_function: The loss function for the PDE.
+ :type loss_function: function
+ :param input_tensors_list: The list containing the input tensors.
+ :type input_tensors_list: list
+ :param orig_factor_matrices: The list containing the original factor matrices.
+ :type orig_factor_matrices: list
+ :param force_function_list: The force function matrix.
+ :type force_function_list: tf.Tensor
+ :param tensor_dtype: The tensorflow dtype to be used for all the tensors.
+ :type tensor_dtype: tf.DType
+ :param use_attention: Flag to use attention layer after input, defaults to False.
+ :type use_attention: bool, optional
+ :param activation: The activation function to be used for the dense layers, defaults to "tanh".
+ :type activation: str, optional
+ :param hessian: Flag to use hessian loss, defaults to False.
+ :type hessian: bool, optional
+ """
+
+ def __init__(
+ self,
+ layer_dims,
+ learning_rate_dict,
+ params_dict,
+ loss_function,
+ input_tensors_list,
+ orig_factor_matrices,
+ force_function_list,
+ tensor_dtype,
+ use_attention=False,
+ activation="tanh",
+ hessian=False,
+ ):
+ super(DenseModel, self).__init__()
+ self.layer_dims = layer_dims
+ self.use_attention = use_attention
+ self.activation = activation
+ self.layer_list = []
+ self.loss_function = loss_function
+ self.hessian = hessian
+
+ self.tensor_dtype = tensor_dtype
+
+ # if dtype is not a valid tensorflow dtype, raise an error
+ if not isinstance(self.tensor_dtype, tf.DType):
+ raise TypeError("The given dtype is not a valid tensorflow dtype")
+
+ self.orig_factor_matrices = orig_factor_matrices
+ self.shape_function_mat_list = copy.deepcopy(orig_factor_matrices[0])
+ self.shape_function_grad_x_factor_mat_list = copy.deepcopy(orig_factor_matrices[1])
+ self.shape_function_grad_y_factor_mat_list = copy.deepcopy(orig_factor_matrices[2])
+
+ self.force_function_list = force_function_list
+
+ self.input_tensors_list = input_tensors_list
+ self.input_tensor = copy.deepcopy(input_tensors_list[0])
+ self.dirichlet_input = copy.deepcopy(input_tensors_list[1])
+ self.dirichlet_actual = copy.deepcopy(input_tensors_list[2])
+
+ self.params_dict = params_dict
+
+ self.pre_multiplier_val = self.shape_function_mat_list
+ self.pre_multiplier_grad_x = self.shape_function_grad_x_factor_mat_list
+ self.pre_multiplier_grad_y = self.shape_function_grad_y_factor_mat_list
+
+ self.force_matrix = self.force_function_list
+
+ print(f"{'-'*74}")
+ print(f"| {'PARAMETER':<25} | {'SHAPE':<25} |")
+ print(f"{'-'*74}")
+ print(
+ f"| {'input_tensor':<25} | {str(self.input_tensor.shape):<25} | {self.input_tensor.dtype}"
+ )
+ print(
+ f"| {'force_matrix':<25} | {str(self.force_matrix.shape):<25} | {self.force_matrix.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_grad_x':<25} | {str(self.pre_multiplier_grad_x.shape):<25} | {self.pre_multiplier_grad_x.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_grad_y':<25} | {str(self.pre_multiplier_grad_y.shape):<25} | {self.pre_multiplier_grad_y.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_val':<25} | {str(self.pre_multiplier_val.shape):<25} | {self.pre_multiplier_val.dtype}"
+ )
+ print(
+ f"| {'dirichlet_input':<25} | {str(self.dirichlet_input.shape):<25} | {self.dirichlet_input.dtype}"
+ )
+ print(
+ f"| {'dirichlet_actual':<25} | {str(self.dirichlet_actual.shape):<25} | {self.dirichlet_actual.dtype}"
+ )
+ print(f"{'-'*74}")
+
+ self.n_cells = params_dict["n_cells"]
+
+ ## ----------------------------------------------------------------- ##
+ ## ---------- LEARNING RATE AND OPTIMISER FOR THE MODEL ------------ ##
+ ## ----------------------------------------------------------------- ##
+
+ # parse the learning rate dictionary
+ self.learning_rate_dict = learning_rate_dict
+ initial_learning_rate = learning_rate_dict["initial_learning_rate"]
+ use_lr_scheduler = learning_rate_dict["use_lr_scheduler"]
+ decay_steps = learning_rate_dict["decay_steps"]
+ decay_rate = learning_rate_dict["decay_rate"]
+ # staircase = learning_rate_dict["staircase"]
+
+ if use_lr_scheduler:
+ learning_rate_fn = tf.keras.optimizers.schedules.ExponentialDecay(
+ initial_learning_rate, decay_steps, decay_rate, staircase=True
+ )
+ else:
+ learning_rate_fn = initial_learning_rate
+
+ self.optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate_fn)
+
+ ## ----------------------------------------------------------------- ##
+ ## --------------------- MODEL ARCHITECTURE ------------------------ ##
+ ## ----------------------------------------------------------------- ##
+
+ # Build dense layers based on the input list
+ for dim in range(len(self.layer_dims) - 2):
+ self.layer_list.append(
+ layers.Dense(
+ self.layer_dims[dim + 1],
+ activation=self.activation,
+ kernel_initializer="glorot_uniform",
+ dtype=self.tensor_dtype,
+ bias_initializer="zeros",
+ )
+ )
+
+ # Add a output layer with no activation
+ self.layer_list.append(
+ layers.Dense(
+ self.layer_dims[-1],
+ activation=None,
+ kernel_initializer="glorot_uniform",
+ dtype=self.tensor_dtype,
+ bias_initializer="zeros",
+ )
+ )
+
+ # Add attention layer if required
+ if self.use_attention:
+ self.attention_layer = layers.Attention()
+
+ # Compile the model
+ self.compile(optimizer=self.optimizer)
+ self.build(input_shape=(None, self.layer_dims[0]))
+
+ # print the summary of the model
+ self.summary()
+
+ # def build(self, input_shape):
+ # super(DenseModel, self).build(input_shape)
+
+
+[docs]
+ def call(self, inputs):
+ """
+ The call method for the model.
+
+ :param inputs: The input tensor for the model.
+ :type inputs: tf.Tensor
+ :return: The output tensor of the model.
+ :rtype: tf.Tensor
+ """
+ x = inputs
+
+ # Apply attention layer after input if flag is True
+ if self.use_attention:
+ x = self.attention_layer([x, x])
+
+ # Loop through the dense layers
+ for layer in self.layer_list:
+ x = layer(x)
+
+ return x
+
+
+
+[docs]
+ def get_config(self):
+ """
+ Get the configuration of the model.
+
+ Returns:
+ dict: The configuration of the model.
+ """
+ # Get the base configuration
+ base_config = super().get_config()
+
+ # Add the non-serializable arguments to the configuration
+ base_config.update(
+ {
+ "learning_rate_dict": self.learning_rate_dict,
+ "loss_function": self.loss_function,
+ "input_tensors_list": self.input_tensors_list,
+ "orig_factor_matrices": self.orig_factor_matrices,
+ "force_function_list": self.force_function_list,
+ "params_dict": self.params_dict,
+ "use_attention": self.use_attention,
+ "activation": self.activation,
+ "hessian": self.hessian,
+ "layer_dims": self.layer_dims,
+ "tensor_dtype": self.tensor_dtype,
+ }
+ )
+
+ return base_config
+
+
+
+[docs]
+ @tf.function
+ def train_step(self, beta=10, bilinear_params_dict=None): # pragma: no cover
+ """
+ The train step method for the model.
+
+ :param beta: The beta parameter for the training step, defaults to 10.
+ :type beta: int, optional
+ :param bilinear_params_dict: The dictionary containing the bilinear parameters, defaults to None.
+ :type bilinear_params_dict: dict, optional
+ :return: The output of the training step.
+ :rtype: varies based on implementation
+ """
+
+ with tf.GradientTape(persistent=True) as tape:
+ # Predict the values for dirichlet boundary conditions
+ predicted_values_dirichlet = self(self.dirichlet_input)
+
+ # initialize total loss as a tensor with shape (1,) and value 0.0
+ total_pde_loss = 0.0
+
+ with tf.GradientTape(persistent=True) as tape1:
+ # tape gradient
+ tape1.watch(self.input_tensor)
+ # Compute the predicted values from the model
+ predicted_values = self(self.input_tensor)
+
+ # compute the gradients of the predicted values wrt the input which is (x, y)
+ gradients = tape1.gradient(predicted_values, self.input_tensor)
+
+ # Split the gradients into x and y components and reshape them to (-1, 1)
+ # the reshaping is done for the tensorial operations purposes (refer Notebook)
+ pred_grad_x = tf.reshape(
+ gradients[:, 0], [self.n_cells, self.pre_multiplier_grad_x.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+ pred_grad_y = tf.reshape(
+ gradients[:, 1], [self.n_cells, self.pre_multiplier_grad_y.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+
+ pred_val = tf.reshape(
+ predicted_values, [self.n_cells, self.pre_multiplier_val.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+
+ cells_residual = self.loss_function(
+ test_shape_val_mat=self.pre_multiplier_val,
+ test_grad_x_mat=self.pre_multiplier_grad_x,
+ test_grad_y_mat=self.pre_multiplier_grad_y,
+ pred_nn=pred_val,
+ pred_grad_x_nn=pred_grad_x,
+ pred_grad_y_nn=pred_grad_y,
+ forcing_function=self.force_matrix,
+ bilinear_params=bilinear_params_dict,
+ )
+
+ residual = tf.reduce_sum(cells_residual)
+
+ # Compute the total loss for the PDE
+ total_pde_loss = total_pde_loss + residual
+
+ # print shapes of the predicted values and the actual values
+ boundary_loss = tf.reduce_mean(
+ tf.square(predicted_values_dirichlet - self.dirichlet_actual), axis=0
+ )
+
+ # Compute Total Loss
+ total_loss = total_pde_loss + beta * boundary_loss
+
+ trainable_vars = self.trainable_variables
+ self.gradients = tape.gradient(total_loss, trainable_vars)
+ self.optimizer.apply_gradients(zip(self.gradients, trainable_vars))
+
+ return {"loss_pde": total_pde_loss, "loss_dirichlet": boundary_loss, "loss": total_loss}
+
+
+
+"""
+file: model_hard.py
+description: This file contains the DenseModel class which is a custom model for the Neural Network
+ for solving Variational PINNs. This model is used for enforcing hard boundary constraints
+ on the solution.
+author: Thivin Anandh D, Divij Ghose, Sashikumaar Ganesan
+date: 22/01/2024
+changelog: 22/01/2024 - file created
+ 22/01/2024 -
+
+known issues: None
+"""
+
+import copy
+import numpy as np
+import tensorflow as tf
+from tensorflow.keras import layers
+from tensorflow.keras import initializers
+
+
+# Custom Model
+
+[docs]
+class DenseModel_Hard(tf.keras.Model):
+ """The DenseModel_Hard class is a custom model class that hosts the neural network model.
+
+ The class inherits from the tf.keras.Model class and is used
+ to define the neural network model architecture and the training loop for FastVPINNs.
+ :param layer_dims: List of integers representing the number of neurons in each layer
+ :type layer_dims: list
+ :param learning_rate_dict: Dictionary containing the learning rate parameters
+ :type learning_rate_dict: dict
+ :param params_dict: Dictionary containing the parameters for the model
+ :type params_dict: dict
+ :param loss_function: Loss function for the model
+ :type loss_function: function
+ :param input_tensors_list: List of input tensors for the model
+ :type input_tensors_list: list
+ :param orig_factor_matrices: List of original factor matrices
+ :type orig_factor_matrices: list
+ :param force_function_list: List of force functions
+ :type force_function_list: list
+ :param tensor_dtype: Tensor data type
+ :type tensor_dtype: tf.DType
+ :param use_attention: Flag to use attention layer
+ :type use_attention: bool
+ :param activation: Activation function for the model
+ :type activation: str
+ :param hessian: Flag to compute hessian
+ :type hessian: bool
+
+ Methods
+ -------
+ call(inputs)
+ This method is used to define the forward pass of the model.
+ get_config()
+ This method is used to get the configuration of the model.
+ train_step(beta=10, bilinear_params_dict=None)
+ This method is used to define the training step of the model.
+
+ """
+
+ def __init__(
+ self,
+ layer_dims,
+ learning_rate_dict,
+ params_dict,
+ loss_function,
+ input_tensors_list,
+ orig_factor_matrices,
+ force_function_list,
+ tensor_dtype,
+ use_attention=False,
+ activation="tanh",
+ hessian=False,
+ hard_constraint_function=None,
+ ):
+ super(DenseModel_Hard, self).__init__()
+ self.layer_dims = layer_dims
+ self.use_attention = use_attention
+ self.activation = activation
+ self.layer_list = []
+ self.loss_function = loss_function
+ self.hessian = hessian
+ if hard_constraint_function is None:
+ self.hard_constraint_function = lambda x, y: y
+ else:
+ self.hard_constraint_function = hard_constraint_function
+
+ self.tensor_dtype = tensor_dtype
+
+ # if dtype is not a valid tensorflow dtype, raise an error
+ if not isinstance(self.tensor_dtype, tf.DType):
+ raise TypeError("The given dtype is not a valid tensorflow dtype")
+
+ self.orig_factor_matrices = orig_factor_matrices
+ self.shape_function_mat_list = copy.deepcopy(orig_factor_matrices[0])
+ self.shape_function_grad_x_factor_mat_list = copy.deepcopy(orig_factor_matrices[1])
+ self.shape_function_grad_y_factor_mat_list = copy.deepcopy(orig_factor_matrices[2])
+
+ self.force_function_list = force_function_list
+
+ self.input_tensors_list = input_tensors_list
+ self.input_tensor = copy.deepcopy(input_tensors_list[0])
+ self.dirichlet_input = copy.deepcopy(input_tensors_list[1])
+ self.dirichlet_actual = copy.deepcopy(input_tensors_list[2])
+
+ self.params_dict = params_dict
+
+ self.pre_multiplier_val = self.shape_function_mat_list
+ self.pre_multiplier_grad_x = self.shape_function_grad_x_factor_mat_list
+ self.pre_multiplier_grad_y = self.shape_function_grad_y_factor_mat_list
+
+ self.force_matrix = self.force_function_list
+
+ self.gradients = None
+
+ print(f"{'-'*74}")
+ print(f"| {'PARAMETER':<25} | {'SHAPE':<25} |")
+ print(f"{'-'*74}")
+ print(
+ f"| {'input_tensor':<25} | {str(self.input_tensor.shape):<25} | {self.input_tensor.dtype}"
+ )
+ print(
+ f"| {'force_matrix':<25} | {str(self.force_matrix.shape):<25} | {self.force_matrix.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_grad_x':<25} | {str(self.pre_multiplier_grad_x.shape):<25} | {self.pre_multiplier_grad_x.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_grad_y':<25} | {str(self.pre_multiplier_grad_y.shape):<25} | {self.pre_multiplier_grad_y.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_val':<25} | {str(self.pre_multiplier_val.shape):<25} | {self.pre_multiplier_val.dtype}"
+ )
+ print(
+ f"| {'dirichlet_input':<25} | {str(self.dirichlet_input.shape):<25} | {self.dirichlet_input.dtype}"
+ )
+ print(
+ f"| {'dirichlet_actual':<25} | {str(self.dirichlet_actual.shape):<25} | {self.dirichlet_actual.dtype}"
+ )
+ print(f"{'-'*74}")
+
+ self.n_cells = params_dict["n_cells"]
+
+ ## ----------------------------------------------------------------- ##
+ ## ---------- LEARNING RATE AND OPTIMISER FOR THE MODEL ------------ ##
+ ## ----------------------------------------------------------------- ##
+
+ # parse the learning rate dictionary
+ self.learning_rate_dict = learning_rate_dict
+ initial_learning_rate = learning_rate_dict["initial_learning_rate"]
+ use_lr_scheduler = learning_rate_dict["use_lr_scheduler"]
+ decay_steps = learning_rate_dict["decay_steps"]
+ decay_rate = learning_rate_dict["decay_rate"]
+ staircase = learning_rate_dict["staircase"]
+
+ if use_lr_scheduler:
+ learning_rate_fn = tf.keras.optimizers.schedules.ExponentialDecay(
+ initial_learning_rate, decay_steps, decay_rate, staircase=True
+ )
+ else:
+ learning_rate_fn = initial_learning_rate
+
+ self.optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate_fn)
+
+ ## ----------------------------------------------------------------- ##
+ ## --------------------- MODEL ARCHITECTURE ------------------------ ##
+ ## ----------------------------------------------------------------- ##
+
+ # Build dense layers based on the input list
+ for dim in range(len(self.layer_dims) - 2):
+ self.layer_list.append(
+ layers.Dense(
+ self.layer_dims[dim + 1],
+ activation=self.activation,
+ kernel_initializer="glorot_uniform",
+ dtype=self.tensor_dtype,
+ bias_initializer="zeros",
+ )
+ )
+
+ # Add a output layer with no activation
+ self.layer_list.append(
+ layers.Dense(
+ self.layer_dims[-1],
+ activation=None,
+ kernel_initializer="glorot_uniform",
+ dtype=self.tensor_dtype,
+ bias_initializer="zeros",
+ )
+ )
+
+ # Add attention layer if required
+ if self.use_attention:
+ self.attention_layer = layers.Attention()
+
+ # Compile the model
+ self.compile(optimizer=self.optimizer)
+ self.build(input_shape=(None, self.layer_dims[0]))
+
+ # print the summary of the model
+ self.summary()
+
+ # def build(self, input_shape):
+ # super(DenseModel, self).build(input_shape)
+
+
+[docs]
+ def call(self, inputs):
+ """This method is used to define the forward pass of the model.
+ :param inputs: Input tensor
+ :type inputs: tf.Tensor
+ :return: Output tensor from the model
+ :rtype: tf.Tensor
+ """
+ x = inputs
+
+ # Apply attention layer after input if flag is True
+ if self.use_attention:
+ x = self.attention_layer([x, x])
+
+ # Loop through the dense layers
+ for layer in self.layer_list:
+ x = layer(x)
+
+ x = self.hard_constraint_function(inputs, x)
+
+ return x
+
+
+
+[docs]
+ def get_config(self):
+ """This method is used to get the configuration of the model.
+ :return: Configuration of the model
+ :rtype: dict
+ """
+ # Get the base configuration
+ base_config = super().get_config()
+
+ # Add the non-serializable arguments to the configuration
+ base_config.update(
+ {
+ "learning_rate_dict": self.learning_rate_dict,
+ "loss_function": self.loss_function,
+ "input_tensors_list": self.input_tensors_list,
+ "orig_factor_matrices": self.orig_factor_matrices,
+ "force_function_list": self.force_function_list,
+ "params_dict": self.params_dict,
+ "use_attention": self.use_attention,
+ "activation": self.activation,
+ "hessian": self.hessian,
+ "layer_dims": self.layer_dims,
+ "tensor_dtype": self.tensor_dtype,
+ }
+ )
+
+ return base_config
+
+
+
+[docs]
+ @tf.function
+ def train_step(self, beta=10, bilinear_params_dict=None): # pragma: no cover
+ """This method is used to define the training step of the mode.
+ :param bilinear_params_dict: Dictionary containing the bilinear parameters
+ :type bilinear_params_dict: dict
+ :return: Dictionary containing the loss values
+ :rtype: dict
+ """
+
+ with tf.GradientTape(persistent=True) as tape:
+ # Predict the values for dirichlet boundary conditions
+
+ # initialize total loss as a tensor with shape (1,) and value 0.0
+ total_pde_loss = 0.0
+
+ with tf.GradientTape(persistent=True) as tape1:
+ # tape gradient
+ tape1.watch(self.input_tensor)
+ # Compute the predicted values from the model
+ predicted_values = self(self.input_tensor)
+
+ # compute the gradients of the predicted values wrt the input which is (x, y)
+ gradients = tape1.gradient(predicted_values, self.input_tensor)
+
+ # Split the gradients into x and y components and reshape them to (-1, 1)
+ # the reshaping is done for the tensorial operations purposes (refer Notebook)
+ pred_grad_x = tf.reshape(
+ gradients[:, 0], [self.n_cells, self.pre_multiplier_grad_x.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+ pred_grad_y = tf.reshape(
+ gradients[:, 1], [self.n_cells, self.pre_multiplier_grad_y.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+
+ pred_val = tf.reshape(
+ predicted_values, [self.n_cells, self.pre_multiplier_val.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+
+ cells_residual = self.loss_function(
+ test_shape_val_mat=self.pre_multiplier_val,
+ test_grad_x_mat=self.pre_multiplier_grad_x,
+ test_grad_y_mat=self.pre_multiplier_grad_y,
+ pred_nn=pred_val,
+ pred_grad_x_nn=pred_grad_x,
+ pred_grad_y_nn=pred_grad_y,
+ forcing_function=self.force_matrix,
+ bilinear_params=bilinear_params_dict,
+ )
+
+ residual = tf.reduce_sum(cells_residual)
+
+ # tf.print("Residual : ", residual)
+ # tf.print("Residual Shape : ", residual.shape)
+
+ # Compute the total loss for the PDE
+ total_pde_loss = total_pde_loss + residual
+
+ # convert predicted_values_dirichlet to tf.float64
+ # predicted_values_dirichlet = tf.cast(predicted_values_dirichlet, tf.float64)
+
+ # tf.print("Boundary Loss : ", boundary_loss)
+ # tf.print("Boundary Loss Shape : ", boundary_loss.shape)
+ # tf.print("Total PDE Loss : ", total_pde_loss)
+ # tf.print("Total PDE Loss Shape : ", total_pde_loss.shape)
+ boundary_loss = 0.0
+ # Compute Total Loss
+ total_loss = total_pde_loss
+
+ trainable_vars = self.trainable_variables
+ self.gradients = tape.gradient(total_loss, trainable_vars)
+ self.optimizer.apply_gradients(zip(self.gradients, trainable_vars))
+
+ return {"loss_pde": total_pde_loss, "loss_dirichlet": boundary_loss, "loss": total_loss}
+
+
+
+"""
+file: model_inverse.py
+description: This file hosts the Neural Network (NN) model and the training loop for variational Physics-Informed Neural Networks (PINNs).
+ This focuses on training variational PINNs for inverse problems where the inverse parameter is constant on the domain.
+ The focus is on the model architecture and the training loop, and not on the loss functions.
+authors: Thivin Anandh D
+changelog: 22/Sep/2023 - Initial implementation with basic model architecture and training loop
+known_issues: Currently out of the box, supports only one constant inverse parameters.
+dependencies: None specified.
+"""
+
+import tensorflow as tf
+from tensorflow.keras import layers
+from tensorflow.keras import initializers
+import copy
+
+
+# Custom Model
+
+[docs]
+class DenseModel_Inverse(tf.keras.Model):
+ """
+ A subclass of tf.keras.Model that defines a dense model for an inverse problem.
+
+ :param list layer_dims: The dimensions of the layers in the model.
+ :param dict learning_rate_dict: A dictionary containing the learning rates.
+ :param dict params_dict: A dictionary containing the parameters of the model.
+ :param function loss_function: The loss function to be used in the model.
+ :param list input_tensors_list: A list of input tensors.
+ :param list orig_factor_matrices: The original factor matrices.
+ :param list force_function_list: A list of force functions.
+ :param list sensor_list: A list of sensors for the inverse problem.
+ :param dict inverse_params_dict: A dictionary containing the parameters for the inverse problem.
+ :param tf.DType tensor_dtype: The data type of the tensors.
+ :param bool use_attention: Whether to use attention mechanism in the model. Defaults to False.
+ :param str activation: The activation function to be used in the model. Defaults to 'tanh'.
+ :param bool hessian: Whether to use Hessian in the model. Defaults to False.
+ """
+
+ def __init__(
+ self,
+ layer_dims,
+ learning_rate_dict,
+ params_dict,
+ loss_function,
+ input_tensors_list,
+ orig_factor_matrices,
+ force_function_list,
+ sensor_list, # for inverse problem
+ inverse_params_dict, # for inverse problem
+ tensor_dtype,
+ use_attention=False,
+ activation="tanh",
+ hessian=False,
+ ):
+ super(DenseModel_Inverse, self).__init__()
+ self.layer_dims = layer_dims
+ self.use_attention = use_attention
+ self.activation = activation
+ self.layer_list = []
+ self.loss_function = loss_function
+ self.hessian = hessian
+
+ self.tensor_dtype = tensor_dtype
+
+ self.sensor_list = sensor_list
+ # obtain sensor values
+ self.sensor_points = sensor_list[0]
+ self.sensor_values = sensor_list[1]
+
+ # inverse params dict
+ self.inverse_params_dict = inverse_params_dict
+
+ # Conver all the values within inverse_params_dict to trainable variables
+ for key, value in self.inverse_params_dict.items():
+ self.inverse_params_dict[key] = tf.Variable(
+ value, dtype=self.tensor_dtype, trainable=True
+ )
+ tf.print(f"Key : {key} , Value : {self.inverse_params_dict[key]}")
+
+ # add the sensor points to the trainable variables of the model
+ self.trainable_variables.extend(self.inverse_params_dict.values())
+
+ # if dtype is not a valid tensorflow dtype, raise an error
+ if not isinstance(self.tensor_dtype, tf.DType):
+ raise TypeError("The given dtype is not a valid tensorflow dtype")
+
+ self.orig_factor_matrices = orig_factor_matrices
+ self.shape_function_mat_list = copy.deepcopy(orig_factor_matrices[0])
+ self.shape_function_grad_x_factor_mat_list = copy.deepcopy(orig_factor_matrices[1])
+ self.shape_function_grad_y_factor_mat_list = copy.deepcopy(orig_factor_matrices[2])
+
+ self.force_function_list = force_function_list
+
+ self.input_tensors_list = input_tensors_list
+ self.input_tensor = copy.deepcopy(input_tensors_list[0])
+ self.dirichlet_input = copy.deepcopy(input_tensors_list[1])
+ self.dirichlet_actual = copy.deepcopy(input_tensors_list[2])
+
+ self.params_dict = params_dict
+
+ self.pre_multiplier_val = self.shape_function_mat_list
+ self.pre_multiplier_grad_x = self.shape_function_grad_x_factor_mat_list
+ self.pre_multiplier_grad_y = self.shape_function_grad_y_factor_mat_list
+
+ self.force_matrix = self.force_function_list
+
+ print(f"{'-'*74}")
+ print(f"| {'PARAMETER':<25} | {'SHAPE':<25} |")
+ print(f"{'-'*74}")
+ print(
+ f"| {'input_tensor':<25} | {str(self.input_tensor.shape):<25} | {self.input_tensor.dtype}"
+ )
+ print(
+ f"| {'force_matrix':<25} | {str(self.force_matrix.shape):<25} | {self.force_matrix.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_grad_x':<25} | {str(self.pre_multiplier_grad_x.shape):<25} | {self.pre_multiplier_grad_x.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_grad_y':<25} | {str(self.pre_multiplier_grad_y.shape):<25} | {self.pre_multiplier_grad_y.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_val':<25} | {str(self.pre_multiplier_val.shape):<25} | {self.pre_multiplier_val.dtype}"
+ )
+ print(
+ f"| {'dirichlet_input':<25} | {str(self.dirichlet_input.shape):<25} | {self.dirichlet_input.dtype}"
+ )
+ print(
+ f"| {'dirichlet_actual':<25} | {str(self.dirichlet_actual.shape):<25} | {self.dirichlet_actual.dtype}"
+ )
+ print(f"{'-'*74}")
+
+ self.n_cells = params_dict["n_cells"]
+
+ ## ----------------------------------------------------------------- ##
+ ## ---------- LEARNING RATE AND OPTIMISER FOR THE MODEL ------------ ##
+ ## ----------------------------------------------------------------- ##
+
+ # parse the learning rate dictionary
+ self.learning_rate_dict = learning_rate_dict
+ initial_learning_rate = learning_rate_dict["initial_learning_rate"]
+ use_lr_scheduler = learning_rate_dict["use_lr_scheduler"]
+ decay_steps = learning_rate_dict["decay_steps"]
+ decay_rate = learning_rate_dict["decay_rate"]
+ staircase = learning_rate_dict["staircase"]
+
+ if use_lr_scheduler:
+ learning_rate_fn = tf.keras.optimizers.schedules.ExponentialDecay(
+ initial_learning_rate, decay_steps, decay_rate, staircase=True
+ )
+ else:
+ learning_rate_fn = initial_learning_rate
+
+ self.optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate_fn)
+
+ ## ----------------------------------------------------------------- ##
+ ## --------------------- MODEL ARCHITECTURE ------------------------ ##
+ ## ----------------------------------------------------------------- ##
+
+ # Build dense layers based on the input list
+ for dim in range(len(self.layer_dims) - 2):
+ self.layer_list.append(
+ layers.Dense(
+ self.layer_dims[dim + 1],
+ activation=self.activation,
+ kernel_initializer="glorot_uniform",
+ dtype=self.tensor_dtype,
+ bias_initializer="zeros",
+ )
+ )
+
+ # Add a output layer with no activation
+ self.layer_list.append(
+ layers.Dense(
+ self.layer_dims[-1],
+ activation=None,
+ kernel_initializer="glorot_uniform",
+ dtype=self.tensor_dtype,
+ bias_initializer="zeros",
+ )
+ )
+
+ # Add attention layer if required
+ if self.use_attention:
+ self.attention_layer = layers.Attention()
+
+ # Compile the model
+ self.compile(optimizer=self.optimizer)
+ self.build(input_shape=(None, self.layer_dims[0]))
+
+ # print the summary of the model
+ self.summary()
+
+
+[docs]
+ def call(self, inputs):
+ """
+ Applies the model to the input data.
+
+ Args:
+ inputs: The input data.
+
+ Returns:
+ The output of the model after applying all the layers.
+ """
+ x = inputs
+
+ # Apply attention layer after input if flag is True
+ if self.use_attention:
+ x = self.attention_layer([x, x])
+
+ # Loop through the dense layers
+ for layer in self.layer_list:
+ x = layer(x)
+
+ return x
+
+
+
+[docs]
+ def get_config(self):
+ """
+ Returns the configuration of the model.
+
+ This method is used to serialize the model configuration. It returns a dictionary
+ containing all the necessary information to recreate the model.
+
+ Returns:
+ dict: The configuration dictionary of the model.
+ """
+ # Get the base configuration
+ base_config = super().get_config()
+
+ # Add the non-serializable arguments to the configuration
+ base_config.update(
+ {
+ "learning_rate_dict": self.learning_rate_dict,
+ "loss_function": self.loss_function,
+ "input_tensors_list": self.input_tensors_list,
+ "orig_factor_matrices": self.orig_factor_matrices,
+ "force_function_list": self.force_function_list,
+ "params_dict": self.params_dict,
+ "use_attention": self.use_attention,
+ "activation": self.activation,
+ "hessian": self.hessian,
+ "layer_dims": self.layer_dims,
+ "tensor_dtype": self.tensor_dtype,
+ "sensor_list": self.sensor_list,
+ "inverse_params_dict": self.inverse_params_dict,
+ }
+ )
+
+ return base_config
+
+
+
+[docs]
+ @tf.function
+ def train_step(self, beta=10, bilinear_params_dict=None): # pragma: no cover
+
+ with tf.GradientTape(persistent=True) as tape:
+ # Predict the values for dirichlet boundary conditions
+ predicted_values_dirichlet = self(self.dirichlet_input)
+
+ # predict the sensor values
+ predicted_sensor_values = self(self.sensor_points)
+
+ # initialize total loss as a tensor with shape (1,) and value 0.0
+ total_pde_loss = 0.0
+
+ with tf.GradientTape(persistent=True) as tape1:
+ # tape gradient
+ tape1.watch(self.input_tensor)
+ # Compute the predicted values from the model
+ predicted_values = self(self.input_tensor)
+
+ # compute the gradients of the predicted values wrt the input which is (x, y)
+ gradients = tape1.gradient(predicted_values, self.input_tensor)
+
+ # Split the gradients into x and y components and reshape them to (-1, 1)
+ # the reshaping is done for the tensorial operations purposes (refer Notebook)
+ pred_grad_x = tf.reshape(
+ gradients[:, 0], [self.n_cells, self.pre_multiplier_grad_x.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+ pred_grad_y = tf.reshape(
+ gradients[:, 1], [self.n_cells, self.pre_multiplier_grad_y.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+
+ pred_val = tf.reshape(
+ predicted_values, [self.n_cells, self.pre_multiplier_val.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+
+ cells_residual = self.loss_function(
+ test_shape_val_mat=self.pre_multiplier_val,
+ test_grad_x_mat=self.pre_multiplier_grad_x,
+ test_grad_y_mat=self.pre_multiplier_grad_y,
+ pred_nn=pred_val,
+ pred_grad_x_nn=pred_grad_x,
+ pred_grad_y_nn=pred_grad_y,
+ forcing_function=self.force_matrix,
+ bilinear_params=bilinear_params_dict,
+ inverse_params_dict=self.inverse_params_dict,
+ )
+
+ residual = tf.reduce_sum(cells_residual)
+
+ # tf.print("Residual : ", residual)
+ # tf.print("Residual Shape : ", residual.shape)
+
+ # Compute the total loss for the PDE
+ total_pde_loss = total_pde_loss + residual
+
+ # convert predicted_values_dirichlet to tf.float64
+ # predicted_values_dirichlet = tf.cast(predicted_values_dirichlet, tf.float64)
+
+ # print shapes of the predicted values and the actual values
+ boundary_loss = tf.reduce_mean(
+ tf.square(predicted_values_dirichlet - self.dirichlet_actual), axis=0
+ )
+
+ # Sensor loss
+ sensor_loss = tf.reduce_mean(
+ tf.square(predicted_sensor_values - self.sensor_values), axis=0
+ )
+
+ # tf.print("Boundary Loss : ", boundary_loss)
+ # tf.print("Boundary Loss Shape : ", boundary_loss.shape)
+ # tf.print("Total PDE Loss : ", total_pde_loss)
+ # tf.print("Total PDE Loss Shape : ", total_pde_loss.shape)
+
+ # Compute Total Loss
+ total_loss = total_pde_loss + beta * boundary_loss + 10 * sensor_loss
+
+ trainable_vars = self.trainable_variables
+ self.gradients = tape.gradient(total_loss, trainable_vars)
+ self.optimizer.apply_gradients(zip(self.gradients, trainable_vars))
+
+ return {
+ "loss_pde": total_pde_loss,
+ "loss_dirichlet": boundary_loss,
+ "loss": total_loss,
+ "inverse_params": self.inverse_params_dict,
+ "sensor_loss": sensor_loss,
+ }
+
+
+
+"""
+file: model_inverse_domain.py
+description: This file hosts the Neural Network (NN) model and the training loop for variational Physics-Informed Neural Networks (PINNs).
+ This focuses on training variational PINNs for inverse problems where the inverse parameter is constant over the entire domain.
+ The focus is on the model architecture and the training loop, and not on the loss functions.
+authors: Thivin Anandh D
+changelog: 22/Sep/2023 - Initial implementation with basic model architecture and training loop
+known_issues: None
+dependencies: None specified.
+"""
+
+import tensorflow as tf
+from tensorflow.keras import layers
+from tensorflow.keras import initializers
+import copy
+
+
+# Custom Model
+
+[docs]
+class DenseModel_Inverse_Domain(tf.keras.Model):
+ """
+ A subclass of tf.keras.Model that defines a dense model for an inverse problem.
+
+ :param list layer_dims: The dimensions of the layers in the model.
+ :param dict learning_rate_dict: A dictionary containing the learning rates.
+ :param dict params_dict: A dictionary containing the parameters of the model.
+ :param function loss_function: The loss function to be used in the model.
+ :param list input_tensors_list: A list of input tensors.
+ :param list orig_factor_matrices: The original factor matrices.
+ :param list force_function_list: A list of force functions.
+ :param list sensor_list: A list of sensors for the inverse problem.
+ :param dict inverse_params_dict: A dictionary containing the parameters for the inverse problem.
+ :param tf.DType tensor_dtype: The data type of the tensors.
+ :param bool use_attention: Whether to use attention mechanism in the model. Defaults to False.
+ :param str activation: The activation function to be used in the model. Defaults to 'tanh'.
+ :param bool hessian: Whether to use Hessian in the model. Defaults to False.
+ """
+
+ def __init__(
+ self,
+ layer_dims,
+ learning_rate_dict,
+ params_dict,
+ loss_function,
+ input_tensors_list,
+ orig_factor_matrices,
+ force_function_list,
+ sensor_list, # for inverse problem
+ tensor_dtype,
+ use_attention=False,
+ activation="tanh",
+ hessian=False,
+ ):
+ super(DenseModel_Inverse_Domain, self).__init__()
+ self.layer_dims = layer_dims
+ self.use_attention = use_attention
+ self.activation = activation
+ self.layer_list = []
+ self.loss_function = loss_function
+ self.hessian = hessian
+
+ self.tensor_dtype = tensor_dtype
+
+ self.sensor_list = sensor_list
+ # obtain sensor values
+ self.sensor_points = sensor_list[0]
+ self.sensor_values = sensor_list[1]
+
+ # if dtype is not a valid tensorflow dtype, raise an error
+ if not isinstance(self.tensor_dtype, tf.DType):
+ raise TypeError("The given dtype is not a valid tensorflow dtype")
+
+ self.orig_factor_matrices = orig_factor_matrices
+ self.shape_function_mat_list = copy.deepcopy(orig_factor_matrices[0])
+ self.shape_function_grad_x_factor_mat_list = copy.deepcopy(orig_factor_matrices[1])
+ self.shape_function_grad_y_factor_mat_list = copy.deepcopy(orig_factor_matrices[2])
+
+ self.force_function_list = force_function_list
+
+ self.input_tensors_list = input_tensors_list
+ self.input_tensor = copy.deepcopy(input_tensors_list[0])
+ self.dirichlet_input = copy.deepcopy(input_tensors_list[1])
+ self.dirichlet_actual = copy.deepcopy(input_tensors_list[2])
+
+ self.params_dict = params_dict
+
+ self.pre_multiplier_val = self.shape_function_mat_list
+ self.pre_multiplier_grad_x = self.shape_function_grad_x_factor_mat_list
+ self.pre_multiplier_grad_y = self.shape_function_grad_y_factor_mat_list
+
+ self.force_matrix = self.force_function_list
+
+ print(f"{'-'*74}")
+ print(f"| {'PARAMETER':<25} | {'SHAPE':<25} |")
+ print(f"{'-'*74}")
+ print(
+ f"| {'input_tensor':<25} | {str(self.input_tensor.shape):<25} | {self.input_tensor.dtype}"
+ )
+ print(
+ f"| {'force_matrix':<25} | {str(self.force_matrix.shape):<25} | {self.force_matrix.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_grad_x':<25} | {str(self.pre_multiplier_grad_x.shape):<25} | {self.pre_multiplier_grad_x.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_grad_y':<25} | {str(self.pre_multiplier_grad_y.shape):<25} | {self.pre_multiplier_grad_y.dtype}"
+ )
+ print(
+ f"| {'pre_multiplier_val':<25} | {str(self.pre_multiplier_val.shape):<25} | {self.pre_multiplier_val.dtype}"
+ )
+ print(
+ f"| {'dirichlet_input':<25} | {str(self.dirichlet_input.shape):<25} | {self.dirichlet_input.dtype}"
+ )
+ print(
+ f"| {'dirichlet_actual':<25} | {str(self.dirichlet_actual.shape):<25} | {self.dirichlet_actual.dtype}"
+ )
+ print(f"{'-'*74}")
+
+ self.n_cells = params_dict["n_cells"]
+
+ ## ----------------------------------------------------------------- ##
+ ## ---------- LEARNING RATE AND OPTIMISER FOR THE MODEL ------------ ##
+ ## ----------------------------------------------------------------- ##
+
+ # parse the learning rate dictionary
+ self.learning_rate_dict = learning_rate_dict
+ initial_learning_rate = learning_rate_dict["initial_learning_rate"]
+ use_lr_scheduler = learning_rate_dict["use_lr_scheduler"]
+ decay_steps = learning_rate_dict["decay_steps"]
+ decay_rate = learning_rate_dict["decay_rate"]
+ # staircase = learning_rate_dict["staircase"]
+
+ if use_lr_scheduler:
+ learning_rate_fn = tf.keras.optimizers.schedules.ExponentialDecay(
+ initial_learning_rate, decay_steps, decay_rate, staircase=True
+ )
+ else:
+ learning_rate_fn = initial_learning_rate
+
+ self.optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate_fn)
+
+ # build the model using the input shape of the first layer in self.layer_dims
+ input_shape = (None, self.layer_dims[0])
+ # build the model
+ self.build(input_shape=input_shape)
+ # Compile the model
+ self.compile(optimizer=self.optimizer)
+ # print model summary
+ self.summary()
+
+ ## ----------------------------------------------------------------- ##
+ ## --------------------- MODEL ARCHITECTURE ------------------------ ##
+ ## ----------------------------------------------------------------- ##
+
+ # Build dense layers based on the input list
+ for dim in range(len(self.layer_dims) - 2):
+ self.layer_list.append(
+ layers.Dense(
+ self.layer_dims[dim + 1],
+ activation=self.activation,
+ kernel_initializer="glorot_uniform",
+ dtype=self.tensor_dtype,
+ bias_initializer="zeros",
+ )
+ )
+
+ # Add a output layer with no activation
+ self.layer_list.append(
+ layers.Dense(
+ self.layer_dims[-1],
+ activation=None,
+ kernel_initializer="glorot_uniform",
+ dtype=self.tensor_dtype,
+ bias_initializer="zeros",
+ )
+ )
+
+ # Add attention layer if required
+ if self.use_attention:
+ self.attention_layer = layers.Attention()
+
+ # Compile the model
+ self.compile(optimizer=self.optimizer)
+ self.build(input_shape=(None, self.layer_dims[0]))
+
+ # print the summary of the model
+ self.summary()
+
+
+[docs]
+ def call(self, inputs):
+ """
+ The call method for the model.
+
+ :param inputs: The input tensor for the model.
+ :type inputs: tf.Tensor
+ :return: The output tensor of the model.
+ :rtype: tf.Tensor
+ """
+ x = inputs
+
+ # Apply attention layer after input if flag is True
+ if self.use_attention:
+ x = self.attention_layer([x, x])
+
+ # Loop through the dense layers
+ for layer in self.layer_list:
+ x = layer(x)
+
+ return x
+
+
+
+[docs]
+ def get_config(self):
+ """
+ Get the configuration of the model.
+
+ Returns:
+ dict: The configuration of the model.
+ """
+ # Get the base configuration
+ base_config = super().get_config()
+
+ # Add the non-serializable arguments to the configuration
+ base_config.update(
+ {
+ "learning_rate_dict": self.learning_rate_dict,
+ "loss_function": self.loss_function,
+ "input_tensors_list": self.input_tensors_list,
+ "orig_factor_matrices": self.orig_factor_matrices,
+ "force_function_list": self.force_function_list,
+ "params_dict": self.params_dict,
+ "use_attention": self.use_attention,
+ "activation": self.activation,
+ "hessian": self.hessian,
+ "layer_dims": self.layer_dims,
+ "tensor_dtype": self.tensor_dtype,
+ "sensor_list": self.sensor_list,
+ }
+ )
+
+ return base_config
+
+
+
+[docs]
+ @tf.function
+ def train_step(self, beta=10, bilinear_params_dict=None): # pragma: no cover
+ """
+ The train step method for the model.
+
+ :param beta: The beta parameter for the training step, defaults to 10.
+ :type beta: int, optional
+ :param bilinear_params_dict: The dictionary containing the bilinear parameters, defaults to None.
+ :type bilinear_params_dict: dict, optional
+ :return: The output of the training step.
+ :rtype: varies based on implementation
+ """
+
+ with tf.GradientTape(persistent=True) as tape:
+ # Predict the values for dirichlet boundary conditions
+ predicted_values_dirichlet = self(self.dirichlet_input)
+ # reshape the predicted values to (, 1)
+ predicted_values_dirichlet = tf.reshape(predicted_values_dirichlet[:, 0], [-1, 1])
+
+ # predict the sensor values
+ predicted_sensor_values = self(self.sensor_points)
+ # reshape the predicted values to (, 1)
+ predicted_sensor_values = tf.reshape(predicted_sensor_values[:, 0], [-1, 1])
+
+ # initialize total loss as a tensor with shape (1,) and value 0.0
+ total_pde_loss = 0.0
+
+ with tf.GradientTape(persistent=True) as tape1:
+ # tape gradient
+ tape1.watch(self.input_tensor)
+ # Compute the predicted values from the model
+ predicted_values_actual = self(self.input_tensor)
+
+ predicted_values = predicted_values_actual[:, 0]
+ inverse_param_values = predicted_values_actual[:, 1]
+
+ # compute the gradients of the predicted values wrt the input which is (x, y)
+ # First column of the predicted values is the predicted value of the PDE
+ gradients = tape1.gradient(predicted_values, self.input_tensor)
+
+ # obtain inverse param gradients
+ inverse_param_gradients = tape1.gradient(inverse_param_values, self.input_tensor)
+
+ # Split the gradients into x and y components and reshape them to (-1, 1)
+ # the reshaping is done for the tensorial operations purposes (refer Notebook)
+ pred_grad_x = tf.reshape(
+ gradients[:, 0], [self.n_cells, self.pre_multiplier_grad_x.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+ pred_grad_y = tf.reshape(
+ gradients[:, 1], [self.n_cells, self.pre_multiplier_grad_y.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+
+ # First column of the predicted values is the predicted value of the PDE and reshape it to (N_cells, N_quadrature_points)
+ pred_val = tf.reshape(
+ predicted_values, [self.n_cells, self.pre_multiplier_val.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+
+ # reshape the second column of the predicted value and reshape it to (N_cells, N_quadrature_points)
+ inverse_param_values = tf.reshape(
+ inverse_param_values, [self.n_cells, self.pre_multiplier_val.shape[-1]]
+ ) # shape : (N_cells , N_quadrature_points)
+
+ cells_residual = self.loss_function(
+ test_shape_val_mat=self.pre_multiplier_val,
+ test_grad_x_mat=self.pre_multiplier_grad_x,
+ test_grad_y_mat=self.pre_multiplier_grad_y,
+ pred_nn=pred_val,
+ pred_grad_x_nn=pred_grad_x,
+ pred_grad_y_nn=pred_grad_y,
+ forcing_function=self.force_matrix,
+ bilinear_params=bilinear_params_dict,
+ inverse_params_list=[inverse_param_values],
+ )
+
+ residual = tf.reduce_sum(cells_residual)
+
+ # Compute the total loss for the PDE
+ total_pde_loss = total_pde_loss + residual
+
+ # print shapes of the predicted values and the actual values
+ boundary_loss = tf.reduce_mean(
+ tf.square(predicted_values_dirichlet - self.dirichlet_actual), axis=0
+ )
+
+ # Sensor loss
+ sensor_loss = tf.reduce_mean(
+ tf.square(predicted_sensor_values - self.sensor_values), axis=0
+ )
+
+ # Compute Total Loss
+ total_loss = total_pde_loss + beta * boundary_loss + 10 * sensor_loss
+
+ trainable_vars = self.trainable_variables
+ self.gradients = tape.gradient(total_loss, trainable_vars)
+ self.optimizer.apply_gradients(zip(self.gradients, trainable_vars))
+
+ return {
+ "loss_pde": total_pde_loss,
+ "loss_dirichlet": boundary_loss,
+ "loss": total_loss,
+ "sensor_loss": sensor_loss,
+ }
+
+
+
+"""
+This function is implemntation of our efficient tensor-based loss calculation for cd2d equation
+Author: Thivin Anandh D
+Date: 21-Sep-2023
+History: Initial implementation
+Refer: https://arxiv.org/abs/2404.12063
+"""
+
+import tensorflow as tf
+
+
+# PDE loss function for the cd2d problem
+
+[docs]
+@tf.function
+def pde_loss_cd2d(
+ test_shape_val_mat,
+ test_grad_x_mat,
+ test_grad_y_mat,
+ pred_nn,
+ pred_grad_x_nn,
+ pred_grad_y_nn,
+ forcing_function,
+ bilinear_params,
+): # pragma: no cover
+ """
+ Calculates and returns the loss for the CD2D problem
+
+ :param test_shape_val_mat: The test shape value matrix.
+ :type test_shape_val_mat: tf.Tensor
+ :param test_grad_x_mat: The x-gradient of the test matrix.
+ :type test_grad_x_mat: tf.Tensor
+ :param test_grad_y_mat: The y-gradient of the test matrix.
+ :type test_grad_y_mat: tf.Tensor
+ :param pred_nn: The predicted neural network output.
+ :type pred_nn: tf.Tensor
+ :param pred_grad_x_nn: The x-gradient of the predicted neural network output.
+ :type pred_grad_x_nn: tf.Tensor
+ :param pred_grad_y_nn: The y-gradient of the predicted neural network output.
+ :type pred_grad_y_nn: tf.Tensor
+ :param forcing_function: The forcing function used in the PDE.
+ :type forcing_function: function
+ :param bilinear_params: The parameters for the bilinear form.
+ :type bilinear_params: list
+
+
+ :return: The calculated loss.
+ :rtype: tf.Tensor
+ """
+
+ # Loss Function : ∫du/dx. dv/dx + ∫du/dy. dv/dy - ∫f.v
+
+ # ∫du/dx. dv/dx dΩ
+ pde_diffusion_x = tf.transpose(tf.linalg.matvec(test_grad_x_mat, pred_grad_x_nn))
+
+ # ∫du/dy. dv/dy dΩ
+ pde_diffusion_y = tf.transpose(tf.linalg.matvec(test_grad_y_mat, pred_grad_y_nn))
+
+ # eps * ∫ (du/dx. dv/dx + du/dy. dv/dy) dΩ
+ pde_diffusion = bilinear_params["eps"] * (pde_diffusion_x + pde_diffusion_y)
+
+ # ∫du/dx. v dΩ
+ conv_x = tf.transpose(tf.linalg.matvec(test_shape_val_mat, pred_grad_x_nn))
+
+ # # ∫du/dy. v dΩ
+ conv_y = tf.transpose(tf.linalg.matvec(test_shape_val_mat, pred_grad_y_nn))
+
+ # # b(x) * ∫du/dx. v dΩ + b(y) * ∫du/dy. v dΩ
+ conv = bilinear_params["b_x"] * conv_x + bilinear_params["b_y"] * conv_y
+
+ # reaction term
+ # ∫c.u.v dΩ
+ reaction = bilinear_params["c"] * tf.transpose(tf.linalg.matvec(test_shape_val_mat, pred_nn))
+
+ residual_matrix = (pde_diffusion + conv + reaction) - forcing_function
+
+ # Perform Reduce mean along the axis 0
+ residual_cells = tf.reduce_mean(tf.square(residual_matrix), axis=0)
+
+ return residual_cells
+
+
+"""
+This function is implemntation of our efficient tensor-based loss calculation for cd2d equation with inverse problem (Constant)
+Author: Thivin Anandh D
+Date: 21-Sep-2023
+History: Initial implementation
+Refer: https://arxiv.org/abs/2404.12063
+"""
+
+import tensorflow as tf
+
+
+# PDE loss function for the CD2D problem Inverse (constant)
+# @tf.function #- Commented due to compatibility issues
+
+[docs]
+def pde_loss_cd2d(
+ test_shape_val_mat,
+ test_grad_x_mat,
+ test_grad_y_mat,
+ pred_nn,
+ pred_grad_x_nn,
+ pred_grad_y_nn,
+ forcing_function,
+ bilinear_params_dict,
+ inverse_param_dict,
+): # pragma: no cover
+ """
+ Calculates and returns the loss for the CD2D problem Inverse (constant)
+
+ :param test_shape_val_mat: The test shape value matrix.
+ :type test_shape_val_mat: tf.Tensor
+ :param test_grad_x_mat: The x-gradient of the test matrix.
+ :type test_grad_x_mat: tf.Tensor
+ :param test_grad_y_mat: The y-gradient of the test matrix.
+ :type test_grad_y_mat: tf.Tensor
+ :param pred_nn: The predicted neural network output.
+ :type pred_nn: tf.Tensor
+ :param pred_grad_x_nn: The x-gradient of the predicted neural network output.
+ :type pred_grad_x_nn: tf.Tensor
+ :param pred_grad_y_nn: The y-gradient of the predicted neural network output.
+ :type pred_grad_y_nn: tf.Tensor
+ :param forcing_function: The forcing function used in the PDE.
+ :type forcing_function: function
+ :param bilinear_params_dict: The dictionary containing the bilinear parameters.
+ :type bilinear_params_dict: dict
+ :param inverse_param_dict: The dictionary containing the parameters for the inverse problem.
+ :type inverse_param_dict: dict
+
+ :return: The calculated loss.
+ :rtype: tf.Tensor
+ """
+
+ # Loss Function : ∫du/dx. dv/dx + ∫du/dy. dv/dy - ∫f.v
+
+ # ∫du/dx. dv/dx dΩ
+ pde_diffusion_x = tf.transpose(tf.linalg.matvec(test_grad_x_mat, pred_grad_x_nn))
+
+ # ∫du/dy. dv/dy dΩ
+ pde_diffusion_y = tf.transpose(tf.linalg.matvec(test_grad_y_mat, pred_grad_y_nn))
+
+ # eps * ∫ (du/dx. dv/dx + du/dy. dv/dy) dΩ
+ pde_diffusion = inverse_param_dict["eps"] * (pde_diffusion_x + pde_diffusion_y)
+
+ # ∫du/dx. v dΩ
+ conv_x = tf.transpose(tf.linalg.matvec(test_shape_val_mat, pred_grad_x_nn))
+
+ # # ∫du/dy. v dΩ
+ conv_y = tf.transpose(tf.linalg.matvec(test_shape_val_mat, pred_grad_y_nn))
+
+ # # b(x) * ∫du/dx. v dΩ + b(y) * ∫du/dy. v dΩ
+ conv = bilinear_params_dict["b_x"] * conv_x + bilinear_params_dict["b_y"] * conv_y
+
+ # reaction term
+ # ∫c.u.v dΩ
+ reaction = bilinear_params_dict["c"] * tf.transpose(
+ tf.linalg.matvec(test_shape_val_mat, pred_nn)
+ )
+
+ residual_matrix = (pde_diffusion + conv + reaction) - forcing_function
+
+ # Perform Reduce mean along the axis 0
+ residual_cells = tf.reduce_mean(tf.square(residual_matrix), axis=0)
+
+ return residual_cells
+
+
+"""
+This function is implemntation of our efficient tensor-based loss calculation for cd2d equation with inverse problem (Domain)
+Author: Thivin Anandh D
+Date: 21-Sep-2023
+History: Initial implementation
+Refer: https://arxiv.org/abs/2404.12063
+"""
+
+import tensorflow as tf
+
+
+# PDE loss function for the CD2D inverse problem (Domain)
+
+[docs]
+@tf.function
+def pde_loss_cd2d_inverse_domain(
+ test_shape_val_mat,
+ test_grad_x_mat,
+ test_grad_y_mat,
+ pred_nn,
+ pred_grad_x_nn,
+ pred_grad_y_nn,
+ forcing_function,
+ bilinear_params,
+ inverse_params_list,
+): # pragma: no cover
+ """
+ Calculates and returns the loss for the CD2D inverse problem (Domain)
+
+ :param test_shape_val_mat: The test shape value matrix.
+ :type test_shape_val_mat: tf.Tensor
+ :param test_grad_x_mat: The x-gradient of the test matrix.
+ :type test_grad_x_mat: tf.Tensor
+ :param test_grad_y_mat: The y-gradient of the test matrix.
+ :type test_grad_y_mat: tf.Tensor
+ :param pred_nn: The predicted neural network output.
+ :type pred_nn: tf.Tensor
+ :param pred_grad_x_nn: The x-gradient of the predicted neural network output.
+ :type pred_grad_x_nn: tf.Tensor
+ :param pred_grad_y_nn: The y-gradient of the predicted neural network output.
+ :type pred_grad_y_nn: tf.Tensor
+ :param forcing_function: The forcing function used in the PDE.
+ :type forcing_function: function
+ :param bilinear_params: The parameters for the bilinear form.
+ :type bilinear_params: list
+ :param inverse_params_list: The parameters for the inverse problem.
+ :type inverse_params_list: list
+
+ :return: The calculated loss.
+ :rtype: tf.Tensor
+ """
+
+ # The first values in the inverse_params_list is the number of inverse problems
+ diffusion_coeff_NN = inverse_params_list[0]
+
+ # ∫ε.du/dx. dv/dx dΩ
+ pde_diffusion_x = tf.transpose(
+ tf.linalg.matvec(test_grad_x_mat, pred_grad_x_nn * diffusion_coeff_NN)
+ )
+
+ # ∫ε.du/dy. dv/dy dΩ
+ pde_diffusion_y = tf.transpose(
+ tf.linalg.matvec(test_grad_y_mat, pred_grad_y_nn * diffusion_coeff_NN)
+ )
+
+ # eps * ∫ (du/dx. dv/dx + du/dy. dv/dy) dΩ
+ # Here our eps is a variable which is to be learned, Which is already premultiplied with the predicted gradient of the neural network
+ pde_diffusion = pde_diffusion_x + pde_diffusion_y
+
+ # ∫du/dx. v dΩ
+ conv_x = tf.transpose(tf.linalg.matvec(test_shape_val_mat, pred_grad_x_nn))
+
+ # # ∫du/dy. v dΩ
+ conv_y = tf.transpose(tf.linalg.matvec(test_shape_val_mat, pred_grad_y_nn))
+
+ # # b(x) * ∫du/dx. v dΩ + b(y) * ∫du/dy. v dΩ
+ conv = bilinear_params["b_x"] * conv_x + bilinear_params["b_y"] * conv_y
+
+ # reaction term
+ # ∫c.u.v dΩ
+ reaction = bilinear_params["c"] * tf.transpose(tf.linalg.matvec(test_shape_val_mat, pred_nn))
+
+ residual_matrix = (pde_diffusion + conv + reaction) - forcing_function
+
+ # Perform Reduce mean along the axis 0
+ residual_cells = tf.reduce_mean(tf.square(residual_matrix), axis=0)
+
+ return residual_cells
+
+
+"""
+This function is implemntation of our efficient tensor-based loss calculation for Helmholtz equation with inverse problem (Domain)
+Author: Thivin Anandh D
+Date: 21-Sep-2023
+History: Initial implementation
+Refer: https://arxiv.org/abs/2404.12063
+"""
+
+import tensorflow as tf
+
+
+# PDE loss function for the poisson problem
+
+[docs]
+@tf.function
+def pde_loss_helmholtz(
+ test_shape_val_mat,
+ test_grad_x_mat,
+ test_grad_y_mat,
+ pred_nn,
+ pred_grad_x_nn,
+ pred_grad_y_nn,
+ forcing_function,
+ bilinear_params,
+): # pragma: no cover
+ """
+ Calculates and returns the loss for the helmholtz problem
+
+ :param test_shape_val_mat: The test shape value matrix.
+ :type test_shape_val_mat: tf.Tensor
+ :param test_grad_x_mat: The x-gradient of the test matrix.
+ :type test_grad_x_mat: tf.Tensor
+ :param test_grad_y_mat: The y-gradient of the test matrix.
+ :type test_grad_y_mat: tf.Tensor
+ :param pred_nn: The predicted neural network output.
+ :type pred_nn: tf.Tensor
+ :param pred_grad_x_nn: The x-gradient of the predicted neural network output.
+ :type pred_grad_x_nn: tf.Tensor
+ :param pred_grad_y_nn: The y-gradient of the predicted neural network output.
+ :type pred_grad_y_nn: tf.Tensor
+ :param forcing_function: The forcing function used in the PDE.
+ :type forcing_function: function
+ :param bilinear_params: The parameters for the bilinear form.
+ :type bilinear_params: list
+
+
+ :return: The calculated loss.
+ :rtype: tf.Tensor
+ """
+ # ∫ (du/dx. dv/dx ) dΩ
+ pde_diffusion_x = tf.transpose(tf.linalg.matvec(test_grad_x_mat, pred_grad_x_nn))
+
+ # ∫ (du/dy. dv/dy ) dΩ
+ pde_diffusion_y = tf.transpose(tf.linalg.matvec(test_grad_y_mat, pred_grad_y_nn))
+
+ # eps * ∫ (du/dx. dv/dx + du/dy. dv/dy) dΩ
+ pde_diffusion = bilinear_params["eps"] * (pde_diffusion_x + pde_diffusion_y)
+
+ # \int(k^2 (u).v) dw
+ helmholtz_additional = (bilinear_params["k"] ** 2) * tf.transpose(
+ tf.linalg.matvec(test_shape_val_mat, pred_nn)
+ )
+
+ residual_matrix = -1.0 * (pde_diffusion) + helmholtz_additional - forcing_function
+
+ residual_cells = tf.reduce_mean(tf.square(residual_matrix), axis=0)
+
+ return residual_cells
+
+
+"""
+This function is implemntation of our efficient tensor-based loss calculation for poisson equation
+Author: Thivin Anandh D
+Date: 21-Sep-2023
+History: Initial implementation
+Refer: https://arxiv.org/abs/2404.12063
+"""
+
+import tensorflow as tf
+
+
+# PDE loss function for the poisson problem
+
+[docs]
+@tf.function
+def pde_loss_poisson(
+ test_shape_val_mat,
+ test_grad_x_mat,
+ test_grad_y_mat,
+ pred_nn,
+ pred_grad_x_nn,
+ pred_grad_y_nn,
+ forcing_function,
+ bilinear_params,
+): # pragma: no cover
+ """
+ This method returns the loss for the Poisson Problem of the PDE
+ """
+ # ∫du/dx. dv/dx dΩ
+ pde_diffusion_x = tf.transpose(tf.linalg.matvec(test_grad_x_mat, pred_grad_x_nn))
+
+ # ∫du/dy. dv/dy dΩ
+ pde_diffusion_y = tf.transpose(tf.linalg.matvec(test_grad_y_mat, pred_grad_y_nn))
+
+ # eps * ∫ (du/dx. dv/dx + du/dy. dv/dy) dΩ
+ pde_diffusion = bilinear_params["eps"] * (pde_diffusion_x + pde_diffusion_y)
+
+ residual_matrix = pde_diffusion - forcing_function
+
+ residual_cells = tf.reduce_mean(tf.square(residual_matrix), axis=0)
+
+ return residual_cells
+
+
+"""
+This function is implemntation of our efficient tensor-based loss calculation for poisson equation with inverse problem (constant)
+Author: Thivin Anandh D
+Date: 21-Sep-2023
+History: Initial implementation
+Refer: https://arxiv.org/abs/2404.12063
+"""
+
+import tensorflow as tf
+
+
+# PDE loss function for the poisson problem inverse
+# @tf.function - Commented due to compatibility issues
+
+[docs]
+def pde_loss_poisson_inverse(
+ test_shape_val_mat,
+ test_grad_x_mat,
+ test_grad_y_mat,
+ pred_nn,
+ pred_grad_x_nn,
+ pred_grad_y_nn,
+ forcing_function,
+ bilinear_params,
+ inverse_params_dict,
+): # pragma: no cover
+ """
+ Calculates and returns the loss for the Poisson problem Inverse (constant)
+
+ :param test_shape_val_mat: The test shape value matrix.
+ :type test_shape_val_mat: tf.Tensor
+ :param test_grad_x_mat: The x-gradient of the test matrix.
+ :type test_grad_x_mat: tf.Tensor
+ :param test_grad_y_mat: The y-gradient of the test matrix.
+ :type test_grad_y_mat: tf.Tensor
+ :param pred_nn: The predicted neural network output.
+ :type pred_nn: tf.Tensor
+ :param pred_grad_x_nn: The x-gradient of the predicted neural network output.
+ :type pred_grad_x_nn: tf.Tensor
+ :param pred_grad_y_nn: The y-gradient of the predicted neural network output.
+ :type pred_grad_y_nn: tf.Tensor
+ :param forcing_function: The forcing function used in the PDE.
+ :type forcing_function: function
+ :param bilinear_params_dict: The dictionary containing the bilinear parameters.
+ :type bilinear_params_dict: dict
+ :param inverse_param_dict: The dictionary containing the parameters for the inverse problem.
+ :type inverse_param_dict: dict
+
+ :return: The calculated loss.
+ :rtype: tf.Tensor
+ """
+ # ∫du/dx. dv/dx dΩ
+ pde_diffusion_x = tf.transpose(tf.linalg.matvec(test_grad_x_mat, pred_grad_x_nn))
+
+ # ∫du/dy. dv/dy dΩ
+ pde_diffusion_y = tf.transpose(tf.linalg.matvec(test_grad_y_mat, pred_grad_y_nn))
+
+ # eps * ∫ (du/dx. dv/dx + du/dy. dv/dy) dΩ
+ pde_diffusion = inverse_params_dict["eps"] * (pde_diffusion_x + pde_diffusion_y)
+
+ residual_matrix = pde_diffusion - forcing_function
+
+ residual_cells = tf.reduce_mean(tf.square(residual_matrix), axis=0)
+
+ return residual_cells
+
+
+"""
+filename: compute_utils.py
+description: This file contains the utility functions for
+ computing the errors between the exact and
+ predicted solutions
+author: Thivin Anandh D
+date: 02/11/2023
+changelog: 02/11/2023 - file created
+ 02/11/2023 - added functions to compute L1, L2, L_inf errors
+
+known_issues: None
+"""
+
+# Importing the required libraries
+import numpy as np
+
+
+
+[docs]
+def compute_l2_error(u_exact, u_approx):
+ """This function will compute the L2 error between the exact solution and the approximate solution.
+ The L2 error is defined as:
+
+ ..math::
+ \\sqrt{\\frac{1}{N} \\sum_{i=1}^{N} (u_{exact} - u_{approx})^2}
+
+ :param u_exact: numpy array containing the exact solution
+ :type u_exact: numpy.ndarray
+ :param u_approx: numpy array containing the approximate solution
+ :type u_approx: numpy.ndarray
+ :return: L2 error between the exact and approximate solutions
+ :rtype: float
+ """
+ # Flatten the arrays
+ u_exact = u_exact.flatten()
+ u_approx = u_approx.flatten()
+
+ # compute the L2 error
+ l2_error = np.sqrt(np.mean(np.square(u_exact - u_approx)))
+ return l2_error
+
+
+
+
+[docs]
+def compute_l1_error(u_exact, u_approx):
+ """This function will compute the L1 error between the exact solution and the approximate solution.
+ The L1 error is defined as:
+ ..math::
+ \\frac{1}{N} \\sum_{i=1}^{N} |u_{exact} - u_{approx}|
+ :param u_exact: numpy array containing the exact solution
+ :type u_exact: numpy.ndarray
+ :param u_approx: numpy array containing the approximate solution
+ :type u_approx: numpy.ndarray
+ :return: L1 error between the exact and approximate solutions
+ :rtype: float
+ """
+ # Flatten the arrays
+ u_exact = u_exact.flatten()
+ u_approx = u_approx.flatten()
+ # compute the L1 error
+ l1_error = np.mean(np.abs(u_exact - u_approx))
+ return l1_error
+
+
+
+
+[docs]
+def compute_linf_error(u_exact, u_approx):
+ """This function will compute the L_inf error between the exact solution and the approximate solution.
+ The L_inf error is defined as
+ ..math::
+ \\max_{i=1}^{N} |u_{exact} - u_{approx}|
+ :param u_exact: numpy array containing the exact solution
+ :type u_exact: numpy.ndarray
+ :param u_approx: numpy array containing the approximate solution
+ :type u_approx: numpy.ndarray
+ :return: L_inf error between the exact and approximate solutions
+ :rtype: float
+ """
+ # flatten the arrays
+ u_exact = u_exact.flatten()
+ u_approx = u_approx.flatten()
+
+ # compute the L_inf error
+ linf_error = np.max(np.abs(u_exact - u_approx))
+ return linf_error
+
+
+
+
+[docs]
+def compute_l2_error_relative(u_exact, u_approx):
+ """This function will compute the relative L2 error between the exact solution and the approximate solution.
+ The relative L2 error is defined as:
+ ..math::
+ \\frac{\\sqrt{\\frac{1}{N} \\sum_{i=1}^{N} (u_{exact} - u_{approx})^2}}{\\sqrt{\\frac{1}{N} \\sum_{i=1}^{N} u_{exact}^2}}
+ :param u_exact: numpy array containing the exact solution
+ :type u_exact: numpy.ndarray
+ :param u_approx: numpy array containing the approximate solution
+ :type u_approx: numpy.ndarray
+ :return: relative L2 error between the exact and approximate solutions
+ :rtype: float
+ """
+ # flatten the arrays
+ u_exact = u_exact.flatten()
+ u_approx = u_approx.flatten()
+
+ # compute the L2 error
+ l2_error = compute_l2_error(u_exact, u_approx)
+ # compute the relative L2 error
+ l2_error_relative = l2_error / np.sqrt(np.mean(np.square(u_exact)))
+ return l2_error_relative
+
+
+
+
+[docs]
+def compute_linf_error_relative(u_exact, u_approx):
+ """This function will compute the relative L_inf error between the exact solution and the approximate solution.
+ The relative L_inf error is defined as:
+ ..math::
+ \\frac{\\max_{i=1}^{N} |u_{exact} - u_{approx}|}{\\max_{i=1}^{N} |u_{exact}|}
+ :param u_exact: numpy array containing the exact solution
+ :type u_exact: numpy.ndarray
+ :param u_approx: numpy array containing the approximate solution
+ :type u_approx: numpy.ndarray
+ :return: relative L_inf error between the exact and approximate solutions
+ :rtype: float
+ """
+ # flatten the arrays
+ u_exact = u_exact.flatten()
+ u_approx = u_approx.flatten()
+
+ # compute the L_inf error
+ linf_error = compute_linf_error(u_exact, u_approx)
+ # compute the relative L_inf error
+ linf_error_relative = linf_error / np.max(np.abs(u_exact))
+ return linf_error_relative
+
+
+
+
+[docs]
+def compute_l1_error_relative(u_exact, u_approx):
+ """This function will compute the relative L1 error between the exact solution and the approximate solution.
+ The relative L1 error is defined as:
+ ..math::
+ \\frac{\\frac{1}{N} \\sum_{i=1}^{N} |u_{exact} - u_{approx}|}{\\frac{1}{N} \\sum_{i=1}^{N} |u_{exact}|}
+ :param u_exact: numpy array containing the exact solution
+ :type u_exact: numpy.ndarray
+ :param u_approx: numpy array containing the approximate solution
+ :type u_approx: numpy.ndarray
+ :return: relative L1 error between the exact and approximate solutions
+ :rtype: float
+ """
+ # flatten the arrays
+ u_exact = u_exact.flatten()
+ u_approx = u_approx.flatten()
+
+ # compute the L2 error
+ l1_error = compute_l1_error(u_exact, u_approx)
+ # compute the relative l1 error
+ l1_error_relative = l1_error / np.mean(np.abs(u_exact))
+ return l1_error_relative
+
+
+
+
+[docs]
+def compute_errors_combined(u_exact, u_approx):
+ """This function will compute the L1, L2 and L_inf absolute and relative errors.
+ :param u_exact: numpy array containing the exact solution
+ :type u_exact: numpy.ndarray
+ :param u_approx: numpy array containing the approximate solution
+ :type u_approx: numpy.ndarray
+ :return: L1, L2 and L_inf absolute and relative errors
+ :rtype: tuple
+
+ """
+ # flatten the arrays
+ u_exact = u_exact.flatten()
+ u_approx = u_approx.flatten()
+
+ # compute the L2 error
+ l2_error = compute_l2_error(u_exact, u_approx)
+ # compute the L_inf error
+ linf_error = compute_linf_error(u_exact, u_approx)
+ # compute the relative L2 error
+ l2_error_relative = compute_l2_error_relative(u_exact, u_approx)
+ # compute the relative L_inf error
+ linf_error_relative = compute_linf_error_relative(u_exact, u_approx)
+
+ # compute L1 Error
+ l1_error = compute_l1_error(u_exact, u_approx)
+
+ # compute the relative L1 error
+ l1_error_relative = compute_l1_error_relative(u_exact, u_approx)
+
+ return (
+ l2_error,
+ linf_error,
+ l2_error_relative,
+ linf_error_relative,
+ l1_error,
+ l1_error_relative,
+ )
+
+
+"""
+filename: plot_utils.py
+description: This file contains the utility functions for
+ plotting the loss functions and the predicted inverse parameters
+
+author: Thivin Anandh D
+date: 02/11/2023
+changelog: 02/11/2023 - file created
+ 02/11/2023 - added functions to plot the loss functions and the predicted
+ inverse parameters
+
+known_issues: None
+"""
+
+import matplotlib.pyplot as plt
+from cycler import cycler
+import numpy as np
+
+
+plt.rcParams["xtick.labelsize"] = 20
+plt.rcParams["axes.titlesize"] = 20
+plt.rcParams["axes.labelsize"] = 20
+
+plt.rcParams["legend.fontsize"] = 20
+plt.rcParams["ytick.labelsize"] = 20
+plt.rcParams["axes.prop_cycle"] = cycler(
+ color=[
+ "darkblue",
+ "#d62728",
+ "#2ca02c",
+ "#ff7f0e",
+ "#bcbd22",
+ "#8c564b",
+ "#17becf",
+ "#9467bd",
+ "#e377c2",
+ "#7f7f7f",
+ ]
+)
+
+
+# plot the loss function
+
+[docs]
+def plot_loss_function(loss_function, output_path):
+ """This function will plot the loss function.
+ :param loss_function: list of loss values
+ :type loss_function: list
+ :param output_path: path to save the plot
+ :type output_path: str
+ :return: None
+ :rtype: None
+ """
+ # plot the loss function
+ plt.figure(figsize=(6.4, 4.8))
+ plt.plot(loss_function)
+ # plot y axis in log scale
+ plt.yscale("log")
+ plt.xlabel("Epochs")
+ plt.ylabel("Loss")
+ plt.title("Loss Function")
+ plt.tight_layout()
+ plt.grid()
+ plt.savefig(output_path + "/loss_function.png", dpi=300)
+
+ plt.close()
+
+
+
+
+[docs]
+def plot_array(array, output_path, filename, title, x_label="Epochs", y_label="Loss"):
+ """This function will plot the loss function.
+ :param array: list of loss values
+ :type array: list
+ :param output_path: path to save the plot
+ :type output_path: str
+ :param filename: filename to save the plot
+ :type filename: str
+ :param title: title of the plot
+ :type title: str
+ :param x_label: x-axis label, defaults to "Epochs"
+ :type x_label: str, optional
+ :param y_label: y-axis label, defaults to "Loss"
+ :type y_label: str, optional
+ :return: None
+ :rtype: None
+ """
+ # plot the loss function
+ plt.figure(figsize=(6.4, 4.8))
+ plt.plot(array)
+ # plot y axis in log scale
+ plt.yscale("log")
+ plt.xlabel(x_label)
+ plt.ylabel(y_label)
+ plt.title(title)
+ plt.tight_layout()
+ plt.grid()
+ plt.savefig(output_path + f"/{filename}.png", dpi=300)
+ plt.close()
+
+
+
+# general utility to plot multiple parameters
+
+[docs]
+def plot_multiple_loss_function(
+ loss_function_list, output_path, filename, legend_labels, y_label, title, x_label="Epochs"
+):
+ """This function will plot the loss function in log scale for multiple parameters.
+ :param loss_function_list: list of loss values for multiple parameters
+ :type loss_function_list: list
+ :param output_path: path to save the plot
+ :type output_path: str
+ :param filename: filename to save the plot
+ :type filename: str
+ :param legend_labels: list of legend labels
+ :type legend_labels: list
+ :param y_label: y-axis label
+ :type y_label: str
+ :param title: title of the plot
+ :type title: str
+ :param x_label: x-axis label, defaults to "Epochs"
+ :type x_label: str, optional
+ :return: None
+ :rtype: None
+ """
+
+ # plot the loss function
+ plt.figure(figsize=(6.4, 4.8))
+ for loss_function, label in zip(loss_function_list, legend_labels):
+ plt.plot(loss_function, label=label)
+
+ # plot y axis in log scale
+ plt.yscale("log")
+ plt.xlabel(x_label)
+ plt.ylabel(y_label)
+ plt.title(title)
+ plt.tight_layout()
+ plt.legend()
+ plt.grid()
+ plt.savefig(output_path + f"/{filename}.png", dpi=300)
+ plt.close()
+
+
+
+# plot the loss function
+
+[docs]
+def plot_inverse_test_loss_function(loss_function, output_path):
+ """This function will plot the test loss function of the inverse parameter.
+ :param loss_function: list of loss values
+ :type loss_function: list
+ :param output_path: path to save the plot
+ :type output_path: str
+ :return: None
+ :rtype: None
+ """
+ # plot the loss function
+ plt.figure(figsize=(6.4, 4.8))
+ plt.plot(loss_function)
+ # plot y axis in log scale
+ plt.yscale("log")
+ plt.xlabel("Epochs")
+ plt.ylabel("Loss")
+ plt.title("Loss Function")
+ plt.tight_layout()
+ plt.savefig(output_path + "/test_inverse_loss_function.png", dpi=300)
+ plt.close()
+
+
+
+
+[docs]
+def plot_test_loss_function(loss_function, output_path, fileprefix=""):
+ """This function will plot the test loss function.
+ :param loss_function: list of loss values
+ :type loss_function: list
+ :param output_path: path to save the plot
+ :type output_path: str
+ :param fileprefix: prefix for the filename, defaults to ""
+ :type fileprefix: str, optional
+ :return: None
+ :rtype: None
+ """
+ # plot the loss function
+ plt.figure(figsize=(6.4, 4.8))
+ plt.plot(loss_function)
+ # plot y axis in log scale
+ plt.yscale("log")
+ plt.xlabel("Epochs")
+ plt.ylabel("Loss")
+ plt.title("Loss Function")
+ plt.tight_layout()
+ if fileprefix == "":
+ plt.savefig(output_path + "/test_loss_function.png", dpi=300)
+ else:
+ plt.savefig(output_path + "/" + fileprefix + "_test_loss_function.png", dpi=300)
+ plt.close()
+
+
+
+
+[docs]
+def plot_test_time_loss_function(time_array, loss_function, output_path):
+ """This function will plot the test loss as a function of time in seconds.
+ :param time_array: array of time values
+ :type time_array: numpy.ndarray
+ :param loss_function: list of loss values
+ :type loss_function: list
+ :param output_path: path to save the plot
+ :type output_path: str
+ :return: None
+ :rtype: None
+ """
+ # plot the loss function
+ plt.figure(figsize=(6.4, 4.8))
+ plt.plot(time_array, loss_function)
+ # plot y axis in log scale
+ plt.yscale("log")
+ plt.xscale("log")
+ plt.xlabel("Time [s]")
+ plt.ylabel("MAE Loss")
+ plt.title("Loss Function")
+ plt.tight_layout()
+ plt.savefig(output_path + "/test_time_loss_function.png", dpi=300)
+ plt.close()
+
+
+
+
+[docs]
+def plot_contour(x, y, z, output_path, filename, title):
+ """This function will plot the contour plot.
+ :param x: x values
+ :type x: numpy.ndarray
+ :param y: y values
+ :type y: numpy.ndarray
+ :param z: z values
+ :type z: numpy.ndarray
+ :param output_path: path to save the plot
+ :type output_path: str
+ :param filename: filename to save the plot
+ :type filename: str
+ :param title: title of the plot
+ :type title: str
+ :return: None
+ :rtype: None
+ """
+
+ plt.figure(figsize=(6.4, 4.8))
+ plt.contourf(x, y, z, levels=100, cmap="jet")
+ plt.title(title)
+ plt.colorbar()
+ plt.savefig(output_path + "/" + filename + ".png", dpi=300)
+
+ plt.close()
+
+
+
+# plot the Inverse parameter prediction
+
+[docs]
+def plot_inverse_param_function(
+ inverse_predicted, inverse_param_name, actual_value, output_path, file_prefix
+):
+ """This function will plot the predicted inverse parameter.
+ :param inverse_predicted: list of predicted inverse parameter values
+ :type inverse_predicted: list
+ :param inverse_param_name: name of the inverse parameter
+ :type inverse_param_name: str
+ :param actual_value: actual value of the inverse parameter
+ :type actual_value: float
+ :param output_path: path to save the plot
+ :type output_path: str
+ :param file_prefix: prefix for the filename
+ :type file_prefix: str
+ :return: None
+ :rtype: None
+ """
+ # plot the loss function
+ plt.figure(figsize=(6.4, 4.8), dpi=300)
+ plt.plot(inverse_predicted, label="Predicted " + inverse_param_name)
+
+ # draw a horizontal dotted line at the actual value
+ plt.hlines(
+ actual_value,
+ 0,
+ len(inverse_predicted),
+ colors="k",
+ linestyles="dashed",
+ label="Actual " + inverse_param_name,
+ )
+
+ # plot y axis in log scale
+ # plt.yscale("log")
+ plt.xlabel("Epochs")
+ plt.ylabel(inverse_param_name)
+
+ # plt.title("Loss Function")
+ plt.tight_layout()
+ plt.legend()
+ plt.grid()
+ plt.savefig(output_path + f"/{file_prefix}.png", dpi=300)
+ plt.close()
+
+ # plot the loss of inverse parameter
+ plt.figure(figsize=(6.4, 4.8), dpi=300)
+ actual_val_array = np.ones_like(inverse_predicted) * actual_value
+ plt.plot(abs(actual_val_array - inverse_predicted))
+ plt.xlabel("Epochs")
+ plt.ylabel("Absolute Error")
+ plt.yscale("log")
+ plt.title("Absolute Error of " + inverse_param_name)
+ plt.tight_layout()
+ plt.savefig(output_path + f"/{file_prefix}_absolute_error.png", dpi=300)
+ plt.close()
+
+
+# File for Printing utilities
+# of all the cells within the given mesh
+# Author: Thivin Anandh D
+# Date: 02/Nov/2023
+
+from rich.console import Console
+from rich.table import Table
+
+
+
+[docs]
+def print_table(title, columns, col_1_values, col_2_values):
+ """This function prints a table with two columns to the console.
+ :param title: Title of the table
+ :type title: str
+ :param columns: List of column names
+ :type columns: list
+ :param col_1_values: List of values for column 1
+ :type col_1_values: list
+ :param col_2_values: List of values for column 2
+ :type col_2_values: list
+ :return: None
+ :rtype: None
+ """
+
+ # Create a console object
+ console = Console()
+
+ # Create a table with a title
+ table = Table(show_header=True, header_style="bold magenta", title=title)
+
+ # Add columns to the table
+ for column in columns:
+ table.add_column(column)
+
+ # Add rows to the table
+ for val_1, val_2 in zip(col_1_values, col_2_values):
+ # check if val_2 is a float
+ if isinstance(val_2, float):
+ # add the row to the table
+ table.add_row(val_1, f"{val_2:.4f}")
+ else:
+ table.add_row(val_1, str(val_2))
+
+ # Print the table to the console
+ console.print(table)
+
+
If you use fastvpinns for your research, please cite the following paper:
+@misc{anandh2024fastvpinns,
+ title={FastVPINNs: Tensor-Driven Acceleration of VPINNs for Complex Geometries},
+ author={Thivin Anandh and Divij Ghose and Himanshu Jain and Sashikumaar Ganesan},
+ year={2024},
+ eprint={2404.12063},
+ archivePrefix={arXiv},
+ primaryClass={cs.LG}
+}
+
We welcome contributions to this project! Here are some guidelines to help you get started:
+Open an Issue or Feature Request: Before starting your work, please open an issue or feature request. This allows us to assign the task to you and avoid duplicate work.
Fork and Create a Branch: Fork the repository and create a branch for your issue or feature. All changes should be made on this branch, not on the main branch.
Follow Coding Standards: Please use proper indentation and linting. Install pre-commit
and run it locally to format files using the black
library and check the linting score.
Write Tests: If you add new code, please also add corresponding test cases. Compute and update the test coverage by running build_release.py
.
Commit Messages: Add a proper commit message that references the issue or feature request and includes well-documented comments.
Squash Commits: Before raising a Pull Request (PR), squash your commits on the feature branch into a single commit.
Thank you for contributing!
+The build of the code is currently tested on Python 3.8, 3.9, 3.10 and 3.11. The code is CI tested on enviroinments provided by Github Actions such as ubuntu-20.04, ubuntu-latest, macos-latest,`windows-latest` , +refer to Compatibility-CI page for more details.
+It is recommended to create a virtual environment to install the package. You can create a virtual environment using the following command:
+$ python3 -m venv venv
+$ source venv/bin/activate
+
For conda users, you can create a virtual environment using the following command:
+$ conda create -n fastvpinns python=3.8
+$ conda activate fastvpinns
+
You can simply install the package using pip as follows:
+$ pip install fastvpinns
+
On ubuntu/mac systems with libGL issues caused due to matplotlib or gmsh, please run the following command to install the required dependencies.
+$ sudo apt-get install libgl1-mesa-glx
+
The official distribution is on GitHub, and you can clone the repository using
+$ git clone https://github.com/cmgcds/fastvpinns.git
+
To install the package just type:
+$ pip install -e .
+
MIT License
+Copyright (c) [2024] [Thivin Anandh, Divij Ghose, Sashikumaar Ganesan]
+Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
+FastVPINNs is currently developed by Thivin Anandh, Divij Ghose, +under supervision of Prof. Sashikumaar Ganesan at STARS LAB, Indian Institute of Science, Bangalore.
+The Authors would like to acknowkedge the support of Shell Technology Centre, India for their partial funding, We are thankful to the MHRD Grant No. STARS-1/388 (SPADE) +for partial support.
+This page contains tutorials for solving 2D Partial Differential Equations (PDEs) using the FastVPINNs library. The tutorials are organized as follows:
+This section covers the documentation of the FastVPINNs modules. The package is divided into several subpackages, each of which is documented in its own section. The main module documentation is provided below.
+FE_2D - Finite Element routines for 2D domains
Geometry - Meshing and Geometry routines
Model - Dense Neural Network Model
Physics - Physics classes for the problem
Data - Data handling routines
Utils - Utility functions
This section holds the documentation for the FE_2D module of the FastVPINNs package. The module provides the necessary classes and functions to obtain the finite element test functions and quadrature points for the 2D domain within the FastVPINNs package. This section is broadly classified into
+This section holds the documentation for the the geometry module of the FastVPINNs package. The module provides the necessary classes and functions to obtain the geometry information either from the external mesh file or from the internal mesh generation routines. This supports the generation of VTK and GMSH files for visualization purposes.
+ +This section holds the documentation for the the Model module of the FastVPINNs package. The module provides the necessary classes and functions to train a Variational physics informed neural network (VPINN) model for the given physics problem. The model contains only the information about the neural network architecture and the training process. The physics information is provided by the user in the form of a physics class. The model class is responsible for training the neural network and providing the necessary prediction functions.
+This section holds the documentation for the the Physics module of the FastVPINNs package. The module provides the necessary classes and functions to define the physics of the problem. The physics class contains the information about the governing equations and their loss calculation in the variational form.
+This section holds the documentation for the the datahandler module of the FastVPINNs package. The module provides the necessary classes and functions to handle the data for the training of the VPINN model. The datahandler class is responsible for generating and assembling the tensors into required format for the training process.
+This section holds the documentation for all the helper functions, which are responsible for plotting and console outputs.
+Bases: object
This class is to handle data for 2D problems, convert them into tensors using custom tf functions. +It is responsible for all type conversions and data handling.
+Note
+All inputs to these functions are generally numpy arrays with dtype np.float64. +So we can either maintain the same dtype or convert them to tf.float32 ( for faster computation ).
+fespace (FESpace2D) – The FESpace2D object.
domain (Domain2D) – The Domain2D object.
shape_val_mat_list (list) – List of shape function values for each cell.
grad_x_mat_list (list) – List of shape function derivatives with respect to x for each cell.
grad_y_mat_list (list) – List of shape function derivatives with respect to y for each cell.
x_pde_list (list) – List of actual coordinates of the quadrature points for each cell.
forcing_function_list (list) – List of forcing function values for each cell.
dtype (tf.DType) – The tensorflow dtype to be used for all the tensors.
Accepts a function from example file and converts all the values into tensors of the given dtype
+Parameters: +- function (function): The function from the example file which returns the bilinear parameters dictionary
+Returns: +- bilinear_params_dict (dict): The bilinear parameters dictionary with all the values converted to tensors
+function (function) – The function from the example file which returns the bilinear parameters dictionary
+The bilinear parameters dictionary with all the values converted to tensors
+dict
+This function will return the input for the Dirichlet boundary data
+input_dirichlet (tf.Tensor): The input for the Dirichlet boundary data
actual_dirichlet (tf.Tensor): The actual Dirichlet boundary data
Accepts a function from example file and converts all the values into tensors of the given dtype
+inverse_params_dict_function (function) – The function from the example file which returns the inverse parameters dictionary
+The inverse parameters dictionary with all the values converted to tensors
+dict
+Accepts a function from example file and converts all the values into tensors of the given dtype
+exact_sol (function) – The function from the example file which returns the exact solution
num_sensor_points (int) – The number of sensor points to be generated
mesh_type (str) – The type of mesh to be used for sensor data generation
file_name (str, optional) – The name of the file to be used for external mesh generation, defaults to None
The sensor points and sensor values as tensors
+tuple[tf.Tensor, tf.Tensor]
+file: quad_affine.py +description: This file defines the Quad Affine transformation of the reference element.
+++The implementation is referenced from the ParMooN project (File: QuadAffine.C).
+
authors: Thivin Anandh D +changelog: 30/Aug/2023 - Initial version +known_issues: None +dependencies: None specified.
+Bases: FETransforamtion2D
Defines the Quad Affine transformation of the reference element.
+co_ordinates (numpy.ndarray) – The coordinates of the reference element.
+Returns the Jacobian of the transformation.
+xi (float) – The xi coordinate.
eta (float) – The eta coordinate.
The Jacobian of the transformation.
+float
+Returns the derivatives of the original co-ordinates with respect to the reference co-ordinates.
+ref_gradx (numpy.ndarray) – The reference gradient in the x-direction.
ref_grady (numpy.ndarray) – The reference gradient in the y-direction.
xi (float) – The xi coordinate.
eta (float) – The eta coordinate.
The derivatives of the original co-ordinates with respect to the reference co-ordinates.
+tuple
+Returns the second derivatives (xx, xy, yy) of the original co-ordinates with respect to the reference co-ordinates.
+grad_xx_ref (numpy.ndarray) – The reference second derivative in the xx-direction.
grad_xy_ref (numpy.ndarray) – The reference second derivative in the xy-direction.
grad_yy_ref (numpy.ndarray) – The reference second derivative in the yy-direction.
xi (float) – The xi coordinate.
eta (float) – The eta coordinate.
The second derivatives (xx, xy, yy) of the original co-ordinates with respect to the reference co-ordinates.
+tuple
+file: basis_2d_QN_Jacobi.py +description: This file contains the class Basis2DQNJacobi which is used
+++to define the basis functions for a Jacobi Polynomial. +Test functions and derivatives are inferred from the work by Ehsan Kharazmi et.al +(hp-VPINNs: Variational Physics-Informed Neural Networks With Domain Decomposition) +available at https://github.com/ehsankharazmi/hp-VPINNs/
+
authors: Thivin Anandh D +changelog: 30/Aug/2023 - Initial version +known_issues: None +dependencies: Requires scipy and numpy.
+Bases: BasisFunction2D
This class defines the basis functions for a 2D Q1 element.
+Compute the x-derivatives of the test functions for a given number of test functions and x-coordinates.
+n_test (int) – Number of test functions.
x (array_like) – x-coordinates at which to evaluate the test functions.
Values of the x-derivatives of the test functions.
+array_like
+Evaluate the k-th derivative of the Jacobi polynomial of degree n with parameters a and b at the given points x.
+n (int) – Degree of the Jacobi polynomial.
a (float) – First parameter of the Jacobi polynomial.
b (float) – Second parameter of the Jacobi polynomial.
x (array_like) – Points at which to evaluate the Jacobi polynomial.
k (int) – Order of the derivative.
Values of the k-th derivative of the Jacobi polynomial at the given points x.
+array_like
+ValueError – If the derivative order is not 1 or 2.
ImportError – If the required module ‘jacobi’ is not found.
Exception – If an unknown error occurs during the computation.
Compute the x-derivatives of the test functions for a given number of test functions and x-coordinates.
+n_test (int) – Number of test functions.
x (array_like) – x-coordinates at which to evaluate the test functions.
Values of the x-derivatives of the test functions.
+array_like
+This method returns the x-derivatives of the basis functions at the given (xi, eta) coordinates.
+xi (array_like) – x-coordinates at which to evaluate the basis functions.
eta (array_like) – y-coordinates at which to evaluate the basis functions.
Values of the x-derivatives of the basis functions.
+array_like
+This method returns the xx-derivatives of the basis functions at the given (xi, eta) coordinates.
+xi (array_like) – x-coordinates at which to evaluate the basis functions.
eta (array_like) – y-coordinates at which to evaluate the basis functions.
Values of the xx-derivatives of the basis functions.
+array_like
+This method returns the xy-derivatives of the basis functions at the given (xi, eta) coordinates.
+xi (array_like) – x-coordinates at which to evaluate the basis functions.
eta (array_like) – y-coordinates at which to evaluate the basis functions.
Values of the xy-derivatives of the basis functions.
+array_like
+This method returns the y-derivatives of the basis functions at the given (xi, eta) coordinates.
+xi (array_like) – x-coordinates at which to evaluate the basis functions.
eta (array_like) – y-coordinates at which to evaluate the basis functions.
Values of the y-derivatives of the basis functions.
+array_like
+This method returns the yy-derivatives of the basis functions at the given (xi, eta) coordinates.
+xi (array_like) – x-coordinates at which to evaluate the basis functions.
eta (array_like) – y-coordinates at which to evaluate the basis functions.
Values of the yy-derivatives of the basis functions.
+array_like
+Evaluate the Jacobi polynomial of degree n with parameters a and b at the given points x.
+n (int) – Degree of the Jacobi polynomial.
a (float) – First parameter of the Jacobi polynomial.
b (float) – Second parameter of the Jacobi polynomial.
x (array_like) – Points at which to evaluate the Jacobi polynomial.
Values of the Jacobi polynomial at the given points x.
+array_like
+Compute the x-component of the test functions for a given number of test functions and x-coordinates.
+n_test (int) – Number of test functions.
x (array_like) – x-coordinates at which to evaluate the test functions.
Values of the x-component of the test functions.
+array_like
+Compute the y-component of the test functions for a given number of test functions and y-coordinates.
+n_test (int) – Number of test functions.
y (array_like) – y-coordinates at which to evaluate the test functions.
Values of the y-component of the test functions.
+array_like
+This method returns the values of the basis functions at the given (xi, eta) coordinates.
+xi (array_like) – x-coordinates at which to evaluate the basis functions.
eta (array_like) – y-coordinates at which to evaluate the basis functions.
Values of the basis functions.
+array_like
+file: quad_bilinear.py +description: This file defines the Quad Affine transformation of the reference element.
+++The implementation is referenced from the ParMooN project (File: QuadBilineare.C).
+
authors: Thivin Anandh D +changelog: 30/Aug/2023 - Initial version +known_issues: Second derivative Calculations are not implemented as of now. +dependencies: None specified.
+Bases: FETransforamtion2D
Defines the Quad Bilinear transformation of the reference element.
+co_ordinates (numpy.ndarray) – The coordinates of the reference element.
+This method returns the Jacobian of the transformation.
+xi (float) – The xi coordinate in the reference element.
eta (float) – The eta coordinate in the reference element.
The Jacobian of the transformation at the given reference co-ordinates.
+float
+This method returns the derivatives of the original co-ordinates with respect to the reference co-ordinates.
+ref_gradx (numpy.ndarray) – The gradient of the xi coordinate in the reference element.
ref_grady (numpy.ndarray) – The gradient of the eta coordinate in the reference element.
xi (float) – The xi coordinate in the reference element.
eta (float) – The eta coordinate in the reference element.
The derivatives of the original co-ordinates [x, y] with respect to the reference co-ordinates.
+numpy.ndarray
+This method returns the second derivatives of the original co-ordinates with respect to the reference co-ordinates.
+grad_xx_ref (numpy.ndarray) – The second derivative of the xi coordinate in the reference element.
grad_xy_ref (numpy.ndarray) – The mixed second derivative of the xi and eta coordinates in the reference element.
grad_yy_ref (numpy.ndarray) – The second derivative of the eta coordinate in the reference element.
xi (float) – The xi coordinate in the reference element.
eta (float) – The eta coordinate in the reference element.
The second derivatives of the original co-ordinates [xx, xy, yy] with respect to the reference co-ordinates.
+numpy.ndarray
+This method returns the original co-ordinates from the reference co-ordinates.
+xi (float) – The xi coordinate in the reference element.
eta (float) – The eta coordinate in the reference element.
The original co-ordinates [x, y] corresponding to the given reference co-ordinates.
+numpy.ndarray
+Bases: object
This class is used to Store the FE Values, Such as Coordinates, Basis Functions, Quadrature Rules, etc. for a given cell.
+Assigns the basis function class based on the cell type and the FE order.
+An instance of the BasisFunction2D class.
+Assigns the basis function values at the quadrature points.
+This method calculates the values of the basis functions and their gradients at the quadrature points. +The basis function values are stored in self.basis_at_quad, while the gradients are stored in +self.basis_gradx_at_quad, self.basis_grady_at_quad, self.basis_gradxy_at_quad, +self.basis_gradxx_at_quad, and self.basis_gradyy_at_quad.
+The basis function values are of size N_basis_functions x N_quad_points.
+None
+Assigns the FE Transformation class based on the cell type and the FE order.
+This method assigns the appropriate FE Transformation class based on the cell type and the FE order. +It sets the cell coordinates for the FE Transformation and obtains the Jacobian of the transformation.
+None
+Assigns the forcing function values at the quadrature points.
+This function computes the values of the forcing function at the quadrature points +and assigns them to the forcing_at_quad attribute of the FE2D_Cell object.
+forcing_function (callable) – The forcing function that takes the coordinates (x, y) +as input and returns the value of the forcing function at those coordinates.
+None
+Notes
+The final shape of forcing_at_quad will be N_shape_functions x 1.
This function is for backward compatibility with old code and currently assigns +the values as zeros. The actual calculation is performed in the fespace class.
Assigns the quadrature weights and the Jacobian of the transformation.
+This method calculates and assigns the quadrature weights and the Jacobian of the transformation +for the current cell. The quadrature weights are multiplied by the flattened Jacobian array +and stored in the mult attribute of the class.
+None
+Assigns the quadrature points and weights based on the cell type and the quadrature order.
+None
+Assigns the actual coordinates of the quadrature points.
+This method calculates the actual coordinates of the quadrature points based on the given Xi and Eta values. +The Xi and Eta values are obtained from the quad_xi and quad_eta attributes of the class. +The calculated coordinates are stored in the quad_actual_coordinates attribute as a NumPy array.
+None
+Bases: object
This class is used to setup the FE2D and quadrature rule for a given cell based on the given mesh and the degree of the basis functions.
+Assigns the basis function based on the cell type and the fe_order.
+An instance of the BasisFunction2D class representing the assigned basis function.
+ValueError – If the fe_order is invalid.
+Assigns the FE transformation based on the cell type.
+fe_transformation_type (str) – The type of FE transformation.
cell_coordinates (list) – The coordinates of the cell.
The FE transformation object.
+ValueError – If the cell type or FE transformation type is invalid.
+file: fespace2d.py +description: This file contains the main class that holds the information of all the
+++Finite Element (FE) spaces of all the cells within the given mesh.
+
authors: Thivin Anandh D +changelog: 30/Aug/2023 - Initial version +known_issues: None +dependencies: None specified.
+Bases: object
Represents a finite element space in 2D.
+mesh (Mesh) – The mesh object.
cells (ndarray) – The array of cell indices.
boundary_points (dict) – The dictionary of boundary points.
cell_type (str) – The type of cell.
fe_order (int) – The order of the finite element.
fe_type (str) – The type of finite element.
quad_order (int) – The order of the quadrature.
quad_type (str) – The type of quadrature.
fe_transformation_type (str) – The type of finite element transformation.
bound_function_dict (dict) – The dictionary of boundary functions.
bound_condition_dict (dict) – The dictionary of boundary conditions.
forcing_function (function) – The forcing function.
output_path (str) – The output path.
generate_mesh_plot (bool, optional) – Whether to generate a plot of the mesh. Defaults to False.
Generate Dirichlet boundary data.
+This function returns the boundary points and their corresponding values.
+A tuple containing two arrays: +- The first array contains the boundary points as numpy arrays. +- The second array contains the values of the boundary points as numpy arrays.
+Tuple[np.ndarray, np.ndarray]
+Generate the boundary data vector for the Dirichlet boundary condition.
+This function returns the boundary points and their corresponding values for a specific component.
+component (int) – The component for which the boundary data vector is generated.
+The boundary points and their values as numpy arrays.
+tuple(numpy.ndarray, numpy.ndarray)
+Generate a plot of the mesh.
+output_path (str) – The path to save the generated plot.
+Get the forcing function values at the quadrature points.
+cell_index (int) – The index of the cell.
+The forcing function values at the quadrature points.
+np.ndarray
+ValueError – If cell_index is greater than the number of cells.
+This function computes the forcing function values at the quadrature points for a given cell. +It loops over all the basis functions and computes the integral using the actual coordinates +and the basis functions at the quadrature points. The resulting values are stored in the +forcing_at_quad attribute of the corresponding fe_cell object.
+Note: The forcing function is evaluated using the forcing_function method of the fe_cell +object.
+>>> fespace = FESpace2D()
+>>> cell_index = 0
+>>> forcing_values = fespace.get_forcing_function_values(cell_index)
+
This function will return the forcing function values at the quadrature points +based on the Component of the RHS Needed, for vector valued problems
+cell_index (int) – The index of the cell
component (int) – The component of the RHS needed
The forcing function values at the quadrature points
+np.ndarray
+ValueError – If cell_index is greater than the number of cells
+Get the actual coordinates of the quadrature points for a given cell.
+cell_index (int) – The index of the cell.
+An array containing the actual coordinates of the quadrature points.
+np.ndarray
+ValueError – If the cell_index is greater than the number of cells.
+>>> fespace = FESpace2D()
+>>> fespace.get_quadrature_actual_coordinates(0)
+array([[0.1, 0.2],
+ [0.3, 0.4],
+ [0.5, 0.6]])
+
Return the quadrature weights for a given cell.
+cell_index (int) – The index of the cell for which the quadrature weights are needed.
+The quadrature weights for the given cell of dimension (N_Quad_Points, 1).
+np.ndarray
+ValueError – If cell_index is greater than the number of cells.
+Notes
+This function returns the quadrature weights associated with a specific cell. +The quadrature weights are stored in the mult attribute of the fe_cell object.
+Example
+>>> fespace = FESpace2D()
+>>> weights = fespace.get_quadrature_weights(0)
+>>> print(weights)
+[0.1, 0.2, 0.3, 0.4]
+
cell_index (int) – The index of the cell for which the quadrature weights are needed.
+The quadrature weights for the given cell.
+np.ndarray
+ValueError – If cell_index is greater than the number of cells.
+Obtain sensor data (actual solution) at random points.
+This method is used in the inverse problem to obtain the sensor data at random points within the domain. +Currently, it only works for problems with an analytical solution. +Methodologies to obtain sensor data for problems from a file are not implemented yet. +It is also not implemented for external or complex meshes.
+exact_solution (function) – A function that computes the exact solution at a given point.
num_points (int) – The number of random points to generate.
A tuple containing the generated points and the exact solution at those points.
+tuple(numpy.ndarray, numpy.ndarray)
+This method is used to obtain the sensor data from an external file.
+exact_sol (array-like) – The exact solution values.
num_points (int) – The number of points to sample from the data.
file_name (str) – The path to the file containing the sensor data.
A tuple containing two arrays: +- points (ndarray): The sampled points from the data. +- exact_sol (ndarray): The corresponding exact solution values.
+tuple
+Get the gradient of the shape function with respect to the x-coordinate.
+cell_index (int) – The index of the cell.
+An array containing the gradient of the shape function with respect to the x-coordinate.
+np.ndarray
+ValueError – If the cell_index is greater than the number of cells.
+This function returns the actual values of the gradient of the shape function on a given cell.
+Get the gradient of the shape function with respect to the x-coordinate on the reference element.
+cell_index (int) – The index of the cell.
+An array containing the gradient of the shape function with respect to the x-coordinate.
+np.ndarray
+ValueError – If the cell_index is greater than the number of cells.
+Get the gradient of the shape function with respect to y at the given cell index.
+cell_index (int) – The index of the cell.
+The gradient of the shape function with respect to y.
+np.ndarray
+ValueError – If the cell_index is greater than the total number of cells.
+Get the gradient of the shape function with respect to y at the reference element.
+cell_index (int) – The index of the cell.
+The gradient of the shape function with respect to y at the reference element.
+np.ndarray
+ValueError – If cell_index is greater than the number of cells.
+This function returns the gradient of the shape function with respect to y at the reference element +for a given cell. The shape function gradient values are stored in the basis_grady_at_quad_ref array +of the corresponding finite element cell. The cell_index parameter specifies the index of the cell +for which the shape function gradient is required. If the cell_index is greater than the total number +of cells, a ValueError is raised.
+Note
+The returned gradient values are copied from the basis_grady_at_quad_ref array to ensure immutability.
+Get the actual values of the shape functions on a given cell.
+cell_index (int) – The index of the cell.
+An array containing the actual values of the shape functions.
+np.ndarray
+ValueError – If the cell_index is greater than the number of cells.
+Assigns the finite elements to each cell.
+This method initializes the finite element objects for each cell in the mesh. +It creates an instance of the FE2D_Cell class for each cell, passing the necessary parameters. +The finite element objects store information about the basis functions, gradients, Jacobians, +quadrature points, weights, actual coordinates, and forcing functions associated with each cell.
+After initializing the finite element objects, this method prints the shape details of various matrices +and updates the total number of degrees of freedom (dofs) for the entire mesh.
+None
+file: basis_function_2d.py +description: This file contains a wrapper class for all the finite element basis functions
+++used in the FE2D code. The 2D basis functions have methods to return the value +of the basis function and its derivatives at the reference point (xi, eta).
+
authors: Thivin Anandh D +changelog: 30/Aug/2023 - First version +known_issues: None +dependencies: None specified.
+Bases: object
Represents a basis function in 2D.
+num_shape_functions (int) – The number of shape functions.
+Computes the partial derivative of the basis function with respect to xi.
+Computes the partial derivative of the basis function with respect to eta.
+Computes the second partial derivative of the basis function with respect to xi.
+Computes the mixed partial derivative of the basis function with respect to xi and eta.
+Computes the second partial derivative of the basis function with respect to eta.
+Computes the partial derivative of the basis function with respect to xi.
+xi (float) – The xi coordinate.
eta (float) – The eta coordinate.
The partial derivative of the basis function with respect to xi.
+float
+Computes the second partial derivative of the basis function with respect to xi.
+xi (float) – The xi coordinate.
eta (float) – The eta coordinate.
The second partial derivative of the basis function with respect to xi.
+float
+Computes the mixed partial derivative of the basis function with respect to xi and eta.
+xi (float) – The xi coordinate.
eta (float) – The eta coordinate.
The mixed partial derivative of the basis function with respect to xi and eta.
+float
+Computes the partial derivative of the basis function with respect to eta.
+xi (float) – The xi coordinate.
eta (float) – The eta coordinate.
The partial derivative of the basis function with respect to eta.
+float
+Computes the second partial derivative of the basis function with respect to eta.
+xi (float) – The xi coordinate.
eta (float) – The eta coordinate.
The second partial derivative of the basis function with respect to eta.
+float
+file: quadratureformulas_quad2d.py +description: This file defines the Quadrature Formulas for the 2D Quadrilateral elements.
+++It supports both Gauss-Legendre and Gauss-Jacobi quadrature types. +The quadrature points and weights are calculated based on the specified quadrature order and type.
+
authors: Not specified +changelog: Not specified +known_issues: None +dependencies: numpy, scipy
+Bases: object
Defines the Quadrature Formulas for the 2D Quadrilateral elements.
+quad_order (int) – The order of the quadrature.
quad_type (str) – The type of the quadrature.
Bases: object
This class represents a 2D finite element transformation.
+This method returns the Jacobian of the transformation.
+xi (float) – The xi coordinate.
eta (float) – The eta coordinate.
The Jacobian matrix.
+numpy.ndarray
+This method returns the original co-ordinates from the reference co-ordinates.
+xi (float) – The xi value of the reference co-ordinates.
eta (float) – The eta value of the reference co-ordinates.
The original co-ordinates corresponding to the given reference co-ordinates.
+tuple
+This module, geometry_2d.py, contains the Geometry_2D class which defines functions to read mesh from Gmsh and +generate internal mesh for 2D problems. It supports different types of meshes and mesh generation methods. +The class also allows for specifying the number of test points in the x and y directions, and the output folder for storing results.
+Author: Thivin Anandh D +Date: 21-Sep-2023
+Bases: object
Defines functions to read mesh from Gmsh and internal mesh for 2D problems.
+mesh_type (str) – The type of mesh to be used.
mesh_generation_method (str) – The method used to generate the mesh.
n_test_points_x (int) – The number of test points in the x-direction.
n_test_points_y (int) – The number of test points in the y-direction.
output_folder (str) – The path to the output folder.
Generate and save a quadrilateral mesh with physical curves.
+x_limits (tuple) – The lower and upper limits in the x-direction (x_min, x_max).
y_limits (tuple) – The lower and upper limits in the y-direction (y_min, y_max).
n_cells_x (int) – The number of cells in the x-direction.
n_cells_y (int) – The number of cells in the y-direction.
num_boundary_points (int) – The number of boundary points.
The cell points and the dictionary of boundary points.
+tuple[numpy.ndarray, dict]
+Generates a VTK from Mesh file (External) or using gmsh (for Internal).
+None
+This function is used to extract the test points from the given mesh
+Parameters: +None
+Returns: +test_points (numpy.ndarray): The test points for the given domain
+Plots the residuals in each cell of the mesh.
+cells_list (list) – The list of cells.
area_averaged_cell_loss_list (list) – The list of area averaged cell residual (or the normal residual).
epoch (int) – The epoch number (for file name).
filename (str, optional) – The name of the output file, defaults to “cell_residual”.
None
+Reads mesh from a Gmsh .msh file and extracts cell information.
+mesh_file (str) – The path to the mesh file.
boundary_point_refinement_level (int) – The number of boundary points to be generated.
bd_sampling_method (str) – The method used to generate the boundary points.
refinement_level (int) – The number of times the mesh should be refined.
The cell points and the dictionary of boundary points.
+tuple
+Writes the data to a VTK file.
+solution (numpy.ndarray) – The solution vector.
output_path (str) – The path to the output folder.
filename (str) – The name of the output file.
data_names (list) – The list of data names in the VTK file to be written as scalars.
None
+file: model.py +description: This file hosts the Neural Network (NN) model and the training loop for variational Physics-Informed Neural Networks (PINNs).
+++The focus is on the model architecture and the training loop, and not on the loss functions.
+
authors: Thivin Anandh D +changelog: 22/Sep/2023 - Initial implementation with basic model architecture and training loop +known_issues: None +dependencies: None specified.
+Bases: Model
Defines the Dense Model for the Neural Network for solving Variational PINNs.
+layer_dims (list) – List of dimensions of the dense layers.
learning_rate_dict (dict) – The dictionary containing the learning rate parameters.
params_dict (dict) – The dictionary containing the parameters.
loss_function (function) – The loss function for the PDE.
input_tensors_list (list) – The list containing the input tensors.
orig_factor_matrices (list) – The list containing the original factor matrices.
force_function_list (tf.Tensor) – The force function matrix.
tensor_dtype (tf.DType) – The tensorflow dtype to be used for all the tensors.
use_attention (bool, optional) – Flag to use attention layer after input, defaults to False.
activation (str, optional) – The activation function to be used for the dense layers, defaults to “tanh”.
hessian (bool, optional) – Flag to use hessian loss, defaults to False.
The call method for the model.
+inputs (tf.Tensor) – The input tensor for the model.
+The output tensor of the model.
+tf.Tensor
+Get the configuration of the model.
+The configuration of the model.
+dict
+The train step method for the model.
+beta (int, optional) – The beta parameter for the training step, defaults to 10.
bilinear_params_dict (dict, optional) – The dictionary containing the bilinear parameters, defaults to None.
The output of the training step.
+varies based on implementation
+file: model_hard.py +description: This file contains the DenseModel class which is a custom model for the Neural Network
+++for solving Variational PINNs. This model is used for enforcing hard boundary constraints +on the solution.
+
author: Thivin Anandh D, Divij Ghose, Sashikumaar Ganesan +date: 22/01/2024 +changelog: 22/01/2024 - file created
+++22/01/2024 -
+
known issues: None
+Bases: Model
The DenseModel_Hard class is a custom model class that hosts the neural network model.
+The class inherits from the tf.keras.Model class and is used +to define the neural network model architecture and the training loop for FastVPINNs. +:param layer_dims: List of integers representing the number of neurons in each layer +:type layer_dims: list +:param learning_rate_dict: Dictionary containing the learning rate parameters +:type learning_rate_dict: dict +:param params_dict: Dictionary containing the parameters for the model +:type params_dict: dict +:param loss_function: Loss function for the model +:type loss_function: function +:param input_tensors_list: List of input tensors for the model +:type input_tensors_list: list +:param orig_factor_matrices: List of original factor matrices +:type orig_factor_matrices: list +:param force_function_list: List of force functions +:type force_function_list: list +:param tensor_dtype: Tensor data type +:type tensor_dtype: tf.DType +:param use_attention: Flag to use attention layer +:type use_attention: bool +:param activation: Activation function for the model +:type activation: str +:param hessian: Flag to compute hessian +:type hessian: bool
+ + + + +This method is used to define the training step of the model.
+This method is used to define the forward pass of the model. +:param inputs: Input tensor +:type inputs: tf.Tensor +:return: Output tensor from the model +:rtype: tf.Tensor
+file: model_inverse.py +description: This file hosts the Neural Network (NN) model and the training loop for variational Physics-Informed Neural Networks (PINNs).
+++This focuses on training variational PINNs for inverse problems where the inverse parameter is constant on the domain. +The focus is on the model architecture and the training loop, and not on the loss functions.
+
authors: Thivin Anandh D +changelog: 22/Sep/2023 - Initial implementation with basic model architecture and training loop +known_issues: Currently out of the box, supports only one constant inverse parameters. +dependencies: None specified.
+Bases: Model
A subclass of tf.keras.Model that defines a dense model for an inverse problem.
+layer_dims (list) – The dimensions of the layers in the model.
learning_rate_dict (dict) – A dictionary containing the learning rates.
params_dict (dict) – A dictionary containing the parameters of the model.
loss_function (function) – The loss function to be used in the model.
input_tensors_list (list) – A list of input tensors.
orig_factor_matrices (list) – The original factor matrices.
force_function_list (list) – A list of force functions.
sensor_list (list) – A list of sensors for the inverse problem.
inverse_params_dict (dict) – A dictionary containing the parameters for the inverse problem.
tensor_dtype (tf.DType) – The data type of the tensors.
use_attention (bool) – Whether to use attention mechanism in the model. Defaults to False.
activation (str) – The activation function to be used in the model. Defaults to ‘tanh’.
hessian (bool) – Whether to use Hessian in the model. Defaults to False.
Applies the model to the input data.
+inputs – The input data.
+The output of the model after applying all the layers.
+file: model_inverse_domain.py +description: This file hosts the Neural Network (NN) model and the training loop for variational Physics-Informed Neural Networks (PINNs).
+++This focuses on training variational PINNs for inverse problems where the inverse parameter is constant over the entire domain. +The focus is on the model architecture and the training loop, and not on the loss functions.
+
authors: Thivin Anandh D +changelog: 22/Sep/2023 - Initial implementation with basic model architecture and training loop +known_issues: None +dependencies: None specified.
+Bases: Model
A subclass of tf.keras.Model that defines a dense model for an inverse problem.
+layer_dims (list) – The dimensions of the layers in the model.
learning_rate_dict (dict) – A dictionary containing the learning rates.
params_dict (dict) – A dictionary containing the parameters of the model.
loss_function (function) – The loss function to be used in the model.
input_tensors_list (list) – A list of input tensors.
orig_factor_matrices (list) – The original factor matrices.
force_function_list (list) – A list of force functions.
sensor_list (list) – A list of sensors for the inverse problem.
inverse_params_dict (dict) – A dictionary containing the parameters for the inverse problem.
tensor_dtype (tf.DType) – The data type of the tensors.
use_attention (bool) – Whether to use attention mechanism in the model. Defaults to False.
activation (str) – The activation function to be used in the model. Defaults to ‘tanh’.
hessian (bool) – Whether to use Hessian in the model. Defaults to False.
The call method for the model.
+inputs (tf.Tensor) – The input tensor for the model.
+The output tensor of the model.
+tf.Tensor
+Get the configuration of the model.
+The configuration of the model.
+dict
+The train step method for the model.
+beta (int, optional) – The beta parameter for the training step, defaults to 10.
bilinear_params_dict (dict, optional) – The dictionary containing the bilinear parameters, defaults to None.
The output of the training step.
+varies based on implementation
+This function is implemntation of our efficient tensor-based loss calculation for cd2d equation +Author: Thivin Anandh D +Date: 21-Sep-2023 +History: Initial implementation +Refer: https://arxiv.org/abs/2404.12063
+Calculates and returns the loss for the CD2D problem
+test_shape_val_mat (tf.Tensor) – The test shape value matrix.
test_grad_x_mat (tf.Tensor) – The x-gradient of the test matrix.
test_grad_y_mat (tf.Tensor) – The y-gradient of the test matrix.
pred_nn (tf.Tensor) – The predicted neural network output.
pred_grad_x_nn (tf.Tensor) – The x-gradient of the predicted neural network output.
pred_grad_y_nn (tf.Tensor) – The y-gradient of the predicted neural network output.
forcing_function (function) – The forcing function used in the PDE.
bilinear_params (list) – The parameters for the bilinear form.
The calculated loss.
+tf.Tensor
+This function is implemntation of our efficient tensor-based loss calculation for cd2d equation with inverse problem (Constant) +Author: Thivin Anandh D +Date: 21-Sep-2023 +History: Initial implementation +Refer: https://arxiv.org/abs/2404.12063
+Calculates and returns the loss for the CD2D problem Inverse (constant)
+test_shape_val_mat (tf.Tensor) – The test shape value matrix.
test_grad_x_mat (tf.Tensor) – The x-gradient of the test matrix.
test_grad_y_mat (tf.Tensor) – The y-gradient of the test matrix.
pred_nn (tf.Tensor) – The predicted neural network output.
pred_grad_x_nn (tf.Tensor) – The x-gradient of the predicted neural network output.
pred_grad_y_nn (tf.Tensor) – The y-gradient of the predicted neural network output.
forcing_function (function) – The forcing function used in the PDE.
bilinear_params_dict (dict) – The dictionary containing the bilinear parameters.
inverse_param_dict (dict) – The dictionary containing the parameters for the inverse problem.
The calculated loss.
+tf.Tensor
+This function is implemntation of our efficient tensor-based loss calculation for cd2d equation with inverse problem (Domain) +Author: Thivin Anandh D +Date: 21-Sep-2023 +History: Initial implementation +Refer: https://arxiv.org/abs/2404.12063
+Calculates and returns the loss for the CD2D inverse problem (Domain)
+test_shape_val_mat (tf.Tensor) – The test shape value matrix.
test_grad_x_mat (tf.Tensor) – The x-gradient of the test matrix.
test_grad_y_mat (tf.Tensor) – The y-gradient of the test matrix.
pred_nn (tf.Tensor) – The predicted neural network output.
pred_grad_x_nn (tf.Tensor) – The x-gradient of the predicted neural network output.
pred_grad_y_nn (tf.Tensor) – The y-gradient of the predicted neural network output.
forcing_function (function) – The forcing function used in the PDE.
bilinear_params (list) – The parameters for the bilinear form.
inverse_params_list (list) – The parameters for the inverse problem.
The calculated loss.
+tf.Tensor
+This function is implemntation of our efficient tensor-based loss calculation for Helmholtz equation with inverse problem (Domain) +Author: Thivin Anandh D +Date: 21-Sep-2023 +History: Initial implementation +Refer: https://arxiv.org/abs/2404.12063
+Calculates and returns the loss for the helmholtz problem
+test_shape_val_mat (tf.Tensor) – The test shape value matrix.
test_grad_x_mat (tf.Tensor) – The x-gradient of the test matrix.
test_grad_y_mat (tf.Tensor) – The y-gradient of the test matrix.
pred_nn (tf.Tensor) – The predicted neural network output.
pred_grad_x_nn (tf.Tensor) – The x-gradient of the predicted neural network output.
pred_grad_y_nn (tf.Tensor) – The y-gradient of the predicted neural network output.
forcing_function (function) – The forcing function used in the PDE.
bilinear_params (list) – The parameters for the bilinear form.
The calculated loss.
+tf.Tensor
+This function is implemntation of our efficient tensor-based loss calculation for poisson equation +Author: Thivin Anandh D +Date: 21-Sep-2023 +History: Initial implementation +Refer: https://arxiv.org/abs/2404.12063
+ + +This function is implemntation of our efficient tensor-based loss calculation for poisson equation with inverse problem (constant) +Author: Thivin Anandh D +Date: 21-Sep-2023 +History: Initial implementation +Refer: https://arxiv.org/abs/2404.12063
+Calculates and returns the loss for the Poisson problem Inverse (constant)
+test_shape_val_mat (tf.Tensor) – The test shape value matrix.
test_grad_x_mat (tf.Tensor) – The x-gradient of the test matrix.
test_grad_y_mat (tf.Tensor) – The y-gradient of the test matrix.
pred_nn (tf.Tensor) – The predicted neural network output.
pred_grad_x_nn (tf.Tensor) – The x-gradient of the predicted neural network output.
pred_grad_y_nn (tf.Tensor) – The y-gradient of the predicted neural network output.
forcing_function (function) – The forcing function used in the PDE.
bilinear_params_dict (dict) – The dictionary containing the bilinear parameters.
inverse_param_dict (dict) – The dictionary containing the parameters for the inverse problem.
The calculated loss.
+tf.Tensor
+filename: compute_utils.py +description: This file contains the utility functions for
+++computing the errors between the exact and +predicted solutions
+
author: Thivin Anandh D +date: 02/11/2023 +changelog: 02/11/2023 - file created
+++02/11/2023 - added functions to compute L1, L2, L_inf errors
+
known_issues: None
+This function will compute the L1, L2 and L_inf absolute and relative errors. +:param u_exact: numpy array containing the exact solution +:type u_exact: numpy.ndarray +:param u_approx: numpy array containing the approximate solution +:type u_approx: numpy.ndarray +:return: L1, L2 and L_inf absolute and relative errors +:rtype: tuple
+This function will compute the L1 error between the exact solution and the approximate solution. +The L1 error is defined as: +..math:
+\frac{1}{N} \sum_{i=1}^{N} |u_{exact} - u_{approx}|
+
u_exact (numpy.ndarray) – numpy array containing the exact solution
u_approx (numpy.ndarray) – numpy array containing the approximate solution
L1 error between the exact and approximate solutions
+float
+This function will compute the relative L1 error between the exact solution and the approximate solution. +The relative L1 error is defined as:
++++
+- ..math::
- +
frac{frac{1}{N} sum_{i=1}^{N} |u_{exact} - u_{approx}|}{frac{1}{N} sum_{i=1}^{N} |u_{exact}|}
+
u_exact (numpy.ndarray) – numpy array containing the exact solution
u_approx (numpy.ndarray) – numpy array containing the approximate solution
relative L1 error between the exact and approximate solutions
+float
+This function will compute the L2 error between the exact solution and the approximate solution. +The L2 error is defined as:
+sqrt{frac{1}{N} sum_{i=1}^{N} (u_{exact} - u_{approx})^2}
+u_exact (numpy.ndarray) – numpy array containing the exact solution
u_approx (numpy.ndarray) – numpy array containing the approximate solution
L2 error between the exact and approximate solutions
+float
+This function will compute the relative L2 error between the exact solution and the approximate solution. +The relative L2 error is defined as:
++++
+- ..math::
- +
frac{sqrt{frac{1}{N} sum_{i=1}^{N} (u_{exact} - u_{approx})^2}}{sqrt{frac{1}{N} sum_{i=1}^{N} u_{exact}^2}}
+
u_exact (numpy.ndarray) – numpy array containing the exact solution
u_approx (numpy.ndarray) – numpy array containing the approximate solution
relative L2 error between the exact and approximate solutions
+float
+This function will compute the L_inf error between the exact solution and the approximate solution. +The L_inf error is defined as
++++
+- ..math::
- +
max_{i=1}^{N} |u_{exact} - u_{approx}|
+
u_exact (numpy.ndarray) – numpy array containing the exact solution
u_approx (numpy.ndarray) – numpy array containing the approximate solution
L_inf error between the exact and approximate solutions
+float
+This function will compute the relative L_inf error between the exact solution and the approximate solution. +The relative L_inf error is defined as:
++++
+- ..math::
- +
frac{max_{i=1}^{N} |u_{exact} - u_{approx}|}{max_{i=1}^{N} |u_{exact}|}
+
u_exact (numpy.ndarray) – numpy array containing the exact solution
u_approx (numpy.ndarray) – numpy array containing the approximate solution
relative L_inf error between the exact and approximate solutions
+float
+filename: plot_utils.py +description: This file contains the utility functions for
+++plotting the loss functions and the predicted inverse parameters
+
author: Thivin Anandh D +date: 02/11/2023 +changelog: 02/11/2023 - file created
++++
+- 02/11/2023 - added functions to plot the loss functions and the predicted
- +
inverse parameters
+
known_issues: None
+This function will plot the loss function. +:param array: list of loss values +:type array: list +:param output_path: path to save the plot +:type output_path: str +:param filename: filename to save the plot +:type filename: str +:param title: title of the plot +:type title: str +:param x_label: x-axis label, defaults to “Epochs” +:type x_label: str, optional +:param y_label: y-axis label, defaults to “Loss” +:type y_label: str, optional +:return: None +:rtype: None
+This function will plot the contour plot. +:param x: x values +:type x: numpy.ndarray +:param y: y values +:type y: numpy.ndarray +:param z: z values +:type z: numpy.ndarray +:param output_path: path to save the plot +:type output_path: str +:param filename: filename to save the plot +:type filename: str +:param title: title of the plot +:type title: str +:return: None +:rtype: None
+This function will plot the predicted inverse parameter. +:param inverse_predicted: list of predicted inverse parameter values +:type inverse_predicted: list +:param inverse_param_name: name of the inverse parameter +:type inverse_param_name: str +:param actual_value: actual value of the inverse parameter +:type actual_value: float +:param output_path: path to save the plot +:type output_path: str +:param file_prefix: prefix for the filename +:type file_prefix: str +:return: None +:rtype: None
+This function will plot the test loss function of the inverse parameter. +:param loss_function: list of loss values +:type loss_function: list +:param output_path: path to save the plot +:type output_path: str +:return: None +:rtype: None
+This function will plot the loss function. +:param loss_function: list of loss values +:type loss_function: list +:param output_path: path to save the plot +:type output_path: str +:return: None +:rtype: None
+This function will plot the loss function in log scale for multiple parameters. +:param loss_function_list: list of loss values for multiple parameters +:type loss_function_list: list +:param output_path: path to save the plot +:type output_path: str +:param filename: filename to save the plot +:type filename: str +:param legend_labels: list of legend labels +:type legend_labels: list +:param y_label: y-axis label +:type y_label: str +:param title: title of the plot +:type title: str +:param x_label: x-axis label, defaults to “Epochs” +:type x_label: str, optional +:return: None +:rtype: None
+This function will plot the test loss function. +:param loss_function: list of loss values +:type loss_function: list +:param output_path: path to save the plot +:type output_path: str +:param fileprefix: prefix for the filename, defaults to “” +:type fileprefix: str, optional +:return: None +:rtype: None
+This function will plot the test loss as a function of time in seconds. +:param time_array: array of time values +:type time_array: numpy.ndarray +:param loss_function: list of loss values +:type loss_function: list +:param output_path: path to save the plot +:type output_path: str +:return: None +:rtype: None
+This function prints a table with two columns to the console. +:param title: Title of the table +:type title: str +:param columns: List of column names +:type columns: list +:param col_1_values: List of values for column 1 +:type col_1_values: list +:param col_2_values: List of values for column 2 +:type col_2_values: list +:return: None +:rtype: None
+This example demonstrates how to solve a Poisson equation in 2D on a circular domain using the fastvpinns
package.
+All the necesary files can be found in the examples folder of the fastvpinns GitHub repository
The Poisson equation is given by
+where \(\Omega\) is the circular domain and \(f\) is the source term. The boundary conditions are given by
+For this problem, the parameters are
+The exact solution is given by
+Note : The forcing formulation for this problem is obtained by +substituting the exact solution into the cd2d equation with given pde +parameters.
+The computational domain is a circular domain with radius 1 centered at +(0, 0).
+ +To run the code, execute the following command:
+python3 main_cd2d.py input.yaml
+
This file hosts all the details about the bilinear parameters for the +PDE, boundary conditions, source term, and the exact solution.
+The function circle_boundary
returns the boundary value for a given
+component of the boundary. The function get_boundary_function_dict
+returns a dictionary of boundary functions. The key of the dictionary is
+the boundary id and the value is the boundary function. The function
+get_bound_cond_dict
returns a dictionary of boundary conditions. The
+key of the dictionary is the boundary id and the value is the boundary
+condition.
Note : As of now, only Dirichlet boundary conditions are supported.
+def circle_boundary(x, y):
+ """
+ This function will return the boundary value for given component of a boundary
+ """
+ return 0.0
+
+def get_boundary_function_dict():
+ """
+ This function will return a dictionary of boundary functions
+ """
+ return {1000: circle_boundary}
+
+
+def get_bound_cond_dict():
+ """
+ This function will return a dictionary of boundary conditions
+ """
+ return {1000: "dirichlet"}
+
The function rhs
returns the value of the source term at a given
+point.
def rhs(x, y):
+ """
+ This function will return the value of the rhs at a given point
+ """
+ f_temp = 32 * (x * (1 - x) + y * (1 - y))
+
+ return f_temp
+
The function exact_solution
returns the value of the exact solution
+at a given point.
def exact_solution(x, y):
+ """
+ This function will return the value of the exact solution at a given point
+ """
+ u_temp = 16 * x * (1 - x) * y * (1 - y)
+
+ return u_temp
+
The function get_bilinear_params_dict
returns a dictionary of
+bilinear parameters. The dictionary contains the values of the
+parameters \(\epsilon\) (epsilon), \(b_x\) (convection in
+x-direction), \(b_y\) (convection in y-direction), and \(c\)
+(reaction term).
Note : If any of the bilinear parameters are not present in the +dictionary (for the cd2d model), then the code will throw an error.
+def get_bilinear_params_dict():
+ """
+ This function will return a dictionary of bilinear parameters
+ """
+ eps = 1.0
+ b_x = 1.0
+ b_y = 0.0
+ c = 0.0
+
+ return {"eps": eps, "b_x": b_x, "b_y": b_y, "c": c}
+
This is the file that contains all the details about the problem. The +input file is in the YAML format. The input file for this example is +given below. The contents of the yaml files are as follows
+Defines the output path where the results will be saved.
+experimentation:
+ output_path: "output/cd2d/1" # Path to the output directory where the results will be saved.
+
It contains the details about the geometry of the domain. The mesh
+generation method can be either “internal” or “external”. If the mesh
+generation method is “internal”, then the internal_mesh_params
are
+used to generate the mesh. If the mesh generation method is “external”,
+then the mesh is read from the file specified in the mesh_file
+parameter.
In this case, we will use an external mesh. The mesh
+../meshes/circle_quad.mesh
is generated using the Gmsh software.
+The mesh needs to have physical elements defined for the boundary. In
+this case, the physical element is defined as 1000 (which is defined
+in the circle_boundary
function in the cd2d_example.py
file).
exact_solution_generation
is set to “internal” which means that
+the exact solution is generated using the exact_solution
function
+in the cd2d_example.py
file. For external check the other
+examples cd2d_gear
mesh_type
is set to “quadrilateral” which means that the mesh is
+a quadrilateral mesh. Note: As of now, only quadrilateral meshes are
+supported.
boundary_refinement_level
is set to 4 which means that the
+boundary is refined 4 times. (i.e), when the mesh is read, only the
+boundary points of an edge in quadrilateral mesh are read. this
+refinement will refine the boundary points to get more boundary
+points within the edge.
boundary_sampling_method
is set to “uniform” which means that the
+boundary points are sampled using the “uniform” method. (Use only
+uniform sampling as of now.)
generate_mesh_plot
is set to True which means that the mesh plot
+is generated and saved in the output directory.
geometry:
+ mesh_generation_method: "external" # Method for generating the mesh. Can be "internal" or "external".
+ generate_mesh_plot: True # Flag indicating whether to generate a plot of the mesh.
+
+ # internal mesh generated quadrilateral mesh, depending on the parameters specified below.
+
+ internal_mesh_params: # Parameters for internal mesh generation method.
+ x_min: 0 # Minimum x-coordinate of the domain.
+ x_max: 1 # Maximum x-coordinate of the domain.
+ y_min: 0 # Minimum y-coordinate of the domain.
+ y_max: 1 # Maximum y-coordinate of the domain.
+ n_cells_x: 4 # Number of cells in the x-direction.
+ n_cells_y: 4 # Number of cells in the y-direction.
+ n_boundary_points: 400 # Number of boundary points.
+ n_test_points_x: 100 # Number of test points in the x-direction.
+ n_test_points_y: 100 # Number of test points in the y-direction.
+
+ exact_solution:
+ exact_solution_generation: "internal" # whether the exact solution needs to be read from external file.
+ exact_solution_file_name: "" # External solution file name.
+
+ mesh_type: "quadrilateral" # Type of mesh. Can be "quadrilateral" or other supported types.
+
+ external_mesh_params: # Parameters for external mesh generation method.
+ mesh_file_name: "../meshes/circle_quad.mesh" # Path to the external mesh file (should be a .mesh file).
+ boundary_refinement_level: 4 # Level of refinement for the boundary.
+ boundary_sampling_method: "lhs" # Method for sampling the boundary. Can be "uniform" or "lhs".
+
This section contains the details about the finite element spaces.
+fe:
+ fe_order: 6 # Order of the finite element basis functions.
+ fe_type: "legendre" # Type of finite element basis functions. Can be "jacobi" or other supported types.
+ quad_order: 10 # Order of the quadrature rule.
+ quad_type: "gauss-jacobi"
+
Here the fe_order
is set to 6 which means it has 6 basis functions
+in each direction. The quad_order
is set to 10 which means it uses a
+10-points in each direction for the quadrature rule. The supported
+quadrature rules are “gauss-jacobi” and “gauss-legendre”. In this
+version of code, both “jacobi” and “legendre” refer to the same basis
+functions (to maintain backward compatibility). The basis functions are
+special type of Jacobi polynomials defined by
, where $J_{n} is the nth Jacobi polynomial.
+ +This value provides the beta values for the Dirichlet boundary conditions. The beta values are the multipliers that are used to multiply the boundary losses.
+pde:
+ beta: 10 # Parameter for the PDE.
+
The model section contains the details about the dense model to be used.
+The model architecture is given by the model_architecture
parameter.
+The activation function used in the model is given by the activation
+parameter. The epochs
parameter is the number of training epochs.
+The dtype
parameter is the data type used for computations. The
+learning_rate
section contains the parameters for learning rate
+scheduling. The initial_learning_rate
parameter is the initial
+learning rate. The use_lr_scheduler
parameter is a flag indicating
+whether to use the learning rate scheduler. The decay_steps
+parameter is the number of steps between each learning rate decay. The
+decay_rate
parameter is the decay rate for the learning rate. The
+staircase
parameter is a flag indicating whether to use the
+staircase decay.
Any parameter which are not mentioned above are archived parameters,
+which are not used in the current version of the code. (like
+use_attention
, set_memory_growth
)
model:
+ model_architecture: [2, 50,50,50,50, 1] # Architecture of the neural network model.
+ activation: "tanh" # Activation function used in the neural network.
+ use_attention: False # Flag indicating whether to use attention mechanism in the model.
+ epochs: 50000 # Number of training epochs.
+ dtype: "float32" # Data type used for computations.
+ set_memory_growth: False # Flag indicating whether to set memory growth for GPU.
+
+ learning_rate: # Parameters for learning rate scheduling.
+ initial_learning_rate: 0.001 # Initial learning rate.
+ use_lr_scheduler: False # Flag indicating whether to use learning rate scheduler.
+ decay_steps: 1000 # Number of steps between each learning rate decay.
+ decay_rate: 0.99 # Decay rate for the learning rate.
+ staircase: False
+
update_console_output
defines the epochs at which you need to log
+parameters like loss, time taken, etc.
logging:
+ update_console_output: 100 # Number of epochs after which to update the console output.
+
The other parameters such as update_progress_bar
,
+update_solution_images
are archived parameters which are not used in
+the current version of the code.
This file contains the main code to solve the Poisson equation in 2D on
+a circular domain. The code reads the input file, sets up the problem,
+and solves the Poisson equation using the fastvpinns
package.
The following libraries are imported in the main file.
+import numpy as np
+import pandas as pd
+import pytest
+import tensorflow as tf
+from pathlib import Path
+from tqdm import tqdm
+import yaml
+import sys
+import copy
+from tensorflow.keras import layers
+from tensorflow.keras import initializers
+from rich.console import Console
+import copy
+import time
+
The following imports are used from the fastvpinns
package.
Imports the geometry module from the fastvpinns
package, which
+contains the Geometry_2D
class responsible for setting up the
+geometry of the domain.
from fastvpinns.Geometry.geometry_2d import Geometry_2D
+
Imports the fespace module from the fastvpinns
package, which
+contains the FE_2D
class responsible for setting up the finite
+element spaces.
from fastvpinns.FE_2D.fespace2d import Fespace2D
+
Imports the datahandler module from the fastvpinns
package, which
+contains the DataHandler
class responsible for handling and
+converting the data to necessary shape for training purposes
from fastvpinns.DataHandler.datahandler import DataHandler
+
Imports the model module from the fastvpinns
package, which
+contains the Model
class responsible for training the neural
+network model.
from fastvpinns.Model.model import DenseModel
+
Import the Loss module from the fastvpinns
package, which
+contains the loss function of the PDE to be solved in tensor form.
from fastvpinns.physics.cd2d import pde_loss_cd2d
+
Import additional functionalities from the fastvpinns
package.
from fastvpinns.utils.plot_utils import plot_contour, plot_loss_function, plot_test_loss_function
+from fastvpinns.utils.compute_utils import compute_errors_combined
+from fastvpinns.utils.print_utils import print_table
+
The input file is read using the yaml
library.
if len(sys.argv) != 2:
+ print("Usage: python main.py <input file>")
+ sys.exit(1)
+
+ # Read the YAML file
+ with open(sys.argv[1], 'r') as f:
+ config = yaml.safe_load(f)
+
# Extract the values from the YAML file
+ i_output_path = config['experimentation']['output_path']
+
+ i_mesh_generation_method = config['geometry']['mesh_generation_method']
+ i_generate_mesh_plot = config['geometry']['generate_mesh_plot']
+ i_mesh_type = config['geometry']['mesh_type']
+ i_x_min = config['geometry']['internal_mesh_params']['x_min']
+ i_x_max = config['geometry']['internal_mesh_params']['x_max']
+ i_y_min = config['geometry']['internal_mesh_params']['y_min']
+ i_y_max = config['geometry']['internal_mesh_params']['y_max']
+ i_n_cells_x = config['geometry']['internal_mesh_params']['n_cells_x']
+ i_n_cells_y = config['geometry']['internal_mesh_params']['n_cells_y']
+ i_n_boundary_points = config['geometry']['internal_mesh_params']['n_boundary_points']
+ i_n_test_points_x = config['geometry']['internal_mesh_params']['n_test_points_x']
+ i_n_test_points_y = config['geometry']['internal_mesh_params']['n_test_points_y']
+ i_exact_solution_generation = config['geometry']['exact_solution']['exact_solution_generation']
+ i_exact_solution_file_name = config['geometry']['exact_solution']['exact_solution_file_name']
+
+ i_mesh_file_name = config['geometry']['external_mesh_params']['mesh_file_name']
+ i_boundary_refinement_level = config['geometry']['external_mesh_params'][
+ 'boundary_refinement_level'
+ ]
+ i_boundary_sampling_method = config['geometry']['external_mesh_params'][
+ 'boundary_sampling_method'
+ ]
+
+ i_fe_order = config['fe']['fe_order']
+ i_fe_type = config['fe']['fe_type']
+ i_quad_order = config['fe']['quad_order']
+ i_quad_type = config['fe']['quad_type']
+
+ i_model_architecture = config['model']['model_architecture']
+ i_activation = config['model']['activation']
+ i_use_attention = config['model']['use_attention']
+ i_epochs = config['model']['epochs']
+ i_dtype = config['model']['dtype']
+ if i_dtype == "float64":
+ i_dtype = tf.float64
+ elif i_dtype == "float32":
+ i_dtype = tf.float32
+ else:
+ print("[ERROR] The given dtype is not a valid tensorflow dtype")
+ raise ValueError("The given dtype is not a valid tensorflow dtype")
+
+ i_set_memory_growth = config['model']['set_memory_growth']
+ i_learning_rate_dict = config['model']['learning_rate']
+
+ i_beta = config['pde']['beta']
+
+ i_update_console_output = config['logging']['update_console_output']
+
all the variables which are named with the prefix i_
are input
+parameters which are read from the input file. Return to
+top
Obtain the bounndary condition and boundary values from the
+cd2d_example.py
file and initialise the Geometry_2D
class. After
+that use the domain.read_mesh
functionality to read the external
+mesh file.
cells, boundary_points = domain.read_mesh(
+ i_mesh_file_name,
+ i_boundary_refinement_level,
+ i_boundary_sampling_method,
+ refinement_level=1,
+ )
+
Initialise the Fespace2D
class with the required parameters.
fespace = Fespace2D(
+ mesh=domain.mesh,
+ cells=cells,
+ boundary_points=boundary_points,
+ cell_type=domain.mesh_type,
+ fe_order=i_fe_order,
+ fe_type=i_fe_type,
+ quad_order=i_quad_order,
+ quad_type=i_quad_type,
+ fe_transformation_type="bilinear",
+ bound_function_dict=bound_function_dict,
+ bound_condition_dict=bound_condition_dict,
+ forcing_function=rhs,
+ output_path=i_output_path,
+ generate_mesh_plot=i_generate_mesh_plot,
+ )
+
Initialise the DataHandler
class with the required parameters.
datahandler = DataHandler2D(fespace, domain, dtype=i_dtype)
+
Setup the necesary parameters for the model and initialise the Model
+class. Before that fill the params
dictionary with the required
+parameters.
model = DenseModel(
+ layer_dims=[2, 30, 30, 30, 1],
+ learning_rate_dict=i_learning_rate_dict,
+ params_dict=params_dict,
+ loss_function=pde_loss_cd2d,
+ input_tensors_list=[datahandler.x_pde_list, train_dirichlet_input, train_dirichlet_output],
+ orig_factor_matrices=[
+ datahandler.shape_val_mat_list,
+ datahandler.grad_x_mat_list,
+ datahandler.grad_y_mat_list,
+ ],
+ force_function_list=datahandler.forcing_function_list,
+ tensor_dtype=i_dtype,
+ use_attention=i_use_attention,
+ activation=i_activation,
+ hessian=False,
+ )
+
test_points = domain.get_test_points()
+ print(f"[bold]Number of Test Points = [/bold] {test_points.shape[0]}")
+ y_exact = exact_solution(test_points[:, 0], test_points[:, 1])
+
+ # plot the exact solution
+ num_epochs = i_epochs # num_epochs
+ progress_bar = tqdm(
+ total=num_epochs,
+ desc='Training',
+ unit='epoch',
+ bar_format="{l_bar}{bar:40}{r_bar}{bar:-10b}",
+ colour="green",
+ ncols=100,
+ )
+ loss_array = [] # total loss
+ test_loss_array = [] # test loss
+ time_array = [] # time per epoc
+ # beta - boundary loss parameters
+ beta = tf.constant(i_beta, dtype=i_dtype)
+
This sets up the test points and the exact solution. The progress bar is +initialised and the loss arrays are set up. The beta value is set up as +a constant tensor. Return to top
+for epoch in range(num_epochs):
+
+ # Train the model
+ batch_start_time = time.time()
+ loss = model.train_step(beta=beta, bilinear_params_dict=bilinear_params_dict)
+ elapsed = time.time() - batch_start_time
+
+ # print(elapsed)
+ time_array.append(elapsed)
+
+ loss_array.append(loss['loss'])
+
This train_step
function trains the model for one epoch and returns
+the loss. The loss is appended to the loss array. Then for every epoch
+where
+(epoch + 1) % i_update_console_output == 0 or epoch == num_epochs - 1:
y_pred = model(test_points).numpy()
+y_pred = y_pred.reshape(-1)
+
+error = np.abs(y_exact - y_pred)
+
+# get errors
+(
+ l2_error,
+ linf_error,
+ l2_error_relative,
+ linf_error_relative,
+ l1_error,
+ l1_error_relative,
+) = compute_errors_combined(y_exact, y_pred)
+
+loss_pde = float(loss['loss_pde'].numpy())
+loss_dirichlet = float(loss['loss_dirichlet'].numpy())
+total_loss = float(loss['loss'].numpy())
+
+# Append test loss
+test_loss_array.append(l1_error)
+
+solution_array = np.c_[y_pred, y_exact, np.abs(y_exact - y_pred)]
+domain.write_vtk(
+ solution_array,
+ output_path=i_output_path,
+ filename=f"prediction_{epoch+1}.vtk",
+ data_names=["Sol", "Exact", "Error"],
+)
+
+console.print(f"\nEpoch [bold]{epoch+1}/{num_epochs}[/bold]")
+console.print("[bold]--------------------[/bold]")
+console.print("[bold]Beta : [/bold]", beta.numpy(), end=" ")
+console.print(
+ f"Variational Losses || Pde Loss : [red]{loss_pde:.3e}[/red] Dirichlet Loss : [red]{loss_dirichlet:.3e}[/red] Total Loss : [red]{total_loss:.3e}[/red]"
+)
+console.print(
+ f"Test Losses || L1 Error : {l1_error:.3e} L2 Error : {l2_error:.3e} Linf Error : {linf_error:.3e}"
+)
+
We will compute all the test errors and write the solution to a vtk file +for a complex mesh. Further, the console output will be printed with the +loss values and the test errors. Return to top
+# Save the model
+ model.save_weights(str(Path(i_output_path) / "model_weights"))
+
+ solution_array = np.c_[y_pred, y_exact, np.abs(y_exact - y_pred)]
+ domain.write_vtk(
+ solution_array,
+ output_path=i_output_path,
+ filename=f"prediction_{epoch+1}.vtk",
+ data_names=["Sol", "Exact", "Error"],
+ )
+ # print the Error values in table
+ print_table(
+ "Error Values",
+ ["Error Type", "Value"],
+ [
+ "L2 Error",
+ "Linf Error",
+ "Relative L2 Error",
+ "Relative Linf Error",
+ "L1 Error",
+ "Relative L1 Error",
+ ],
+ [l2_error, linf_error, l2_error_relative, linf_error_relative, l1_error, l1_error_relative],
+ )
+
+ # print the time values in table
+ print_table(
+ "Time Values",
+ ["Time Type", "Value"],
+ [
+ "Time per Epoch(s) - Median",
+ "Time per Epoch(s) IQR-25% ",
+ "Time per Epoch(s) IQR-75% ",
+ "Mean (s)",
+ "Epochs per second",
+ "Total Train Time",
+ ],
+ [
+ np.median(time_array),
+ np.percentile(time_array, 25),
+ np.percentile(time_array, 75),
+ np.mean(time_array),
+ int(i_epochs / np.sum(time_array)),
+ np.sum(time_array),
+ ],
+ )
+
+ # save all the arrays as numpy arrays
+ np.savetxt(str(Path(i_output_path) / "loss_function.txt"), np.array(loss_array))
+ np.savetxt(str(Path(i_output_path) / "prediction.txt"), y_pred)
+ np.savetxt(str(Path(i_output_path) / "exact.txt"), y_exact)
+ np.savetxt(str(Path(i_output_path) / "error.txt"), error)
+ np.savetxt(str(Path(i_output_path) / "time_per_epoch.txt"), np.array(time_array))
+
This part of the code saves the model weights, writes the solution to a +vtk file, prints the error values in a table, prints the time values in +a table, and saves all the arrays as numpy arrays.
+All the outputs will be saved in the output directory specified in the +input file. The output directory will contain the following files: - +prediction_{epoch}.vtk : The solution file for each epoch. - +loss_function.txt : The loss function values for each epoch. - +prediction.txt : The predicted values at last epoch at the test points. +- exact.txt : The exact values at last epoch at the test points. - +error.txt : The error values at last epoch at the test points. - +time_per_epoch.txt : The time taken for each epoch. Return to +top
+This example demonstrates how to solve a Poisson equation in 2D on a
+circular domain using the fastvpinns
package.
+All the necesary files can be found in the examples folder of the fastvpinns GitHub repository
+The Poisson equation is given by
where (\(\Omega\)) is the circular domain and (f) is the source +term. The boundary conditions are given by
+For this problem, the parameters are
+The exact solution is computed using +ParMOON, an inhouse FEM +package.
+The computational domain is a circular domain with radius 1 centered at +(0, 0).
+ +To run the code, execute the following command:
+python3 main_cd2d_gear.py input.yaml
+
This file hosts all the details about the bilinear parameters for the +PDE, boundary conditions, source term, and the exact solution.
+The function get_boundary_function_dict
returns a dictionary of
+boundary functions. The key of the dictionary is the boundary id and the
+value is the boundary function. The function get_bound_cond_dict
+returns a dictionary of boundary conditions. The key of the dictionary
+is the boundary id and the value is the boundary condition.
here inner_boundary
and outer_boundary
are the functions which
+return the boundary values for the inner and outer boundaries of the
+gear geometry. The boundary ids are defined as 1000 and 1001
+respectively. The boundary conditions are defined as “dirichlet” for
+both the boundaries.
Note : As of now, only Dirichlet boundary conditions are supported.
+def inner_boundary(x, y):
+ """
+ This function will return the boundary value for given component of a boundary
+ """
+ return 0.0
+
+
+def outer_boundary(x, y):
+ """
+ This function will return the boundary value for given component of a boundary
+ """
+
+ return 0.0
+
+def get_boundary_function_dict():
+ """
+ This function will return a dictionary of boundary functions
+ """
+ return {1000: outer_boundary, 1001: inner_boundary}
+
+
+def get_bound_cond_dict():
+ """
+ This function will return a dictionary of boundary conditions
+ """
+ return {1000: "dirichlet", 1001: "dirichlet"}
+
The function rhs
returns the value of the source term at a given
+point.
def rhs(x, y):
+ """
+ This function will return the value of the rhs at a given point
+ """
+ f_temp = 50 * np.sin(x) + np.cos(x)
+
+ return f_temp
+
The function exact_solution
returns the value of the exact solution
+at a given point.
Note :Here the exact solution function does not matter, since the +exact solution will be read externally from a file, which contains the +fem solution to the problem.
+def exact_solution(x, y):
+ """
+ This function will return the exact solution at a given point
+ """
+ r = np.sqrt(x**2 + y**2)
+
+ return np.ones_like(x) * 0
+
The function get_bilinear_params_dict
returns a dictionary of
+bilinear parameters. The dictionary contains the values of the
+parameters \(\epsilon\) (epsilon), \(b_x\) (convection)
Note : If any of the bilinear parameters are not present in the +dictionary (for the cd2d model), then the code will throw an error.
+def get_bilinear_params_dict():
+ """
+ This function will return a dictionary of bilinear parameters
+ """
+ eps = 1.0
+ b_x = 0.1
+ b_y = 0.0
+ c = 0.0
+
+ return {"eps": eps, "b_x": b_x, "b_y": b_y, "c": c}
+
This is the file that contains all the details about the problem. The +input file is in the YAML format. The input file for this example is +given below. The contents of the yaml files are as follows
+Defines the output path where the results will be saved.
+experimentation:
+ output_path: "output/cd2d_gear" # Path to the output directory where the results will be saved.
+
It contains the details about the geometry of the domain. The mesh
+generation method can be either “internal” or “external”. If the mesh
+generation method is “internal”, then the internal_mesh_params
are
+used to generate the mesh. If the mesh generation method is “external”,
+then the mesh is read from the file specified in the mesh_file
+parameter.
In this case, we will use an external mesh. The mesh
+../meshes/circle_quad.mesh
is generated using the Gmsh software.
+The mesh needs to have physical elements defined for the boundary. In
+this case, the physical element is defined as 1000 (which is defined
+in the circle_boundary
function in the cd2d_gear_example.py
+file).
exact_solution_generation
is set to “external” which means that
+the exact solution is read from an external file.
mesh_type
is set to “quadrilateral” which means that the mesh is
+a quadrilateral mesh. Note: As of now, only quadrilateral meshes are
+supported.
boundary_refinement_level
is set to 2 which means that the
+boundary is refined 2 times. (i.e), when the mesh is read, only the
+boundary points of an edge in quadrilateral mesh are read. this
+refinement will refine the boundary points to get more boundary
+points within the edge.
boundary_sampling_method
is set to “uniform” which means that the
+boundary points are sampled using the “uniform” method. (Use only
+uniform sampling as of now.)
generate_mesh_plot
is set to True which means that the mesh plot
+is generated and saved in the output directory.
geometry:
+ mesh_generation_method: "external"
+ generate_mesh_plot: False
+ internal_mesh_params:
+ x_min: 0
+ x_max: 1
+ y_min: 0
+ y_max: 1
+ n_cells_x: 8
+ n_cells_y: 8
+ n_boundary_points: 2000
+ n_test_points_x: 100
+ n_test_points_y: 100
+
+ exact_solution:
+ exact_solution_generation: "external" # whether the exact solution needs to be read from external file.
+ exact_solution_file_name: "fem_output_gear_forward_sin.csv" # External solution file name.
+
+ mesh_type: "quadrilateral"
+ external_mesh_params:
+ mesh_file_name: "../meshes/gear.mesh" # should be a .mesh file
+ boundary_refinement_level: 2
+ boundary_sampling_method: "uniform" # "uniform"
+
This section contains the details about the finite element spaces.
+fe:
+ fe_order: 4
+ fe_type: "jacobi"
+ quad_order: 5
+ quad_type: "gauss-jacobi"
+
Here the fe_order
is set to 6 which means it has 6 basis functions
+in each direction. The quad_order
is set to 10 which means it uses a
+10-points in each direction for the quadrature rule. The supported
+quadrature rules are “gauss-jacobi” and “gauss-legendre”. In this
+version of code, both “jacobi” and “legendre” refer to the same basis
+functions (to maintain backward compatibility). The basis functions are
+special type of Jacobi polynomials defined by
, where $J_{n} is the nth Jacobi polynomial.
+ +This value provides the beta values for the dirichlet boundary +conditions. The beta values are the multipliers that are used to multiply +the boundary losses. $loss_{total} = loss_{pde} + beta cdot loss_{dirichlet}$
+pde:
+ beta: 5 # Parameter for the PDE.
+
The model section contains the details about the dense model to be used.
+The model architecture is given by the model_architecture
parameter.
+The activation function used in the model is given by the activation
+parameter. The epochs
parameter is the number of training epochs.
+The dtype
parameter is the data type used for computations. The
+learning_rate
section contains the parameters for learning rate
+scheduling. The initial_learning_rate
parameter is the initial
+learning rate. The use_lr_scheduler
parameter is a flag indicating
+whether to use the learning rate scheduler. The decay_steps
+parameter is the number of steps between each learning rate decay. The
+decay_rate
parameter is the decay rate for the learning rate. The
+staircase
parameter is a flag indicating whether to use the
+staircase decay.
Any parameter which are not mentioned above are archived parameters,
+which are not used in the current version of the code. (like
+use_attention
, set_memory_growth
)
model:
+ model_architecture: [2, 50,50,50, 1]
+ activation: "tanh"
+ use_attention: False
+ epochs: 150000
+ dtype: "float32"
+ set_memory_growth: False
+ learning_rate:
+ initial_learning_rate: 0.005
+ use_lr_scheduler: True
+ decay_steps: 1000
+ decay_rate: 0.99
+ staircase: False
+
update_console_output
defines the epochs at which you need to log
+parameters like loss, time taken, etc.
logging:
+ update_console_output: 10000
+
This file contains the main code to solve the Poisson equation in 2D on
+a circular domain. The code reads the input file, sets up the problem,
+and solves the Poisson equation using the fastvpinns
package.
The following libraries are imported in the main file.
+import numpy as np
+import pandas as pd
+import pytest
+import tensorflow as tf
+from pathlib import Path
+from tqdm import tqdm
+import yaml
+import sys
+import copy
+from tensorflow.keras import layers
+from tensorflow.keras import initializers
+from rich.console import Console
+import copy
+import time
+
The following imports are used from the fastvpinns
package.
Imports the geometry module from the fastvpinns
package, which
+contains the Geometry_2D
class responsible for setting up the
+geometry of the domain.
from fastvpinns.Geometry.geometry_2d import Geometry_2D
+
Imports the fespace module from the fastvpinns
package, which
+contains the FE_2D
class responsible for setting up the finite
+element spaces.
from fastvpinns.FE_2D.fespace2d import Fespace2D
+
Imports the datahandler module from the fastvpinns
package, which
+contains the DataHandler
class responsible for handling and
+converting the data to necessary shape for training purposes
from fastvpinns.DataHandler.datahandler import DataHandler
+
Imports the model module from the fastvpinns
package, which
+contains the Model
class responsible for training the neural
+network model.
from fastvpinns.Model.model import DenseModel
+
Import the Loss module from the fastvpinns
package, which
+contains the loss function of the PDE to be solved in tensor form.
from fastvpinns.physics.cd2d import pde_loss_cd2d
+
Import additional functionalities from the fastvpinns
package.
from fastvpinns.utils.plot_utils import plot_contour, plot_loss_function, plot_test_loss_function
+from fastvpinns.utils.compute_utils import compute_errors_combined
+from fastvpinns.utils.print_utils import print_table
+
The input file is read using the yaml
library.
if len(sys.argv) != 2:
+ print("Usage: python main.py <input file>")
+ sys.exit(1)
+
+ # Read the YAML file
+ with open(sys.argv[1], 'r') as f:
+ config = yaml.safe_load(f)
+
# Extract the values from the YAML file
+ i_output_path = config['experimentation']['output_path']
+
+ i_mesh_generation_method = config['geometry']['mesh_generation_method']
+ i_generate_mesh_plot = config['geometry']['generate_mesh_plot']
+ i_mesh_type = config['geometry']['mesh_type']
+ i_x_min = config['geometry']['internal_mesh_params']['x_min']
+ i_x_max = config['geometry']['internal_mesh_params']['x_max']
+ i_y_min = config['geometry']['internal_mesh_params']['y_min']
+ i_y_max = config['geometry']['internal_mesh_params']['y_max']
+ i_n_cells_x = config['geometry']['internal_mesh_params']['n_cells_x']
+ i_n_cells_y = config['geometry']['internal_mesh_params']['n_cells_y']
+ i_n_boundary_points = config['geometry']['internal_mesh_params']['n_boundary_points']
+ i_n_test_points_x = config['geometry']['internal_mesh_params']['n_test_points_x']
+ i_n_test_points_y = config['geometry']['internal_mesh_params']['n_test_points_y']
+ i_exact_solution_generation = config['geometry']['exact_solution']['exact_solution_generation']
+ i_exact_solution_file_name = config['geometry']['exact_solution']['exact_solution_file_name']
+
+ i_mesh_file_name = config['geometry']['external_mesh_params']['mesh_file_name']
+ i_boundary_refinement_level = config['geometry']['external_mesh_params'][
+ 'boundary_refinement_level'
+ ]
+ i_boundary_sampling_method = config['geometry']['external_mesh_params'][
+ 'boundary_sampling_method'
+ ]
+
+ i_fe_order = config['fe']['fe_order']
+ i_fe_type = config['fe']['fe_type']
+ i_quad_order = config['fe']['quad_order']
+ i_quad_type = config['fe']['quad_type']
+
+ i_model_architecture = config['model']['model_architecture']
+ i_activation = config['model']['activation']
+ i_use_attention = config['model']['use_attention']
+ i_epochs = config['model']['epochs']
+ i_dtype = config['model']['dtype']
+ if i_dtype == "float64":
+ i_dtype = tf.float64
+ elif i_dtype == "float32":
+ i_dtype = tf.float32
+ else:
+ print("[ERROR] The given dtype is not a valid tensorflow dtype")
+ raise ValueError("The given dtype is not a valid tensorflow dtype")
+
+ i_set_memory_growth = config['model']['set_memory_growth']
+ i_learning_rate_dict = config['model']['learning_rate']
+
+ i_beta = config['pde']['beta']
+
+ i_update_console_output = config['logging']['update_console_output']
+
all the variables which are named with the prefix i_
are input
+parameters which are read from the input file. Return to
+top
Obtain the bounndary condition and boundary values from the
+cd2d_gear_example.py
file and initialise the Geometry_2D
class.
+After that use the domain.read_mesh
functionality to read the
+external mesh file.
cells, boundary_points = domain.read_mesh(
+ i_mesh_file_name,
+ i_boundary_refinement_level,
+ i_boundary_sampling_method,
+ refinement_level=1,
+ )
+
Initialise the Fespace2D
class with the required parameters.
fespace = Fespace2D(
+ mesh=domain.mesh,
+ cells=cells,
+ boundary_points=boundary_points,
+ cell_type=domain.mesh_type,
+ fe_order=i_fe_order,
+ fe_type=i_fe_type,
+ quad_order=i_quad_order,
+ quad_type=i_quad_type,
+ fe_transformation_type="bilinear",
+ bound_function_dict=bound_function_dict,
+ bound_condition_dict=bound_condition_dict,
+ forcing_function=rhs,
+ output_path=i_output_path,
+ generate_mesh_plot=i_generate_mesh_plot,
+ )
+
Initialise the DataHandler
class with the required parameters.
datahandler = DataHandler2D(fespace, domain, dtype=i_dtype)
+
Setup the necesary parameters for the model and initialise the Model
+class. Before that fill the params
dictionary with the required
+parameters.
model = DenseModel(
+ layer_dims=i_model_architecture,
+ learning_rate_dict=i_learning_rate_dict,
+ params_dict=params_dict,
+ loss_function=pde_loss_cd2d,
+ input_tensors_list=[datahandler.x_pde_list, train_dirichlet_input, train_dirichlet_output],
+ orig_factor_matrices=[
+ datahandler.shape_val_mat_list,
+ datahandler.grad_x_mat_list,
+ datahandler.grad_y_mat_list,
+ ],
+ force_function_list=datahandler.forcing_function_list,
+ tensor_dtype=i_dtype,
+ use_attention=i_use_attention,
+ activation=i_activation,
+ hessian=False,
+ )
+
if i_exact_solution_generation == "internal":
+ y_exact = exact_solution(test_points[:, 0], test_points[:, 1])
+else:
+ exact_db = pd.read_csv(f"{i_exact_solution_file_name}", header=None, delimiter=",")
+ y_exact = exact_db.iloc[:, 2].values.reshape(-1)
+
+# plot the exact solution
+num_epochs = i_epochs # num_epochs
+progress_bar = tqdm(
+ total=num_epochs,
+ desc='Training',
+ unit='epoch',
+ bar_format="{l_bar}{bar:40}{r_bar}{bar:-10b}",
+ colour="green",
+ ncols=100,
+)
+loss_array = [] # total loss
+test_loss_array = [] # test loss
+time_array = [] # time per epoc
+# beta - boundary loss parameters
+beta = tf.constant(i_beta, dtype=i_dtype)
+
Here the exact solution is being read from the external file. The +external solution at the test points is computed by FEM and stored in a +csv file. This sets up the test points and the exact solution. The +progress bar is initialised and the loss arrays are set up. The beta +value is set up as a constant tensor. Return to top
+for epoch in range(num_epochs):
+
+ # Train the model
+ batch_start_time = time.time()
+ loss = model.train_step(beta=beta, bilinear_params_dict=bilinear_params_dict)
+ elapsed = time.time() - batch_start_time
+
+ # print(elapsed)
+ time_array.append(elapsed)
+
+ loss_array.append(loss['loss'])
+
This train_step
function trains the model for one epoch and returns
+the loss. The loss is appended to the loss array. Then for every epoch
+where
+(epoch + 1) % i_update_console_output == 0 or epoch == num_epochs - 1:
y_pred = model(test_points).numpy()
+y_pred = y_pred.reshape(-1)
+
+error = np.abs(y_exact - y_pred)
+
+# get errors
+(
+ l2_error,
+ linf_error,
+ l2_error_relative,
+ linf_error_relative,
+ l1_error,
+ l1_error_relative,
+) = compute_errors_combined(y_exact, y_pred)
+
+loss_pde = float(loss['loss_pde'].numpy())
+loss_dirichlet = float(loss['loss_dirichlet'].numpy())
+total_loss = float(loss['loss'].numpy())
+
+# Append test loss
+test_loss_array.append(l1_error)
+
+solution_array = np.c_[y_pred, y_exact, np.abs(y_exact - y_pred)]
+domain.write_vtk(
+ solution_array,
+ output_path=i_output_path,
+ filename=f"prediction_{epoch+1}.vtk",
+ data_names=["Sol", "Exact", "Error"],
+)
+
+console.print(f"\nEpoch [bold]{epoch+1}/{num_epochs}[/bold]")
+console.print("[bold]--------------------[/bold]")
+console.print("[bold]Beta : [/bold]", beta.numpy(), end=" ")
+console.print(
+ f"Variational Losses || Pde Loss : [red]{loss_pde:.3e}[/red] Dirichlet Loss : [red]{loss_dirichlet:.3e}[/red] Total Loss : [red]{total_loss:.3e}[/red]"
+)
+console.print(
+ f"Test Losses || L1 Error : {l1_error:.3e} L2 Error : {l2_error:.3e} Linf Error : {linf_error:.3e}"
+)
+
We will compute all the test errors and write the solution to a vtk file +for a complex mesh. Further, the console output will be printed with the +loss values and the test errors. Return to top
+# Save the model
+ model.save_weights(str(Path(i_output_path) / "model_weights"))
+
+ solution_array = np.c_[y_pred, y_exact, np.abs(y_exact - y_pred)]
+ domain.write_vtk(
+ solution_array,
+ output_path=i_output_path,
+ filename=f"prediction_{epoch+1}.vtk",
+ data_names=["Sol", "Exact", "Error"],
+ )
+ # print the Error values in table
+ print_table(
+ "Error Values",
+ ["Error Type", "Value"],
+ [
+ "L2 Error",
+ "Linf Error",
+ "Relative L2 Error",
+ "Relative Linf Error",
+ "L1 Error",
+ "Relative L1 Error",
+ ],
+ [l2_error, linf_error, l2_error_relative, linf_error_relative, l1_error, l1_error_relative],
+ )
+
+ # print the time values in table
+ print_table(
+ "Time Values",
+ ["Time Type", "Value"],
+ [
+ "Time per Epoch(s) - Median",
+ "Time per Epoch(s) IQR-25% ",
+ "Time per Epoch(s) IQR-75% ",
+ "Mean (s)",
+ "Epochs per second",
+ "Total Train Time",
+ ],
+ [
+ np.median(time_array),
+ np.percentile(time_array, 25),
+ np.percentile(time_array, 75),
+ np.mean(time_array),
+ int(i_epochs / np.sum(time_array)),
+ np.sum(time_array),
+ ],
+ )
+
+ # save all the arrays as numpy arrays
+ np.savetxt(str(Path(i_output_path) / "loss_function.txt"), np.array(loss_array))
+ np.savetxt(str(Path(i_output_path) / "prediction.txt"), y_pred)
+ np.savetxt(str(Path(i_output_path) / "exact.txt"), y_exact)
+ np.savetxt(str(Path(i_output_path) / "error.txt"), error)
+ np.savetxt(str(Path(i_output_path) / "time_per_epoch.txt"), np.array(time_array))
+
This part of the code saves the model weights, writes the solution to a +vtk file, prints the error values in a table, prints the time values in +a table, and saves all the arrays as numpy arrays.
+All the outputs will be saved in the output directory specified in the +input file. The output directory will contain the following files: - +prediction_{epoch}.vtk : The solution file for each epoch. - +loss_function.txt : The loss function values for each epoch. - +prediction.txt : The predicted values at last epoch at the test points. +- exact.txt : The exact values at last epoch at the test points. - +error.txt : The error values at last epoch at the test points. - +time_per_epoch.txt : The time taken for each epoch. Return to +top
+This example demonstrates how to solve a Poisson equation in 2D on a
+circular domain using the fastvpinns
package.
+All the necesary files can be found in the examples folder of the fastvpinns GitHub repository
The Poisson equation is given by
+where (\(\Omega\)) is the circular domain and (f) is the source +term. The boundary conditions are given by
+For this problem, the parameters are
+The exact solution is given by
+The computational domain is a circular domain with radius 1 centered at +(0, 0).
+ +To run the code, execute the following command:
+python3 main_helmholtz.py input.yaml
+
This file hosts all the details about the bilinear parameters for the +PDE, boundary conditions, source term, and the exact solution.
+The function circle_boundary
returns the boundary value for a given
+component of the boundary. The function get_boundary_function_dict
+returns a dictionary of boundary functions. The key of the dictionary is
+the boundary id and the value is the boundary function. The function
+get_bound_cond_dict
returns a dictionary of boundary conditions. The
+key of the dictionary is the boundary id and the value is the boundary
+condition.
Note : As of now, only Dirichlet boundary conditions are supported.
+def circle_boundary(x, y):
+ """
+ This function will return the boundary value for given component of a boundary
+ """
+ return (x + y) * np.sin(np.pi * x) * np.sin(np.pi * y)
+
+def get_boundary_function_dict():
+ """
+ This function will return a dictionary of boundary functions
+ """
+ return {1000: circle_boundary}
+
+
+def get_bound_cond_dict():
+ """
+ This function will return a dictionary of boundary conditions
+ """
+ return {1000: "dirichlet"}
+
The function rhs
returns the value of the source term at a given
+point.
def rhs(x, y):
+ """
+ This function will return the value of the rhs at a given point
+ """
+ # f_temp = 32 * (x * (1 - x) + y * (1 - y))
+ # f_temp = 1
+
+ term1 = 2 * np.pi * np.cos(np.pi * y) * np.sin(np.pi * x)
+ term2 = 2 * np.pi * np.cos(np.pi * x) * np.sin(np.pi * y)
+ term3 = (x + y) * np.sin(np.pi * x) * np.sin(np.pi * y)
+ term4 = -2 * (np.pi**2) * (x + y) * np.sin(np.pi * x) * np.sin(np.pi * y)
+
+ result = term1 + term2 + term3 + term4
+ return result
+
The function exact_solution
returns the value of the exact solution
+at a given point.
def exact_solution(x, y):
+ """
+ This function will return the exact solution at a given point
+ """
+
+ return (x + y) * np.sin(np.pi * x) * np.sin(np.pi * y)
+
The function get_bilinear_params_dict
returns a dictionary of
+bilinear parameters. The dictionary contains the values of the
+parameters (\(\epsilon\)), (b_x), (b_y), and (c).
Note : If any of the bilinear parameters are not present in the +dictionary (for the cd2d model), then the code will throw an error.
+def get_bilinear_params_dict():
+ """
+ This function will return a dictionary of bilinear parameters
+ """
+ k = 1.0
+ eps = 1.0
+
+ return {"k": k, "eps": eps}
+
This is the file that contains all the details about the problem. The +input file is in the YAML format. The input file for this example is +given below. The contents of the yaml files are as follows
+Defines the output path where the results will be saved.
+experimentation:
+ output_path: "output/helmholtz/1"
+
It contains the details about the geometry of the domain. The mesh
+generation method can be either “internal” or “external”. If the mesh
+generation method is “internal”, then the internal_mesh_params
are
+used to generate the mesh. If the mesh generation method is “external”,
+then the mesh is read from the file specified in the mesh_file
+parameter.
In this case, we will use an external mesh. The mesh
+../meshes/circle_quad.mesh
is generated using the Gmsh software.
+The mesh needs to have physical elements defined for the boundary. In
+this case, the physical element is defined as 1000 (which is defined
+in the circle_boundary
function in the helmholtz_example.py
+file).
exact_solution_generation
is set to “internal” which means that
+the exact solution is generated using the exact_solution
function
+in the helmholtz_example.py
file. For external check the other
+examples cd2d_gear
mesh_type
is set to “quadrilateral” which means that the mesh is
+a quadrilateral mesh. Note: As of now, only quadrilateral meshes are
+supported.
boundary_refinement_level
is set to 4 which means that the
+boundary is refined 4 times. (i.e), when the mesh is read, only the
+boundary points of an edge in quadrilateral mesh are read. this
+refinement will refine the boundary points to get more boundary
+points within the edge.
boundary_sampling_method
is set to “uniform” which means that the
+boundary points are sampled using the “uniform” method. (Use only
+uniform sampling as of now.)
generate_mesh_plot
is set to True which means that the mesh plot
+is generated and saved in the output directory.
geometry:
+ mesh_generation_method: "external" # Method for generating the mesh. Can be "internal" or "external".
+ generate_mesh_plot: True # Flag indicating whether to generate a plot of the mesh.
+
+ # internal mesh generated quadrilateral mesh, depending on the parameters specified below.
+
+ internal_mesh_params: # Parameters for internal mesh generation method.
+ x_min: 0 # Minimum x-coordinate of the domain.
+ x_max: 1 # Maximum x-coordinate of the domain.
+ y_min: 0 # Minimum y-coordinate of the domain.
+ y_max: 1 # Maximum y-coordinate of the domain.
+ n_cells_x: 4 # Number of cells in the x-direction.
+ n_cells_y: 4 # Number of cells in the y-direction.
+ n_boundary_points: 400 # Number of boundary points.
+ n_test_points_x: 100 # Number of test points in the x-direction.
+ n_test_points_y: 100 # Number of test points in the y-direction.
+
+ exact_solution:
+ exact_solution_generation: "internal" # whether the exact solution needs to be read from external file.
+ exact_solution_file_name: "" # External solution file name.
+
+ mesh_type: "quadrilateral" # Type of mesh. Can be "quadrilateral" or other supported types.
+
+ external_mesh_params: # Parameters for external mesh generation method.
+ mesh_file_name: "../meshes/circle_quad.mesh" # Path to the external mesh file (should be a .mesh file).
+ boundary_refinement_level: 4 # Level of refinement for the boundary.
+ boundary_sampling_method: "lhs" # Method for sampling the boundary. Can be "uniform" or "lhs".
+
This section contains the details about the finite element spaces.
+fe:
+ fe_order: 4 # Order of the finite element basis functions.
+ fe_type: "jacobi" # Type of finite element basis functions.
+ quad_order: 5 # Order of the quadrature rule.
+ quad_type: "gauss-jacobi" # Type of quadrature rule.
+
Here the fe_order
is set to 6 which means it has 6 basis functions
+in each direction. The quad_order
is set to 10 which means it uses a
+10-points in each direction for the quadrature rule. The supported
+quadrature rules are “gauss-jacobi” and “gauss-legendre”. In this
+version of code, both “jacobi” and “legendre” refer to the same basis
+functions (to maintain backward compatibility). The basis functions are
+special type of Jacobi polynomials defined by
, where $J_{n} is the nth Jacobi polynomial.
+ +This value provides the beta values for the Dirichlet boundary conditions. The beta values are the multipliers that are used to multiply the boundary losses. The total loss is calculated as follows:
+pde:
+ beta: 10 # Parameter for the PDE.
+
The model section contains the details about the dense model to be used.
+The model architecture is given by the model_architecture
parameter.
+The activation function used in the model is given by the activation
+parameter. The epochs
parameter is the number of training epochs.
+The dtype
parameter is the data type used for computations. The
+learning_rate
section contains the parameters for learning rate
+scheduling. The initial_learning_rate
parameter is the initial
+learning rate. The use_lr_scheduler
parameter is a flag indicating
+whether to use the learning rate scheduler. The decay_steps
+parameter is the number of steps between each learning rate decay. The
+decay_rate
parameter is the decay rate for the learning rate. The
+staircase
parameter is a flag indicating whether to use the
+staircase decay.
Any parameter which are not mentioned above are archived parameters,
+which are not used in the current version of the code. (like
+use_attention
, set_memory_growth
)
model:
+ model_architecture: [2, 30,30,30, 1] # Architecture of the neural network model.
+ activation: "tanh" # Activation function used in the neural network.
+ use_attention: False # Flag indicating whether to use attention mechanism in the model.
+ epochs: 10000 # Number of training epochs.
+ dtype: "float32" # Data type used for computations.
+ set_memory_growth: False # Flag indicating whether to set memory growth for GPU.
+
+ learning_rate: # Parameters for learning rate scheduling.
+ initial_learning_rate: 0.001 # Initial learning rate.
+ use_lr_scheduler: False # Flag indicating whether to use learning rate scheduler.
+ decay_steps: 1000 # Number of steps between each learning rate decay.
+ decay_rate: 0.99 # Decay rate for the learning rate.
+ staircase: False # Flag indicating whether to use staircase decay.
+
update_console_output
defines the epochs at which you need to log
+parameters like loss, time taken, etc.
logging:
+ update_console_output: 5000
+
This file contains the main code to solve the Poisson equation in 2D on
+a circular domain. The code reads the input file, sets up the problem,
+and solves the Poisson equation using the fastvpinns
package.
The following libraries are imported in the main file.
+import numpy as np
+import pandas as pd
+import pytest
+import tensorflow as tf
+from pathlib import Path
+from tqdm import tqdm
+import yaml
+import sys
+import copy
+from tensorflow.keras import layers
+from tensorflow.keras import initializers
+from rich.console import Console
+import copy
+import time
+
The following imports are used from the fastvpinns
package.
Imports the geometry module from the fastvpinns
package, which
+contains the Geometry_2D
class responsible for setting up the
+geometry of the domain.
from fastvpinns.Geometry.geometry_2d import Geometry_2D
+
Imports the fespace module from the fastvpinns
package, which
+contains the FE_2D
class responsible for setting up the finite
+element spaces.
from fastvpinns.FE_2D.fespace2d import Fespace2D
+
Imports the datahandler module from the fastvpinns
package, which
+contains the DataHandler
class responsible for handling and
+converting the data to necessary shape for training purposes
from fastvpinns.DataHandler.datahandler import DataHandler
+
Imports the model module from the fastvpinns
package, which
+contains the Model
class responsible for training the neural
+network model.
from fastvpinns.Model.model import DenseModel
+
Import the Loss module from the fastvpinns
package, which
+contains the loss function of the PDE to be solved in tensor form.
from fastvpinns.physics.helmholtz2d import pde_loss_helmholtz
+
Import additional functionalities from the fastvpinns
package.
from fastvpinns.utils.plot_utils import plot_contour, plot_loss_function, plot_test_loss_function
+from fastvpinns.utils.compute_utils import compute_errors_combined
+from fastvpinns.utils.print_utils import print_table
+
The input file is read using the yaml
library.
if len(sys.argv) != 2:
+ print("Usage: python main.py <input file>")
+ sys.exit(1)
+
+ # Read the YAML file
+ with open(sys.argv[1], 'r') as f:
+ config = yaml.safe_load(f)
+
# Extract the values from the YAML file
+ i_output_path = config['experimentation']['output_path']
+
+ i_mesh_generation_method = config['geometry']['mesh_generation_method']
+ i_generate_mesh_plot = config['geometry']['generate_mesh_plot']
+ i_mesh_type = config['geometry']['mesh_type']
+ i_x_min = config['geometry']['internal_mesh_params']['x_min']
+ i_x_max = config['geometry']['internal_mesh_params']['x_max']
+ i_y_min = config['geometry']['internal_mesh_params']['y_min']
+ i_y_max = config['geometry']['internal_mesh_params']['y_max']
+ i_n_cells_x = config['geometry']['internal_mesh_params']['n_cells_x']
+ i_n_cells_y = config['geometry']['internal_mesh_params']['n_cells_y']
+ i_n_boundary_points = config['geometry']['internal_mesh_params']['n_boundary_points']
+ i_n_test_points_x = config['geometry']['internal_mesh_params']['n_test_points_x']
+ i_n_test_points_y = config['geometry']['internal_mesh_params']['n_test_points_y']
+ i_exact_solution_generation = config['geometry']['exact_solution']['exact_solution_generation']
+ i_exact_solution_file_name = config['geometry']['exact_solution']['exact_solution_file_name']
+
+ i_mesh_file_name = config['geometry']['external_mesh_params']['mesh_file_name']
+ i_boundary_refinement_level = config['geometry']['external_mesh_params'][
+ 'boundary_refinement_level'
+ ]
+ i_boundary_sampling_method = config['geometry']['external_mesh_params'][
+ 'boundary_sampling_method'
+ ]
+
+ i_fe_order = config['fe']['fe_order']
+ i_fe_type = config['fe']['fe_type']
+ i_quad_order = config['fe']['quad_order']
+ i_quad_type = config['fe']['quad_type']
+
+ i_model_architecture = config['model']['model_architecture']
+ i_activation = config['model']['activation']
+ i_use_attention = config['model']['use_attention']
+ i_epochs = config['model']['epochs']
+ i_dtype = config['model']['dtype']
+ if i_dtype == "float64":
+ i_dtype = tf.float64
+ elif i_dtype == "float32":
+ i_dtype = tf.float32
+ else:
+ print("[ERROR] The given dtype is not a valid tensorflow dtype")
+ raise ValueError("The given dtype is not a valid tensorflow dtype")
+
+ i_set_memory_growth = config['model']['set_memory_growth']
+ i_learning_rate_dict = config['model']['learning_rate']
+
+ i_beta = config['pde']['beta']
+
+ i_update_console_output = config['logging']['update_console_output']
+
all the variables which are named with the prefix i_
are input
+parameters which are read from the input file. Return to
+top
Obtain the bounndary condition and boundary values from the
+helmholtz_example.py
file and initialise the Geometry_2D
class.
+After that use the domain.read_mesh
functionality to read the
+external mesh file.
cells, boundary_points = domain.read_mesh(
+ i_mesh_file_name,
+ i_boundary_refinement_level,
+ i_boundary_sampling_method,
+ refinement_level=1,
+ )
+
Initialise the Fespace2D
class with the required parameters.
fespace = Fespace2D(
+ mesh=domain.mesh,
+ cells=cells,
+ boundary_points=boundary_points,
+ cell_type=domain.mesh_type,
+ fe_order=i_fe_order,
+ fe_type=i_fe_type,
+ quad_order=i_quad_order,
+ quad_type=i_quad_type,
+ fe_transformation_type="bilinear",
+ bound_function_dict=bound_function_dict,
+ bound_condition_dict=bound_condition_dict,
+ forcing_function=rhs,
+ output_path=i_output_path,
+ generate_mesh_plot=i_generate_mesh_plot,
+ )
+
Initialise the DataHandler
class with the required parameters.
datahandler = DataHandler2D(fespace, domain, dtype=i_dtype)
+
Setup the necesary parameters for the model and initialise the Model
+class. Before that fill the params
dictionary with the required
+parameters.
model = DenseModel(
+ layer_dims=i_model_architecture,
+ learning_rate_dict=i_learning_rate_dict,
+ params_dict=params_dict,
+ loss_function=pde_loss_cd2d,
+ input_tensors_list=[datahandler.x_pde_list, train_dirichlet_input, train_dirichlet_output],
+ orig_factor_matrices=[
+ datahandler.shape_val_mat_list,
+ datahandler.grad_x_mat_list,
+ datahandler.grad_y_mat_list,
+ ],
+ force_function_list=datahandler.forcing_function_list,
+ tensor_dtype=i_dtype,
+ use_attention=i_use_attention,
+ activation=i_activation,
+ hessian=False,
+ )
+
test_points = domain.get_test_points()
+print(f"[bold]Number of Test Points = [/bold] {test_points.shape[0]}")
+y_exact = exact_solution(test_points[:, 0], test_points[:, 1])
+
+
+# plot the exact solution
+num_epochs = i_epochs # num_epochs
+progress_bar = tqdm(
+ total=num_epochs,
+ desc='Training',
+ unit='epoch',
+ bar_format="{l_bar}{bar:40}{r_bar}{bar:-10b}",
+ colour="green",
+ ncols=100,
+)
+loss_array = [] # total loss
+test_loss_array = [] # test loss
+time_array = [] # time per epoc
+# beta - boundary loss parameters
+beta = tf.constant(i_beta, dtype=i_dtype)
+
Here the exact solution is being read from the external file. The +external solution at the test points is computed by FEM and stored in a +csv file. This sets up the test points and the exact solution. The +progress bar is initialised and the loss arrays are set up. The beta +value is set up as a constant tensor. Return to top
+for epoch in range(num_epochs):
+
+ # Train the model
+ batch_start_time = time.time()
+ loss = model.train_step(beta=beta, bilinear_params_dict=bilinear_params_dict)
+ elapsed = time.time() - batch_start_time
+
+ # print(elapsed)
+ time_array.append(elapsed)
+
+ loss_array.append(loss['loss'])
+
This train_step
function trains the model for one epoch and returns
+the loss. The loss is appended to the loss array. Then for every epoch
+where
+(epoch + 1) % i_update_console_output == 0 or epoch == num_epochs - 1:
y_pred = model(test_points).numpy()
+y_pred = y_pred.reshape(-1)
+
+error = np.abs(y_exact - y_pred)
+
+# get errors
+(
+ l2_error,
+ linf_error,
+ l2_error_relative,
+ linf_error_relative,
+ l1_error,
+ l1_error_relative,
+) = compute_errors_combined(y_exact, y_pred)
+
+loss_pde = float(loss['loss_pde'].numpy())
+loss_dirichlet = float(loss['loss_dirichlet'].numpy())
+total_loss = float(loss['loss'].numpy())
+
+# Append test loss
+test_loss_array.append(l1_error)
+
+solution_array = np.c_[y_pred, y_exact, np.abs(y_exact - y_pred)]
+domain.write_vtk(
+ solution_array,
+ output_path=i_output_path,
+ filename=f"prediction_{epoch+1}.vtk",
+ data_names=["Sol", "Exact", "Error"],
+)
+
+console.print(f"\nEpoch [bold]{epoch+1}/{num_epochs}[/bold]")
+console.print("[bold]--------------------[/bold]")
+console.print("[bold]Beta : [/bold]", beta.numpy(), end=" ")
+console.print(
+ f"Variational Losses || Pde Loss : [red]{loss_pde:.3e}[/red] Dirichlet Loss : [red]{loss_dirichlet:.3e}[/red] Total Loss : [red]{total_loss:.3e}[/red]"
+)
+console.print(
+ f"Test Losses || L1 Error : {l1_error:.3e} L2 Error : {l2_error:.3e} Linf Error : {linf_error:.3e}"
+)
+
We will compute all the test errors and write the solution to a vtk file +for a complex mesh. Further, the console output will be printed with the +loss values and the test errors. Return to top
+# Save the model
+ model.save_weights(str(Path(i_output_path) / "model_weights"))
+
+ solution_array = np.c_[y_pred, y_exact, np.abs(y_exact - y_pred)]
+ domain.write_vtk(
+ solution_array,
+ output_path=i_output_path,
+ filename=f"prediction_{epoch+1}.vtk",
+ data_names=["Sol", "Exact", "Error"],
+ )
+ # print the Error values in table
+ print_table(
+ "Error Values",
+ ["Error Type", "Value"],
+ [
+ "L2 Error",
+ "Linf Error",
+ "Relative L2 Error",
+ "Relative Linf Error",
+ "L1 Error",
+ "Relative L1 Error",
+ ],
+ [l2_error, linf_error, l2_error_relative, linf_error_relative, l1_error, l1_error_relative],
+ )
+
+ # print the time values in table
+ print_table(
+ "Time Values",
+ ["Time Type", "Value"],
+ [
+ "Time per Epoch(s) - Median",
+ "Time per Epoch(s) IQR-25% ",
+ "Time per Epoch(s) IQR-75% ",
+ "Mean (s)",
+ "Epochs per second",
+ "Total Train Time",
+ ],
+ [
+ np.median(time_array),
+ np.percentile(time_array, 25),
+ np.percentile(time_array, 75),
+ np.mean(time_array),
+ int(i_epochs / np.sum(time_array)),
+ np.sum(time_array),
+ ],
+ )
+
+ # save all the arrays as numpy arrays
+ np.savetxt(str(Path(i_output_path) / "loss_function.txt"), np.array(loss_array))
+ np.savetxt(str(Path(i_output_path) / "prediction.txt"), y_pred)
+ np.savetxt(str(Path(i_output_path) / "exact.txt"), y_exact)
+ np.savetxt(str(Path(i_output_path) / "error.txt"), error)
+ np.savetxt(str(Path(i_output_path) / "time_per_epoch.txt"), np.array(time_array))
+
This part of the code saves the model weights, writes the solution to a +vtk file, prints the error values in a table, prints the time values in +a table, and saves all the arrays as numpy arrays.
+All the outputs will be saved in the output directory specified in the +input file. The output directory will contain the following files: - +prediction_{epoch}.vtk : The solution file for each epoch. - +loss_function.txt : The loss function values for each epoch. - +prediction.txt : The predicted values at last epoch at the test points. +- exact.txt : The exact values at last epoch at the test points. - +error.txt : The error values at last epoch at the test points. - +time_per_epoch.txt : The time taken for each epoch.
+ +This example demonstrates how to solve a Poisson equation in 2D on a
+circular domain using the fastvpinns
package.
+All the necesary files can be found in the examples folder of the fastvpinns GitHub repository
The Poisson equation is given by
+where (\(\Omega\)) is the circular domain and (f) is the source +term. The boundary conditions are given by
+For this problem, the parameters are
+The exact solution is given by
+The computational domain is a circular domain with radius 1 centered at +(0, 0).
+ +To run the code, execute the following command:
+python3 main_poisson2d.py input.yaml
+
This file hosts all the details about the bilinear parameters for the +PDE, boundary conditions, source term, and the exact solution.
+The function circle_boundary
returns the boundary value for a given
+component of the boundary. The function get_boundary_function_dict
+returns a dictionary of boundary functions. The key of the dictionary is
+the boundary id and the value is the boundary function. The function
+get_bound_cond_dict
returns a dictionary of boundary conditions. The
+key of the dictionary is the boundary id and the value is the boundary
+condition.
Note : As of now, only Dirichlet boundary conditions are supported.
+def circle_boundary(x, y):
+ """
+ This function will return the boundary value for given component of a boundary
+ """
+ omegaX = 2.0 * np.pi
+ omegaY = 2.0 * np.pi
+ return -1.0 * np.sin(omegaX * x) * np.sin(omegaY * y)
+
+def get_boundary_function_dict():
+ """
+ This function will return a dictionary of boundary functions
+ """
+ return {1000: circle_boundary}
+
+def get_bound_cond_dict():
+ """
+ This function will return a dictionary of boundary conditions
+ """
+ return {1000: "dirichlet"}
+
The function rhs
returns the value of the source term at a given
+point.
def rhs(x, y):
+ """
+ This function will return the value of the rhs at a given point
+ """
+ # f_temp = 32 * (x * (1 - x) + y * (1 - y))
+ # f_temp = 1
+
+ omegaX = 2.0 * np.pi
+ omegaY = 2.0 * np.pi
+ f_temp = -2.0 * (omegaX**2) * (np.sin(omegaX * x) * np.sin(omegaY * y))
+
+ return f_temp
+
The function exact_solution
returns the value of the exact solution
+at a given point.
def exact_solution(x, y):
+ """
+ This function will return the exact solution at a given point
+ """
+
+ # val = 16 * x * (1 - x) * y * (1 - y)
+ omegaX = 2.0 * np.pi
+ omegaY = 2.0 * np.pi
+ val = -1.0 * np.sin(omegaX * x) * np.sin(omegaY * y)
+
+ return val
+
The function get_bilinear_params_dict
returns a dictionary of
+bilinear parameters. The dictionary contains the values of the
+parameters \(\epsilon\) (diffusion coefficient).
Note : If any of the bilinear parameters are not present in the +dictionary (for the cd2d model), then the code will throw an error.
+def get_bilinear_params_dict():
+ """
+ This function will return a dictionary of bilinear parameters
+ """
+ eps = 1.0
+
+ return {"eps": eps}
+
This is the file that contains all the details about the problem. The +input file is in the YAML format. The input file for this example is +given below. The contents of the yaml files are as follows
+Defines the output path where the results will be saved.
+experimentation:
+ output_path: "output/helmholtz/1"
+
It contains the details about the geometry of the domain. The mesh
+generation method can be either “internal” or “external”. If the mesh
+generation method is “internal”, then the internal_mesh_params
are
+used to generate the mesh. If the mesh generation method is “external”,
+then the mesh is read from the file specified in the mesh_file
+parameter.
In this case, we will use an external mesh. The mesh
+../meshes/circle_quad.mesh
is generated using the Gmsh software.
+The mesh needs to have physical elements defined for the boundary. In
+this case, the physical element is defined as 1000 (which is defined
+in the circle_boundary
function in the sin_cos.py
file).
exact_solution_generation
is set to “internal” which means that
+the exact solution is generated using the exact_solution
function
+in the sin_cos.py
file. For external check the other examples
+cd2d_gear
mesh_type
is set to “quadrilateral” which means that the mesh is
+a quadrilateral mesh. Note: As of now, only quadrilateral meshes are
+supported.
boundary_refinement_level
is set to 4 which means that the
+boundary is refined 4 times. (i.e), when the mesh is read, only the
+boundary points of an edge in quadrilateral mesh are read. this
+refinement will refine the boundary points to get more boundary
+points within the edge.
boundary_sampling_method
is set to “uniform” which means that the
+boundary points are sampled using the “uniform” method. (Use only
+uniform sampling as of now.)
generate_mesh_plot
is set to True which means that the mesh plot
+is generated and saved in the output directory.
experimentation:
+ output_path: "output/poisson2d/1" # Path to the output directory where the results will be saved.
+
+geometry:
+ mesh_generation_method: "external" # Method for generating the mesh. Can be "internal" or "external".
+ generate_mesh_plot: True # Flag indicating whether to generate a plot of the mesh.
+
+ # internal mesh generated quadrilateral mesh, depending on the parameters specified below.
+
+ internal_mesh_params: # Parameters for internal mesh generation method.
+ x_min: 0 # Minimum x-coordinate of the domain.
+ x_max: 1 # Maximum x-coordinate of the domain.
+ y_min: 0 # Minimum y-coordinate of the domain.
+ y_max: 1 # Maximum y-coordinate of the domain.
+ n_cells_x: 4 # Number of cells in the x-direction.
+ n_cells_y: 4 # Number of cells in the y-direction.
+ n_boundary_points: 400 # Number of boundary points.
+ n_test_points_x: 100 # Number of test points in the x-direction.
+ n_test_points_y: 100 # Number of test points in the y-direction.
+
+ exact_solution:
+ exact_solution_generation: "internal" # whether the exact solution needs to be read from external file.
+ exact_solution_file_name: "" # External solution file name.
+
+ mesh_type: "quadrilateral" # Type of mesh. Can be "quadrilateral" or other supported types.
+
+ external_mesh_params: # Parameters for external mesh generation method.
+ mesh_file_name: "../meshes/circle_quad.mesh" # Path to the external mesh file (should be a .mesh file).
+ boundary_refinement_level: 4 # Level of refinement for the boundary.
+ boundary_sampling_method: "lhs" # Method for sampling the boundary. Can be "uniform" or "lhs".
+
This section contains the details about the finite element spaces.
+fe:
+ fe_order: 2 # Order of the finite element basis functions.
+ fe_type: "legendre" # Type of finite element basis functions. Can be "jacobi" or other supported types.
+ quad_order: 3 # Order of the quadrature rule.
+ quad_type: "gauss-jacobi" # Type of quadrature rule. Can be "gauss-jacobi" or other supported types.
+
Here the fe_order
is set to 6 which means it has 6 basis functions
+in each direction. The quad_order
is set to 10 which means it uses a
+10-points in each direction for the quadrature rule. The supported
+quadrature rules are “gauss-jacobi” and “gauss-legendre”. In this
+version of code, both “jacobi” and “legendre” refer to the same basis
+functions (to maintain backward compatibility). The basis functions are
+special type of Jacobi polynomials defined by
, where $J_{n} is the nth Jacobi polynomial.
+ +This value provides the beta values for the Dirichlet boundary conditions. The beta values are the multipliers that are used to multiply the boundary losses. The total loss is calculated as the sum of the PDE loss and the Dirichlet boundary loss, weighted by the beta values:
+pde:
+ beta: 10 # Parameter for the PDE.
+
The model section contains the details about the dense model to be used.
+The model architecture is given by the model_architecture
parameter.
+The activation function used in the model is given by the activation
+parameter. The epochs
parameter is the number of training epochs.
+The dtype
parameter is the data type used for computations. The
+learning_rate
section contains the parameters for learning rate
+scheduling. The initial_learning_rate
parameter is the initial
+learning rate. The use_lr_scheduler
parameter is a flag indicating
+whether to use the learning rate scheduler. The decay_steps
+parameter is the number of steps between each learning rate decay. The
+decay_rate
parameter is the decay rate for the learning rate. The
+staircase
parameter is a flag indicating whether to use the
+staircase decay.
Any parameter which are not mentioned above are archived parameters,
+which are not used in the current version of the code. (like
+use_attention
, set_memory_growth
)
model:
+ model_architecture: [2, 30, 30, 30, 1] # Architecture of the neural network model.
+ activation: "tanh" # Activation function used in the neural network.
+ use_attention: False # Flag indicating whether to use attention mechanism in the model.
+ epochs: 5000 # Number of training epochs.
+ dtype: "float32" # Data type used for computations.
+ set_memory_growth: False # Flag indicating whether to set memory growth for GPU.
+
+ learning_rate: # Parameters for learning rate scheduling.
+ initial_learning_rate: 0.001 # Initial learning rate.
+ use_lr_scheduler: False # Flag indicating whether to use learning rate scheduler.
+ decay_steps: 1000 # Number of steps between each learning rate decay.
+ decay_rate: 0.99 # Decay rate for the learning rate.
+ staircase: False # Flag indicating whether to use staircase decay.
+
update_console_output
defines the epochs at which you need to log
+parameters like loss, time taken, etc.
logging:
+ update_console_output: 2000
+
This file contains the main code to solve the Poisson equation in 2D on
+a circular domain. The code reads the input file, sets up the problem,
+and solves the Poisson equation using the fastvpinns
package.
The following libraries are imported in the main file.
+import numpy as np
+import pandas as pd
+import pytest
+import tensorflow as tf
+from pathlib import Path
+from tqdm import tqdm
+import yaml
+import sys
+import copy
+from tensorflow.keras import layers
+from tensorflow.keras import initializers
+from rich.console import Console
+import copy
+import time
+
The following imports are used from the fastvpinns
package.
Imports the geometry module from the fastvpinns
package, which
+contains the Geometry_2D
class responsible for setting up the
+geometry of the domain.
from fastvpinns.Geometry.geometry_2d import Geometry_2D
+
Imports the fespace module from the fastvpinns
package, which
+contains the FE_2D
class responsible for setting up the finite
+element spaces.
from fastvpinns.FE_2D.fespace2d import Fespace2D
+
Imports the datahandler module from the fastvpinns
package, which
+contains the DataHandler
class responsible for handling and
+converting the data to necessary shape for training purposes
from fastvpinns.DataHandler.datahandler import DataHandler
+
Imports the model module from the fastvpinns
package, which
+contains the Model
class responsible for training the neural
+network model.
from fastvpinns.Model.model import DenseModel
+
Import the Loss module from the fastvpinns
package, which
+contains the loss function of the PDE to be solved in tensor form.
from fastvpinns.physics.helmholtz2d import pde_loss_helmholtz
+
Import additional functionalities from the fastvpinns
package.
from fastvpinns.utils.plot_utils import plot_contour, plot_loss_function, plot_test_loss_function
+from fastvpinns.utils.compute_utils import compute_errors_combined
+from fastvpinns.utils.print_utils import print_table
+
The input file is read using the yaml
library.
if len(sys.argv) != 2:
+ print("Usage: python main.py <input file>")
+ sys.exit(1)
+
+ # Read the YAML file
+ with open(sys.argv[1], 'r') as f:
+ config = yaml.safe_load(f)
+
# Extract the values from the YAML file
+ i_output_path = config['experimentation']['output_path']
+
+ i_mesh_generation_method = config['geometry']['mesh_generation_method']
+ i_generate_mesh_plot = config['geometry']['generate_mesh_plot']
+ i_mesh_type = config['geometry']['mesh_type']
+ i_x_min = config['geometry']['internal_mesh_params']['x_min']
+ i_x_max = config['geometry']['internal_mesh_params']['x_max']
+ i_y_min = config['geometry']['internal_mesh_params']['y_min']
+ i_y_max = config['geometry']['internal_mesh_params']['y_max']
+ i_n_cells_x = config['geometry']['internal_mesh_params']['n_cells_x']
+ i_n_cells_y = config['geometry']['internal_mesh_params']['n_cells_y']
+ i_n_boundary_points = config['geometry']['internal_mesh_params']['n_boundary_points']
+ i_n_test_points_x = config['geometry']['internal_mesh_params']['n_test_points_x']
+ i_n_test_points_y = config['geometry']['internal_mesh_params']['n_test_points_y']
+ i_exact_solution_generation = config['geometry']['exact_solution']['exact_solution_generation']
+ i_exact_solution_file_name = config['geometry']['exact_solution']['exact_solution_file_name']
+
+ i_mesh_file_name = config['geometry']['external_mesh_params']['mesh_file_name']
+ i_boundary_refinement_level = config['geometry']['external_mesh_params'][
+ 'boundary_refinement_level'
+ ]
+ i_boundary_sampling_method = config['geometry']['external_mesh_params'][
+ 'boundary_sampling_method'
+ ]
+
+ i_fe_order = config['fe']['fe_order']
+ i_fe_type = config['fe']['fe_type']
+ i_quad_order = config['fe']['quad_order']
+ i_quad_type = config['fe']['quad_type']
+
+ i_model_architecture = config['model']['model_architecture']
+ i_activation = config['model']['activation']
+ i_use_attention = config['model']['use_attention']
+ i_epochs = config['model']['epochs']
+ i_dtype = config['model']['dtype']
+ if i_dtype == "float64":
+ i_dtype = tf.float64
+ elif i_dtype == "float32":
+ i_dtype = tf.float32
+ else:
+ print("[ERROR] The given dtype is not a valid tensorflow dtype")
+ raise ValueError("The given dtype is not a valid tensorflow dtype")
+
+ i_set_memory_growth = config['model']['set_memory_growth']
+ i_learning_rate_dict = config['model']['learning_rate']
+
+ i_beta = config['pde']['beta']
+
+ i_update_console_output = config['logging']['update_console_output']
+
all the variables which are named with the prefix i_
are input
+parameters which are read from the input file. Return to
+top
Obtain the bounndary condition and boundary values from the
+sin_cos.py
file and initialise the Geometry_2D
class. After that
+use the domain.read_mesh
functionality to read the external mesh
+file.
cells, boundary_points = domain.read_mesh(
+ i_mesh_file_name,
+ i_boundary_refinement_level,
+ i_boundary_sampling_method,
+ refinement_level=1,
+ )
+
Initialise the Fespace2D
class with the required parameters.
fespace = Fespace2D(
+ mesh=domain.mesh,
+ cells=cells,
+ boundary_points=boundary_points,
+ cell_type=domain.mesh_type,
+ fe_order=i_fe_order,
+ fe_type=i_fe_type,
+ quad_order=i_quad_order,
+ quad_type=i_quad_type,
+ fe_transformation_type="bilinear",
+ bound_function_dict=bound_function_dict,
+ bound_condition_dict=bound_condition_dict,
+ forcing_function=rhs,
+ output_path=i_output_path,
+ generate_mesh_plot=i_generate_mesh_plot,
+ )
+
Initialise the DataHandler
class with the required parameters.
datahandler = DataHandler2D(fespace, domain, dtype=i_dtype)
+
Setup the necesary parameters for the model and initialise the Model
+class. Before that fill the params
dictionary with the required
+parameters.
model = DenseModel(
+ layer_dims=i_model_architecture,
+ learning_rate_dict=i_learning_rate_dict,
+ params_dict=params_dict,
+ loss_function=pde_loss_cd2d,
+ input_tensors_list=[datahandler.x_pde_list, train_dirichlet_input, train_dirichlet_output],
+ orig_factor_matrices=[
+ datahandler.shape_val_mat_list,
+ datahandler.grad_x_mat_list,
+ datahandler.grad_y_mat_list,
+ ],
+ force_function_list=datahandler.forcing_function_list,
+ tensor_dtype=i_dtype,
+ use_attention=i_use_attention,
+ activation=i_activation,
+ hessian=False,
+ )
+
test_points = domain.get_test_points()
+print(f"[bold]Number of Test Points = [/bold] {test_points.shape[0]}")
+y_exact = exact_solution(test_points[:, 0], test_points[:, 1])
+
+
+# plot the exact solution
+num_epochs = i_epochs # num_epochs
+progress_bar = tqdm(
+ total=num_epochs,
+ desc='Training',
+ unit='epoch',
+ bar_format="{l_bar}{bar:40}{r_bar}{bar:-10b}",
+ colour="green",
+ ncols=100,
+)
+loss_array = [] # total loss
+test_loss_array = [] # test loss
+time_array = [] # time per epoc
+# beta - boundary loss parameters
+beta = tf.constant(i_beta, dtype=i_dtype)
+
Here the exact solution is being read from the external file. The +external solution at the test points is computed by FEM and stored in a +csv file. This sets up the test points and the exact solution. The +progress bar is initialised and the loss arrays are set up. The beta +value is set up as a constant tensor. Return to top
+for epoch in range(num_epochs):
+
+ # Train the model
+ batch_start_time = time.time()
+ loss = model.train_step(beta=beta, bilinear_params_dict=bilinear_params_dict)
+ elapsed = time.time() - batch_start_time
+
+ # print(elapsed)
+ time_array.append(elapsed)
+
+ loss_array.append(loss['loss'])
+
This train_step
function trains the model for one epoch and returns
+the loss. The loss is appended to the loss array. Then for every epoch
+where
+(epoch + 1) % i_update_console_output == 0 or epoch == num_epochs - 1:
y_pred = model(test_points).numpy()
+y_pred = y_pred.reshape(-1)
+
+error = np.abs(y_exact - y_pred)
+
+# get errors
+(
+ l2_error,
+ linf_error,
+ l2_error_relative,
+ linf_error_relative,
+ l1_error,
+ l1_error_relative,
+) = compute_errors_combined(y_exact, y_pred)
+
+loss_pde = float(loss['loss_pde'].numpy())
+loss_dirichlet = float(loss['loss_dirichlet'].numpy())
+total_loss = float(loss['loss'].numpy())
+
+# Append test loss
+test_loss_array.append(l1_error)
+
+solution_array = np.c_[y_pred, y_exact, np.abs(y_exact - y_pred)]
+domain.write_vtk(
+ solution_array,
+ output_path=i_output_path,
+ filename=f"prediction_{epoch+1}.vtk",
+ data_names=["Sol", "Exact", "Error"],
+)
+
+console.print(f"\nEpoch [bold]{epoch+1}/{num_epochs}[/bold]")
+console.print("[bold]--------------------[/bold]")
+console.print("[bold]Beta : [/bold]", beta.numpy(), end=" ")
+console.print(
+ f"Variational Losses || Pde Loss : [red]{loss_pde:.3e}[/red] Dirichlet Loss : [red]{loss_dirichlet:.3e}[/red] Total Loss : [red]{total_loss:.3e}[/red]"
+)
+console.print(
+ f"Test Losses || L1 Error : {l1_error:.3e} L2 Error : {l2_error:.3e} Linf Error : {linf_error:.3e}"
+)
+
We will compute all the test errors and write the solution to a vtk file +for a complex mesh. Further, the console output will be printed with the +loss values and the test errors. Return to top
+# Save the model
+ model.save_weights(str(Path(i_output_path) / "model_weights"))
+
+ solution_array = np.c_[y_pred, y_exact, np.abs(y_exact - y_pred)]
+ domain.write_vtk(
+ solution_array,
+ output_path=i_output_path,
+ filename=f"prediction_{epoch+1}.vtk",
+ data_names=["Sol", "Exact", "Error"],
+ )
+ # print the Error values in table
+ print_table(
+ "Error Values",
+ ["Error Type", "Value"],
+ [
+ "L2 Error",
+ "Linf Error",
+ "Relative L2 Error",
+ "Relative Linf Error",
+ "L1 Error",
+ "Relative L1 Error",
+ ],
+ [l2_error, linf_error, l2_error_relative, linf_error_relative, l1_error, l1_error_relative],
+ )
+
+ # print the time values in table
+ print_table(
+ "Time Values",
+ ["Time Type", "Value"],
+ [
+ "Time per Epoch(s) - Median",
+ "Time per Epoch(s) IQR-25% ",
+ "Time per Epoch(s) IQR-75% ",
+ "Mean (s)",
+ "Epochs per second",
+ "Total Train Time",
+ ],
+ [
+ np.median(time_array),
+ np.percentile(time_array, 25),
+ np.percentile(time_array, 75),
+ np.mean(time_array),
+ int(i_epochs / np.sum(time_array)),
+ np.sum(time_array),
+ ],
+ )
+
+ # save all the arrays as numpy arrays
+ np.savetxt(str(Path(i_output_path) / "loss_function.txt"), np.array(loss_array))
+ np.savetxt(str(Path(i_output_path) / "prediction.txt"), y_pred)
+ np.savetxt(str(Path(i_output_path) / "exact.txt"), y_exact)
+ np.savetxt(str(Path(i_output_path) / "error.txt"), error)
+ np.savetxt(str(Path(i_output_path) / "time_per_epoch.txt"), np.array(time_array))
+
This part of the code saves the model weights, writes the solution to a +vtk file, prints the error values in a table, prints the time values in +a table, and saves all the arrays as numpy arrays.
+All the outputs will be saved in the output directory specified in the +input file. The output directory will contain the following files: - +prediction_{epoch}.vtk : The solution file for each epoch. - +loss_function.txt : The loss function values for each epoch. - +prediction.txt : The predicted values at last epoch at the test points. +- exact.txt : The exact values at last epoch at the test points. - +error.txt : The error values at last epoch at the test points. - +time_per_epoch.txt : The time taken for each epoch.
+ +In this example, we will learn how to use hard boundary constraints using FastVPINNs. +All the necesary files can be found in the examples folder of the fastvpinns GitHub repository
+where
+We begin by introducing the various files required to run this example
+Main File - main_poisson2d_hard.py: The main file is +used to run the experiment.
+ +The code in this example can be run using
+python3 main_poisson2d_hard.py input.yaml
+
The example file, sin_cos.py
, defines the boundary conditions and
+boundary values, the forcing function and exact function (if test error
+needs to be calculated), bilinear parameters and the actual value of the
+parameter that needs to be estimated (if the error between the actual
+and estimated parameter needs to be calculated) #Defining boundary
+values Since this example ecforces zero Dirichlet boundary conditions
+using hard constraints, the boundary functions defined in the example
+file are not used. Instead, the ansatz function for hard boundary
+constraints is defined in the main file
rhs
can be used to define the forcing function \(f\).
def rhs(x, y):
+ """
+ This function will return the value of the rhs at a given point
+ """
+ # f_temp = 32 * (x * (1 - x) + y * (1 - y))
+ # f_temp = 1
+
+ omegaX = 4.0 * np.pi
+ omegaY = 4.0 * np.pi
+ f_temp = -2.0 * (omegaX**2) * (np.sin(omegaX * x) * np.sin(omegaY * y))
+
+ return f_temp
+
The bilinear parameters like diffusion constant can be defined by
+get_bilinear_params_dict
def get_bilinear_params_dict():
+ """
+ This function will return a dictionary of bilinear parameters
+ """
+ eps = 1.0
+
+ return {"eps": eps}
+
Here, eps
denoted the diffusion constant.
The input file, input_inverse.yaml
, is used to define inputs to your
+solver. These will usually parameters that will changed often throughout
+your experimentation, hence it is best practice to pass these parameters
+externally. The input file is divided based on the modules which use the
+parameter in question, as follows - #``experimentation`` This
+contains output_path
, a string which specifies which folder will be
+used to store your outputs.
This section defines the geometrical parameters for your domain. 1. In
+this example, we set the mesh_generation_method
as "internal"
.
+This generates a regular quadrilateral domain with a uniform mesh. 2.
+The parameters in internal_mesh_params
define the x and y limits of
+the quadrilateral domain(xmin
, xmax
, ymin
and ymax
),
+number of cells in the domain in the x and y direction (n_cells_x
+and n_cells_y
), number of total boundary points
+(n_boundary_points
) and number of test points in x and y direction
+(n_test_points_x
and n_test_points_y
). 3. mesh_type
:
+FastVPINNs currently provides support for quadrilateral elements only.
+4. external_mesh_params
can be used to specify parameters for the
+external mesh, and can be ignored for this example
The parameters related to the finite element space are defined here. 1.
+fe_order
sets the order of the finite element test functions. 2.
+fe_type
set which type of polynomial will be used as the finite
+element test function. 3. quad_order
is the number of quadrature in
+each direction in each cell. Thus the total number of quadrature points
+in each cell will be quad_order
\(^2\) 4. quad_type
+specifies the quadrature rule to be used.
beta
specifies the weight by which the boundary loss will be
+multiplied before being added to the PDE loss.
The parameters pertaining to the neural network are specified here. 1.
+model_architecture
is used to specify the dimensions of the neural
+network. In this example, [2, 30, 30, 30, 1] corresponds to a neural
+network with 2 inputs (for a 2-dimensional problem), 1 output (for a
+scalar problem) and 3 hidden layers with 30 neurons each. 2.
+activation
specifies the activation function to be used. 3.
+use_attention
specifies if attnention layers are to be used in the
+model. This feature is currently under development and hence should be
+set to false
for now. 4. epochs
is the number of iterations for
+which the network must be trained. 5. dtype
specifies which datatype
+(float32
or float64
) will be used for the tensor calculations.
+6. set_memory_growth
, when set to True
will enable tensorflow’s
+memory growth function, restricting the memory usage on the GPU. This is
+currently under development and must be set to False
for now. 7.
+learning_rate
sets the learning rate initial_learning_rate
if a
+constant learning rate is used. A learning rate scheduler can be used by
+toggling use_lr_scheduler
to True and setting the corresponding
+decay parameters below it.
It specifies the frequency with which the progress bar and console +output will be updated, and at what interval will inference be carried +out to print the solution image in the output folder.
+ +This is the main file which needs to be run for the experiment, with the
+input file as an argument. For the example, we will use the main file
+main_poisson2d_hard.py
Following are the key components of a FastVPINNs main file
+from fastvpinns.data.datahandler2d import DataHandler2D
+from fastvpinns.FE_2D.fespace2d import Fespace2D
+from fastvpinns.Geometry.geometry_2d import Geometry_2D
+
Will import the functions related to setting up the finite element +space, 2D Geometry and the datahandler required to manage data and make +it available to the model.
+from fastvpinns.model.model_hard import DenseModel_Hard
+
Will import the model file where the neural network and its training
+function is defined. The model file model_hard.py
contains the
+DenseModel_Hard
class. The call
function in this model applies
+the hard boundary constraint function to the output of the neural
+network, and the train_step
function does not add a supervised
+boundary loss to the PDE residual for training.
from fastvpinns.physics.poisson2d import pde_loss_poisson
+
Imports the loss function for the 2-dimensional Poisson problem.
+from fastvpinns.utils.compute_utils import compute_errors_combined
+from fastvpinns.utils.plot_utils import plot_contour, plot_loss_function, plot_test_loss_function
+from fastvpinns.utils.print_utils import print_table
+
Imports functions to calculate the loss, plot the results and print +outputs to the console.
+The input file is loaded into config
and the input parameters are
+read and assigned to their respective variables.
Geometry_2D
objectdomain = Geometry_2D(i_mesh_type, i_mesh_generation_method, i_n_test_points_x, i_n_test_points_y, i_output_path)
+
will instantiate a Geometry_2D
object, domain
, with the mesh
+type, mesh generation method and test points. In our example, the mesh
+generation method is internal
, so the cells and boundary points will
+be obtained using the generate_quad_mesh_internal
method.
cells, boundary_points = domain.generate_quad_mesh_internal(
+ x_limits=[i_x_min, i_x_max],
+ y_limits=[i_y_min, i_y_max],
+ n_cells_x=i_n_cells_x,
+ n_cells_y=i_n_cells_y,
+ num_boundary_points=i_n_boundary_points,
+)
+
As explained in the example file section, the +boundary conditions and values are read as a dictionary from the example +file
+bound_function_dict, bound_condition_dict = get_boundary_function_dict(), get_bound_cond_dict()
+
fespace = Fespace2D(
+ mesh=domain.mesh,
+ cells=cells,
+ boundary_points=boundary_points,
+ cell_type=domain.mesh_type,
+ fe_order=i_fe_order,
+ fe_type=i_fe_type,
+ quad_order=i_quad_order,
+ quad_type=i_quad_type,
+ fe_transformation_type="bilinear",
+ bound_function_dict=bound_function_dict,
+ bound_condition_dict=bound_condition_dict,
+ forcing_function=rhs,
+ output_path=i_output_path,
+)
+
fespace
will contain all the information about the finite element
+space, including those read from the input file
The ansatz function for applying zero Dirichlet hard boundary contraints
+can be defined using apply_hard_boundary_constraints
@tf.function
+def apply_hard_boundary_constraints(inputs, x):
+ """This method applies hard boundary constraints to the model.
+ :param inputs: Input tensor
+ :type inputs: tf.Tensor
+ :param x: Output tensor from the model
+ :type x: tf.Tensor
+ :return: Output tensor with hard boundary constraints
+ :rtype: tf.Tensor
+ """
+ ansatz = (
+ tf.tanh(4.0 * np.pi * inputs[:, 0:1])
+ * tf.tanh(4.0 * np.pi * inputs[:, 1:2])
+ * tf.tanh(4.0 * np.pi * (inputs[:, 0:1] - 1.0))
+ * tf.tanh(4.0 * np.pi * (inputs[:, 1:2] - 1.0))
+ )
+ ansatz = tf.cast(ansatz, i_dtype)
+ return ansatz * x
+
Here, the ansatz we use is of the form +\(\tanh{(4\pi x)}\times\tanh{(4\pi(x-1))}\times\tanh{(4\pi y)}\times\tanh{(4\pi(y-1))}\)
+model = DenseModel_Hard(
+ layer_dims=[2, 30, 30, 30, 1],
+ learning_rate_dict=i_learning_rate_dict,
+ params_dict=params_dict,
+ loss_function=pde_loss_poisson,
+ input_tensors_list=[datahandler.x_pde_list, train_dirichlet_input, train_dirichlet_output],
+ orig_factor_matrices=[
+ datahandler.shape_val_mat_list,
+ datahandler.grad_x_mat_list,
+ datahandler.grad_y_mat_list,
+ ],
+ force_function_list=datahandler.forcing_function_list,
+ tensor_dtype=i_dtype,
+ use_attention=i_use_attention,
+ activation=i_activation,
+ hessian=False,
+ hard_constraint_function=apply_hard_boundary_constraints,
+)
+
DenseModel_Hard
is a model written for inverse problems with
+spatially varying parameter estimation. In this problem, we pass the
+loss function pde_loss_poisson
from the physics
file
+poisson2d.py
.
We are now ready to train the model to approximate the solution of the +PDE.
+for epoch in range(num_epochs):
+
+ # Train the model
+ batch_start_time = time.time()
+
+ loss = model.train_step(beta=beta, bilinear_params_dict=bilinear_params_dict)
+ ...
+
In this example, we will learn to solve the Helmholtz equation in 2D +All the necesary files can be found in the examples folder of the fastvpinns GitHub repository
+where
+For this problem, the parameters are
+The exact solution is given by
+We begin by introducing the various files required to run this example
+`experimentation`
`geometry`
`fe`
`pde`
`model`
`logging`
The code in this example can be run using
+python3 main_helmholtz_hard.py input.yaml
+
The example file, helmholtz_example.py
, defines the boundary
+conditions and boundary values, the forcing function and exact function
+(if test error needs to be calculated), bilinear parameters and the
+actual value of the parameter that needs to be estimated (if the error
+between the actual and estimated parameter needs to be calculated) ###
+Defining boundary values Since this example ecforces zero Dirichlet
+boundary conditions using hard constraints, the boundary functions
+defined in the example file are not used. Instead, the ansatz function
+for hard boundary constraints is defined in the main
+file
rhs
can be used to define the forcing function \(f\).
def rhs(x, y):
+ """
+ This function will return the value of the rhs at a given point
+ """
+ # f_temp = 32 * (x * (1 - x) + y * (1 - y))
+ # f_temp = 1
+
+ term1 = 2 * np.pi * np.cos(np.pi * y) * np.sin(np.pi * x)
+ term2 = 2 * np.pi * np.cos(np.pi * x) * np.sin(np.pi * y)
+ term3 = (x + y) * np.sin(np.pi * x) * np.sin(np.pi * y)
+ term4 = -2 * (np.pi**2) * (x + y) * np.sin(np.pi * x) * np.sin(np.pi * y)
+
+ result = term1 + term2 + term3 + term4
+ return result
+
The bilinear parameters like diffusion constant can be defined by
+get_bilinear_params_dict
def get_bilinear_params_dict():
+ """
+ This function will return a dictionary of bilinear parameters
+ """
+ k = 1.0
+ eps = 1.0
+
+ return {"k": k, "eps": eps}
+
Here, eps
denoted the diffusion constant.
The input file, input.yaml
, is used to define inputs to your solver.
+These will usually parameters that will changed often throughout your
+experimentation, hence it is best practice to pass these parameters
+externally. The input file is divided based on the modules which use the
+parameter in question, as follows - #``experimentation`` This
+contains output_path
, a string which specifies which folder will be
+used to store your outputs.
geometry
This section defines the geometrical parameters for your domain. 1. In
+this example, we set the mesh_generation_method
as "internal"
.
+This generates a regular quadrilateral domain with a uniform mesh. 2.
+The parameters in internal_mesh_params
define the x and y limits of
+the quadrilateral domain(xmin
, xmax
, ymin
and ymax
),
+number of cells in the domain in the x and y direction (n_cells_x
+and n_cells_y
), number of total boundary points
+(n_boundary_points
) and number of test points in x and y direction
+(n_test_points_x
and n_test_points_y
). 3. mesh_type
:
+FastVPINNs currently provides support for quadrilateral elements only.
+4. external_mesh_params
can be used to specify parameters for the
+external mesh, and can be ignored for this example
fe
The parameters related to the finite element space are defined here. 1.
+fe_order
sets the order of the finite element test functions. 2.
+fe_type
set which type of polynomial will be used as the finite
+element test function. 3. quad_order
is the number of quadrature in
+each direction in each cell. Thus the total number of quadrature points
+in each cell will be quad_order
\(^2\) 4. quad_type
+specifies the quadrature rule to be used.
pde
beta
specifies the weight by which the boundary loss will be
+multiplied before being added to the PDE loss.
model
The parameters pertaining to the neural network are specified here. 1.
+model_architecture
is used to specify the dimensions of the neural
+network. In this example, [2, 30, 30, 30, 1] corresponds to a neural
+network with 2 inputs (for a 2-dimensional problem), 1 output (for a
+scalar problem) and 3 hidden layers with 30 neurons each. 2.
+activation
specifies the activation function to be used. 3.
+use_attention
specifies if attention layers are to be used in the
+model. This feature is currently under development and hence should be
+set to false
for now. 4. epochs
is the number of iterations for
+which the network must be trained. 5. dtype
specifies which datatype
+(float32
or float64
) will be used for the tensor calculations.
+6. set_memory_growth
, when set to True
will enable tensorflow’s
+memory growth function, restricting the memory usage on the GPU. This is
+currently under development and must be set to False
for now. 7.
+learning_rate
sets the learning rate initial_learning_rate
if a
+constant learning rate is used. A learning rate scheduler can be used by
+toggling use_lr_scheduler
to True and setting the corresponding
+decay parameters below it.
logging
It specifies the frequency with which the progress bar and console +output will be updated, and at what interval will inference be carried +out to print the solution image in the output folder.
+ +This is the main file which needs to be run for the experiment, with the
+input file as an argument. For the example, we will use the main file
+main_helmholtz.py
Following are the key components of a FastVPINNs main file
+from fastvpinns.data.datahandler2d import DataHandler2D
+from fastvpinns.FE_2D.fespace2d import Fespace2D
+from fastvpinns.Geometry.geometry_2d import Geometry_2D
+
Will import the functions related to setting up the finite element +space, 2D Geometry and the datahandler required to manage data and make +it available to the model.
+from fastvpinns.model.modelimport DenseModel
+
Will import the model file where the neural network and its training
+function is defined. The model file model.py
contains the
+DenseModel
class. the train_step
function of this model is used
+to train the model.
from fastvpinns.physics.poisson2d import pde_loss_helmholtz
+
Imports the loss function for the 2-dimensional Poisson problem.
+from fastvpinns.utils.compute_utils import compute_errors_combined
+from fastvpinns.utils.plot_utils import plot_contour, plot_loss_function, plot_test_loss_function
+from fastvpinns.utils.print_utils import print_table
+
Imports functions to calculate the loss, plot the results and print +outputs to the console.
+The input file is loaded into config
and the input parameters are
+read and assigned to their respective variables.
Geometry_2D
objectdomain = Geometry_2D(i_mesh_type, i_mesh_generation_method, i_n_test_points_x, i_n_test_points_y, i_output_path)
+
will instantiate a Geometry_2D
object, domain
, with the mesh
+type, mesh generation method and test points. In our example, the mesh
+generation method is internal
, so the cells and boundary points will
+be obtained using the generate_quad_mesh_internal
method.
cells, boundary_points = domain.generate_quad_mesh_internal(
+ x_limits=[i_x_min, i_x_max],
+ y_limits=[i_y_min, i_y_max],
+ n_cells_x=i_n_cells_x,
+ n_cells_y=i_n_cells_y,
+ num_boundary_points=i_n_boundary_points,
+)
+
As explained in the example file section, the +boundary conditions and values are read as a dictionary from the example +file
+bound_function_dict, bound_condition_dict = get_boundary_function_dict(), get_bound_cond_dict()
+
fespace = Fespace2D(
+ mesh=domain.mesh,
+ cells=cells,
+ boundary_points=boundary_points,
+ cell_type=domain.mesh_type,
+ fe_order=i_fe_order,
+ fe_type=i_fe_type,
+ quad_order=i_quad_order,
+ quad_type=i_quad_type,
+ fe_transformation_type="bilinear",
+ bound_function_dict=bound_function_dict,
+ bound_condition_dict=bound_condition_dict,
+ forcing_function=rhs,
+ output_path=i_output_path,
+)
+
fespace
will contain all the information about the finite element
+space, including those read from the input file
model = DenseModel(
+ layer_dims=[2, 30, 30, 30, 1],
+ learning_rate_dict=i_learning_rate_dict,
+ params_dict=params_dict,
+ loss_function=pde_loss_helmholtz,
+ input_tensors_list=[datahandler.x_pde_list, train_dirichlet_input, train_dirichlet_output],
+ orig_factor_matrices=[
+ datahandler.shape_val_mat_list,
+ datahandler.grad_x_mat_list,
+ datahandler.grad_y_mat_list,
+ ],
+ force_function_list=datahandler.forcing_function_list,
+ tensor_dtype=i_dtype,
+ use_attention=i_use_attention,
+ activation=i_activation,
+ hessian=False,
+)
+
In this problem, we pass the loss function pde_loss_helmholtz
from
+the physics
file helmholtz2d
.
We are now ready to train the model to approximate the solution of the +PDE.
+for epoch in range(num_epochs):
+
+ # Train the model
+ batch_start_time = time.time()
+
+ loss = model.train_step(beta=beta, bilinear_params_dict=bilinear_params_dict)
+ ...
+
In this example, we will learn how to solve a 2-dimensional Poisson problem using FastVPINNs. +All the necesary files can be found in the examples folder of the fastvpinns GitHub repository
+where
+For an \(\epsilon = 1\), and considering \(\omega = 2 \pi\) the +exact solution is given by
+We begin by introducing the various files required to run this example
+`experimentation`
`geometry`
`fe`
`pde`
`model`
`logging`
The code in this example can be run using
+python3 main_poisson2d_hard.py input.yaml
+
The example file, sin_cos.py
, defines the boundary conditions and
+boundary values, the forcing function and exact function (if test error
+needs to be calculated), bilinear parameters and the actual value of the
+parameter that needs to be estimated (if the error between the actual
+and estimated parameter needs to be calculated) ### Defining boundary
+values Since this example ecforces zero Dirichlet boundary conditions
+using hard constraints, the boundary functions defined in the example
+file are not used. Instead, the ansatz function for hard boundary
+constraints is defined in the main file
rhs
can be used to define the forcing function \(f\).
def rhs(x, y):
+ """
+ This function will return the value of the rhs at a given point
+ """
+ # f_temp = 32 * (x * (1 - x) + y * (1 - y))
+ # f_temp = 1
+
+ omegaX = 4.0 * np.pi
+ omegaY = 4.0 * np.pi
+ f_temp = -2.0 * (omegaX**2) * (np.sin(omegaX * x) * np.sin(omegaY * y))
+
+ return f_temp
+
The bilinear parameters like diffusion constant can be defined by
+get_bilinear_params_dict
def get_bilinear_params_dict():
+ """
+ This function will return a dictionary of bilinear parameters
+ """
+ eps = 1.0
+
+ return {"eps": eps}
+
Here, eps
denoted the diffusion constant.
The input file, input.yaml
, is used to define inputs to your solver.
+These will usually parameters that will changed often throughout your
+experimentation, hence it is best practice to pass these parameters
+externally. The input file is divided based on the modules which use the
+parameter in question, as follows - ### experimentation
This
+contains output_path
, a string which specifies which folder will be
+used to store your outputs.
geometry
This section defines the geometrical parameters for your domain. 1. In
+this example, we set the mesh_generation_method
as "internal"
.
+This generates a regular quadrilateral domain with a uniform mesh. 2.
+The parameters in internal_mesh_params
define the x and y limits of
+the quadrilateral domain(xmin
, xmax
, ymin
and ymax
),
+number of cells in the domain in the x and y direction (n_cells_x
+and n_cells_y
), number of total boundary points
+(n_boundary_points
) and number of test points in x and y direction
+(n_test_points_x
and n_test_points_y
). 3. mesh_type
:
+FastVPINNs currently provides support for quadrilateral elements only.
+4. external_mesh_params
can be used to specify parameters for the
+external mesh, and can be ignored for this example
fe
The parameters related to the finite element space are defined here. 1.
+fe_order
sets the order of the finite element test functions. 2.
+fe_type
set which type of polynomial will be used as the finite
+element test function. 3. quad_order
is the number of quadrature in
+each direction in each cell. Thus the total number of quadrature points
+in each cell will be quad_order
\(^2\) 4. quad_type
+specifies the quadrature rule to be used.
pde
beta
specifies the weight by which the boundary loss will be
+multiplied before being added to the PDE loss.
model
The parameters pertaining to the neural network are specified here. 1.
+model_architecture
is used to specify the dimensions of the neural
+network. In this example, [2, 30, 30, 30, 1] corresponds to a neural
+network with 2 inputs (for a 2-dimensional problem), 1 output (for a
+scalar problem) and 3 hidden layers with 30 neurons each. 2.
+activation
specifies the activation function to be used. 3.
+use_attention
specifies if attention layers are to be used in the
+model. This feature is currently under development and hence should be
+set to false
for now. 4. epochs
is the number of iterations for
+which the network must be trained. 5. dtype
specifies which datatype
+(float32
or float64
) will be used for the tensor calculations.
+6. set_memory_growth
, when set to True
will enable tensorflow’s
+memory growth function, restricting the memory usage on the GPU. This is
+currently under development and must be set to False
for now. 7.
+learning_rate
sets the learning rate initial_learning_rate
if a
+constant learning rate is used. A learning rate scheduler can be used by
+toggling use_lr_scheduler
to True and setting the corresponding
+decay parameters below it.
logging
It specifies the frequency with which the progress bar and console +output will be updated, and at what interval will inference be carried +out to print the solution image in the output folder.
+ +This is the main file which needs to be run for the experiment, with the
+input file as an argument. For the example, we will use the main file
+main_poisson2d.py
Following are the key components of a FastVPINNs main file
+from fastvpinns.data.datahandler2d import DataHandler2D
+from fastvpinns.FE_2D.fespace2d import Fespace2D
+from fastvpinns.Geometry.geometry_2d import Geometry_2D
+
Will import the functions related to setting up the finite element +space, 2D Geometry and the datahandler required to manage data and make +it available to the model.
+from fastvpinns.model.modelimport DenseModel
+
Will import the model file where the neural network and its training
+function is defined. The model file model.py
contains the
+DenseModel
class. the train_step
function of this model is used
+to train the model.
from fastvpinns.physics.poisson2d import pde_loss_poisson
+
Imports the loss function for the 2-dimensional Poisson problem.
+from fastvpinns.utils.compute_utils import compute_errors_combined
+from fastvpinns.utils.plot_utils import plot_contour, plot_loss_function, plot_test_loss_function
+from fastvpinns.utils.print_utils import print_table
+
Imports functions to calculate the loss, plot the results and print +outputs to the console.
+The input file is loaded into config
and the input parameters are
+read and assigned to their respective variables.
Geometry_2D
objectdomain = Geometry_2D(i_mesh_type, i_mesh_generation_method, i_n_test_points_x, i_n_test_points_y, i_output_path)
+
will instantiate a Geometry_2D
object, domain
, with the mesh
+type, mesh generation method and test points. In our example, the mesh
+generation method is internal
, so the cells and boundary points will
+be obtained using the generate_quad_mesh_internal
method.
cells, boundary_points = domain.generate_quad_mesh_internal(
+ x_limits=[i_x_min, i_x_max],
+ y_limits=[i_y_min, i_y_max],
+ n_cells_x=i_n_cells_x,
+ n_cells_y=i_n_cells_y,
+ num_boundary_points=i_n_boundary_points,
+)
+
As explained in the example file section, the +boundary conditions and values are read as a dictionary from the example +file
+bound_function_dict, bound_condition_dict = get_boundary_function_dict(), get_bound_cond_dict()
+
fespace = Fespace2D(
+ mesh=domain.mesh,
+ cells=cells,
+ boundary_points=boundary_points,
+ cell_type=domain.mesh_type,
+ fe_order=i_fe_order,
+ fe_type=i_fe_type,
+ quad_order=i_quad_order,
+ quad_type=i_quad_type,
+ fe_transformation_type="bilinear",
+ bound_function_dict=bound_function_dict,
+ bound_condition_dict=bound_condition_dict,
+ forcing_function=rhs,
+ output_path=i_output_path,
+)
+
fespace
will contain all the information about the finite element
+space, including those read from the input file
model = DenseModel(
+ layer_dims=[2, 30, 30, 30, 1],
+ learning_rate_dict=i_learning_rate_dict,
+ params_dict=params_dict,
+ loss_function=pde_loss_poisson,
+ input_tensors_list=[datahandler.x_pde_list, train_dirichlet_input, train_dirichlet_output],
+ orig_factor_matrices=[
+ datahandler.shape_val_mat_list,
+ datahandler.grad_x_mat_list,
+ datahandler.grad_y_mat_list,
+ ],
+ force_function_list=datahandler.forcing_function_list,
+ tensor_dtype=i_dtype,
+ use_attention=i_use_attention,
+ activation=i_activation,
+ hessian=False,
+)
+
In this problem, we pass the loss function pde_loss_poisson
from the
+physics
file poisson2d.py
.
We are now ready to train the model to approximate the solution of the +PDE.
+for epoch in range(num_epochs):
+
+ # Train the model
+ batch_start_time = time.time()
+
+ loss = model.train_step(beta=beta, bilinear_params_dict=bilinear_params_dict)
+ ...
+
In this example, we will learn how to solve inverse problems using +FastVPINNs. In particular, we will solve the 2-dimensional Poisson +equation, as shown below, while simultaneously estimating the uniform +diffusion parameter \(\epsilon\) using synthetically generated +sensor data.
+for the actual solution +\(u(x, y) = 10 \sin(x) \tanh(x) e^{-\epsilon x^2}\) In this problem, +the actual value of the diffusion parameter, +\(\epsilon_{\text{actual}}\) is 0.3, and we start with an initial +guess of \(\epsilon_{\text{initial}}=2.0\).
+We begin by introducing the various files required to run this example
+Example File - inverse_uniform.py: The boundary +conditions, forcing function \(f\) and parameters are defined in +this file.
Input File - input_inverse.py: The input file +contains parameters for the finite element space and neural networks +that can be tuned.
Main File - main_inverse.py: The main file is the +file that is actually run.
The code in this example can be run using
+python3 main_inverse.py input_inverse.yaml
+
The example file, inverse_uniform.py
, defines the boundary
+conditions and boundary values, the forcing function and exact function
+(if test error needs to be calculated), bilinear parameters and the
+actual value of the parameter that needs to be estimated (if the error
+between the actual and estimated parameter needs to be calculated) ###
+Defining boundary values The current version of FastVPINNs only
+implements Dirichlet boundary conditions. The boundary values can be set
+defining a function for each boundary,
EPS = 0.3
+
+
+def left_boundary(x, y):
+ """
+ This function will return the boundary value for given component of a boundary
+ """
+ val = np.sin(x) * np.tanh(x) * np.exp(-1.0 * EPS * (x**2)) * 10
+ return val
+
Here EPS
is the actual value of the diffusion parameter to be
+estimated. In the above snippet, we define a function left_boundary
+which returns the Dirichlet values to be enforced at that boundary.
+Similarly, we can define more boundary functions like
+right_boundary
, top_boundary
and bottom_boundary
. Once these
+functions are defined, we can assign them to the respective boundaries
+using get_boundary_function_dict
def get_boundary_function_dict():
+ """
+ This function will return a dictionary of boundary functions
+ """
+ return {1000: bottom_boundary, 1001: right_boundary, 1002: top_boundary, 1003: left_boundary}
+
Here, 1000
, 1001
, etc. are the boundary identifiers obtained
+from the geometry. Thus, each boundary gets mapped to it boundary value
+in the dictionary.
As explained above, each boundary has an identifier. The function
+get_bound_cond_dict
maps the boundary identifier to the boundary
+condition (only Dirichlet boundary condition is implemented at this
+point).
def get_bound_cond_dict():
+ """
+ This function will return a dictionary of boundary conditions
+ """
+ return {1000: "dirichlet", 1001: "dirichlet", 1002: "dirichlet", 1003: "dirichlet"}
+
rhs
can be used to define the forcing function \(f\).
def rhs(x, y):
+ """
+ This function will return the value of the rhs at a given point
+ """
+
+ X = x
+ Y = y
+ eps = EPS
+
+ return (
+ -EPS
+ * (
+ 40.0 * X * eps * (np.tanh(X) ** 2 - 1) * np.sin(X)
+ - 40.0 * X * eps * np.cos(X) * np.tanh(X)
+ + 10 * eps * (4.0 * X**2 * eps - 2.0) * np.sin(X) * np.tanh(X)
+ + 20 * (np.tanh(X) ** 2 - 1) * np.sin(X) * np.tanh(X)
+ - 20 * (np.tanh(X) ** 2 - 1) * np.cos(X)
+ - 10 * np.sin(X) * np.tanh(X)
+ )
+ * np.exp(-1.0 * X**2 * eps)
+ )
+
The bilinear parameters like diffusion constant and convective velocity
+can be defined by get_bilinear_params_dict
def get_bilinear_params_dict():
+ """
+ This function will return a dictionary of bilinear parameters
+ """
+ # Initial Guess
+ eps = EPS
+
+ return {"eps": eps}
+
Here, eps
denoted the diffusion constant.
To test if our solver converges to the correct value of the parameter to
+be estimated, we use the function get_inverse_params_actual_dict
.
def get_inverse_params_actual_dict():
+ """
+ This function will return a dictionary of inverse parameters
+ """
+ # Initial Guess
+ eps = EPS
+
+ return {"eps": eps}
+
This can then be used to calculate some error metric that assesses the +performance of our solver.
+ +The input file, input_inverse.yaml
, is used to define inputs to your
+solver. These will usually parameters that will changed often throughout
+your experimentation, hence it is best practice to pass these parameters
+externally. The input file is divided based on the modules which use the
+parameter in question, as follows - ### experimentation
This
+contains output_path
, a string which specifies which folder will be
+used to store your outputs.
geometry
This section defines the geometrical parameters for your domain. 1. In
+this example, we set the mesh_generation_method
as "internal"
.
+This generates a regular quadrilateral domain with a uniform mesh. 2.
+The parameters in internal_mesh_params
define the x and y limits of
+the quadrilateral domain(xmin
, xmax
, ymin
and ymax
),
+number of cells in the domain in the x and y direction (n_cells_x
+and n_cells_y
), number of total boundary points
+(n_boundary_points
) and number of test points in x and y direction
+(n_test_points_x
and n_test_points_y
). 3. mesh_type
:
+FastVPINNs currently provides support for quadrilateral elements only.
+4. external_mesh_params
can be used to specify parameters for the
+external mesh, and can be ignored for this example
fe
The parameters related to the finite element space are defined here.
+1. fe_order
sets the order of the finite element test functions.
2. fe_type
set which type of polynomial will be used as the finite
+element test function.
3. quad_order
is the number of quadrature in
+each direction in each cell. Thus the total number of quadrature points
+in each cell will be quad_order
\(^2\)
quad_type
specifies the quadrature rule to be used.
pde
beta
specifies the weight by which the boundary loss will be
+multiplied before being added to the PDE loss.
model
The parameters pertaining to the neural network are specified here. 1.
+model_architecture
is used to specify the dimensions of the neural
+network. In this example, [2, 30, 30, 30, 1] corresponds to a neural
+network with 2 inputs (for a 2-dimensional problem), 1 output (for a
+scalar problem) and 3 hidden layers with 30 neurons each. 2.
+activation
specifies the activation function to be used. 3.
+use_attention
specifies if attnention layers are to be used in the
+model. This feature is currently under development and hence should be
+set to false
for now. 4. epochs
is the number of iterations for
+which the network must be trained. 5. dtype
specifies which datatype
+(float32
or float64
) will be used for the tensor calculations.
+6. set_memory_growth
, when set to True
will enable tensorflow’s
+memory growth function, restricting the memory usage on the GPU. This is
+currently under development and must be set to False
for now. 7.
+learning_rate
sets the learning rate initial_learning_rate
if a
+constant learning rate is used. A learning rate scheduler can be used by
+toggling use_lr_scheduler
to True and setting the corresponding
+decay parameters below it.
logging
It specifies the frequency with which the progress bar and console +output will be updated, and at what interval will inference be carried +out to print the solution image in the output folder.
+inverse
Specific inputs only for inverse problems. num_sensor_points
+specifies the number of points in the domain at which the solution is
+known (or “sensed”).
This is the main file which needs to be run for the experiment, with the
+input file as an argument. For the example, we will use the main file
+main_inverse.py
Following are the key components of a FastVPINNs main file
+from fastvpinns.data.datahandler2d import DataHandler2D
+from fastvpinns.FE_2D.fespace2d import Fespace2D
+from fastvpinns.Geometry.geometry_2d import Geometry_2D
+
Will import the functions related to setting up the finite element +space, 2D Geometry and the datahandler required to manage data and make +it available to the model.
+from fastvpinns.model.model_inverse import DenseModel_Inverse
+
Will import the model file where the neural network and its training
+function is defined. The model file model_inverse.py
contains the
+DenseModel_Inverse
class specifically designed for inverse problems
+where a spatially varying parameter has to be estimated along with the
+solution.
from fastvpinns.physics.poisson2d_inverse import *
+
Imports the loss function specifically designed for this problem, with a +sensor loss added to the PDE and boundary losses.
+from fastvpinns.utils.compute_utils import compute_errors_combined
+from fastvpinns.utils.plot_utils import plot_contour, plot_loss_function, plot_test_loss_function
+from fastvpinns.utils.print_utils import print_table
+
Imports functions to calculate the loss, plot the results and print +outputs to the console.
+The input file is loaded into config
and the input parameters are
+read and assigned to their respective variables.
Geometry_2D
objectdomain = Geometry_2D(i_mesh_type, i_mesh_generation_method, i_n_test_points_x, i_n_test_points_y, i_output_path)
+
will instantiate a Geometry_2D
object, domain
, with the mesh
+type, mesh generation method and test points. In our example, the mesh
+generation method is internal
, so the cells and boundary points will
+be obtained using the generate_quad_mesh_internal
method.
cells, boundary_points = domain.generate_quad_mesh_internal(
+ x_limits=[i_x_min, i_x_max],
+ y_limits=[i_y_min, i_y_max],
+ n_cells_x=i_n_cells_x,
+ n_cells_y=i_n_cells_y,
+ num_boundary_points=i_n_boundary_points,
+)
+
As explained in the example file section, the +boundary conditions and values are read as a dictionary from the example +file
+bound_function_dict, bound_condition_dict = get_boundary_function_dict(), get_bound_cond_dict()
+
fespace = Fespace2D(
+ mesh=domain.mesh,
+ cells=cells,
+ boundary_points=boundary_points,
+ cell_type=domain.mesh_type,
+ fe_order=i_fe_order,
+ fe_type=i_fe_type,
+ quad_order=i_quad_order,
+ quad_type=i_quad_type,
+ fe_transformation_type="bilinear",
+ bound_function_dict=bound_function_dict,
+ bound_condition_dict=bound_condition_dict,
+ forcing_function=rhs,
+ output_path=i_output_path,
+)
+
fespace
will contain all the information about the finite element
+space, including those read from the input file
model = DenseModel_Inverse(
+ layer_dims=i_model_architecture,
+ learning_rate_dict=i_learning_rate_dict,
+ params_dict=params_dict,
+ loss_function=pde_loss_poisson_inverse,
+ input_tensors_list=[datahandler.x_pde_list, train_dirichlet_input, train_dirichlet_output],
+ orig_factor_matrices=[
+ datahandler.shape_val_mat_list,
+ datahandler.grad_x_mat_list,
+ datahandler.grad_y_mat_list,
+ ],
+ force_function_list=datahandler.forcing_function_list,
+ sensor_list=[points, sensor_values],
+ inverse_params_dict=inverse_params_dict,
+ tensor_dtype=i_dtype,
+ use_attention=i_use_attention,
+ activation=i_activation,
+ hessian=False,
+)
+
DenseModel_Inverse
is a model written for inverse problems with
+spatially varying parameter estimation. In this problem, we pass the
+loss function pde_loss_poisson_inverse
from the physics
file
+poisson_inverse.py
.
We are now ready to train the model to approximate the solution of the +PDE while estimating the unknown diffusion parameter using the sensor +data.
+for epoch in range(num_epochs):
+
+ # Train the model
+ batch_start_time = time.time()
+
+ loss = model.train_step(beta=beta, bilinear_params_dict=bilinear_params_dict)
+ ...
+
<figure>
+ <img src="exact_solution.png" alt="Exact Solution">
+ <figcaption style="text-align: center;">Exact Solution</figcaption>
+</figure>
+<figure>
+ <img src="predicted_solution.png" alt="Predicted Solution">
+ <figcaption style="text-align: center;">Predicted Solution</figcaption>
+</figure>
+<figure>
+ <img src="error.png" alt="Error">
+ <figcaption style="text-align: center;">Error</figcaption>
+</figure>
+
<figure>
+ <img src="sensor_points.png" alt="Sensor points">
+ <figcaption style="text-align: center;">Sensor points</figcaption>
+</figure>
+<figure>
+ <img src="inverse_eps_prediction.png" alt="inverse_eps_prediction">
+ <figcaption style="text-align: center;">inverse_eps_prediction</figcaption>
+</figure>
+<figure>
+ <img src="loss_function.png" alt="Train Loss">
+ <figcaption style="text-align: center;">Train Loss</figcaption>
+</figure>
+
In this example, we will learn how to solve inverse problems on a +complex geometry using FastVPINNs. In particular, we will solve the +2-dimensional convection-diffusion equation, as shown below, while +simultaneously estimating the spatially dependent diffusion parameter +\(\epsilon(x,y)\) using synthetically generated sensor data.
+where
+We begin by introducing the various files required to run this example
+Example File: The boundary conditions, forcing +function \(f\) and parameters are defined in this file.
Input File: The input file contains parameters for +the finite element space and neural networks that can be tuned.
Main File: The main file is the file that is +actually run.
The code in this example can be run using
+python3 main_inverse_domain_circle.py input_inverse_domain.yaml
+
The example file, cd2d_inverse_circle_example.py
, defines the
+boundary conditions and boundary values, the forcing function and exact
+function (if test error needs to be calculated), bilinear parameters and
+the actual value of the parameter that needs to be estimated (if the
+error between the actual and estimated parameter needs to be calculated)
The current version of FastVPINNs only +implements Dirichlet boundary conditions. The boundary values can be set +defining a function for each boundary,
+def left_boundary(x, y):
+ """
+ This function will return the boundary value for given component of a boundary
+ """
+ val = np.ones_like(x) * 0.0
+ return val
+
In the above snippet, we define a function left_boundary
which
+returns the Dirichlet values to be enforced at that boundary. Similarly,
+we can define more boundary functions like right_boundary
,
+top_boundary
and bottom_boundary
. Once these functions are
+defined, we can assign them to the respective boundaries using
+get_boundary_function_dict
def get_boundary_function_dict():
+ """
+ This function will return a dictionary of boundary functions
+ """
+ return {1000: bottom_boundary, 1001: right_boundary, 1002: top_boundary, 1003: left_boundary}
+
Here, 1000
, 1001
, etc. are the boundary identifiers obtained
+from the geometry. Thus, each boundary gets mapped to it boundary value
+in the dictionary.
As explained above, each boundary has an identifier. The function
+get_bound_cond_dict
maps the boundary identifier to the boundary
+condition (only Dirichlet boundary condition is implemented at this
+point).
def get_bound_cond_dict():
+ """
+ This function will return a dictionary of boundary conditions
+ """
+ return {1000: "dirichlet", 1001: "dirichlet", 1002: "dirichlet", 1003: "dirichlet"}
+
rhs
can be used to define the forcing function \(f\).
def rhs(x, y):
+ """
+ This function will return the value of the rhs at a given point
+ """
+ return 10.0 * np.ones_like(x)
+
The bilinear parameters like diffusion constant and convective velocity
+can be defined by get_bilinear_params_dict
def get_bilinear_params_dict():
+ """
+ This function will return a dictionary of bilinear parameters
+ """
+
+ eps = 0.1 # will not be used in the loss function, as it will be replaced by the predicted value of NN
+ b1 = 1
+ b2 = 0
+ c = 0.0
+
+ return {"eps": eps, "b_x": b1, "b_y": b2, "c": c}
+
Here, eps
denoted the diffusion constant, b_x
and b_y
denote
+the convective velocity in x and y direction respectively, and c
+denotes the reaction term. In this particular example, eps
is not
+used in the loss calculation since it is the parameter to be estimated
+and c
is zero since this is simply a convection-diffusion problem.
To test if our solver converges to the correct value of the parameter to
+be estimated, we use the function get_inverse_params_actual_dict
.
def get_inverse_params_actual_dict(x, y):
+ """
+ This function will return a dictionary of inverse parameters
+ """
+ # Initial Guess
+ eps = 0.5 * (np.sin(x) + np.cos(y))
+ return {"eps": eps}
+
This can then be used to calculate some error metric that assesses the +performance of our solver.
+ +The input file, input_inverse_domain.yaml
, is used to define inputs
+to your solver. These will usually parameters that will changed often
+throughout your experimentation, hence it is best practice to pass these
+parameters externally. The input file is divided based on the modules
+which use the parameter in question, as follows - ###
+experimentation
This contains output_path
, a string which
+specifies which folder will be used to store your outputs.
geometry
This section defines the geometrical parameters for your domain. 1. In
+this example, we set the mesh_generation_method
as "external"
+since we want to read the mesh file for the circular domain,
+circular_quad.mesh
. 2. For the purposes of this example, the
+parameters in internal_mesh_params
can be ignored as they are used
+exclusively for internal meshes. 3. mesh_type
: FastVPINNs currently
+provides support for quadrilateral elements only. 4.
+external_mesh_params
can be used to specify parameters for the
+external mesh. mesh_file_name
takes a string (circular_quad_mesh
+in this case). boundary_refinement_level
controls how many times the
+boundaries are refined and in effect decides the number of boundary
+points sampled. This sampling can be set to uniform
for uniform
+sampling.
fe
The parameters related to the finite element space are defined here. 1.
+fe_order
sets the order of the finite element test functions. 2.
+fe_type
set which type of polynomial will be used as the finite
+element test function. 3. quad_order
is the number of quadrature in
+each direction in each cell. Thus the total number of quadrature points
+in each cell will be quad_order
\(^2\) 4. quad_type
+specifies the quadrature rule to be used.
pde
beta
specifies the weight by which the boundary loss will be
+multiplied before being added to the PDE loss.
model
The parameters pertaining to the neural network are specified here. 1.
+model_architecture
is used to specify the dimensions of the neural
+network. In this example, [2, 30, 30, 30, 1] corresponds to a neural
+network with 2 inputs (for a 2-dimensional problem), 1 output (for a
+scalar problem) and 3 hidden layers with 30 neurons each. 2.
+activation
specifies the activation function to be used. 3.
+use_attention
specifies if attnention layers are to be used in the
+model. This feature is currently under development and hence should be
+set to false
for now. 4. epochs
is the number of iterations for
+which the network must be trained. 5. dtype
specifies which datatype
+(float32
or float64
) will be used for the tensor calculations.
+6. set_memory_growth
, when set to True
will enable tensorflow’s
+memory growth function, restricting the memory usage on the GPU. This is
+currently under development and must be set to False
for now. 7.
+learning_rate
sets the learning rate initial_learning_rate
if a
+constant learning rate is used. A learning rate scheduler can be used by
+toggling use_lr_scheduler
to True and setting the corresponding
+decay parameters below it.
logging
It specifies the frequency with which the progress bar and console +output will be updated, and at what interval will inference be carried +out to print the solution image in the output folder.
+inverse
Specific inputs only for inverse problems. num_sensor_points
+specifies the number of points in the domain at which the solution is
+known (or “sensed”). This sensor data can be synthetic or be read from a
+file given by sensor_data_file
.
This is the main file which needs to be run for the experiment, with the
+input file as an argument. For the example, we will use the main file
+main_inverse_domain_circle.py
Following are the key components of a FastVPINNs main file
+from fastvpinns.data.datahandler2d import DataHandler2D
+from fastvpinns.FE_2D.fespace2d import Fespace2D
+from fastvpinns.Geometry.geometry_2d import Geometry_2D
+
Will import the functions related to setting up the finite element +space, 2D Geometry and the datahandler required to manage data and make +it available to the model.
+from fastvpinns.model.model_inverse_domain import DenseModel_Inverse_Domain
+
Will import the model file where the neural network and its training
+function is defined. The model file model_inverse_domain.py
contains
+the DenseModel_Inverse_Domain
class specifically designed for
+inverse problems where a spatially varying parameter has to be estimated
+along with the solution.
from fastvpinns.physics.cd2d_inverse_domain import *
+
Imports the loss function specifically designed for this problem, with a +sensor loss added to the PDE and boundary losses.
+from fastvpinns.utils.compute_utils import compute_errors_combined
+from fastvpinns.utils.plot_utils import plot_contour, plot_loss_function, plot_test_loss_function
+from fastvpinns.utils.print_utils import print_table
+
Imports functions to calculate the loss, plot the results and print +outputs to the console.
+The input file is loaded into config
and the input parameters are
+read and assigned to their respective variables.
Geometry_2D
objectdomain = Geometry_2D(i_mesh_type, i_mesh_generation_method, i_n_test_points_x, i_n_test_points_y, i_output_path)
+
will instantiate a Geometry_2D
object, domain
, with the mesh
+type, mesh generation method and test points. In our example, the mesh
+generation method is external
, so the cells and boundary points will
+be obtained using the read_mesh
method.
cells, boundary_points = domain.read_mesh(mesh_file=i_mesh_file_name, boundary_point_refinement_level=i_boundary_refinement_level,
+ bd_sampling_method=i_boundary_sampling_method,
+ refinement_level=0)
+
As explained in the example file section, the +boundary conditions and values are read as a dictionary from the example +file
+bound_function_dict, bound_condition_dict = get_boundary_function_dict(), get_bound_cond_dict()
+
fespace = Fespace2D(
+ mesh=domain.mesh,
+ cells=cells,
+ boundary_points=boundary_points,
+ cell_type=domain.mesh_type,
+ fe_order=i_fe_order,
+ fe_type=i_fe_type,
+ quad_order=i_quad_order,
+ quad_type=i_quad_type,
+ fe_transformation_type="bilinear",
+ bound_function_dict=bound_function_dict,
+ bound_condition_dict=bound_condition_dict,
+ forcing_function=rhs,
+ output_path=i_output_path,
+)
+
fespace
will contain all the information about the finite element
+space, including those read from the input filemodel = DenseModel_Inverse_Domain(
+ layer_dims=i_model_architecture,
+ learning_rate_dict=i_learning_rate_dict,
+ params_dict=params_dict,
+ loss_function=pde_loss_cd2d_inverse_domain,
+ input_tensors_list=[datahandler.x_pde_list, train_dirichlet_input, train_dirichlet_output],
+ orig_factor_matrices=[
+ datahandler.shape_val_mat_list,
+ datahandler.grad_x_mat_list,
+ datahandler.grad_y_mat_list,
+ ],
+ force_function_list=datahandler.forcing_function_list,
+ sensor_list=[points, sensor_values],
+ tensor_dtype=i_dtype,
+ use_attention=i_use_attention,
+ activation=i_activation,
+ hessian=False,
+)
+
DenseModel_Inverse_Domain
is a model written for inverse problems
+with spatially varying parameter estimation. In this problem, we pass
+the loss function pde_loss_cd2d_inverse_domain
from the physics
+file cd2d_inverse_domain.py
.
We are now ready to train the model to approximate the solution of the +PDE while estimating the unknown diffusion parameter using the sensor +data.
+for epoch in range(num_epochs):
+
+ # Train the model
+ batch_start_time = time.time()
+
+ loss = model.train_step(beta=beta, bilinear_params_dict=bilinear_params_dict)
+ ...
+
Short
+ */ + .o-tooltip--left { + position: relative; + } + + .o-tooltip--left:after { + opacity: 0; + visibility: hidden; + position: absolute; + content: attr(data-tooltip); + padding: .2em; + font-size: .8em; + left: -.2em; + background: grey; + color: white; + white-space: nowrap; + z-index: 2; + border-radius: 2px; + transform: translateX(-102%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); +} + +.o-tooltip--left:hover:after { + display: block; + opacity: 1; + visibility: visible; + transform: translateX(-100%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); + transition-delay: .5s; +} + +/* By default the copy button shouldn't show up when printing a page */ +@media print { + button.copybtn { + display: none; + } +} diff --git a/_static/copybutton.js b/_static/copybutton.js new file mode 100644 index 0000000..2ea7ff3 --- /dev/null +++ b/_static/copybutton.js @@ -0,0 +1,248 @@ +// Localization support +const messages = { + 'en': { + 'copy': 'Copy', + 'copy_to_clipboard': 'Copy to clipboard', + 'copy_success': 'Copied!', + 'copy_failure': 'Failed to copy', + }, + 'es' : { + 'copy': 'Copiar', + 'copy_to_clipboard': 'Copiar al portapapeles', + 'copy_success': '¡Copiado!', + 'copy_failure': 'Error al copiar', + }, + 'de' : { + 'copy': 'Kopieren', + 'copy_to_clipboard': 'In die Zwischenablage kopieren', + 'copy_success': 'Kopiert!', + 'copy_failure': 'Fehler beim Kopieren', + }, + 'fr' : { + 'copy': 'Copier', + 'copy_to_clipboard': 'Copier dans le presse-papier', + 'copy_success': 'Copié !', + 'copy_failure': 'Échec de la copie', + }, + 'ru': { + 'copy': 'Скопировать', + 'copy_to_clipboard': 'Скопировать в буфер', + 'copy_success': 'Скопировано!', + 'copy_failure': 'Не удалось скопировать', + }, + 'zh-CN': { + 'copy': '复制', + 'copy_to_clipboard': '复制到剪贴板', + 'copy_success': '复制成功!', + 'copy_failure': '复制失败', + }, + 'it' : { + 'copy': 'Copiare', + 'copy_to_clipboard': 'Copiato negli appunti', + 'copy_success': 'Copiato!', + 'copy_failure': 'Errore durante la copia', + } +} + +let locale = 'en' +if( document.documentElement.lang !== undefined + && messages[document.documentElement.lang] !== undefined ) { + locale = document.documentElement.lang +} + +let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; +if (doc_url_root == '#') { + doc_url_root = ''; +} + +/** + * SVG files for our copy buttons + */ +let iconCheck = `` + +// If the user specified their own SVG use that, otherwise use the default +let iconCopy = ``; +if (!iconCopy) { + iconCopy = `` +} + +/** + * Set up copy/paste for code blocks + */ + +const runWhenDOMLoaded = cb => { + if (document.readyState != 'loading') { + cb() + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', cb) + } else { + document.attachEvent('onreadystatechange', function() { + if (document.readyState == 'complete') cb() + }) + } +} + +const codeCellId = index => `codecell${index}` + +// Clears selected text since ClipboardJS will select the text when copying +const clearSelection = () => { + if (window.getSelection) { + window.getSelection().removeAllRanges() + } else if (document.selection) { + document.selection.empty() + } +} + +// Changes tooltip text for a moment, then changes it back +// We want the timeout of our `success` class to be a bit shorter than the +// tooltip and icon change, so that we can hide the icon before changing back. +var timeoutIcon = 2000; +var timeoutSuccessClass = 1500; + +const temporarilyChangeTooltip = (el, oldText, newText) => { + el.setAttribute('data-tooltip', newText) + el.classList.add('success') + // Remove success a little bit sooner than we change the tooltip + // So that we can use CSS to hide the copybutton first + setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) + setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) +} + +// Changes the copy button icon for two seconds, then changes it back +const temporarilyChangeIcon = (el) => { + el.innerHTML = iconCheck; + setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) +} + +const addCopyButtonToCodeCells = () => { + // If ClipboardJS hasn't loaded, wait a bit and try again. This + // happens because we load ClipboardJS asynchronously. + if (window.ClipboardJS === undefined) { + setTimeout(addCopyButtonToCodeCells, 250) + return + } + + // Add copybuttons to all of our code cells + const COPYBUTTON_SELECTOR = 'div.highlight pre'; + const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) + codeCells.forEach((codeCell, index) => { + const id = codeCellId(index) + codeCell.setAttribute('id', id) + + const clipboardButton = id => + `` + codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) + }) + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} + + +var copyTargetText = (trigger) => { + var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); + + // get filtered text + let exclude = '.linenos'; + + let text = filterText(target, exclude); + return formatCopyText(text, '', false, true, true, true, '', '') +} + + // Initialize with a callback so we can modify the text before copy + const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) + + // Update UI with error/success messages + clipboard.on('success', event => { + clearSelection() + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) + temporarilyChangeIcon(event.trigger) + }) + + clipboard.on('error', event => { + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) + }) +} + +runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/_static/copybutton_funcs.js b/_static/copybutton_funcs.js new file mode 100644 index 0000000..dbe1aaa --- /dev/null +++ b/_static/copybutton_funcs.js @@ -0,0 +1,73 @@ +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +export function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 0000000..c718cee --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 0000000..19a446a --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000..4d67807 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000..89435bb --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '1.0.0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/jquery.js b/_static/jquery.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0' + + '' + + _("Hide Search Matches") + + "
" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/genindex.html b/genindex.html new file mode 100644 index 0000000..9e04451 --- /dev/null +++ b/genindex.html @@ -0,0 +1,753 @@ + + + + + ++ | + |
+ | + |
|
+
|
+
+ |
+ | + |
+ |
+ | + |
+ |
+ |
+ |
A robust tensor-based deep learning framework for solving PDE’s using hp-Variational Physics-Informed Neural Networks (hp-VPINNs). The framework supports handling complex geometries and uses tensor-based loss computation to accelerate the training of conventional hp-VPINNs. +The framework is based on the work by FastVPINNs Paper. The framework is written on Tensorflow 2.0 <https://www.tensorflow.org/> and has support for handling external meshes.
+Note: This framework is an highly optimised version of the the initial implementation of hp-VPINNs by kharazmi. Ref hp-VPINNs(arXiv).
+Variational Physics-Informed neural networks are special form of physics informed neural networks, which uses variational form of the loss function to train the NN. A special form of hp-Variational PINNs which uses h- & p- refinement to enhance the ability of the NN to capture higher frequency solutions. +For more details on the theory and implementation of hp-VPINNs, please refer to the FastVPINNs Paper and hp-VPINNs Paper.
+ +