photosurfactant 1.0.0__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.
- photosurfactant/__init__.py +1 -0
- photosurfactant/fourier.py +47 -0
- photosurfactant/intensity_functions.py +32 -0
- photosurfactant/parameters.py +219 -0
- photosurfactant/py.typed +0 -0
- photosurfactant/scripts/__init__.py +0 -0
- photosurfactant/scripts/plot_all.sh +19 -0
- photosurfactant/scripts/plot_error.py +75 -0
- photosurfactant/scripts/plot_first_order.py +506 -0
- photosurfactant/scripts/plot_leading_order.py +288 -0
- photosurfactant/scripts/plot_spectrum.py +146 -0
- photosurfactant/scripts/plot_sweep.py +234 -0
- photosurfactant/semi_analytic/__init__.py +3 -0
- photosurfactant/semi_analytic/first_order.py +540 -0
- photosurfactant/semi_analytic/leading_order.py +237 -0
- photosurfactant/semi_analytic/limits.py +240 -0
- photosurfactant/semi_analytic/utils.py +43 -0
- photosurfactant/utils/__init__.py +0 -0
- photosurfactant/utils/arg_parser.py +162 -0
- photosurfactant/utils/func_parser.py +10 -0
- photosurfactant-1.0.0.dist-info/METADATA +25 -0
- photosurfactant-1.0.0.dist-info/RECORD +24 -0
- photosurfactant-1.0.0.dist-info/WHEEL +4 -0
- photosurfactant-1.0.0.dist-info/entry_points.txt +6 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"""First order solution to the photosurfactant model."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from photosurfactant.parameters import Parameters
|
|
10
|
+
from photosurfactant.semi_analytic.leading_order import LeadingOrder
|
|
11
|
+
from photosurfactant.semi_analytic.utils import Y, cosh, polyder, sinh, to_arr
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Symbols(Enum): # TODO: This is unnecessary
|
|
15
|
+
"""Symbols used in the first order solution."""
|
|
16
|
+
|
|
17
|
+
A = "A"
|
|
18
|
+
B = "B"
|
|
19
|
+
C = "C"
|
|
20
|
+
D = "D"
|
|
21
|
+
E = "E"
|
|
22
|
+
F = "F"
|
|
23
|
+
G = "G"
|
|
24
|
+
H = "H"
|
|
25
|
+
gamma_tr = "Gamma_tr"
|
|
26
|
+
gamma_ci = "Gamma_ci"
|
|
27
|
+
J_tr = "J_tr"
|
|
28
|
+
J_ci = "J_ci"
|
|
29
|
+
S = "S"
|
|
30
|
+
f = "f"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Variables(object): # TODO: Try to make this an enum
|
|
34
|
+
"""Variables used in the first order solution."""
|
|
35
|
+
|
|
36
|
+
A = to_arr({Symbols.A: 1}, Symbols)
|
|
37
|
+
B = to_arr({Symbols.B: 1}, Symbols)
|
|
38
|
+
C = to_arr({Symbols.C: 1}, Symbols)
|
|
39
|
+
D = to_arr({Symbols.D: 1}, Symbols)
|
|
40
|
+
E = to_arr({Symbols.E: 1}, Symbols)
|
|
41
|
+
F = to_arr({Symbols.F: 1}, Symbols)
|
|
42
|
+
G = to_arr({Symbols.G: 1}, Symbols)
|
|
43
|
+
H = to_arr({Symbols.H: 1}, Symbols)
|
|
44
|
+
gamma_tr = to_arr({Symbols.gamma_tr: 1}, Symbols)
|
|
45
|
+
gamma_ci = to_arr({Symbols.gamma_ci: 1}, Symbols)
|
|
46
|
+
J_tr = to_arr({Symbols.J_tr: 1}, Symbols)
|
|
47
|
+
J_ci = to_arr({Symbols.J_ci: 1}, Symbols)
|
|
48
|
+
S = to_arr({Symbols.S: 1}, Symbols)
|
|
49
|
+
f = to_arr({Symbols.f: 1}, Symbols)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FirstOrder(object):
|
|
53
|
+
"""First order solution to the photosurfactant model."""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
wavenumbers: np.ndarray,
|
|
58
|
+
params: Parameters,
|
|
59
|
+
leading: LeadingOrder,
|
|
60
|
+
):
|
|
61
|
+
"""Initalise solution to the first order model.
|
|
62
|
+
|
|
63
|
+
:param wavenumbers: Array of wavenumbers.
|
|
64
|
+
:param params: :class:`~.parameters.Parameters` object containing the
|
|
65
|
+
model parameters.
|
|
66
|
+
:param leading: :class:`~.leadingrder.LeadingOrder` object containing
|
|
67
|
+
the leading order solution.
|
|
68
|
+
"""
|
|
69
|
+
self.wavenumbers = wavenumbers
|
|
70
|
+
self.params = params
|
|
71
|
+
self.leading = leading
|
|
72
|
+
|
|
73
|
+
self.solution = np.zeros([len(self.wavenumbers), len(Symbols)], dtype=complex)
|
|
74
|
+
|
|
75
|
+
def solve(self, constraint: Callable[[int], tuple]):
|
|
76
|
+
"""Initialize the first order solution.
|
|
77
|
+
|
|
78
|
+
:param constraint: Prescription to close the system. Should be a linear
|
|
79
|
+
function in the given variables.
|
|
80
|
+
"""
|
|
81
|
+
zeros = np.zeros([len(Symbols) - 1], dtype=complex)
|
|
82
|
+
|
|
83
|
+
bc = BoundaryConditions(self)
|
|
84
|
+
for n, k in enumerate(self.wavenumbers):
|
|
85
|
+
# Formulate the boundary conditions
|
|
86
|
+
sys = bc.formulate(k)
|
|
87
|
+
|
|
88
|
+
# Apply prescrition
|
|
89
|
+
cond, val = constraint(n)
|
|
90
|
+
sys = np.vstack([sys, cond])
|
|
91
|
+
|
|
92
|
+
# Solve the system of equations
|
|
93
|
+
self.solution[n, :] = np.linalg.solve(sys, np.hstack([zeros, val]))
|
|
94
|
+
|
|
95
|
+
def _invert(self, func):
|
|
96
|
+
"""Invert the function to real space."""
|
|
97
|
+
|
|
98
|
+
@wraps(func)
|
|
99
|
+
def wrapper(x, *args, x_order=0, **kwargs):
|
|
100
|
+
if x_order < 0:
|
|
101
|
+
raise ValueError("Cannot integrate arbitrary functions in x.")
|
|
102
|
+
|
|
103
|
+
vals = np.array([func(k, *args, **kwargs) for k in self.wavenumbers])
|
|
104
|
+
coeffs = np.einsum("ij,ij->i", self.solution, vals)
|
|
105
|
+
|
|
106
|
+
res = (
|
|
107
|
+
coeffs[0] * (x_order == 0)
|
|
108
|
+
+ 2
|
|
109
|
+
* np.sum(
|
|
110
|
+
(1.0j * self.wavenumbers[1:, np.newaxis]) ** x_order
|
|
111
|
+
* coeffs[1:, np.newaxis]
|
|
112
|
+
* np.exp(
|
|
113
|
+
1.0j
|
|
114
|
+
* self.wavenumbers[1:, np.newaxis]
|
|
115
|
+
* (x if isinstance(x, np.ndarray) else np.array([x]))[
|
|
116
|
+
np.newaxis, :
|
|
117
|
+
]
|
|
118
|
+
),
|
|
119
|
+
axis=0,
|
|
120
|
+
)
|
|
121
|
+
).real
|
|
122
|
+
|
|
123
|
+
return res if isinstance(x, np.ndarray) else res[0]
|
|
124
|
+
|
|
125
|
+
return wrapper
|
|
126
|
+
|
|
127
|
+
# Real space variables
|
|
128
|
+
def psi(self, x, y, *, x_order=0, z_order=0):
|
|
129
|
+
"""Stream function at first order."""
|
|
130
|
+
return self._invert(self._psi)(x, y, x_order=x_order, z_order=z_order)
|
|
131
|
+
|
|
132
|
+
def u(self, x, y, *, x_order=0, z_order=0):
|
|
133
|
+
"""Horizontal velocity at first order."""
|
|
134
|
+
return self.psi(x, y, x_order=x_order, z_order=z_order + 1)
|
|
135
|
+
|
|
136
|
+
def w(self, x, y, *, x_order=0, z_order=0):
|
|
137
|
+
"""Vertical velocity at first order."""
|
|
138
|
+
return -self.psi(x, y, x_order=x_order + 1, z_order=z_order)
|
|
139
|
+
|
|
140
|
+
def p(self, x, y, *, x_order=0, z_order=0):
|
|
141
|
+
"""Pressure at first order."""
|
|
142
|
+
return self._invert(self._p)(x, y, x_order=x_order, z_order=z_order)
|
|
143
|
+
|
|
144
|
+
def c_tr(self, x, y, *, x_order=0, z_order=0):
|
|
145
|
+
"""Concentration of trans surfactant at first order."""
|
|
146
|
+
return self._invert(self._c_tr)(x, y, x_order=x_order, z_order=z_order)
|
|
147
|
+
|
|
148
|
+
def c_ci(self, x, y, *, x_order=0, z_order=0):
|
|
149
|
+
"""Concentration of cis surfactant at first order."""
|
|
150
|
+
return self._invert(self._c_ci)(x, y, x_order=x_order, z_order=z_order)
|
|
151
|
+
|
|
152
|
+
def i_c_tr(self, x, y, y_s=0, *, x_order=0):
|
|
153
|
+
"""Integral of trans surfactant concentration at first order."""
|
|
154
|
+
return self._invert(self._c_tr_i)(x, y, y_s, x_order=x_order)
|
|
155
|
+
|
|
156
|
+
def i_c_ci(self, x, y, y_s=0, *, x_order=0):
|
|
157
|
+
"""Integral of cis surfactant concentration at first order."""
|
|
158
|
+
return self._invert(self._c_ci_i)(x, y, y_s, x_order=x_order)
|
|
159
|
+
|
|
160
|
+
def Gamma_tr(self, x, *, x_order=0):
|
|
161
|
+
"""Surface excess of trans surfactant at first order."""
|
|
162
|
+
return self._invert(lambda k: Variables.gamma_tr)(x, x_order=x_order)
|
|
163
|
+
|
|
164
|
+
def Gamma_ci(self, x, *, x_order=0):
|
|
165
|
+
"""Surface excess of cis surfactant at first order."""
|
|
166
|
+
return self._invert(lambda k: Variables.gamma_ci)(x, x_order=x_order)
|
|
167
|
+
|
|
168
|
+
def J_tr(self, x, *, x_order=0):
|
|
169
|
+
"""Kinetic flux of trans surfactant at first order."""
|
|
170
|
+
return self._invert(lambda k: Variables.J_tr)(x, x_order=x_order)
|
|
171
|
+
|
|
172
|
+
def J_ci(self, x, *, x_order=0):
|
|
173
|
+
"""Kinetic flux of cis surfactant at first order."""
|
|
174
|
+
return self._invert(lambda k: Variables.J_ci)(x, x_order=x_order)
|
|
175
|
+
|
|
176
|
+
def S(self, x, *, x_order=0):
|
|
177
|
+
"""Interface shape at first order."""
|
|
178
|
+
return self._invert(lambda k: Variables.S)(x, x_order=x_order)
|
|
179
|
+
|
|
180
|
+
def f(self, x, *, x_order=0):
|
|
181
|
+
"""Light intensity at first order."""
|
|
182
|
+
return self._invert(lambda k: Variables.f)(x, x_order=x_order)
|
|
183
|
+
|
|
184
|
+
def gamma(self, x, *, x_order=0):
|
|
185
|
+
"""Surface tension at first order."""
|
|
186
|
+
return self._invert(lambda k: self._gamma)(x, x_order=x_order)
|
|
187
|
+
|
|
188
|
+
# Fourier space variables
|
|
189
|
+
def _psi(self, k, z, z_order=0):
|
|
190
|
+
"""Stream function at first order in Fourier space."""
|
|
191
|
+
if k == 0:
|
|
192
|
+
return (
|
|
193
|
+
Variables.A * polyder(Y**3, z_order)(z)
|
|
194
|
+
+ Variables.B * polyder(Y**2, z_order)(z)
|
|
195
|
+
+ Variables.C * polyder(Y, z_order)(z)
|
|
196
|
+
+ Variables.D * polyder(Y**0, z_order)(z)
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
return (
|
|
200
|
+
Variables.A * k ** (z_order - 1) * (z_order + k * z) * np.exp(k * z)
|
|
201
|
+
+ Variables.B * k**z_order * np.exp(k * z)
|
|
202
|
+
+ Variables.C
|
|
203
|
+
* (-k) ** (z_order - 1)
|
|
204
|
+
* (z_order - k * z)
|
|
205
|
+
* np.exp(-k * z)
|
|
206
|
+
+ Variables.D * (-k) ** z_order * np.exp(-k * z)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def _p(self, k, z, z_order=0):
|
|
210
|
+
"""Pressure at first order in Fourier space."""
|
|
211
|
+
if k == 0:
|
|
212
|
+
return 0 * Variables.f
|
|
213
|
+
else:
|
|
214
|
+
return (
|
|
215
|
+
self._psi(k, z, z_order=z_order + 3)
|
|
216
|
+
- k**2 * self._psi(k, z, z_order=z_order + 1)
|
|
217
|
+
) / (1.0j * k)
|
|
218
|
+
|
|
219
|
+
def _c_tr(self, k, z, z_order=0):
|
|
220
|
+
"""Concentration of trans surfactant at first order in Fourier space."""
|
|
221
|
+
return self._c(k, z, z_order)[0, ...]
|
|
222
|
+
|
|
223
|
+
def _c_ci(self, k, z, z_order=0):
|
|
224
|
+
"""Concentration of cis surfactant at first order in Fourier space."""
|
|
225
|
+
return self._c(k, z, z_order)[1, ...]
|
|
226
|
+
|
|
227
|
+
def _c(self, k, z, z_order=0):
|
|
228
|
+
"""Concentration of surfactant at first order in Fourier space."""
|
|
229
|
+
return self.params.V @ self._q(k, z, z_order)
|
|
230
|
+
|
|
231
|
+
def _c_tr_i(self, k, z, z_s=0):
|
|
232
|
+
"""Integral of the trans concentration at first order in Fourier space."""
|
|
233
|
+
return self._c_i(k, z, z_s)[0, ...]
|
|
234
|
+
|
|
235
|
+
def _c_ci_i(self, k, z, z_s=0):
|
|
236
|
+
"""Integral of the cis concentration at first order in Fourier space."""
|
|
237
|
+
return self._c_i(k, z, z_s)[1, ...]
|
|
238
|
+
|
|
239
|
+
def _c_i(self, k, z, z_s=0):
|
|
240
|
+
"""Integral of the surfactant concentration at first order in Fourier space."""
|
|
241
|
+
return np.einsum("ij,j...->i...", self.params.V, self._q_i(k, z, z_s))
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def _gamma(self):
|
|
245
|
+
"""Surface tension at first order in Fourier space."""
|
|
246
|
+
return (
|
|
247
|
+
-self.params.Ma
|
|
248
|
+
* (Variables.gamma_tr + Variables.gamma_ci)
|
|
249
|
+
/ (1 - self.leading.Gamma_tr - self.leading.Gamma_ci)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Private variables
|
|
253
|
+
def _q(self, k, z, z_order=0):
|
|
254
|
+
return (
|
|
255
|
+
self._q_0(k, z, z_order)
|
|
256
|
+
+ self._q_1(k, z, z_order)
|
|
257
|
+
+ self._q_2(k, z, z_order)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def _q_i(self, k, z, z_s=0):
|
|
261
|
+
return self._q(k, z, z_order=-1) - self._q(k, z_s, z_order=-1)
|
|
262
|
+
|
|
263
|
+
def _q_0(self, k, z, z_order=0):
|
|
264
|
+
zeta = self.params.zeta
|
|
265
|
+
if k == 0:
|
|
266
|
+
return np.array(
|
|
267
|
+
[
|
|
268
|
+
Variables.E * polyder(Y, z_order)(z)
|
|
269
|
+
+ Variables.F * polyder(Y**0, z_order)(z),
|
|
270
|
+
np.sqrt(zeta) ** z_order
|
|
271
|
+
* (
|
|
272
|
+
Variables.G * np.exp(z * np.sqrt(zeta))
|
|
273
|
+
+ Variables.H * (-1) ** z_order * np.exp(-z * np.sqrt(zeta))
|
|
274
|
+
),
|
|
275
|
+
]
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
return np.array(
|
|
279
|
+
[
|
|
280
|
+
k**z_order
|
|
281
|
+
* (
|
|
282
|
+
Variables.E * sinh(k * z, z_order)
|
|
283
|
+
+ Variables.F * cosh(k * z, z_order)
|
|
284
|
+
),
|
|
285
|
+
np.sqrt(zeta + k**2) ** z_order
|
|
286
|
+
* (
|
|
287
|
+
Variables.G * np.exp(z * np.sqrt(zeta + k**2))
|
|
288
|
+
+ Variables.H
|
|
289
|
+
* (-1) ** z_order
|
|
290
|
+
* np.exp(-z * np.sqrt(zeta + k**2))
|
|
291
|
+
),
|
|
292
|
+
]
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def _q_1(self, k, z, z_order=0):
|
|
296
|
+
zeta = self.params.zeta
|
|
297
|
+
if k == 0:
|
|
298
|
+
return (
|
|
299
|
+
Variables.f
|
|
300
|
+
* self.leading.B
|
|
301
|
+
* np.sqrt(zeta) ** z_order
|
|
302
|
+
/ 2
|
|
303
|
+
* (
|
|
304
|
+
z_order * cosh(z * np.sqrt(zeta), z_order)
|
|
305
|
+
+ z * np.sqrt(zeta) * sinh(z * np.sqrt(zeta), z_order)
|
|
306
|
+
)
|
|
307
|
+
)[np.newaxis, :] * np.array([0, 1])[:, np.newaxis]
|
|
308
|
+
else:
|
|
309
|
+
return (
|
|
310
|
+
Variables.f
|
|
311
|
+
* np.sqrt(zeta) ** z_order
|
|
312
|
+
* -self.leading.B
|
|
313
|
+
* zeta
|
|
314
|
+
/ k**2
|
|
315
|
+
* cosh(z * np.sqrt(zeta), z_order)
|
|
316
|
+
)[np.newaxis, :] * np.array([0, 1])[:, np.newaxis]
|
|
317
|
+
|
|
318
|
+
def _q_2(self, k, z, z_order=0):
|
|
319
|
+
alpha, eta, zeta = self.params.alpha, self.params.eta, self.params.zeta
|
|
320
|
+
null = to_arr(dict(), Symbols)
|
|
321
|
+
if k == 0:
|
|
322
|
+
return np.array([null, null])
|
|
323
|
+
else:
|
|
324
|
+
return (
|
|
325
|
+
-1.0j
|
|
326
|
+
* k
|
|
327
|
+
* self.leading.B
|
|
328
|
+
* np.sqrt(zeta)
|
|
329
|
+
* self.params.Pe_ci
|
|
330
|
+
/ (2 * (alpha + eta))
|
|
331
|
+
* np.einsum(
|
|
332
|
+
"ij...,j->i...",
|
|
333
|
+
np.array(
|
|
334
|
+
[
|
|
335
|
+
[self._q_2_scalar(k, z, 0, z_order), null],
|
|
336
|
+
[null, self._q_2_scalar(k, z, 1, z_order)],
|
|
337
|
+
]
|
|
338
|
+
),
|
|
339
|
+
np.array(
|
|
340
|
+
[
|
|
341
|
+
eta**2 - eta,
|
|
342
|
+
eta**2 + alpha,
|
|
343
|
+
]
|
|
344
|
+
),
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
def _q_2_scalar(self, k, z, index, z_order=0):
|
|
349
|
+
return (
|
|
350
|
+
self._q_2_gfunc(
|
|
351
|
+
k, z, self._a_c(k, index), self._b_c(k, index), 1, 1, z_order
|
|
352
|
+
)
|
|
353
|
+
+ self._q_2_gfunc(
|
|
354
|
+
k, z, self._c_c(k, index), self._d_c(k, index), 1, -1, z_order
|
|
355
|
+
)
|
|
356
|
+
+ self._q_2_gfunc(
|
|
357
|
+
k, z, self._e_c(k, index), self._f_c(k, index), -1, -1, z_order
|
|
358
|
+
)
|
|
359
|
+
+ self._q_2_gfunc(
|
|
360
|
+
k, z, self._g_c(k, index), self._h_c(k, index), -1, 1, z_order
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def _q_2_gfunc(self, k, z, a, b, s_1, s_2, z_order=0):
|
|
365
|
+
return (
|
|
366
|
+
z_order * self._q_2_base(k, s_1, s_2) ** (z_order - 1) * a
|
|
367
|
+
+ self._q_2_base(k, s_1, s_2) ** z_order * (a * z + b)
|
|
368
|
+
) * np.exp(self._q_2_base(k, s_1, s_2) * z)
|
|
369
|
+
|
|
370
|
+
def _q_2_base(self, k, s_1, s_2):
|
|
371
|
+
return s_1 * (k + s_2 * np.sqrt(self.params.zeta))
|
|
372
|
+
|
|
373
|
+
def _a_c(self, k, index):
|
|
374
|
+
zeta = self.params.zeta
|
|
375
|
+
return Variables.A / (2 * k * np.sqrt(zeta) + zeta * (index == 0))
|
|
376
|
+
|
|
377
|
+
def _c_c(self, k, index):
|
|
378
|
+
zeta = self.params.zeta
|
|
379
|
+
return Variables.A / (2 * k * np.sqrt(zeta) - zeta * (index == 0))
|
|
380
|
+
|
|
381
|
+
def _e_c(self, k, index):
|
|
382
|
+
zeta = self.params.zeta
|
|
383
|
+
return -Variables.C / (2 * k * np.sqrt(zeta) - zeta * (index == 0))
|
|
384
|
+
|
|
385
|
+
def _g_c(self, k, index):
|
|
386
|
+
zeta = self.params.zeta
|
|
387
|
+
return -Variables.C / (2 * k * np.sqrt(zeta) + zeta * (index == 0))
|
|
388
|
+
|
|
389
|
+
def _b_c(self, k, index):
|
|
390
|
+
zeta = self.params.zeta
|
|
391
|
+
return (Variables.B - 2 * self._a_c(k, index) * (k + np.sqrt(zeta))) / (
|
|
392
|
+
2 * k * np.sqrt(zeta) + zeta * (index == 0)
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
def _d_c(self, k, index):
|
|
396
|
+
zeta = self.params.zeta
|
|
397
|
+
return (Variables.B + 2 * self._c_c(k, index) * (k - np.sqrt(zeta))) / (
|
|
398
|
+
2 * k * np.sqrt(zeta) - zeta * (index == 0)
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
def _f_c(self, k, index):
|
|
402
|
+
zeta = self.params.zeta
|
|
403
|
+
return -(Variables.D + 2 * self._e_c(k, index) * (k - np.sqrt(zeta))) / (
|
|
404
|
+
2 * k * np.sqrt(zeta) - zeta * (index == 0)
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
def _h_c(self, k, index):
|
|
408
|
+
zeta = self.params.zeta
|
|
409
|
+
return -(Variables.D - 2 * self._g_c(k, index) * (k + np.sqrt(zeta))) / (
|
|
410
|
+
2 * k * np.sqrt(zeta) + zeta * (index == 0)
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class BoundaryConditions(object):
|
|
415
|
+
"""Boundary conditions for the photosurfactant model."""
|
|
416
|
+
|
|
417
|
+
def __init__(self, first: FirstOrder):
|
|
418
|
+
"""Initialise boundary conditions for the photosurfactant model.
|
|
419
|
+
|
|
420
|
+
:param first: :class:`~.first_order.FirstOrder` object containing the
|
|
421
|
+
first order solution.
|
|
422
|
+
"""
|
|
423
|
+
self.params = first.params
|
|
424
|
+
self.leading = first.leading
|
|
425
|
+
self.first = first
|
|
426
|
+
|
|
427
|
+
def formulate(self, k):
|
|
428
|
+
"""Formulate the boundary conditions."""
|
|
429
|
+
return np.vstack(
|
|
430
|
+
[
|
|
431
|
+
self.no_slip(k),
|
|
432
|
+
self.normal_stress(k),
|
|
433
|
+
self.tangential_stress(k),
|
|
434
|
+
self.kinematic(k),
|
|
435
|
+
self.no_flux(k),
|
|
436
|
+
self.kinetic_flux(k),
|
|
437
|
+
self.surface_excess(k),
|
|
438
|
+
self.mass_balance(k),
|
|
439
|
+
]
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
def no_slip(self, k):
|
|
443
|
+
"""No slip boundary condition."""
|
|
444
|
+
return np.array(
|
|
445
|
+
[
|
|
446
|
+
self.first._psi(k, 0),
|
|
447
|
+
self.first._psi(k, 0, z_order=1),
|
|
448
|
+
]
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
def normal_stress(self, k):
|
|
452
|
+
"""Normal stress boundary condition.""" # noqa: D401
|
|
453
|
+
if k == 0:
|
|
454
|
+
return self.first._psi(k, 1, z_order=3)[np.newaxis, ...]
|
|
455
|
+
else:
|
|
456
|
+
return (
|
|
457
|
+
self.first._psi(k, 1, z_order=3)
|
|
458
|
+
- 3 * k**2 * self.first._psi(k, 1, z_order=1)
|
|
459
|
+
- 1.0j * k**3 * self.leading.gamma * Variables.S
|
|
460
|
+
)[np.newaxis, ...]
|
|
461
|
+
|
|
462
|
+
def tangential_stress(self, k):
|
|
463
|
+
"""Tangential stress boundary condition."""
|
|
464
|
+
if k == 0:
|
|
465
|
+
return self.first._psi(k, 1, z_order=2)[np.newaxis, ...]
|
|
466
|
+
else:
|
|
467
|
+
return (
|
|
468
|
+
self.first._psi(k, 1, z_order=2)
|
|
469
|
+
+ k**2 * self.first._psi(k, 1)
|
|
470
|
+
- 1.0j * k * self.first._gamma
|
|
471
|
+
)[np.newaxis, ...]
|
|
472
|
+
|
|
473
|
+
def kinematic(self, k):
|
|
474
|
+
"""Kinematic boundary condition."""
|
|
475
|
+
if k == 0:
|
|
476
|
+
# Replace with conservation of mass
|
|
477
|
+
return Variables.S[np.newaxis, ...]
|
|
478
|
+
else:
|
|
479
|
+
return self.first._psi(k, 1)[np.newaxis, ...]
|
|
480
|
+
|
|
481
|
+
def no_flux(self, k):
|
|
482
|
+
"""No flux boundary condition."""
|
|
483
|
+
return self.first._c(k, 0, z_order=1)
|
|
484
|
+
|
|
485
|
+
def kinetic_flux(self, k):
|
|
486
|
+
"""Kinetic flux boundary condition."""
|
|
487
|
+
return self.params.B @ (
|
|
488
|
+
self.params.K
|
|
489
|
+
@ (
|
|
490
|
+
(
|
|
491
|
+
self.first._c(k, 1)
|
|
492
|
+
+ Variables.S[np.newaxis, :]
|
|
493
|
+
* self.leading.c(1, z_order=1)[:, np.newaxis]
|
|
494
|
+
)
|
|
495
|
+
* (1 - self.leading.Gamma_tr - self.leading.Gamma_ci)
|
|
496
|
+
- self.leading.c(1)[:, np.newaxis]
|
|
497
|
+
* (Variables.gamma_tr + Variables.gamma_ci)
|
|
498
|
+
)
|
|
499
|
+
- np.array([Variables.gamma_tr, Variables.gamma_ci])
|
|
500
|
+
) - np.array([Variables.J_tr, Variables.J_ci])
|
|
501
|
+
|
|
502
|
+
def surface_excess(self, k):
|
|
503
|
+
"""Surface excess boundary condition."""
|
|
504
|
+
gamma_vec = np.array([Variables.gamma_tr, Variables.gamma_ci])
|
|
505
|
+
J_vec = np.array([Variables.J_tr, Variables.J_ci])
|
|
506
|
+
|
|
507
|
+
d_psi_vec = self.first._psi(k, 1, z_order=1)
|
|
508
|
+
|
|
509
|
+
return (
|
|
510
|
+
1.0j
|
|
511
|
+
* k
|
|
512
|
+
* (self.params.P_s @ self.leading.Gamma)[:, np.newaxis]
|
|
513
|
+
* d_psi_vec[np.newaxis, :] # Fix this line
|
|
514
|
+
+ k**2 * gamma_vec
|
|
515
|
+
- self.params.P_s @ J_vec
|
|
516
|
+
+ self.params.A_s
|
|
517
|
+
@ (
|
|
518
|
+
np.array([Variables.gamma_tr, Variables.gamma_ci])
|
|
519
|
+
+ Variables.f[np.newaxis, :] * self.leading.Gamma[:, np.newaxis]
|
|
520
|
+
)
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
def mass_balance(self, k):
|
|
524
|
+
"""Mass balance boundary condition."""
|
|
525
|
+
cond = (self.params.k_tr * self.params.chi_tr) * (
|
|
526
|
+
self.first._c(k, 1, z_order=1)
|
|
527
|
+
+ Variables.S[np.newaxis, :] * self.leading.c(1, z_order=2)[:, np.newaxis]
|
|
528
|
+
) + self.params.P @ np.array([Variables.J_tr, Variables.J_ci])
|
|
529
|
+
|
|
530
|
+
if k == 0:
|
|
531
|
+
# Replace one mass balance with conservation of surfactant
|
|
532
|
+
cond[0, ...] = (
|
|
533
|
+
1
|
|
534
|
+
/ (self.params.k_tr * self.params.chi_tr)
|
|
535
|
+
* (Variables.gamma_tr + Variables.gamma_ci)
|
|
536
|
+
+ self.first._c_tr_i(k, 1)
|
|
537
|
+
+ self.first._c_ci_i(k, 1)
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
return cond
|