photosurfactant 1.0.0__tar.gz

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.
Files changed (33) hide show
  1. photosurfactant-1.0.0/.github/workflows/publish.yml +31 -0
  2. photosurfactant-1.0.0/.github/workflows/pytest.yml +69 -0
  3. photosurfactant-1.0.0/.gitignore +175 -0
  4. photosurfactant-1.0.0/PKG-INFO +25 -0
  5. photosurfactant-1.0.0/README.md +13 -0
  6. photosurfactant-1.0.0/examples/slip-velocity.py +36 -0
  7. photosurfactant-1.0.0/pyproject.toml +36 -0
  8. photosurfactant-1.0.0/src/photosurfactant/__init__.py +1 -0
  9. photosurfactant-1.0.0/src/photosurfactant/fourier.py +47 -0
  10. photosurfactant-1.0.0/src/photosurfactant/intensity_functions.py +32 -0
  11. photosurfactant-1.0.0/src/photosurfactant/parameters.py +219 -0
  12. photosurfactant-1.0.0/src/photosurfactant/py.typed +0 -0
  13. photosurfactant-1.0.0/src/photosurfactant/scripts/__init__.py +0 -0
  14. photosurfactant-1.0.0/src/photosurfactant/scripts/plot_all.sh +19 -0
  15. photosurfactant-1.0.0/src/photosurfactant/scripts/plot_error.py +75 -0
  16. photosurfactant-1.0.0/src/photosurfactant/scripts/plot_first_order.py +506 -0
  17. photosurfactant-1.0.0/src/photosurfactant/scripts/plot_leading_order.py +288 -0
  18. photosurfactant-1.0.0/src/photosurfactant/scripts/plot_spectrum.py +146 -0
  19. photosurfactant-1.0.0/src/photosurfactant/scripts/plot_sweep.py +234 -0
  20. photosurfactant-1.0.0/src/photosurfactant/semi_analytic/__init__.py +3 -0
  21. photosurfactant-1.0.0/src/photosurfactant/semi_analytic/first_order.py +540 -0
  22. photosurfactant-1.0.0/src/photosurfactant/semi_analytic/leading_order.py +237 -0
  23. photosurfactant-1.0.0/src/photosurfactant/semi_analytic/limits.py +240 -0
  24. photosurfactant-1.0.0/src/photosurfactant/semi_analytic/utils.py +43 -0
  25. photosurfactant-1.0.0/src/photosurfactant/utils/__init__.py +0 -0
  26. photosurfactant-1.0.0/src/photosurfactant/utils/arg_parser.py +162 -0
  27. photosurfactant-1.0.0/src/photosurfactant/utils/func_parser.py +10 -0
  28. photosurfactant-1.0.0/tests/__init__.py +0 -0
  29. photosurfactant-1.0.0/tests/test_semi_analytic/__init__.py +0 -0
  30. photosurfactant-1.0.0/tests/test_semi_analytic/test_first_fourier.py +226 -0
  31. photosurfactant-1.0.0/tests/test_semi_analytic/test_first_order.py +254 -0
  32. photosurfactant-1.0.0/tests/test_semi_analytic/test_leading_order.py +87 -0
  33. photosurfactant-1.0.0/uv.lock +680 -0
