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,288 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from matplotlib import colors
|
|
6
|
+
|
|
7
|
+
from photosurfactant.parameters import Parameters, PlottingParameters
|
|
8
|
+
from photosurfactant.semi_analytic.leading_order import LeadingOrder
|
|
9
|
+
from photosurfactant.semi_analytic.limits import HighIntensity, LowIntensity
|
|
10
|
+
from photosurfactant.utils.arg_parser import (
|
|
11
|
+
leading_order_parser,
|
|
12
|
+
parameter_parser,
|
|
13
|
+
plot_parser,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# TODO: Reduced figures at 3 magnitudes?
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def plot_bulk_concentration(
|
|
20
|
+
intensities, default_params, plot_params, root_index, log=False
|
|
21
|
+
):
|
|
22
|
+
"""Plot the bulk concentrations for varying intensity."""
|
|
23
|
+
|
|
24
|
+
default_intensity = default_params.Da_tr
|
|
25
|
+
|
|
26
|
+
# Figure setup
|
|
27
|
+
plt = plot_params.plt
|
|
28
|
+
fig, axs = plt.subplots(2, 1, figsize=(9, 6), constrained_layout=True)
|
|
29
|
+
|
|
30
|
+
cmap = plt.cm.get_cmap("coolwarm")
|
|
31
|
+
|
|
32
|
+
if log:
|
|
33
|
+
norm = colors.LogNorm(
|
|
34
|
+
vmin=intensities[0] / np.sqrt(10), vmax=intensities[-1] * np.sqrt(10)
|
|
35
|
+
)
|
|
36
|
+
else:
|
|
37
|
+
norm = plt.Normalize(vmin=intensities[0], vmax=intensities[-1])
|
|
38
|
+
|
|
39
|
+
yy = np.linspace(0, 1, plot_params.grid_size)
|
|
40
|
+
|
|
41
|
+
# Plot concentrations for varying intensity
|
|
42
|
+
for intensity in intensities:
|
|
43
|
+
params = default_params.update(
|
|
44
|
+
Da_tr=default_params.Da_tr * intensity / default_intensity,
|
|
45
|
+
Da_ci=default_params.Da_ci * intensity / default_intensity,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Solve leading order problem
|
|
49
|
+
leading = LeadingOrder(params, root_index)
|
|
50
|
+
|
|
51
|
+
# Plot bulk concentrations
|
|
52
|
+
axs[0].plot(yy, leading.c_tr(yy), color=cmap(norm(intensity)))
|
|
53
|
+
axs[1].plot(yy, leading.c_ci(yy), color=cmap(norm(intensity)))
|
|
54
|
+
|
|
55
|
+
# Highlight curves for unit intensity
|
|
56
|
+
params = default_params.copy()
|
|
57
|
+
leading = LeadingOrder(params, root_index)
|
|
58
|
+
axs[0].plot(yy, leading.c_tr(yy), color="k", linestyle=(0, (5, 10)))
|
|
59
|
+
axs[1].plot(yy, leading.c_ci(yy), color="k", linestyle=(0, (5, 10)))
|
|
60
|
+
|
|
61
|
+
# Tidy up figure
|
|
62
|
+
axs[0].set_xticklabels([])
|
|
63
|
+
axs[0].set_ylabel(r"$c_{\mathrm{tr}, 0}$")
|
|
64
|
+
axs[0].ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
|
|
65
|
+
|
|
66
|
+
axs[1].set_xlabel(r"$z$")
|
|
67
|
+
axs[1].set_ylabel(r"$c_{\mathrm{ci}, 0}$")
|
|
68
|
+
axs[1].ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
|
|
69
|
+
|
|
70
|
+
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
|
|
71
|
+
cbar = fig.colorbar(
|
|
72
|
+
sm, ax=axs.ravel().tolist(), label=r"Intensity ($Da_\mathrm{tr}$)"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Annotate colorbar
|
|
76
|
+
cbar.ax.scatter(
|
|
77
|
+
np.mean(cbar.ax.set_xlim()) * np.ones_like(intensities),
|
|
78
|
+
intensities,
|
|
79
|
+
s=50,
|
|
80
|
+
facecolors="none",
|
|
81
|
+
edgecolors="k",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
cbar.ax.scatter(
|
|
85
|
+
[np.mean(cbar.ax.set_xlim())],
|
|
86
|
+
[default_intensity],
|
|
87
|
+
s=60,
|
|
88
|
+
facecolors="k",
|
|
89
|
+
edgecolors="k",
|
|
90
|
+
marker="D",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if plot_params.save:
|
|
94
|
+
plt.savefig(
|
|
95
|
+
plot_params.path
|
|
96
|
+
+ f"bulk_concentration{plot_params.label}.{plot_params.format}",
|
|
97
|
+
bbox_inches="tight",
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
plt.show()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def plot_interfacial_concentration(
|
|
104
|
+
intensities, default_params, plot_params, root_index, log=False, limits=False
|
|
105
|
+
):
|
|
106
|
+
"""Plot surface excess for varying intensity."""
|
|
107
|
+
# Collect interfacial values for varying intensity
|
|
108
|
+
gamma_tr, gamma_ci = [], []
|
|
109
|
+
tension = []
|
|
110
|
+
default_intensity = default_params.Da_tr
|
|
111
|
+
|
|
112
|
+
for intensity in intensities:
|
|
113
|
+
params = default_params.update(
|
|
114
|
+
Da_tr=default_params.Da_tr * intensity / default_intensity,
|
|
115
|
+
Da_ci=default_params.Da_ci * intensity / default_intensity,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Solve leading order problem
|
|
119
|
+
leading = LeadingOrder(params, root_index)
|
|
120
|
+
|
|
121
|
+
# Store values
|
|
122
|
+
gamma_tr.append(leading.Gamma_tr)
|
|
123
|
+
gamma_ci.append(leading.Gamma_ci)
|
|
124
|
+
|
|
125
|
+
tension.append(leading.gamma)
|
|
126
|
+
|
|
127
|
+
gamma_tr, gamma_ci = np.array(gamma_tr), np.array(gamma_ci)
|
|
128
|
+
tension = np.array(tension)
|
|
129
|
+
|
|
130
|
+
plt = plot_params.plt
|
|
131
|
+
fig, ax1 = plt.subplots(figsize=(8, 6))
|
|
132
|
+
|
|
133
|
+
# Plot surface excess
|
|
134
|
+
gamma_tr_plt = ax1.plot(
|
|
135
|
+
intensities, gamma_tr, "r--", label=r"$\Gamma_{\mathrm{tr}, 0}$"
|
|
136
|
+
)
|
|
137
|
+
gamma_ci_plt = ax1.plot(
|
|
138
|
+
intensities, gamma_ci, "b-.", label=r"$\Gamma_{\mathrm{ci}, 0}$"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Plot limiting cases
|
|
142
|
+
if limits:
|
|
143
|
+
tan_length = int((3 / 8) * len(intensities))
|
|
144
|
+
|
|
145
|
+
small_dam = LowIntensity(params)
|
|
146
|
+
ax1.plot(
|
|
147
|
+
intensities[:tan_length],
|
|
148
|
+
small_dam.gamma_tr * np.ones_like(intensities[:tan_length]),
|
|
149
|
+
"r:",
|
|
150
|
+
)
|
|
151
|
+
ax1.plot(
|
|
152
|
+
intensities[:tan_length],
|
|
153
|
+
small_dam.gamma_ci * np.ones_like(intensities[:tan_length]),
|
|
154
|
+
"b:",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
large_dam = HighIntensity(params)
|
|
158
|
+
ax1.plot(
|
|
159
|
+
intensities[-tan_length:],
|
|
160
|
+
large_dam.gamma_tr * np.ones_like(intensities[-tan_length:]),
|
|
161
|
+
"r:",
|
|
162
|
+
)
|
|
163
|
+
ax1.plot(
|
|
164
|
+
intensities[-tan_length:],
|
|
165
|
+
large_dam.gamma_ci * np.ones_like(intensities[-tan_length:]),
|
|
166
|
+
"b:",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Set xscale
|
|
170
|
+
if log:
|
|
171
|
+
ax1.set_xscale("log")
|
|
172
|
+
|
|
173
|
+
ax1.set_xlabel(r"Intensity ($Da_\mathrm{tr}$)")
|
|
174
|
+
ax1.set_ylabel(
|
|
175
|
+
r"Surface Excess ($\Gamma_{\mathrm{tr}, 0}, \Gamma_{\mathrm{ci}, 0}$)"
|
|
176
|
+
)
|
|
177
|
+
ax1.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
|
|
178
|
+
ax1.grid()
|
|
179
|
+
|
|
180
|
+
ax2 = ax1.twinx()
|
|
181
|
+
|
|
182
|
+
# Plot surface tension
|
|
183
|
+
tension_plt = ax2.plot(
|
|
184
|
+
intensities,
|
|
185
|
+
tension,
|
|
186
|
+
"k-",
|
|
187
|
+
label=r"$\gamma_0$",
|
|
188
|
+
)
|
|
189
|
+
ax2.set_ylabel(r"$\gamma_0$")
|
|
190
|
+
ax2.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
|
|
191
|
+
|
|
192
|
+
# Annotate points at unit intensity
|
|
193
|
+
leading = LeadingOrder(default_params, root_index)
|
|
194
|
+
ax1.plot([default_intensity], [leading.Gamma_tr], color="r", marker="D")
|
|
195
|
+
ax1.plot([default_intensity], [leading.Gamma_ci], color="b", marker="D")
|
|
196
|
+
ax2.plot([default_intensity], [leading.gamma], color="k", marker="D")
|
|
197
|
+
|
|
198
|
+
# Merge legends
|
|
199
|
+
plots = tension_plt + gamma_tr_plt + gamma_ci_plt
|
|
200
|
+
labels = [plot.get_label() for plot in plots]
|
|
201
|
+
ax2.legend(plots, labels, loc="upper right")
|
|
202
|
+
|
|
203
|
+
if plot_params.save:
|
|
204
|
+
plt.savefig(
|
|
205
|
+
plot_params.path
|
|
206
|
+
+ f"interfacial_concentration{plot_params.label}.{plot_params.format}",
|
|
207
|
+
bbox_inches="tight",
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
plt.show()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def plot_leading_order(): # noqa: D103
|
|
214
|
+
parser = ArgumentParser(
|
|
215
|
+
description="Plot the leading order surfactant concentrations.",
|
|
216
|
+
formatter_class=ArgumentDefaultsHelpFormatter,
|
|
217
|
+
)
|
|
218
|
+
parameter_parser(parser)
|
|
219
|
+
plot_parser(parser)
|
|
220
|
+
leading_order_parser(parser)
|
|
221
|
+
parser.add_argument(
|
|
222
|
+
"--intensities",
|
|
223
|
+
type=float,
|
|
224
|
+
nargs="*",
|
|
225
|
+
default=[1.0],
|
|
226
|
+
help="Intensity factors to plot the concentration for. If count is greater "
|
|
227
|
+
"than one, plots for a range instead.",
|
|
228
|
+
)
|
|
229
|
+
parser.add_argument(
|
|
230
|
+
"--count",
|
|
231
|
+
type=int,
|
|
232
|
+
default=1,
|
|
233
|
+
help="The number of values to plot if using an intensity range.",
|
|
234
|
+
)
|
|
235
|
+
parser.add_argument(
|
|
236
|
+
"--interface",
|
|
237
|
+
action="store_true",
|
|
238
|
+
help="Plot the interfacial values.",
|
|
239
|
+
)
|
|
240
|
+
parser.add_argument(
|
|
241
|
+
"--limits",
|
|
242
|
+
action="store_true",
|
|
243
|
+
help="Plot the equilibrium states for small and large intensity.",
|
|
244
|
+
)
|
|
245
|
+
parser.add_argument(
|
|
246
|
+
"--log",
|
|
247
|
+
action="store_true",
|
|
248
|
+
help="Use a log scale.",
|
|
249
|
+
)
|
|
250
|
+
parser.add_argument(
|
|
251
|
+
"--gamma",
|
|
252
|
+
type=float,
|
|
253
|
+
default=1.0,
|
|
254
|
+
help="The stretching rate when using a range.",
|
|
255
|
+
)
|
|
256
|
+
args = parser.parse_args()
|
|
257
|
+
|
|
258
|
+
params = Parameters.from_dict(vars(args))
|
|
259
|
+
plot_params = PlottingParameters.from_dict(vars(args))
|
|
260
|
+
root_index = args.root_index
|
|
261
|
+
intensities = args.intensities
|
|
262
|
+
|
|
263
|
+
if args.count > 1:
|
|
264
|
+
if len(intensities) != 2:
|
|
265
|
+
raise ValueError("intensities must be of length 2 to plot a range.")
|
|
266
|
+
|
|
267
|
+
grid = abs(np.linspace(-1.0, 1.0, args.count)) ** args.gamma
|
|
268
|
+
grid[: args.count // 2] *= -1
|
|
269
|
+
intensities = grid * (intensities[1] - intensities[0]) / 2 + np.mean(
|
|
270
|
+
intensities
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if args.log:
|
|
274
|
+
intensities = 10.0 ** np.array(intensities)
|
|
275
|
+
|
|
276
|
+
if args.interface:
|
|
277
|
+
plot_interfacial_concentration(
|
|
278
|
+
intensities,
|
|
279
|
+
params,
|
|
280
|
+
plot_params,
|
|
281
|
+
root_index,
|
|
282
|
+
log=args.log,
|
|
283
|
+
limits=args.limits,
|
|
284
|
+
)
|
|
285
|
+
else:
|
|
286
|
+
plot_bulk_concentration(
|
|
287
|
+
intensities, params, plot_params, root_index, log=args.log
|
|
288
|
+
)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from photosurfactant.fourier import convolution_coeff, fourier_series_coeff
|
|
7
|
+
from photosurfactant.intensity_functions import mollifier, square_wave
|
|
8
|
+
from photosurfactant.parameters import Parameters, PlottingParameters
|
|
9
|
+
from photosurfactant.semi_analytic.first_order import FirstOrder, Variables
|
|
10
|
+
from photosurfactant.semi_analytic.leading_order import LeadingOrder
|
|
11
|
+
from photosurfactant.utils.arg_parser import (
|
|
12
|
+
first_order_parser,
|
|
13
|
+
leading_order_parser,
|
|
14
|
+
parameter_parser,
|
|
15
|
+
plot_parser,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def plot_spectrum(): # noqa: D103
|
|
20
|
+
parser = ArgumentParser(
|
|
21
|
+
description="Plot the first order surfactant concentrations.",
|
|
22
|
+
formatter_class=ArgumentDefaultsHelpFormatter,
|
|
23
|
+
)
|
|
24
|
+
parameter_parser(parser)
|
|
25
|
+
plot_parser(parser)
|
|
26
|
+
leading_order_parser(parser)
|
|
27
|
+
first_order_parser(parser)
|
|
28
|
+
args = parser.parse_args()
|
|
29
|
+
|
|
30
|
+
root_index = args.root_index
|
|
31
|
+
|
|
32
|
+
params = Parameters.from_dict(vars(args))
|
|
33
|
+
plot_params = PlottingParameters.from_dict(vars(args))
|
|
34
|
+
|
|
35
|
+
wavenumbers, _ = fourier_series_coeff(
|
|
36
|
+
lambda x: 0.0, params.L, plot_params.wave_count
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
_, square_wave_coeffs = fourier_series_coeff(
|
|
40
|
+
lambda x: square_wave(x), params.L, plot_params.wave_count
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
_, mol_wave_coeffs = convolution_coeff(
|
|
44
|
+
lambda x: square_wave(x),
|
|
45
|
+
mollifier(plot_params.delta),
|
|
46
|
+
params.L,
|
|
47
|
+
plot_params.wave_count,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Solve leading order problem
|
|
51
|
+
leading = LeadingOrder(params, root_index)
|
|
52
|
+
|
|
53
|
+
# Solve first order problem
|
|
54
|
+
first = FirstOrder(wavenumbers, params, leading)
|
|
55
|
+
first.solve(lambda n: (Variables.f, 1.0))
|
|
56
|
+
|
|
57
|
+
S_n = abs(first.solution @ Variables.S)
|
|
58
|
+
|
|
59
|
+
# Calculate the slope of the spectrum on a log plot
|
|
60
|
+
index = S_n > 1e-20
|
|
61
|
+
slope = np.polyfit(wavenumbers[index], np.log(S_n[index]), 1)
|
|
62
|
+
|
|
63
|
+
# Figure setup
|
|
64
|
+
plt = plot_params.plt
|
|
65
|
+
|
|
66
|
+
# Plot the spectrum
|
|
67
|
+
plt.figure()
|
|
68
|
+
plt.plot(wavenumbers[1:], S_n[1:], "k-")
|
|
69
|
+
plt.plot(
|
|
70
|
+
wavenumbers,
|
|
71
|
+
np.exp(slope[1] + slope[0] * wavenumbers),
|
|
72
|
+
"k--",
|
|
73
|
+
label=r"$e^{" f"{slope[0] * np.pi / params.L:.2f}" r"n}$",
|
|
74
|
+
)
|
|
75
|
+
plt.yscale("log")
|
|
76
|
+
plt.xlabel(r"$k_n$")
|
|
77
|
+
plt.ylabel(r"$|\tilde{S}^{(n)}|$")
|
|
78
|
+
plt.legend()
|
|
79
|
+
plt.tight_layout()
|
|
80
|
+
|
|
81
|
+
if plot_params.save:
|
|
82
|
+
plt.savefig(
|
|
83
|
+
plot_params.path
|
|
84
|
+
+ f"interface_spectrum{plot_params.label}.{plot_params.format}",
|
|
85
|
+
bbox_inches="tight",
|
|
86
|
+
)
|
|
87
|
+
else:
|
|
88
|
+
plt.show()
|
|
89
|
+
|
|
90
|
+
# Plot the square wave spectrum
|
|
91
|
+
plt.figure()
|
|
92
|
+
plt.plot(
|
|
93
|
+
wavenumbers,
|
|
94
|
+
square_wave_coeffs.real,
|
|
95
|
+
"k-",
|
|
96
|
+
label="Square wave",
|
|
97
|
+
)
|
|
98
|
+
plt.plot(
|
|
99
|
+
wavenumbers,
|
|
100
|
+
mol_wave_coeffs.real,
|
|
101
|
+
"r--",
|
|
102
|
+
label="Mollified square wave",
|
|
103
|
+
)
|
|
104
|
+
plt.xlabel(r"$k_n$")
|
|
105
|
+
plt.ylabel(r"$S^{(n)}$")
|
|
106
|
+
plt.legend()
|
|
107
|
+
plt.tight_layout()
|
|
108
|
+
|
|
109
|
+
if plot_params.save:
|
|
110
|
+
plt.savefig(
|
|
111
|
+
plot_params.path
|
|
112
|
+
+ f"square_wave_spectrum{plot_params.label}.{plot_params.format}",
|
|
113
|
+
bbox_inches="tight",
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
plt.show()
|
|
117
|
+
|
|
118
|
+
# Plot the square wave
|
|
119
|
+
def invert(fourier_coeffs, x):
|
|
120
|
+
return (
|
|
121
|
+
fourier_coeffs[0].real
|
|
122
|
+
+ 2
|
|
123
|
+
* np.sum(
|
|
124
|
+
fourier_coeffs[1:, np.newaxis]
|
|
125
|
+
* np.exp(1j * wavenumbers[1:, np.newaxis] * x[np.newaxis, :]),
|
|
126
|
+
axis=0,
|
|
127
|
+
).real
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
x = np.linspace(-params.L, params.L, plot_params.grid_size)
|
|
131
|
+
|
|
132
|
+
plt.figure()
|
|
133
|
+
plt.plot(x, invert(square_wave_coeffs, x), "k-", label="Square wave")
|
|
134
|
+
plt.plot(x, invert(mol_wave_coeffs, x), "r--", label="Mollified square wave")
|
|
135
|
+
plt.xlabel(r"$x$")
|
|
136
|
+
plt.ylabel(r"$f_1(x)$")
|
|
137
|
+
plt.legend()
|
|
138
|
+
plt.tight_layout()
|
|
139
|
+
|
|
140
|
+
if plot_params.save:
|
|
141
|
+
plt.savefig(
|
|
142
|
+
plot_params.path + f"square_wave{plot_params.label}.{plot_params.format}",
|
|
143
|
+
bbox_inches="tight",
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
plt.show()
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from alive_progress import alive_it
|
|
7
|
+
from matplotlib import colors
|
|
8
|
+
from scipy.optimize import minimize_scalar
|
|
9
|
+
|
|
10
|
+
from photosurfactant.fourier import fourier_series_coeff
|
|
11
|
+
from photosurfactant.parameters import Parameters, PlottingParameters
|
|
12
|
+
from photosurfactant.semi_analytic.first_order import FirstOrder, Variables
|
|
13
|
+
from photosurfactant.semi_analytic.leading_order import LeadingOrder
|
|
14
|
+
from photosurfactant.utils.arg_parser import (
|
|
15
|
+
first_order_parser,
|
|
16
|
+
parameter_parser,
|
|
17
|
+
plot_parser,
|
|
18
|
+
)
|
|
19
|
+
from photosurfactant.utils.func_parser import parse_func
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def find_max(func: Callable[[float], float], bounds: list[float]) -> float:
|
|
23
|
+
"""Find the maximum of a functional over a given range."""
|
|
24
|
+
res = minimize_scalar(lambda x: -func(x), bounds=bounds)
|
|
25
|
+
assert res.success, "maximum velocity not found"
|
|
26
|
+
|
|
27
|
+
return -res.fun
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def plot_multi_sweep(
|
|
31
|
+
intensity: Callable[[float], float],
|
|
32
|
+
x_func: Callable[[Parameters], float],
|
|
33
|
+
k_tr_vals: list[float],
|
|
34
|
+
params_list: list[Parameters],
|
|
35
|
+
plot_params: PlottingParameters,
|
|
36
|
+
xlabel: str,
|
|
37
|
+
tol: float = 1e-3,
|
|
38
|
+
label: str = "",
|
|
39
|
+
):
|
|
40
|
+
"""Sweep maximum velocity for set of k_tr values."""
|
|
41
|
+
|
|
42
|
+
if label:
|
|
43
|
+
label += "_"
|
|
44
|
+
|
|
45
|
+
# Figure setup
|
|
46
|
+
plt = plot_params.plt
|
|
47
|
+
fig, ax = plt.subplots(figsize=(6, 5), constrained_layout=True) # (9, 6)
|
|
48
|
+
|
|
49
|
+
cmap = plt.get_cmap("viridis")
|
|
50
|
+
|
|
51
|
+
norm = colors.LogNorm(
|
|
52
|
+
vmin=min(k_tr_vals) / np.sqrt(10), vmax=max(k_tr_vals) * np.sqrt(10)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Plot profiles over multile values of k_tr
|
|
56
|
+
for k_tr in k_tr_vals:
|
|
57
|
+
data = []
|
|
58
|
+
for params in alive_it(params_list, title=f"Sweeping at k_tr = {k_tr}"):
|
|
59
|
+
# Preserve the ratio in k parameters
|
|
60
|
+
params = params.update(k_tr=k_tr, k_ci=params.k_ci * (k_tr / params.k_tr))
|
|
61
|
+
|
|
62
|
+
# Find Fourier coeffients of the intensity function
|
|
63
|
+
wavenumbers, func_coeffs = fourier_series_coeff(
|
|
64
|
+
lambda x: intensity(x, params), params.L, plot_params.wave_count
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Solve leading order problem
|
|
68
|
+
leading = LeadingOrder(params)
|
|
69
|
+
|
|
70
|
+
if leading.gamma < tol:
|
|
71
|
+
# TODO: Temp fix for Marangoni sweep
|
|
72
|
+
if label == "ma_":
|
|
73
|
+
data.pop()
|
|
74
|
+
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
# Solve first order problem
|
|
78
|
+
first = FirstOrder(wavenumbers, params, leading)
|
|
79
|
+
first.solve(lambda n: (Variables.f, func_coeffs[n]))
|
|
80
|
+
|
|
81
|
+
# Reduce using the functionals
|
|
82
|
+
data.append(
|
|
83
|
+
(
|
|
84
|
+
x_func(params),
|
|
85
|
+
find_max(lambda x: abs(first.u(x, y=1.0)), [-params.L, params.L]),
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if data:
|
|
90
|
+
x_data, y_data = zip(*data)
|
|
91
|
+
ax.plot(x_data, y_data, color=cmap(norm(k_tr)))
|
|
92
|
+
|
|
93
|
+
# Tidy up figure
|
|
94
|
+
ax.set_xlabel(xlabel)
|
|
95
|
+
ax.set_ylabel(r"$\max_{x}\lvert{u_1}\rvert_{y=1}$")
|
|
96
|
+
ax.set_xscale("log")
|
|
97
|
+
# ax.set_yscale("log")
|
|
98
|
+
ax.ticklabel_format(style="sci", axis="y", scilimits=(0, 0))
|
|
99
|
+
|
|
100
|
+
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
|
|
101
|
+
cbar = plt.colorbar(sm, ax=ax, label=r"$k_\mathrm{tr}$")
|
|
102
|
+
|
|
103
|
+
# Annotate colorbar
|
|
104
|
+
cbar.ax.scatter(
|
|
105
|
+
np.mean(cbar.ax.set_xlim()) * np.ones_like(k_tr_vals),
|
|
106
|
+
k_tr_vals,
|
|
107
|
+
s=50,
|
|
108
|
+
facecolors="none",
|
|
109
|
+
edgecolors="k",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if plot_params.save:
|
|
113
|
+
plt.savefig(
|
|
114
|
+
plot_params.path + f"{label}sweep.{plot_params.format}",
|
|
115
|
+
bbox_inches="tight",
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
plt.show()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def bi_sweep(
|
|
122
|
+
intensity,
|
|
123
|
+
start: float,
|
|
124
|
+
stop: float,
|
|
125
|
+
k_tr_vals: list[float],
|
|
126
|
+
default_params: Parameters,
|
|
127
|
+
plot_params: PlottingParameters,
|
|
128
|
+
):
|
|
129
|
+
"""Sweep Biot number."""
|
|
130
|
+
biot_tr_vals = 10 ** np.linspace(start, stop, plot_params.grid_size)
|
|
131
|
+
params_list = [
|
|
132
|
+
default_params.update(
|
|
133
|
+
Bi_tr=Bi_tr,
|
|
134
|
+
Bi_ci=default_params.Bi_ci * (Bi_tr / default_params.Bi_tr),
|
|
135
|
+
)
|
|
136
|
+
for Bi_tr in biot_tr_vals
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
print("Sweeping Biot number...")
|
|
140
|
+
plot_multi_sweep(
|
|
141
|
+
intensity,
|
|
142
|
+
lambda p: p.Bi_tr,
|
|
143
|
+
k_tr_vals,
|
|
144
|
+
params_list,
|
|
145
|
+
plot_params,
|
|
146
|
+
xlabel=r"$Bi_{\mathrm{tr}}$",
|
|
147
|
+
label="bi",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def da_sweep(
|
|
152
|
+
intensity,
|
|
153
|
+
start: float,
|
|
154
|
+
stop: float,
|
|
155
|
+
k_tr_vals: list[float],
|
|
156
|
+
default_params: Parameters,
|
|
157
|
+
plot_params: PlottingParameters,
|
|
158
|
+
):
|
|
159
|
+
"""Sweep Damkohler number."""
|
|
160
|
+
damkohler_tr_vals = 10 ** np.linspace(start, stop, plot_params.grid_size)
|
|
161
|
+
params_list = [
|
|
162
|
+
default_params.update(
|
|
163
|
+
Da_tr=Da_tr,
|
|
164
|
+
Da_ci=default_params.Da_ci * (Da_tr / default_params.Da_tr),
|
|
165
|
+
)
|
|
166
|
+
for Da_tr in damkohler_tr_vals
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
print("Sweeping Damkohler number...")
|
|
170
|
+
plot_multi_sweep(
|
|
171
|
+
intensity,
|
|
172
|
+
lambda p: p.Da_tr,
|
|
173
|
+
k_tr_vals,
|
|
174
|
+
params_list,
|
|
175
|
+
plot_params,
|
|
176
|
+
xlabel=r"$Da_{\mathrm{tr}}$",
|
|
177
|
+
label="da",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def ma_sweep(
|
|
182
|
+
intensity,
|
|
183
|
+
start: float,
|
|
184
|
+
stop: float,
|
|
185
|
+
k_tr_vals: list[float],
|
|
186
|
+
default_params: Parameters,
|
|
187
|
+
plot_params: PlottingParameters,
|
|
188
|
+
):
|
|
189
|
+
"""Sweep Marangoni number."""
|
|
190
|
+
marangoni_vals = 10 ** np.linspace(start, stop, plot_params.grid_size)
|
|
191
|
+
params_list = [default_params.update(Ma=Ma) for Ma in marangoni_vals]
|
|
192
|
+
|
|
193
|
+
print("Sweeping Marangoni number...")
|
|
194
|
+
plot_multi_sweep(
|
|
195
|
+
intensity,
|
|
196
|
+
lambda p: p.Ma,
|
|
197
|
+
k_tr_vals,
|
|
198
|
+
params_list,
|
|
199
|
+
plot_params,
|
|
200
|
+
xlabel="$Ma$",
|
|
201
|
+
label="ma",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def main():
|
|
206
|
+
# Parse parameters
|
|
207
|
+
parser = ArgumentParser(
|
|
208
|
+
description="Plot the maximum interfacial velocity.",
|
|
209
|
+
formatter_class=ArgumentDefaultsHelpFormatter,
|
|
210
|
+
)
|
|
211
|
+
parameter_parser(parser)
|
|
212
|
+
plot_parser(parser)
|
|
213
|
+
first_order_parser(parser)
|
|
214
|
+
args = parser.parse_args()
|
|
215
|
+
|
|
216
|
+
default_params = Parameters.from_dict(vars(args))
|
|
217
|
+
plot_params = PlottingParameters.from_dict(vars(args))
|
|
218
|
+
intensity = parse_func(args.func)
|
|
219
|
+
|
|
220
|
+
# Set k values
|
|
221
|
+
k_tr_vals = 10.0 ** np.arange(-4, 1)
|
|
222
|
+
|
|
223
|
+
# Sweep Biot number
|
|
224
|
+
bi_sweep(intensity, -4, 4, k_tr_vals, default_params, plot_params)
|
|
225
|
+
|
|
226
|
+
# Sweep Damkohler number
|
|
227
|
+
da_sweep(intensity, -6, 2, k_tr_vals, default_params, plot_params)
|
|
228
|
+
|
|
229
|
+
# Sweep Marangoni number
|
|
230
|
+
ma_sweep(intensity, -4, 6, k_tr_vals, default_params, plot_params)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
if __name__ == "__main__":
|
|
234
|
+
main()
|