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,237 @@
|
|
|
1
|
+
"""Leading order solution to the photosurfactant model."""
|
|
2
|
+
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from photosurfactant.parameters import Parameters
|
|
8
|
+
from photosurfactant.semi_analytic.utils import Y, cosh, polyder
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LeadingOrder(object):
|
|
12
|
+
"""Leading order solution to the photosurfactant model."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, params: Parameters, root_index: int = -1):
|
|
15
|
+
"""Initialize solution to the leading order model.
|
|
16
|
+
|
|
17
|
+
:param params: :class:`~.parameters.Parameters` object containing the
|
|
18
|
+
model parameters.
|
|
19
|
+
:param root_index: Index of the solution branch to use. If set to -1,
|
|
20
|
+
the branch is selected automatically.
|
|
21
|
+
"""
|
|
22
|
+
self.params = params
|
|
23
|
+
self._initialize()
|
|
24
|
+
|
|
25
|
+
if root_index == -1:
|
|
26
|
+
self._trim_roots()
|
|
27
|
+
if len(self.roots) > 1:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
"Multiple valid solution branches detected. You can override this "
|
|
30
|
+
"message by explicitly setting a solution branch using root_index."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
(self.A, self.B) = self.roots[root_index]
|
|
34
|
+
|
|
35
|
+
def _initialize(self):
|
|
36
|
+
"""Initialize the leading order solution."""
|
|
37
|
+
params = self.params
|
|
38
|
+
|
|
39
|
+
# Define determinant coefficients
|
|
40
|
+
a_0 = (params.Da_tr + params.Da_ci) * (
|
|
41
|
+
params.alpha * params.Bi_tr * params.k_tr + params.Bi_ci * params.k_ci
|
|
42
|
+
) + params.Bi_tr * params.Bi_ci * (params.alpha * params.k_tr + params.k_ci)
|
|
43
|
+
b_0 = (params.Da_tr + params.Da_ci) * (
|
|
44
|
+
params.eta * params.Bi_tr * params.k_tr - params.Bi_ci * params.k_ci
|
|
45
|
+
) + params.Bi_tr * params.Bi_ci * (params.eta * params.k_tr - params.k_ci)
|
|
46
|
+
c_0 = (
|
|
47
|
+
params.Da_tr * params.Bi_ci
|
|
48
|
+
+ params.Da_ci * params.Bi_tr
|
|
49
|
+
+ params.Bi_tr * params.Bi_ci
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Quadratic coefficients
|
|
53
|
+
a = a_0 * (params.alpha + 1)
|
|
54
|
+
b = a_0 * (params.eta - 1) / np.sqrt(params.zeta) * np.sinh(
|
|
55
|
+
np.sqrt(params.zeta)
|
|
56
|
+
) + b_0 * (params.alpha + 1) * np.cosh(np.sqrt(params.zeta))
|
|
57
|
+
c = (
|
|
58
|
+
b_0
|
|
59
|
+
* (params.eta - 1)
|
|
60
|
+
/ np.sqrt(params.zeta)
|
|
61
|
+
* np.sinh(np.sqrt(params.zeta))
|
|
62
|
+
* np.cosh(np.sqrt(params.zeta))
|
|
63
|
+
)
|
|
64
|
+
d = a_0 * (1 / (params.k_tr * params.chi_tr) - 1) + c_0 * (params.alpha + 1)
|
|
65
|
+
e = b_0 * (1 / (params.k_tr * params.chi_tr) - 1) * np.cosh(
|
|
66
|
+
np.sqrt(params.zeta)
|
|
67
|
+
) + c_0 * (params.eta - 1) / np.sqrt(params.zeta) * np.sinh(
|
|
68
|
+
np.sqrt(params.zeta)
|
|
69
|
+
)
|
|
70
|
+
f = -c_0
|
|
71
|
+
|
|
72
|
+
# Second Quadratic
|
|
73
|
+
p = (
|
|
74
|
+
b_0
|
|
75
|
+
* params.k_tr
|
|
76
|
+
* params.chi_tr
|
|
77
|
+
* (params.alpha + params.eta)
|
|
78
|
+
/ np.sqrt(params.zeta)
|
|
79
|
+
* np.sinh(np.sqrt(params.zeta))
|
|
80
|
+
* np.cosh(np.sqrt(params.zeta))
|
|
81
|
+
)
|
|
82
|
+
q = (
|
|
83
|
+
a_0
|
|
84
|
+
* params.k_tr
|
|
85
|
+
* params.chi_tr
|
|
86
|
+
* (params.alpha + params.eta)
|
|
87
|
+
/ np.sqrt(params.zeta)
|
|
88
|
+
* np.sinh(np.sqrt(params.zeta))
|
|
89
|
+
)
|
|
90
|
+
r = params.alpha * params.Bi_tr * params.Bi_ci * (params.k_tr - params.k_ci)
|
|
91
|
+
s = c_0 * params.k_tr * params.chi_tr * (params.alpha + params.eta) / np.sqrt(
|
|
92
|
+
params.zeta
|
|
93
|
+
) * np.sinh(np.sqrt(params.zeta)) + params.Bi_tr * params.Bi_ci * (
|
|
94
|
+
params.eta * params.k_tr + params.alpha * params.k_ci
|
|
95
|
+
) * np.cosh(np.sqrt(params.zeta))
|
|
96
|
+
|
|
97
|
+
# Solve for B
|
|
98
|
+
poly = np.poly1d(
|
|
99
|
+
[
|
|
100
|
+
(0.0 if params.eta == 1 else (a * p**2 - b * p * q + c * q**2)),
|
|
101
|
+
(
|
|
102
|
+
2 * a * p * s
|
|
103
|
+
- b * p * r
|
|
104
|
+
- b * q * s
|
|
105
|
+
+ 2 * c * q * r
|
|
106
|
+
- d * p * q
|
|
107
|
+
+ e * q**2
|
|
108
|
+
),
|
|
109
|
+
(
|
|
110
|
+
a * s**2
|
|
111
|
+
- b * r * s
|
|
112
|
+
+ c * r**2
|
|
113
|
+
- d * p * r
|
|
114
|
+
- d * q * s
|
|
115
|
+
+ 2 * e * q * r
|
|
116
|
+
+ f * q**2
|
|
117
|
+
),
|
|
118
|
+
(-d * r * s + e * r**2 + 2 * f * q * r),
|
|
119
|
+
f * r**2,
|
|
120
|
+
]
|
|
121
|
+
)
|
|
122
|
+
B = poly.roots # noqa: N806
|
|
123
|
+
|
|
124
|
+
# Solve for A
|
|
125
|
+
A = -(p * B + s) / (q * B + r) * B # noqa: N806
|
|
126
|
+
|
|
127
|
+
self.roots = [(a, b) for a, b in zip(A, B)]
|
|
128
|
+
|
|
129
|
+
def _trim_roots(self):
|
|
130
|
+
"""Trim any invalid roots."""
|
|
131
|
+
verified_roots = []
|
|
132
|
+
for A_0, B_0 in self.roots:
|
|
133
|
+
if np.isclose(A_0.imag, 0):
|
|
134
|
+
self.A, self.B = A_0.real, B_0.real
|
|
135
|
+
else:
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
# Check if any of the concentrations are negative
|
|
139
|
+
# (it is sufficient to check the endpoints)
|
|
140
|
+
if self.c_ci(0) < 0 or self.c_ci(1) < 0:
|
|
141
|
+
continue
|
|
142
|
+
elif self.c_tr(0) < 0 or self.c_tr(1) < 0:
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
verified_roots.append([self.A, self.B])
|
|
146
|
+
|
|
147
|
+
self.roots = verified_roots
|
|
148
|
+
|
|
149
|
+
def c_tr(self, z, z_order=0):
|
|
150
|
+
"""Concentration of trans surfactant at leading order."""
|
|
151
|
+
params = self.params
|
|
152
|
+
return params.alpha * self.A * polyder(Y**0, z_order)(
|
|
153
|
+
z
|
|
154
|
+
) + params.eta * self.B * np.sqrt(params.zeta) ** z_order * cosh(
|
|
155
|
+
z * np.sqrt(params.zeta), z_order
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def c_ci(self, z, z_order=0):
|
|
159
|
+
"""Concentration of cis surfactant at leading order."""
|
|
160
|
+
params = self.params
|
|
161
|
+
return self.A * polyder(Y**0, z_order)(z) - self.B * np.sqrt(
|
|
162
|
+
params.zeta
|
|
163
|
+
) ** z_order * cosh(z * np.sqrt(params.zeta), z_order)
|
|
164
|
+
|
|
165
|
+
def c(self, z, z_order=0):
|
|
166
|
+
"""Concentration of surfactant at leading order."""
|
|
167
|
+
return np.array([self.c_tr(z, z_order), self.c_ci(z, z_order)])
|
|
168
|
+
|
|
169
|
+
def i_c_tr(self, z, z_s=0):
|
|
170
|
+
"""Integral of the trans concentration at leading order."""
|
|
171
|
+
return self.c_tr(z, z_order=-1) - self.c_tr(z_s, z_order=-1)
|
|
172
|
+
|
|
173
|
+
def i_c_ci(self, z, z_s=0):
|
|
174
|
+
"""Integral of the cis concentration at leading order."""
|
|
175
|
+
return self.c_ci(z, z_order=-1) - self.c_ci(z_s, z_order=-1)
|
|
176
|
+
|
|
177
|
+
def i_c(self, z, z_s):
|
|
178
|
+
"""Integral of the surfactant concentration at leading order."""
|
|
179
|
+
return np.array([self.i_c_tr(z, z_s), self.i_c_ci(z, z_s)])
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def Gamma_ci(self):
|
|
183
|
+
"""Surface excess of cis surfactant at leading order."""
|
|
184
|
+
return self.Gamma[1]
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def Gamma_tr(self):
|
|
188
|
+
"""Surface excess of trans surfactant at leading order."""
|
|
189
|
+
return self.Gamma[0]
|
|
190
|
+
|
|
191
|
+
@cached_property
|
|
192
|
+
def Gamma(self):
|
|
193
|
+
"""Surface excess of surfactant at leading order."""
|
|
194
|
+
params = self.params
|
|
195
|
+
return np.linalg.solve(self.M, params.P @ params.B @ params.K @ self.c(1))
|
|
196
|
+
|
|
197
|
+
@cached_property
|
|
198
|
+
def J_ci(self):
|
|
199
|
+
"""Kinetic flux of the cis surfactant at leading order."""
|
|
200
|
+
return self.params.Bi_ci * (
|
|
201
|
+
self.params.k_ci * self.c_ci(1) * (1 - self.Gamma_tr - self.Gamma_ci)
|
|
202
|
+
- self.Gamma_ci
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
@cached_property
|
|
206
|
+
def J_tr(self):
|
|
207
|
+
"""Kinetic flux of the trans surfactant at leading order."""
|
|
208
|
+
return self.params.Bi_tr * (
|
|
209
|
+
self.params.k_tr * self.c_tr(1) * (1 - self.Gamma_tr - self.Gamma_ci)
|
|
210
|
+
- self.Gamma_tr
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def J(self):
|
|
215
|
+
"""Kinetic flux of the surfactant at leading order."""
|
|
216
|
+
return np.array([self.J_tr, self.J_ci])
|
|
217
|
+
|
|
218
|
+
@cached_property
|
|
219
|
+
def gamma(self):
|
|
220
|
+
"""Surface tension at leading order."""
|
|
221
|
+
return 1 + self.params.Ma * np.log(1 - self.Gamma_tr - self.Gamma_ci)
|
|
222
|
+
|
|
223
|
+
@cached_property
|
|
224
|
+
def M(self):
|
|
225
|
+
params = self.params
|
|
226
|
+
return params.A + params.P @ params.B @ np.array(
|
|
227
|
+
[
|
|
228
|
+
[
|
|
229
|
+
params.k_tr * self.c_tr(1) + 1,
|
|
230
|
+
params.k_tr * self.c_tr(1),
|
|
231
|
+
],
|
|
232
|
+
[
|
|
233
|
+
params.k_ci * self.c_ci(1),
|
|
234
|
+
params.k_ci * self.c_ci(1) + 1,
|
|
235
|
+
],
|
|
236
|
+
]
|
|
237
|
+
)
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Limiting solutions for large and small Damkohler numbers."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from photosurfactant.parameters import Parameters
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LowIntensity:
|
|
9
|
+
"""Leading order solution for a uniform intensity at small Damkohler number."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, params: Parameters, root_index: int = -1):
|
|
12
|
+
"""Initialise solution to the small Damkohler model.
|
|
13
|
+
|
|
14
|
+
:param params: :class:`~.parameters.Parameters` object containing the
|
|
15
|
+
model parameters.
|
|
16
|
+
:param root_index: Index of the solution branch to use. If set to -1,
|
|
17
|
+
the branch is selected automatically.
|
|
18
|
+
"""
|
|
19
|
+
self.params = params
|
|
20
|
+
self._initialize()
|
|
21
|
+
|
|
22
|
+
if root_index == -1:
|
|
23
|
+
self._trim_roots()
|
|
24
|
+
if len(self.roots) > 1:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
"Multiple valid solution branches detected. You can override this "
|
|
27
|
+
"message by explicitly setting a solution branch using root_index."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
(self._c_tr, self._c_ci) = self.roots[root_index]
|
|
31
|
+
|
|
32
|
+
def _initialize(self):
|
|
33
|
+
"""Initialize the solution."""
|
|
34
|
+
params = self.params
|
|
35
|
+
|
|
36
|
+
# Solve for c_tr
|
|
37
|
+
poly = np.poly1d(
|
|
38
|
+
[
|
|
39
|
+
(params.k_tr * (params.k_ci - params.k_tr)),
|
|
40
|
+
(
|
|
41
|
+
-params.k_tr * params.k_ci
|
|
42
|
+
+ (
|
|
43
|
+
1
|
|
44
|
+
+ (1 + params.alpha * (1 - params.k_tr * params.chi_tr))
|
|
45
|
+
/ ((1 + params.alpha) * params.k_tr * params.chi_tr)
|
|
46
|
+
)
|
|
47
|
+
* (params.k_ci - params.k_tr)
|
|
48
|
+
),
|
|
49
|
+
(
|
|
50
|
+
params.alpha
|
|
51
|
+
/ (1 + params.alpha)
|
|
52
|
+
* (
|
|
53
|
+
params.k_tr
|
|
54
|
+
- (
|
|
55
|
+
2
|
|
56
|
+
+ (1 - params.k_tr * params.chi_tr)
|
|
57
|
+
/ (params.k_tr * params.chi_tr)
|
|
58
|
+
* params.k_tr
|
|
59
|
+
)
|
|
60
|
+
* params.k_ci
|
|
61
|
+
)
|
|
62
|
+
),
|
|
63
|
+
((params.alpha / (1 + params.alpha)) ** 2 * params.k_ci),
|
|
64
|
+
]
|
|
65
|
+
)
|
|
66
|
+
c_tr = poly.roots
|
|
67
|
+
|
|
68
|
+
# Solve for c_ci
|
|
69
|
+
c_ci = -(
|
|
70
|
+
(1 + params.alpha) * params.chi_tr * params.k_tr * c_tr**2
|
|
71
|
+
+ (
|
|
72
|
+
(1 + params.alpha) * (1 + params.chi_tr)
|
|
73
|
+
- params.alpha * params.chi_tr * params.k_tr
|
|
74
|
+
)
|
|
75
|
+
* c_tr
|
|
76
|
+
- params.alpha * params.chi_tr
|
|
77
|
+
) / (params.chi_tr * params.k_ci * (1 + (1 + params.alpha) * (c_tr - 1)))
|
|
78
|
+
|
|
79
|
+
self.roots = [(a, b) for a, b in zip(c_tr, c_ci)]
|
|
80
|
+
|
|
81
|
+
def _trim_roots(self):
|
|
82
|
+
"""Trim any invalid roots."""
|
|
83
|
+
verified_roots = []
|
|
84
|
+
for c_tr, c_ci in self.roots:
|
|
85
|
+
if np.isclose(c_tr.imag, 0):
|
|
86
|
+
self._c_tr, self._c_ci = c_tr.real, c_ci.real
|
|
87
|
+
else:
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
# Check if any of the concentrations are negative
|
|
91
|
+
if self.c_tr < 0 or self.c_ci < 0:
|
|
92
|
+
continue
|
|
93
|
+
elif self.gamma_tr < 0 or self.gamma_ci < 0:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
verified_roots.append([self._c_tr, self._c_ci])
|
|
97
|
+
|
|
98
|
+
self.roots = verified_roots
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def c_tr(self):
|
|
102
|
+
"""Concentration of trans surfactant at small Damkohler number."""
|
|
103
|
+
return self._c_tr
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def c_ci(self):
|
|
107
|
+
"""Concentration of cis surfactant at small Damkohler number."""
|
|
108
|
+
return self._c_ci
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def gamma_tr(self):
|
|
112
|
+
"""Surface excess of trans surfactant at small Damkohler number."""
|
|
113
|
+
params = self.params
|
|
114
|
+
return (
|
|
115
|
+
params.k_tr
|
|
116
|
+
* self.c_tr
|
|
117
|
+
/ (1 + params.k_tr * self.c_tr + params.k_ci * self.c_ci)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def gamma_ci(self):
|
|
122
|
+
"""Surface excess of cis surfactant at small Damkohler number."""
|
|
123
|
+
params = self.params
|
|
124
|
+
return (
|
|
125
|
+
params.k_ci
|
|
126
|
+
* self.c_ci
|
|
127
|
+
/ (1 + params.k_tr * self.c_tr + params.k_ci * self.c_ci)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def tension(self):
|
|
132
|
+
"""Surface tension at small Damkohler number."""
|
|
133
|
+
return 1 + self.params.Ma * np.log(1 - self.gamma_tr - self.gamma_ci)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class HighIntensity:
|
|
137
|
+
"""Leading order solution for a uniform intensity at large Damkohler number."""
|
|
138
|
+
|
|
139
|
+
def __init__(self, params: Parameters, root_index: int = -1):
|
|
140
|
+
"""Initialise solution to the large Damkohler model.
|
|
141
|
+
|
|
142
|
+
:param params: :class:`~.parameters.Parameters` object containing the
|
|
143
|
+
model parameters.
|
|
144
|
+
:param root_index: Index of the solution branch to use. If set to -1,
|
|
145
|
+
the branch is selected automatically.
|
|
146
|
+
"""
|
|
147
|
+
self.params = params
|
|
148
|
+
self._initialize()
|
|
149
|
+
|
|
150
|
+
if root_index == -1:
|
|
151
|
+
self._trim_roots()
|
|
152
|
+
if len(self.roots) > 1:
|
|
153
|
+
raise ValueError(
|
|
154
|
+
"Multiple valid solution branches detected. You can override this "
|
|
155
|
+
"message by explicitly setting a solution branch using root_index."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
self._gamma_ci = self.roots[root_index]
|
|
159
|
+
|
|
160
|
+
def _initialize(self):
|
|
161
|
+
"""Initialize the solution."""
|
|
162
|
+
params = self.params
|
|
163
|
+
|
|
164
|
+
# Define additional parameters
|
|
165
|
+
a = params.alpha * params.Bi_tr * params.k_tr + params.Bi_ci * params.k_ci
|
|
166
|
+
b = params.alpha * params.Bi_tr * (1 + params.k_tr + 1 / params.chi_tr)
|
|
167
|
+
c = params.Bi_ci * (1 + params.k_ci + 1 / params.chi_ci)
|
|
168
|
+
|
|
169
|
+
# Solve for Gamma_ci
|
|
170
|
+
poly = np.poly1d(
|
|
171
|
+
[
|
|
172
|
+
(1 + params.alpha) / (params.k_tr * params.chi_tr) * a,
|
|
173
|
+
-(b + c),
|
|
174
|
+
a / (1 + params.alpha),
|
|
175
|
+
]
|
|
176
|
+
)
|
|
177
|
+
self.roots = poly.roots
|
|
178
|
+
|
|
179
|
+
def _trim_roots(self):
|
|
180
|
+
"""Trim any invalid roots."""
|
|
181
|
+
verified_roots = []
|
|
182
|
+
for root in self.roots:
|
|
183
|
+
if np.isclose(root.imag, 0):
|
|
184
|
+
self._gamma_ci = root.real
|
|
185
|
+
else:
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
# Check if any of the concentrations are negative
|
|
189
|
+
if self.c_ci < 0 or self.c_tr < 0:
|
|
190
|
+
continue
|
|
191
|
+
elif self.gamma_ci < 0:
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
verified_roots.append(self._gamma_ci)
|
|
195
|
+
|
|
196
|
+
self.roots = verified_roots
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def c_tr(self):
|
|
200
|
+
"""Concentration of trans surfactant at large Damkohler number."""
|
|
201
|
+
return self.params.alpha * self.c_ci
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def c_ci(self):
|
|
205
|
+
"""Concentration of cis surfactant at large Damkohler number."""
|
|
206
|
+
params = self.params
|
|
207
|
+
return (
|
|
208
|
+
1 / (1 + params.alpha) - 1 / (params.k_tr * params.chi_tr) * self.gamma_ci
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def gamma_tr(self):
|
|
213
|
+
"""Surface excess of trans surfactant at large Damkohler number."""
|
|
214
|
+
return self.params.alpha * self.gamma_ci
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def gamma_ci(self):
|
|
218
|
+
"""Surface excess of cis surfactant at large Damkohler number."""
|
|
219
|
+
return self._gamma_ci
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def J_tr(self):
|
|
223
|
+
"""Kinetic flux of the trans surfactant at large Damkohler number."""
|
|
224
|
+
return self.params.Bi_tr * (
|
|
225
|
+
self.params.k_tr * self.c_tr * (1 - self.gamma_tr - self.gamma_ci)
|
|
226
|
+
- self.gamma_tr
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def J_ci(self):
|
|
231
|
+
"""Kinetic flux of the cis surfactant at large Damkohler number."""
|
|
232
|
+
return self.params.Bi_ci * (
|
|
233
|
+
self.params.k_ci * self.c_ci * (1 - self.gamma_tr - self.gamma_ci)
|
|
234
|
+
- self.gamma_ci
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def tension(self):
|
|
239
|
+
"""Surface tension at large Damkohler number."""
|
|
240
|
+
return 1 + self.params.Ma * np.log(1 - self.gamma_tr - self.gamma_ci)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Utility functions for the semi-analytic model."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import numpy.typing as npt
|
|
8
|
+
|
|
9
|
+
Y = np.poly1d([1, 0]) # Polynomial for differentiation
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def hyperder(n: int) -> Callable[[float], float]:
|
|
13
|
+
"""Return the n-th derivative of cosh."""
|
|
14
|
+
return np.sinh if n % 2 else np.cosh
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cosh(x: float, x_order: int = 0) -> float:
|
|
18
|
+
return hyperder(x_order)(x)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def sinh(x: float, x_order: int = 0) -> float:
|
|
22
|
+
return hyperder(x_order + 1)(x)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def polyder(p: np.poly1d, n: int) -> np.poly1d:
|
|
26
|
+
"""Wrap np.polyder and np.polyint."""
|
|
27
|
+
return p.deriv(n) if n > 0 else p.integ(-n)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def to_arr(vals: dict, unknowns: Enum) -> npt.NDArray:
|
|
31
|
+
"""Convert a dictionary of values to an array."""
|
|
32
|
+
arr = np.zeros(len(unknowns), dtype=complex)
|
|
33
|
+
for key in vals.keys():
|
|
34
|
+
if key not in unknowns:
|
|
35
|
+
raise ValueError(f"Unknown key: {key}")
|
|
36
|
+
|
|
37
|
+
for i, key in enumerate(unknowns):
|
|
38
|
+
try:
|
|
39
|
+
arr[i] = vals[key]
|
|
40
|
+
except KeyError:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
return arr
|
|
File without changes
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from argparse import ArgumentParser
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def parameter_parser(parser: ArgumentParser):
|
|
5
|
+
"""Add model parameters to the parser."""
|
|
6
|
+
parser.add_argument(
|
|
7
|
+
"--L",
|
|
8
|
+
type=float,
|
|
9
|
+
default=10.0,
|
|
10
|
+
help="The aspect ratio of the domain.",
|
|
11
|
+
)
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
"--Da_tr",
|
|
14
|
+
type=float,
|
|
15
|
+
default=1.0,
|
|
16
|
+
help="The Damkohler number for the trans surfactant.",
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"--Da_ci",
|
|
20
|
+
type=float,
|
|
21
|
+
default=2.0,
|
|
22
|
+
help="The Damkohler number for the cis surfactant.",
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--Pe_tr",
|
|
26
|
+
type=float,
|
|
27
|
+
default=10.0,
|
|
28
|
+
help="The Peclet number for the trans surfactant.",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--Pe_ci",
|
|
32
|
+
type=float,
|
|
33
|
+
default=10.0,
|
|
34
|
+
help="The Peclet number for the cis surfactant.",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--Pe_tr_s",
|
|
38
|
+
type=float,
|
|
39
|
+
default=10.0,
|
|
40
|
+
help="The Peclet number for the trans surfactant on the interface.",
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--Pe_ci_s",
|
|
44
|
+
type=float,
|
|
45
|
+
default=10.0,
|
|
46
|
+
help="The Peclet number for the cis surfactant on the interface.",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--Bi_tr",
|
|
50
|
+
type=float,
|
|
51
|
+
default=1 / 300,
|
|
52
|
+
help="The Biot number for the trans surfactant.",
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--Bi_ci",
|
|
56
|
+
type=float,
|
|
57
|
+
default=1.0,
|
|
58
|
+
help="The Biot number for the cis surfactant.",
|
|
59
|
+
)
|
|
60
|
+
parser.add_argument("--Ma", type=float, default=2.0, help="The Marangoni number.")
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
"--k_tr",
|
|
63
|
+
type=float,
|
|
64
|
+
default=1.0,
|
|
65
|
+
help="The adsorption rate for the trans surfactant.",
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--k_ci",
|
|
69
|
+
type=float,
|
|
70
|
+
default=1 / 30,
|
|
71
|
+
help="The adsorption rate for the cis surfactant.",
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--chi_tr",
|
|
75
|
+
type=float,
|
|
76
|
+
default=100 / 30,
|
|
77
|
+
help="The desorption rate for the trans surfactant.",
|
|
78
|
+
)
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--chi_ci",
|
|
81
|
+
type=float,
|
|
82
|
+
default=100.0,
|
|
83
|
+
help="The desorption rate for the cis surfactant.",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def plot_parser(parser: ArgumentParser):
|
|
88
|
+
"""Add plotting parameters to the parser."""
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"--wave_count", type=int, default=100, help="Number of wavenumbers to use."
|
|
91
|
+
)
|
|
92
|
+
parser.add_argument(
|
|
93
|
+
"--grid_size",
|
|
94
|
+
type=int,
|
|
95
|
+
default=1000,
|
|
96
|
+
help="Number of grid points to evaluate the solution on.",
|
|
97
|
+
)
|
|
98
|
+
parser.add_argument(
|
|
99
|
+
"-s", "--save", action="store_true", help="Save the figures to disk."
|
|
100
|
+
)
|
|
101
|
+
parser.add_argument(
|
|
102
|
+
"--path", type=str, default="./", help="Path to save the figures to."
|
|
103
|
+
)
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
"--label", type=str, help="Label to append to the figure filenames."
|
|
106
|
+
)
|
|
107
|
+
parser.add_argument(
|
|
108
|
+
"--usetex", action="store_true", help="Use LaTeX for rendering text."
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"--format",
|
|
112
|
+
type=str,
|
|
113
|
+
default="png",
|
|
114
|
+
help="Format to save the figures in.",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def leading_order_parser(parser: ArgumentParser):
|
|
119
|
+
"""Add leading order parameters to the parser."""
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
"--root_index",
|
|
122
|
+
type=int,
|
|
123
|
+
default=-1,
|
|
124
|
+
help="The index of solution branch for the leading order problem. If set to "
|
|
125
|
+
"-1, the branch is selected automatically.",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def first_order_parser(parser: ArgumentParser):
|
|
130
|
+
"""Add first order parameters to the parser."""
|
|
131
|
+
parser.add_argument(
|
|
132
|
+
"--func",
|
|
133
|
+
type=str,
|
|
134
|
+
default="super_gaussian(x, 4.0, 1.0)",
|
|
135
|
+
help="An expression in the coordinate x for the light intensity/interface "
|
|
136
|
+
'perturbation. The function should be a quoted string. E.g. "sin(x)". The '
|
|
137
|
+
"function must be 2L-periodic and always return a float.",
|
|
138
|
+
)
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
"--problem",
|
|
141
|
+
choices=["forward", "inverse"],
|
|
142
|
+
default="forward",
|
|
143
|
+
help="The type of problem to solve.",
|
|
144
|
+
)
|
|
145
|
+
parser.add_argument(
|
|
146
|
+
"--mollify",
|
|
147
|
+
action="store_true",
|
|
148
|
+
help="Apply mollification to the light intensity/interface perturbation.",
|
|
149
|
+
)
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
"--delta",
|
|
152
|
+
type=float,
|
|
153
|
+
default=0.5,
|
|
154
|
+
help="The mollification parameter for the light intensity/interface "
|
|
155
|
+
"perturbation.",
|
|
156
|
+
)
|
|
157
|
+
parser.add_argument(
|
|
158
|
+
"--norm_scale",
|
|
159
|
+
choices=["linear", "log"],
|
|
160
|
+
default="linear",
|
|
161
|
+
help="The normalization type.",
|
|
162
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
import numpy as np # noqa: F401
|
|
4
|
+
|
|
5
|
+
from photosurfactant.intensity_functions import * # noqa: F403
|
|
6
|
+
from photosurfactant.parameters import Parameters
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse_func(func_str: str) -> Callable[[float, Parameters], float]:
|
|
10
|
+
return eval("lambda x, params: " + func_str)
|