zoomy-core 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of zoomy-core might be problematic. Click here for more details.

Files changed (57) hide show
  1. zoomy_core/decorators/decorators.py +25 -0
  2. zoomy_core/fvm/flux.py +97 -0
  3. zoomy_core/fvm/nonconservative_flux.py +97 -0
  4. zoomy_core/fvm/ode.py +55 -0
  5. zoomy_core/fvm/solver_numpy.py +305 -0
  6. zoomy_core/fvm/timestepping.py +13 -0
  7. zoomy_core/mesh/gmsh_loader.py +301 -0
  8. zoomy_core/mesh/mesh.py +1192 -0
  9. zoomy_core/mesh/mesh_extrude.py +168 -0
  10. zoomy_core/mesh/mesh_util.py +487 -0
  11. zoomy_core/misc/custom_types.py +6 -0
  12. zoomy_core/misc/gui.py +61 -0
  13. zoomy_core/misc/interpolation.py +140 -0
  14. zoomy_core/misc/io.py +401 -0
  15. zoomy_core/misc/logger_config.py +18 -0
  16. zoomy_core/misc/misc.py +216 -0
  17. zoomy_core/misc/static_class.py +94 -0
  18. zoomy_core/model/analysis.py +147 -0
  19. zoomy_core/model/basefunction.py +113 -0
  20. zoomy_core/model/basemodel.py +512 -0
  21. zoomy_core/model/boundary_conditions.py +193 -0
  22. zoomy_core/model/initial_conditions.py +171 -0
  23. zoomy_core/model/model.py +63 -0
  24. zoomy_core/model/models/GN.py +70 -0
  25. zoomy_core/model/models/advection.py +53 -0
  26. zoomy_core/model/models/basisfunctions.py +181 -0
  27. zoomy_core/model/models/basismatrices.py +377 -0
  28. zoomy_core/model/models/core.py +564 -0
  29. zoomy_core/model/models/coupled_constrained.py +60 -0
  30. zoomy_core/model/models/old_smm copy.py +867 -0
  31. zoomy_core/model/models/poisson.py +41 -0
  32. zoomy_core/model/models/shallow_moments.py +757 -0
  33. zoomy_core/model/models/shallow_moments_sediment.py +378 -0
  34. zoomy_core/model/models/shallow_moments_topo.py +423 -0
  35. zoomy_core/model/models/shallow_moments_variants.py +1509 -0
  36. zoomy_core/model/models/shallow_water.py +266 -0
  37. zoomy_core/model/models/shallow_water_topo.py +111 -0
  38. zoomy_core/model/models/shear_shallow_flow.py +594 -0
  39. zoomy_core/model/models/sme_turbulent.py +613 -0
  40. zoomy_core/model/models/swe_old.py +1018 -0
  41. zoomy_core/model/models/vam.py +455 -0
  42. zoomy_core/postprocessing/postprocessing.py +72 -0
  43. zoomy_core/preprocessing/openfoam_moments.py +452 -0
  44. zoomy_core/transformation/helpers.py +25 -0
  45. zoomy_core/transformation/to_amrex.py +238 -0
  46. zoomy_core/transformation/to_c.py +181 -0
  47. zoomy_core/transformation/to_jax.py +14 -0
  48. zoomy_core/transformation/to_numpy.py +115 -0
  49. zoomy_core/transformation/to_openfoam.py +254 -0
  50. zoomy_core/transformation/to_ufl.py +67 -0
  51. {zoomy_core-0.1.0.dist-info → zoomy_core-0.1.2.dist-info}/METADATA +1 -1
  52. zoomy_core-0.1.2.dist-info/RECORD +55 -0
  53. zoomy_core-0.1.2.dist-info/top_level.txt +1 -0
  54. zoomy_core-0.1.0.dist-info/RECORD +0 -5
  55. zoomy_core-0.1.0.dist-info/top_level.txt +0 -1
  56. {zoomy_core-0.1.0.dist-info → zoomy_core-0.1.2.dist-info}/WHEEL +0 -0
  57. {zoomy_core-0.1.0.dist-info → zoomy_core-0.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,171 @@
1
+ import numpy as np
2
+ from attr import define
3
+ from typing import Callable, Optional
4
+
5
+ from library.zoomy_core.misc.custom_types import FArray
6
+ from library.zoomy_core.mesh.mesh import Mesh
7
+ import library.zoomy_core.misc.io as io
8
+ import library.zoomy_core.misc.interpolation as interpolate_mesh
9
+
10
+ # from library.zoomy_core.mesh import Mesh2D
11
+
12
+
13
+ @define(slots=True, frozen=True)
14
+ class InitialConditions:
15
+ def apply(self, X, Q):
16
+ assert False
17
+ return Q
18
+
19
+
20
+ @define(slots=True, frozen=True)
21
+ class Constant(InitialConditions):
22
+ constants: Callable[[int], FArray] = lambda n_variables: np.array(
23
+ [1.0] + [0.0 for i in range(n_variables - 1)]
24
+ )
25
+
26
+ def apply(self, X, Q):
27
+ n_variables = Q.shape[0]
28
+ for i in range(Q.shape[1]):
29
+ Q[:, i] = self.constants(n_variables)
30
+ return Q
31
+
32
+
33
+ @define(slots=True, frozen=False)
34
+ class RP(InitialConditions):
35
+ low: Callable[[int], FArray] = lambda n_variables: np.array(
36
+ [1.0 * (i == 0) for i in range(n_variables)]
37
+ )
38
+ high: Callable[[int], FArray] = lambda n_variables: np.array(
39
+ [2.0 * (i == 0) for i in range(n_variables)]
40
+ )
41
+ jump_position_x: float = 0.0
42
+
43
+ def apply(self, X, Q):
44
+ assert X.shape[1] == Q.shape[1]
45
+ n_variables = Q.shape[0]
46
+ for i in range(Q.shape[1]):
47
+ if X[0, i] < self.jump_position_x:
48
+ Q[:, i] = self.high(n_variables)
49
+ else:
50
+ Q[:, i] = self.low(n_variables)
51
+ return Q
52
+
53
+
54
+ @define(slots=True, frozen=False)
55
+ class RP2d(InitialConditions):
56
+ low: Callable[[int], FArray] = lambda n_variables: np.array(
57
+ [1.0 * (i == 0) for i in range(n_variables)]
58
+ )
59
+ high: Callable[[int], FArray] = lambda n_variables: np.array(
60
+ [2.0 * (i == 0) for i in range(n_variables)]
61
+ )
62
+ jump_position_x: float = 0.0
63
+ jump_position_y: float = 0.0
64
+
65
+ def apply(self, X, Q):
66
+ assert X.shape[1] == Q.shape[1]
67
+ n_variables = Q.shape[0]
68
+ for i in range(Q.shape[1]):
69
+ if X[0, i] < self.jump_position_x and X[1, i] < self.jump_position_y:
70
+ Q[:, i] = self.high(n_variables)
71
+ else:
72
+ Q[:, i] = self.low(n_variables)
73
+ return Q
74
+
75
+
76
+ @define(slots=True, frozen=False)
77
+ class RP3d(InitialConditions):
78
+ low: Callable[[int], FArray] = lambda n_variables: np.array(
79
+ [1.0 * (i == 0) for i in range(n_variables)]
80
+ )
81
+ high: Callable[[int], FArray] = lambda n_variables: np.array(
82
+ [2.0 * (i == 0) for i in range(n_variables)]
83
+ )
84
+ jump_position_x: float = 0.0
85
+ jump_position_y: float = 0.0
86
+ jump_position_z: float = 0.0
87
+
88
+ def apply(self, X, Q):
89
+ assert X.shape[1] == Q.shape[1]
90
+ n_variables = Q.shape[0]
91
+ for i in range(Q.shape[1]):
92
+ if (
93
+ X[0, i] < self.jump_position_x
94
+ and X[1, i] < self.jump_position_y
95
+ and X[2, i] < self.jump_position_z
96
+ ):
97
+ Q[:, i] = self.high(n_variables)
98
+ else:
99
+ Q[:, i] = self.low(n_variables)
100
+ return Q
101
+
102
+
103
+ @define(slots=True, frozen=False)
104
+ class RadialDambreak(InitialConditions):
105
+ low: Callable[[int], FArray] = lambda n_variables: np.array(
106
+ [1.0 * (i == 0) for i in range(n_variables)]
107
+ )
108
+ high: Callable[[int], FArray] = lambda n_variables: np.array(
109
+ [2.0 * (i == 0) for i in range(n_variables)]
110
+ )
111
+ radius: float = 0.1
112
+
113
+ def apply(self, X, Q):
114
+ dim = X.shape[0]
115
+ center = np.zeros(dim)
116
+ for d in range(dim):
117
+ center[d] = X[d, :].mean()
118
+ assert X.shape[1] == Q.shape[1]
119
+ n_variables = Q.shape[0]
120
+ for i in range(Q.shape[1]):
121
+ if np.linalg.norm(X[:, i] - center) <= self.radius:
122
+ Q[:, i] = self.high(n_variables)
123
+ else:
124
+ Q[:, i] = self.low(n_variables)
125
+ return Q
126
+
127
+
128
+ @define(slots=True, frozen=True)
129
+ class UserFunction(InitialConditions):
130
+ function: Optional[Callable[[FArray], FArray]] = None
131
+
132
+ def apply(self, X, Q):
133
+ assert X.shape[1] == Q.shape[1]
134
+ if self.function is None:
135
+ self.function = lambda x: np.zeros(Q.shape[1])
136
+ for i, x in enumerate(X.T):
137
+ Q[:, i] = self.function(x)
138
+ return Q
139
+
140
+
141
+ # TODO do time interpolation
142
+ @define(slots=True, frozen=True)
143
+ class RestartFromHdf5(InitialConditions):
144
+ path_to_fields: Optional[str] = None
145
+ mesh_new: Optional[Mesh] = None
146
+ mesh_identical: bool = False
147
+ path_to_old_mesh: Optional[str] = None
148
+ snapshot: Optional[int] = -1
149
+ map_fields: Optional[dict] = None
150
+
151
+ def apply(self, X, Q):
152
+ assert self.mesh_new is not None
153
+ assert self.path_to_fields is not None
154
+ assert X.shape[0] == Q.shape[0]
155
+ if self.map_fields is None:
156
+ map_fields = {i: i for i in range(Q.shape[1])}
157
+ else:
158
+ map_fields = self.map_fields
159
+ mesh = Mesh.from_hdf5(self.path_to_old_mesh)
160
+ _Q, _Qaux, time = io.load_fields_from_hdf5(
161
+ self.path_to_fields, i_snapshot=self.snapshot
162
+ )
163
+ Q = np.zeros_like(Q)
164
+ if self.mesh_identical:
165
+ Q[:, list(map_fields.values())] = _Q[:, list(map_fields.keys())]
166
+ else:
167
+ assert self.path_to_old_mesh is not None
168
+ Q[:, list(map_fields.values())] = interpolate_mesh.to_new_mesh(
169
+ _Q, mesh, self.mesh_new
170
+ )[:, list(map_fields.keys())]
171
+ return Q
@@ -0,0 +1,63 @@
1
+ import os
2
+ import numpy as np
3
+ from typing import Union, Type
4
+
5
+ from library.zoomy_core.model.basemodel import Model
6
+ from library.zoomy_core.model.models.advection import Advection
7
+ #
8
+
9
+ # from library.zoomy_core.model.models.shallow_moments_sediment import *
10
+ import library.zoomy_core.model.initial_conditions as IC
11
+ import library.zoomy_core.model.boundary_conditions as BC
12
+ from library.zoomy_core.mesh.fvm_mesh import Mesh
13
+
14
+
15
+ def create_default_mesh_and_model(
16
+ dimension: int = 1,
17
+ cls: Type[Model] = Advection,
18
+ fields: Union[int, list] = 1,
19
+ aux_variables: Union[int, list] = 0,
20
+ parameters: Union[int, list, dict] = 0,
21
+ settings: dict = {},
22
+ ):
23
+ main_dir = os.getenv("ZOOMY_DIR")
24
+ assert main_dir != ""
25
+ ic = IC.Constant()
26
+
27
+ bc_tags = ["left", "right", "top", "bottom"][: 2 * dimension]
28
+ bcs = BC.BoundaryConditions([BC.Wall(tag=tag) for tag in bc_tags])
29
+ if dimension == 1:
30
+ mesh = Mesh.create_1d((-1, 1), 10)
31
+ elif dimension == 2:
32
+ mesh = Mesh.load_mesh(
33
+ os.path.join(main_dir, "meshes/quad_2d/mesh_coarse.msh"),
34
+ "quad",
35
+ 2,
36
+ bc_tags,
37
+ )
38
+ else:
39
+ assert False
40
+ model = cls(
41
+ dimension=dimension,
42
+ fields=fields,
43
+ aux_variables=aux_variables,
44
+ parameters=parameters,
45
+ boundary_conditions=bcs,
46
+ initial_conditions=ic,
47
+ settings=settings,
48
+ )
49
+ n_elements = mesh.n_elements
50
+
51
+ Q = np.linspace(1, fields * n_elements, fields * n_elements).reshape(
52
+ n_elements, fields
53
+ )
54
+ Qaux = np.zeros((Q.shape[0], model.aux_variables.length()))
55
+ parameters = model.parameter_values
56
+ num_normals = mesh.element_n_neighbors
57
+ normals = np.array(
58
+ [mesh.element_face_normals[:, i] for i in range(mesh.n_faces_per_element)]
59
+ )
60
+
61
+ model.initial_conditions.apply(Q, mesh.element_center)
62
+ # model.boundary_conditions.apply()
63
+ return mesh, model, Q, Qaux, parameters, num_normals, normals
@@ -0,0 +1,70 @@
1
+ import numpy as np
2
+ import numpy.polynomial.legendre as L
3
+ import numpy.polynomial.chebyshev as C
4
+ from scipy.optimize import least_squares as lsq
5
+ import sympy
6
+ from sympy import Matrix, sqrt
7
+ from sympy.abc import x
8
+
9
+ from sympy import integrate, diff
10
+ from sympy import legendre
11
+ from sympy import lambdify, Rational
12
+
13
+
14
+ from library.zoomy_core.model.basemodel import (
15
+ register_sympy_attribute,
16
+ eigenvalue_dict_to_matrix,
17
+ )
18
+ from library.zoomy_core.model.basemodel import Model
19
+ import library.zoomy_core.model.initial_conditions as IC
20
+ import library.zoomy_core.model.boundary_conditions as BC
21
+
22
+ class GN(Model):
23
+ def __init__(
24
+ self,
25
+ boundary_conditions= None,
26
+ initial_conditions=None,
27
+ dimension=1,
28
+ fields=2,
29
+ # D = h^3 / 3 * (dt * dx * u + u * dx^2 u - (dx u)^2)
30
+ aux_variables=['dD_dx'],
31
+ parameters={},
32
+ _default_parameters={"g": 9.81},
33
+ settings={},
34
+ settings_default={},
35
+ ):
36
+ self.variables = register_sympy_attribute(fields, "q")
37
+ self.n_variables = self.variables.length()
38
+ super().__init__(
39
+ dimension=dimension,
40
+ fields=fields,
41
+ aux_variables=aux_variables,
42
+ parameters=parameters,
43
+ _default_parameters=_default_parameters,
44
+ boundary_conditions=boundary_conditions,
45
+ initial_conditions=initial_conditions,
46
+ settings={**settings_default, **settings},
47
+ )
48
+
49
+ def flux(self):
50
+ fx = Matrix([0 for i in range(self.n_variables)])
51
+ h = self.variables[0]
52
+ hu = self.variables[1]
53
+
54
+ param = self.parameters
55
+
56
+ fx[0] = hu
57
+ fx[1] = hu**2 / h + 1/2 * param.g * h**2
58
+ return [fx]
59
+
60
+
61
+
62
+ def source_implicit(self):
63
+ R = Matrix([0 for i in range(self.n_variables)])
64
+ dD_dx = self.aux_variables.dD_dx
65
+
66
+
67
+ R[0] = 0
68
+ R[1] = dD_dx
69
+ return R
70
+
@@ -0,0 +1,53 @@
1
+ import numpy as np
2
+ import os
3
+ # import logging
4
+
5
+ import sympy
6
+ from sympy import Symbol, Matrix, lambdify, transpose, Abs, sqrt
7
+
8
+ from sympy import zeros, ones
9
+
10
+ from attr import define
11
+ from typing import Optional
12
+ from types import SimpleNamespace
13
+
14
+ from library.zoomy_core.model.boundary_conditions import BoundaryConditions, Extrapolation
15
+ from library.zoomy_core.model.initial_conditions import InitialConditions, Constant
16
+ from library.zoomy_core.misc.custom_types import FArray
17
+
18
+ # from library.zoomy_core.misc import vectorize # type: ignore
19
+ from library.zoomy_core.model.basemodel import Model
20
+
21
+
22
+ class Advection(Model):
23
+ def flux(self):
24
+ if self.dimension == 1:
25
+ F = Matrix([0 for i in range(self.n_variables)])
26
+ for i_field in range(self.n_variables):
27
+ F[i_field] = self.variables[i_field] * self.parameters[0]
28
+ return [F]
29
+ elif self.dimension == 2:
30
+ F = Matrix([0 for i in range(self.n_variables)])
31
+ G = Matrix([0 for i in range(self.n_variables)])
32
+ for i_field in range(self.n_variables):
33
+ F[i_field] = self.variables[i_field] * self.parameters[0]
34
+ G[i_field] = self.variables[i_field] * self.parameters[1]
35
+ return [F, G]
36
+ elif self.dimension == 3:
37
+ F = Matrix([0 for i in range(self.n_variables)])
38
+ G = Matrix([0 for i in range(self.n_variables)])
39
+ H = Matrix([0 for i in range(self.n_variables)])
40
+ for i_field in range(self.n_variables):
41
+ F[i_field] = self.variables[i_field] * self.parameters[0]
42
+ G[i_field] = self.variables[i_field] * self.parameters[1]
43
+ H[i_field] = self.variables[i_field] * self.parameters[2]
44
+ return [F, G, H]
45
+ else:
46
+ assert False
47
+
48
+ # def eigenvalues(self):
49
+ # assert self.normal.shape[0] == self.parameters.shape[0]
50
+ # ev = self.normal[0] * self.parameters[0]
51
+ # for d in range(1, self.dimension):
52
+ # ev += self.normal[d] * self.parameters[d]
53
+ # self.sympy_eigenvalues = Matrix[[ev for i in range(self.n_variables)]]
@@ -0,0 +1,181 @@
1
+ from copy import deepcopy
2
+
3
+ import numpy as np
4
+ import sympy
5
+ from sympy import Symbol, bspline_basis_set, diff, integrate, lambdify, legendre
6
+ from sympy.abc import z
7
+ from sympy.functions.special.polynomials import chebyshevu
8
+
9
+
10
+ class Basisfunction:
11
+ name = "Basisfunction"
12
+
13
+ def bounds(self):
14
+ return [0, 1]
15
+
16
+ def basis_definition(self):
17
+ z = Symbol("z")
18
+ b = lambda k, z: z**k
19
+ return [b(k, z) for k in range(self.level + 1)]
20
+
21
+ def weight(self, z):
22
+ return 1
23
+
24
+ def weight_eval(self, z):
25
+ z = Symbol("z")
26
+ f = sympy.lambdify(z, self.weight(z))
27
+ return f(z)
28
+
29
+ def __init__(self, level=0, **kwargs):
30
+ self.level = level
31
+ self.basis = self.basis_definition(**kwargs)
32
+
33
+ def get(self, k):
34
+ return self.basis[k]
35
+
36
+ def eval(self, k, _z):
37
+ return self.get(k).subs(z, _z)
38
+
39
+ def eval_psi(self, k, _z):
40
+ z = sympy.Symbol('z')
41
+ psi = sympy.integrate(self.get(k), (z, self.bounds()[0], z))
42
+ return psi.subs(z, _z)
43
+
44
+ def get_lambda(self, k):
45
+ f = lambdify(z, self.get(k))
46
+
47
+ def lam(z):
48
+ if type(z) == int or type(z) == float:
49
+ return f(z)
50
+ elif type(z) == list or type(z) == np.ndarray:
51
+ return np.array([f(xi) for xi in z])
52
+ else:
53
+ assert False
54
+
55
+ return lam
56
+
57
+ def plot(self, ax):
58
+ X = np.linspace(self.bounds()[0], self.bounds()[1], 1000)
59
+ for i in range(len(self.basis)):
60
+ f = lambdify(z, self.get(i))
61
+ y = np.array([f(xi) for xi in X])
62
+ ax.plot(X, y, label=f"basis {i}")
63
+
64
+ def reconstruct_velocity_profile(self, alpha, N=100):
65
+ Z = np.linspace(self.bounds()[0], self.bounds()[1], N)
66
+ u = np.zeros_like(Z)
67
+ for i in range(len(self.basis)):
68
+ b = lambdify(z, self.get(i))
69
+ u[:] += alpha[i] * b(Z)
70
+ return u
71
+
72
+ def reconstruct_velocity_profile_at(self, alpha, z):
73
+ u = 0
74
+ for i in range(len(self.basis)):
75
+ b = lambdify(z, self.eval(i, z))
76
+ u += alpha[i] * b(z)
77
+ return u
78
+
79
+ def reconstruct_alpha(self, velocities, z):
80
+ n_basis = len(self.basis)
81
+ alpha = np.zeros(n_basis)
82
+ for i in range(n_basis):
83
+ b = lambdify(z, self.eval(i, z))
84
+ nom = np.trapz(velocities * b(z) * self.weight(z), z)
85
+ if type(b(z)) == int:
86
+ den = b(z) ** 2
87
+ else:
88
+ den = np.trapz((b(z) * b(z)).reshape(z.shape), z)
89
+ res = nom / den
90
+ alpha[i] = res
91
+ return alpha
92
+
93
+ def project_onto_basis(self, Y):
94
+ Z = np.linspace(self.bounds()[0], self.bounds()[1], Y.shape[0])
95
+ n_basis = len(self.basis)
96
+ alpha = np.zeros(n_basis)
97
+ z = Symbol("z")
98
+ for i in range(n_basis):
99
+ b = lambdify(z, self.eval(i, Z))
100
+ alpha[i] = np.trapz(Y * b(Z) * self.weight_eval(Z), Z)
101
+ return alpha
102
+
103
+ def get_diff_basis(self):
104
+ db = [diff(b, z) for i, b in enumerate(self.basis)]
105
+ return db
106
+
107
+
108
+ class Monomials(Basisfunction):
109
+ name = "Monomials"
110
+
111
+
112
+ class Legendre_shifted(Basisfunction):
113
+ name = "Legendre_shifted"
114
+
115
+ def basis_definition(self):
116
+ z = Symbol("z")
117
+ b = lambda k, z: legendre(k, 2 * z - 1) * (-1) ** (k)
118
+ return [b(k, z) for k in range(self.level + 1)]
119
+
120
+ class Chebyshevu(Basisfunction):
121
+ name = "Chebyshevu"
122
+
123
+ def bounds(self):
124
+ return [-1, 1]
125
+
126
+ def weight(self, z):
127
+ # do not forget to include the jacobian of the coordinate transformation in the weight
128
+ return sympy.sqrt(1-z**2)
129
+
130
+ def basis_definition(self):
131
+ z = Symbol("z")
132
+ b = lambda k, z: sympy.sqrt(2 / sympy.pi) * chebyshevu(k, z)
133
+ return [b(k, z) for k in range(self.level + 1)]
134
+
135
+ class Legendre_DN(Basisfunction):
136
+ name = "Legendre_DN - satifying no-slip and no-stress. This is a non-SWE basis"
137
+
138
+ def bounds(self):
139
+ return [-1, 1]
140
+
141
+ def basis_definition(self):
142
+ z = Symbol("z")
143
+ def b(k, z):
144
+ alpha = sympy.Rational((2*k+3), (k+2)**2)
145
+ beta = -sympy.Rational((k+1),(k+2))**2
146
+ return (legendre(k, z) ) + alpha * (legendre(k+1, z) ) + beta * (legendre(k+2, z))
147
+ #normalizing makes no sence, as b(k, 0) = 0 by construction
148
+ return [b(k, z) for k in range(self.level + 1)]
149
+
150
+
151
+ class Spline(Basisfunction):
152
+ name = "Spline"
153
+
154
+ def basis_definition(self, degree=1, knots=[0, 0, 0.001, 1, 1]):
155
+ z = Symbol("z")
156
+ basis = bspline_basis_set(degree, knots, z)
157
+ return basis
158
+
159
+
160
+ class OrthogonalSplineWithConstant(Basisfunction):
161
+ name = "OrthogonalSplineWithConstant"
162
+
163
+ def basis_definition(self, degree=1, knots=[0, 0, 0.5, 1, 1]):
164
+ z = Symbol("z")
165
+
166
+ def prod(u, v):
167
+ return integrate(u * v, (z, 0, 1))
168
+
169
+ basis = bspline_basis_set(degree, knots, z)
170
+ add_basis = [1]
171
+ # add_basis = [sympy.Piecewise((0, z<0.1), (1, True))]
172
+ basis = add_basis + basis[:-1]
173
+ orth = deepcopy(basis)
174
+ for i in range(1, len(orth)):
175
+ for j in range(0, i):
176
+ orth[i] -= prod(basis[i], orth[j]) / prod(orth[j], orth[j]) * orth[j]
177
+
178
+ for i in range(len(orth)):
179
+ orth[i] /= sympy.sqrt(prod(orth[i], orth[i]))
180
+
181
+ return orth