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.
@@ -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)