@@ -0,0 +1,31 @@
1
+ name: "Publish"
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ # Publish on any tag starting with a `v`, e.g., v0.1.0
7
+ - v*
8
+
9
+ jobs:
10
+ run:
11
+ runs-on: ubuntu-latest
12
+ environment:
13
+ name: pypi
14
+ permissions:
15
+ id-token: write
16
+ contents: read
17
+ steps:
18
+ - name: Checkout
19
+ uses: actions/checkout@v6
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v7
23
+
24
+ - name: Install Python 3.13
25
+ run: uv python install 3.13
26
+
27
+ - name: Build
28
+ run: uv build
29
+
30
+ - name: Publish
31
+ run: uv publish
@@ -0,0 +1,69 @@
1
+ name: CI
2
+
3
+ on:
4
+ # Trigger the workflow on push to main or dev, except tag creation
5
+ push:
6
+ branches:
7
+ - 'main'
8
+ - 'dev'
9
+ # Trigger the workflow on pull request
10
+ pull_request: ~
11
+ # Trigger the workflow manually
12
+ workflow_dispatch: ~
13
+ # Trigger after public PR approved for CI
14
+ pull_request_target:
15
+ types: [labeled]
16
+ release:
17
+ types: [created]
18
+
19
+ jobs:
20
+ qa:
21
+ name: qa
22
+ runs-on: ubuntu-latest
23
+
24
+ steps:
25
+ - name: Checkout Repository
26
+ uses: actions/checkout@v4
27
+
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@v5
30
+ with:
31
+ version: "0.7.3"
32
+
33
+ - name: Install Python
34
+ run: uv python install
35
+
36
+ - name: Install the project
37
+ run: uv sync --locked --all-extras --dev
38
+
39
+ - name: Check isort
40
+ run: uvx ruff check --select I .
41
+
42
+ - name: Check formatting
43
+ run: uvx ruff format --diff .
44
+
45
+ - name: Check linting
46
+ run: uvx ruff check .
47
+
48
+ pytest:
49
+ name: pytest
50
+ needs: qa
51
+ runs-on: ubuntu-latest
52
+
53
+ steps:
54
+ - name: Checkout Repository
55
+ uses: actions/checkout@v4
56
+
57
+ - name: Install uv
58
+ uses: astral-sh/setup-uv@v5
59
+ with:
60
+ version: "0.7.3"
61
+
62
+ - name: Install Python
63
+ run: uv python install
64
+
65
+ - name: Install the project
66
+ run: uv sync --dev
67
+
68
+ - name: Run tests
69
+ run: uv run pytest tests
@@ -0,0 +1,175 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+ .python-version
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+ m4r_venv/
91
+
92
+ # pipenv
93
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
95
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
96
+ # install all needed dependencies.
97
+ #Pipfile.lock
98
+
99
+ # poetry
100
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
101
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
102
+ # commonly ignored for libraries.
103
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
104
+ #poetry.lock
105
+
106
+ # pdm
107
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
108
+ #pdm.lock
109
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
110
+ # in version control.
111
+ # https://pdm.fming.dev/#use-with-ide
112
+ .pdm.toml
113
+
114
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115
+ __pypackages__/
116
+
117
+ # Celery stuff
118
+ celerybeat-schedule
119
+ celerybeat.pid
120
+
121
+ # SageMath parsed files
122
+ *.sage.py
123
+
124
+ # Environments
125
+ .env
126
+ .venv
127
+ env/
128
+ venv/
129
+ ENV/
130
+ env.bak/
131
+ venv.bak/
132
+
133
+ # Spyder project settings
134
+ .spyderproject
135
+ .spyproject
136
+
137
+ # Rope project settings
138
+ .ropeproject
139
+
140
+ # mkdocs documentation
141
+ /site
142
+
143
+ # mypy
144
+ .mypy_cache/
145
+ .dmypy.json
146
+ dmypy.json
147
+
148
+ # Pyre type checker
149
+ .pyre/
150
+
151
+ # pytype static type analyzer
152
+ .pytype/
153
+
154
+ # Cython debug symbols
155
+ cython_debug/
156
+
157
+ # PyCharm
158
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
161
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
162
+ #.idea/
163
+
164
+ # Figures and data
165
+ figures/
166
+ data/
167
+
168
+ # Mac stuff
169
+ .DS_Store
170
+
171
+ # IDEA
172
+ .idea/
173
+
174
+ # Lock files
175
+ # uv.lock
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: photosurfactant
3
+ Version: 1.0.0
4
+ Summary: Models of chromocapillary flows for photo-actuated liquid mixing and sculpting.
5
+ Author-email: Niall Oswald <niall.oswald20@imperial.ac.uk>
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: alive-progress>=3.3.0
8
+ Requires-Dist: matplotlib>=3.10.7
9
+ Requires-Dist: numpy>=2.3.4
10
+ Requires-Dist: scipy>=1.16.2
11
+ Description-Content-Type: text/markdown
12
+
13
+ # Photosurfactant
14
+
15
+ [![DOI](https://zenodo.org/badge/734330906.svg)](https://zenodo.org/badge/latestdoi/734330906)
16
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/NiallOswald/photosurfactant/pytest.yml?label=tests)
17
+ ![PyPI - Version](https://img.shields.io/pypi/v/photosurfactant)
18
+
19
+ Numerical solvers for liquid mixing and sculpting using light-actuated photosurfactants.
20
+
21
+ # Publications
22
+
23
+ This package has been used in the following publications:
24
+
25
+ - (Submitted) M. D. Mayer, N. J. Oswald, D. T. Papageorgiou, 2026. Liquid mixing and sculpting using light-actuated photosurfactants.
@@ -0,0 +1,13 @@
1
+ # Photosurfactant
2
+
3
+ [![DOI](https://zenodo.org/badge/734330906.svg)](https://zenodo.org/badge/latestdoi/734330906)
4
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/NiallOswald/photosurfactant/pytest.yml?label=tests)
5
+ ![PyPI - Version](https://img.shields.io/pypi/v/photosurfactant)
6
+
7
+ Numerical solvers for liquid mixing and sculpting using light-actuated photosurfactants.
8
+
9
+ # Publications
10
+
11
+ This package has been used in the following publications:
12
+
13
+ - (Submitted) M. D. Mayer, N. J. Oswald, D. T. Papageorgiou, 2026. Liquid mixing and sculpting using light-actuated photosurfactants.
@@ -0,0 +1,36 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+
4
+ from photosurfactant import Parameters
5
+ from photosurfactant.fourier import fourier_series_coeff
6
+ from photosurfactant.semi_analytic import (
7
+ FirstOrder,
8
+ LeadingOrder,
9
+ Variables,
10
+ )
11
+
12
+ params = Parameters()
13
+ leading = LeadingOrder(params)
14
+
15
+ # Find the Fourier series of the slip velocity
16
+ wavenumbers, func_coeffs = fourier_series_coeff(
17
+ lambda x: 1e-3 * np.sin(2 * np.pi * x / params.L), params.L, 10
18
+ )
19
+
20
+ # Solve the first-order problem by fixing u(x, 1) = f(x)
21
+ first = FirstOrder(wavenumbers, params, leading)
22
+ first.solve(
23
+ lambda n: (
24
+ (first._psi(wavenumbers[n], 1, z_order=1), func_coeffs[n])
25
+ if n > 0
26
+ else (Variables.f, 0)
27
+ ) # There is no flow at n = 0, so we fix the light intensity instead
28
+ )
29
+
30
+ # Evaluate and plot the light intensity profile
31
+ xx = np.linspace(-params.L, params.L, 100)
32
+
33
+ plt.plot(xx, first.f(xx))
34
+ plt.xlabel("$x$")
35
+ plt.ylabel("$f(x)$")
36
+ plt.show()
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "photosurfactant"
7
+ version = "1.0.0"
8
+ description = "Models of chromocapillary flows for photo-actuated liquid mixing and sculpting."
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Niall Oswald", email = "niall.oswald20@imperial.ac.uk" }
12
+ ]
13
+ requires-python = ">=3.12"
14
+ dependencies = [
15
+ "alive-progress>=3.3.0",
16
+ "matplotlib>=3.10.7",
17
+ "numpy>=2.3.4",
18
+ "scipy>=1.16.2",
19
+ ]
20
+
21
+ [project.scripts]
22
+ plot_error = "photosurfactant.scripts.plot_error:plot_error"
23
+ plot_first_order = "photosurfactant.scripts.plot_first_order:plot_first_order"
24
+ plot_leading_order = "photosurfactant.scripts.plot_leading_order:plot_leading_order"
25
+ plot_spectrum = "photosurfactant.scripts.plot_spectrum:plot_spectrum"
26
+ plot_sweep = "photosurfactant.scripts.plot_sweep:main"
27
+
28
+ [dependency-groups]
29
+ dev = [
30
+ "pytest>=8.4.2",
31
+ "pytest-cases>=3.9.1",
32
+ "scipy-stubs>=1.16.3.0",
33
+ ]
34
+
35
+ [tool.ruff.lint.isort]
36
+ known-first-party = ["photosurfactant"]
@@ -0,0 +1 @@
1
+ from .parameters import Parameters as Parameters
@@ -0,0 +1,47 @@
1
+ """Module for Fourier series calculations."""
2
+
3
+ import numpy as np
4
+ import numpy.typing as npt
5
+
6
+
7
+ def fourier_series_coeff(func, L: float, N: int) -> npt.NDArray[np.complex128]:
8
+ """Calculate the first N+1 Fourier series coeff. of a periodic function.
9
+
10
+ Given a periodic function f(x) with period 2L, this function returns the
11
+ coefficients {c0,c1,c2,...} such that:
12
+
13
+ f(x) ~= sum_{k=-N}^{N} c_k * exp(i*2*pi*k*x/L)
14
+
15
+ where we define c_{-n} = complex_conjugate(c_{n}).
16
+
17
+ :param func: The periodic function, a callable like f(x).
18
+ :param L: Half the period of the function f, so that f(-L)==f(L).
19
+ :param N: The function will return the first N + 1 Fourier coeff.
20
+ """
21
+ xx = np.linspace(-L, L, 2 * N, endpoint=False)
22
+ f_coeffs = np.fft.rfft(np.array([func(x) for x in xx])) / len(xx)
23
+ f_coeffs *= np.exp(1.0j * np.pi * np.arange(N + 1)) # Shift frequency domain
24
+
25
+ return np.arange(0, N + 1) * np.pi / L, f_coeffs
26
+
27
+
28
+ def convolution_coeff(f, g, L: float, N: int):
29
+ """Calculate the first N+1 Fourier series coeff. of a convolution.
30
+
31
+ Given periodic functions f(x), g(x) with period 2L, this function returns the
32
+ coefficients {c0,c1,c2,...} such that:
33
+
34
+ f(x) * g(x) ~= sum_{k=-N}^{N} c_k * exp(i*2*pi*k*x/L)
35
+
36
+ where we define c_{-n} = complex_conjugate(c_{n}).
37
+
38
+ :param f: A periodic function, a callable like f(x).
39
+ :param g: A periodic function, a callable like f(x).
40
+ :param L: Half the period of the function f, so that f(-L)==f(L).
41
+ :param N: The function will return the first 2N + 1 Fourier coeff.
42
+ """
43
+ omega, f_full = fourier_series_coeff(f, L, N)
44
+ _, g_full = fourier_series_coeff(g, L, N)
45
+ f_conv_full = 2 * L * f_full * g_full
46
+
47
+ return omega, f_conv_full
@@ -0,0 +1,32 @@
1
+ """Example light intensity and surface perturbations."""
2
+
3
+ from typing import Callable
4
+
5
+ import numpy as np
6
+ import scipy as sp
7
+
8
+
9
+ def gaussian(x: float, d=1.0) -> float:
10
+ return super_gaussian(x, 2.0, d)
11
+
12
+
13
+ def super_gaussian(x: float, k: float, d=1.0) -> float:
14
+ return np.exp(-(abs(x / d) ** k))
15
+
16
+
17
+ def square_wave(x: float) -> float:
18
+ return float(abs(x) < 1)
19
+
20
+
21
+ def smoothed_square(x: float, delta: float) -> float:
22
+ return 0.5 * (np.tanh((x + 1) / delta) - np.tanh((x - 1) / delta))
23
+
24
+
25
+ def mollifier(delta: float) -> Callable[[float], float]:
26
+ def _(x):
27
+ if abs(x) < delta:
28
+ return np.exp(-(delta**2) / (delta**2 - x**2))
29
+ else:
30
+ return 0.0
31
+
32
+ return lambda x: _(x) / sp.integrate.quad(_, -1.0, 1.0)[0]
@@ -0,0 +1,219 @@
1
+ """A module for the Parameters class."""
2
+
3
+ import inspect
4
+ from copy import copy
5
+ from dataclasses import asdict, dataclass
6
+ from typing import Any
7
+
8
+ import numpy as np
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class Parameters:
13
+ """The Parameters for the model.
14
+
15
+ :param L: The aspect ratio of the domain.
16
+ :param Da_tr: The Damkohler number for the trans surfactant.
17
+ :param Da_ci: The Damkohler number for the cis surfactant.
18
+ :param Pe_tr: The Peclet number for the trans surfactant.
19
+ :param Pe_ci: The Peclet number for the cis surfactant.
20
+ :param Pe_tr_s: The Peclet number for the trans surfactant on the
21
+ interface.
22
+ :param Pe_ci_s: The Peclet number for the cis surfactant on the interface.
23
+ :param Bi_tr: The Biot number for the trans surfactant.
24
+ :param Bi_ci: The Biot number for the cis surfactant.
25
+ :param Ma: The Marangoni number.
26
+ :param k_tr: The adsorption rate for the trans surfactant.
27
+ :param k_ci: The adsorption rate for the cis surfactant.
28
+ :param chi_tr: The desorption rate for the trans surfactant.
29
+ :param chi_ci: The desorption rate for the cis surfactant.
30
+ """
31
+
32
+ # Aspect ratio
33
+ L: float = 10.0
34
+
35
+ # Reynolds numbers
36
+ Re: float = 0.0 # TODO: Deprecate this parameter
37
+
38
+ # Damkohler numbers
39
+ Da_tr: float = 1.0
40
+ Da_ci: float = 2.0
41
+
42
+ # Peclet numbers
43
+ Pe_tr: float = 10.0
44
+ Pe_ci: float = 10.0
45
+
46
+ # Interfacial Peclet numbers
47
+ Pe_tr_s: float = 10.0
48
+ Pe_ci_s: float = 10.0
49
+
50
+ # Biot numbers
51
+ Bi_tr: float = 1 / 300
52
+ Bi_ci: float = 1.0
53
+
54
+ # Marangoni number
55
+ Ma: float = 2.0
56
+
57
+ # Adsorption and desorption rates
58
+ k_tr: float = 1.0
59
+ k_ci: float = 1 / 30
60
+
61
+ chi_tr: float = 100 / 30
62
+ chi_ci: float = 100.0
63
+
64
+ def __post_init__(self): # noqa: D105
65
+ if not np.isclose(self.k_tr * self.chi_tr, self.k_ci * self.chi_ci):
66
+ raise ValueError(
67
+ "Adsorption rates do not satisfy the condition k * chi = const."
68
+ )
69
+
70
+ def from_dict(kwargs: dict[str, float]) -> "Parameters":
71
+ """Load parameters from a dictionary."""
72
+ return Parameters(
73
+ **{
74
+ k: v
75
+ for k, v in kwargs.items()
76
+ if k in inspect.signature(Parameters).parameters
77
+ }
78
+ )
79
+
80
+ def copy(self) -> "Parameters":
81
+ """Return a copy of the `Parameters` object."""
82
+ return copy(self)
83
+
84
+ def update(self, **new_kwargs) -> "Parameters":
85
+ """Return a new `Parameter` object with missing values derived."""
86
+ derived_kwargs = asdict(self)
87
+ # new_kwargs overwrite derived_kwargs
88
+ return Parameters(**(derived_kwargs | new_kwargs))
89
+
90
+ @property
91
+ def alpha(self) -> float:
92
+ return self.Da_ci / self.Da_tr
93
+
94
+ @property
95
+ def eta(self) -> float:
96
+ return self.Pe_tr / self.Pe_ci
97
+
98
+ @property
99
+ def zeta(self) -> float:
100
+ return self.Pe_tr * self.Da_tr + self.Pe_ci * self.Da_ci
101
+
102
+ @property
103
+ def P(self):
104
+ return np.array([[self.Pe_tr, 0.0], [0.0, self.Pe_ci]])
105
+
106
+ @property
107
+ def P_s(self):
108
+ return np.array([[self.Pe_tr_s, 0.0], [0.0, self.Pe_ci_s]])
109
+
110
+ @property
111
+ def B(self):
112
+ return np.array([[self.Bi_tr, 0.0], [0.0, self.Bi_ci]])
113
+
114
+ @property
115
+ def K(self):
116
+ return np.array([[self.k_tr, 0.0], [0.0, self.k_ci]])
117
+
118
+ @property
119
+ def A(self):
120
+ return self.P @ self._D
121
+
122
+ @property
123
+ def A_s(self):
124
+ return self.P_s @ self._D
125
+
126
+ @property
127
+ def V(self):
128
+ return np.array([[self.alpha, self.eta], [1.0, -1.0]])
129
+
130
+ @property
131
+ def Lambda(self):
132
+ return np.array([[0.0, 0.0], [0.0, self.zeta]])
133
+
134
+ @property
135
+ def _D(self):
136
+ return np.array([[self.Da_tr, -self.Da_ci], [-self.Da_tr, self.Da_ci]])
137
+
138
+
139
+ @dataclass
140
+ class PlottingParameters:
141
+ """Additional parameters for plotting.
142
+
143
+ :param wave_count: The number of wave numbers to use.
144
+ :param grid_size: The number of grid points to evaluate the solution on.
145
+ :param mollify: A flag to mollify the input function.
146
+ :param delta: The mollification parameter.
147
+ :param norm_scale: Normalization type. Either "linear" or "log".
148
+ :param save: A flag to save the figures to disk.
149
+ :param path: The path to save the figures to.
150
+ :param label: A label to append to the figure filenames.
151
+ :param format: The format to save the figures in.
152
+ """
153
+
154
+ wave_count: int = 100
155
+ grid_size: int = 1000
156
+ mollify: bool = False
157
+ delta: float = 0.5
158
+ norm_scale: str = "linear"
159
+ save: bool = False
160
+ path: str = "./"
161
+ label: str = ""
162
+ usetex: bool = False
163
+ format: str = "png"
164
+
165
+ def __post_init__(self): # noqa: D105
166
+ self.label = "_" + self.label if self.label else ""
167
+ self.plot_setup()
168
+
169
+ def from_dict(kwargs: dict[str, Any]) -> "PlottingParameters":
170
+ """Load parameters from a dictionary."""
171
+ return PlottingParameters(
172
+ **{
173
+ k: v
174
+ for k, v in kwargs.items()
175
+ if k in inspect.signature(PlottingParameters).parameters
176
+ }
177
+ )
178
+
179
+ def copy(self):
180
+ """Return a copy of the class."""
181
+ raise NotImplementedError
182
+ # TODO: Deprecate this method
183
+ return copy(self)
184
+
185
+ def plot_setup(self):
186
+ """Set up the matplotlib rcParams."""
187
+ import matplotlib.pyplot as plt
188
+
189
+ rcparams = {
190
+ "font.size": 18,
191
+ "axes.labelsize": 18,
192
+ "axes.titlesize": 18,
193
+ "axes.formatter.useoffset": True,
194
+ "xtick.labelsize": 16,
195
+ "ytick.labelsize": 16,
196
+ "legend.fontsize": 16,
197
+ "figure.figsize": [7, 6],
198
+ "figure.dpi": 100,
199
+ "figure.autolayout": True,
200
+ "savefig.dpi": 300,
201
+ }
202
+
203
+ if self.usetex:
204
+ plt.rcParams.update(
205
+ {
206
+ "text.usetex": True,
207
+ "font.family": "serif",
208
+ "font.serif": ["Computer Modern Roman"],
209
+ "axes.formatter.use_mathtext": True,
210
+ "text.latex.preamble": r"\usepackage{amsmath}",
211
+ }
212
+ | rcparams
213
+ )
214
+
215
+ else:
216
+ plt.rcParams.update(rcparams)
217
+
218
+ plt.close("all")
219
+ self.plt = plt
File without changes