fenicsx-beat 0.0.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Henrik Finsberg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.1
2
+ Name: fenicsx-beat
3
+ Version: 0.0.1
4
+ Summary: Library to run cardiac EP simulations
5
+ Author-email: Henrik Finsberg <henriknf@simula.no>
6
+ License: MIT
7
+ Project-URL: Homepage, https://finsberg.github.io/fenicsx-beat
8
+ Project-URL: Documentation, https://finsberg.github.io/fenicsx-beat
9
+ Project-URL: Source, https://github.com/finsberg/fenicsx-beat
10
+ Project-URL: Tracker, https://github.com/finsberg/fenicsx-beat/issues
11
+ Keywords: cardiac,electrophysiology
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: numpy
16
+ Requires-Dist: scipy
17
+ Requires-Dist: scifem>=0.2.14
18
+ Requires-Dist: pint
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest; extra == "test"
21
+ Requires-Dist: pytest-cov; extra == "test"
22
+ Provides-Extra: dev
23
+ Requires-Dist: pre-commit; extra == "dev"
24
+ Provides-Extra: pypi
25
+ Requires-Dist: twine; extra == "pypi"
26
+ Requires-Dist: build; extra == "pypi"
27
+ Provides-Extra: demos
28
+ Requires-Dist: cardiac-geometriesx; extra == "demos"
29
+ Requires-Dist: fenicsx-ldrb; extra == "demos"
30
+ Requires-Dist: gotranx; extra == "demos"
31
+ Provides-Extra: docs
32
+ Requires-Dist: jupyter-book; extra == "docs"
33
+ Requires-Dist: jupytext; extra == "docs"
34
+ Requires-Dist: fenicsx-beat[demos]; extra == "docs"
35
+ Requires-Dist: ipywidgets; extra == "docs"
36
+ Requires-Dist: imageio; extra == "docs"
37
+ Requires-Dist: pyvista[jupyter]; extra == "docs"
38
+ Provides-Extra: all
39
+ Requires-Dist: fenicsx-beat[test]; extra == "all"
40
+ Requires-Dist: fenicsx-beat[docs]; extra == "all"
41
+ Requires-Dist: fenicsx-beat[pypi]; extra == "all"
42
+ Requires-Dist: fenicsx-beat[dev]; extra == "all"
43
+ Requires-Dist: fenicsx-beat[demos]; extra == "all"
44
+ Provides-Extra: testpaths
45
+ Requires-Dist: tests; extra == "testpaths"
46
+
47
+ ![_](https://raw.githubusercontent.com/finsberg/fenicsx-beat/refs/heads/main/docs/_static/logo.png)
48
+
49
+ # fenicsx-beat
50
+ Cardiac electrophysiology simulator in FEniCSx
51
+
52
+ - Source code: https://github.com/finsberg/fenicsx-beat
53
+ - Documentation: https://finsberg.github.io/fenicsx-beat
54
+
55
+
56
+ ## Install
57
+ You can install the library with pip
58
+ ```
59
+ python3 -m pip install fenicsx-beat
60
+ ```
61
+
62
+
63
+ ## Getting started
64
+
65
+ ```python
66
+ from mpi4py import MPI
67
+ import dolfinx
68
+ import ufl
69
+ import beat
70
+
71
+ comm = MPI.COMM_WORLD
72
+ mesh = dolfinx.mesh.create_unit_square(comm, 10, 10, dolfinx.cpp.mesh.CellType.triangle)
73
+ time = dolfinx.fem.Constant(mesh, dolfinx.default_scalar_type(0.0))
74
+ # Create stimulus
75
+ stim_expr = ufl.conditional(ufl.And(ufl.ge(time, 0.0), ufl.le(time, 0.5)),200.0, 0.0)
76
+ stim_marker = 1
77
+ cells = dolfinx.mesh.locate_entities(mesh, mesh.topology.dim, lambda x: np.logical_and(x[0] <= 0.5, x[1] <= 0.5))
78
+ stim_tags = dolfinx.mesh.meshtags(
79
+ mesh,
80
+ mesh.topology.dim,
81
+ cells,
82
+ np.full(len(cells), stim_marker, dtype=np.int32),
83
+ )
84
+ dx = ufl.Measure("dx", domain=mesh, subdomain_data=stim_tags)
85
+ I_s = beat.Stimulus(expr=stim_expr, dZ=dx, marker=stim_marker)
86
+ # Create PDE model
87
+ pde = beat.MonodomainModel(time=time, mesh=mesh, M=0.01, I_s=I_s, dx=dx)
88
+ # Define scheme to solve ODE
89
+ def fun(t, states, parameters, dt):
90
+ v, s = states
91
+ a, b = parameters
92
+ values = np.zeros_like(states)
93
+ values[0] = v - a * s * dt
94
+ values[1] = s + b * v * dt
95
+ return values
96
+ # Define ODE solver
97
+ ode_space = dolfinx.fem.functionspace(mesh, ("P", 1))
98
+ parameters = np.array([1.0, 1.0])
99
+ init_states = np.array([0.0, 0.0])
100
+ ode = beat.odesolver.DolfinODESolver(
101
+ v_ode=dolfinx.fem.Function(ode_space),
102
+ v_pde=pde.state,
103
+ fun=fun,
104
+ init_states=states,
105
+ parameters=parameters,
106
+ num_states=2,
107
+ v_index=1,
108
+ )
109
+ # Combine PDE and ODE solver
110
+ solver = beat.MonodomainSplittingSolver(pde=pde, ode=ode)
111
+ # Solve
112
+ T = 5.0
113
+ t = 0.0
114
+ i = 0
115
+ while t < T:
116
+ v = solver.pde.state.x.array
117
+ solver.step((t, t + dt))
118
+ i += 1
119
+ t += dt
120
+ ```
121
+ ![_](https://raw.githubusercontent.com/finsberg/fenicsx-beat/refs/heads/main/docs/_static/simple.gif)
122
+
123
+ See more examples in the [documentation](https://finsberg.github.io/fenicsx-beat)
124
+
125
+ ## License
126
+ MIT
127
+
128
+ ## Need help or having issues
129
+ Please submit an [issue](https://github.com/finsberg/fenicsx-beat/issues)
@@ -0,0 +1,83 @@
1
+ ![_](https://raw.githubusercontent.com/finsberg/fenicsx-beat/refs/heads/main/docs/_static/logo.png)
2
+
3
+ # fenicsx-beat
4
+ Cardiac electrophysiology simulator in FEniCSx
5
+
6
+ - Source code: https://github.com/finsberg/fenicsx-beat
7
+ - Documentation: https://finsberg.github.io/fenicsx-beat
8
+
9
+
10
+ ## Install
11
+ You can install the library with pip
12
+ ```
13
+ python3 -m pip install fenicsx-beat
14
+ ```
15
+
16
+
17
+ ## Getting started
18
+
19
+ ```python
20
+ from mpi4py import MPI
21
+ import dolfinx
22
+ import ufl
23
+ import beat
24
+
25
+ comm = MPI.COMM_WORLD
26
+ mesh = dolfinx.mesh.create_unit_square(comm, 10, 10, dolfinx.cpp.mesh.CellType.triangle)
27
+ time = dolfinx.fem.Constant(mesh, dolfinx.default_scalar_type(0.0))
28
+ # Create stimulus
29
+ stim_expr = ufl.conditional(ufl.And(ufl.ge(time, 0.0), ufl.le(time, 0.5)),200.0, 0.0)
30
+ stim_marker = 1
31
+ cells = dolfinx.mesh.locate_entities(mesh, mesh.topology.dim, lambda x: np.logical_and(x[0] <= 0.5, x[1] <= 0.5))
32
+ stim_tags = dolfinx.mesh.meshtags(
33
+ mesh,
34
+ mesh.topology.dim,
35
+ cells,
36
+ np.full(len(cells), stim_marker, dtype=np.int32),
37
+ )
38
+ dx = ufl.Measure("dx", domain=mesh, subdomain_data=stim_tags)
39
+ I_s = beat.Stimulus(expr=stim_expr, dZ=dx, marker=stim_marker)
40
+ # Create PDE model
41
+ pde = beat.MonodomainModel(time=time, mesh=mesh, M=0.01, I_s=I_s, dx=dx)
42
+ # Define scheme to solve ODE
43
+ def fun(t, states, parameters, dt):
44
+ v, s = states
45
+ a, b = parameters
46
+ values = np.zeros_like(states)
47
+ values[0] = v - a * s * dt
48
+ values[1] = s + b * v * dt
49
+ return values
50
+ # Define ODE solver
51
+ ode_space = dolfinx.fem.functionspace(mesh, ("P", 1))
52
+ parameters = np.array([1.0, 1.0])
53
+ init_states = np.array([0.0, 0.0])
54
+ ode = beat.odesolver.DolfinODESolver(
55
+ v_ode=dolfinx.fem.Function(ode_space),
56
+ v_pde=pde.state,
57
+ fun=fun,
58
+ init_states=states,
59
+ parameters=parameters,
60
+ num_states=2,
61
+ v_index=1,
62
+ )
63
+ # Combine PDE and ODE solver
64
+ solver = beat.MonodomainSplittingSolver(pde=pde, ode=ode)
65
+ # Solve
66
+ T = 5.0
67
+ t = 0.0
68
+ i = 0
69
+ while t < T:
70
+ v = solver.pde.state.x.array
71
+ solver.step((t, t + dt))
72
+ i += 1
73
+ t += dt
74
+ ```
75
+ ![_](https://raw.githubusercontent.com/finsberg/fenicsx-beat/refs/heads/main/docs/_static/simple.gif)
76
+
77
+ See more examples in the [documentation](https://finsberg.github.io/fenicsx-beat)
78
+
79
+ ## License
80
+ MIT
81
+
82
+ ## Need help or having issues
83
+ Please submit an [issue](https://github.com/finsberg/fenicsx-beat/issues)
@@ -0,0 +1,151 @@
1
+ [build-system] # Require setuptool version due to https://github.com/pypa/setuptools/issues/2938
2
+ requires = ["setuptools>=61.0.0", "wheel"]
3
+
4
+ [project]
5
+ name = "fenicsx-beat"
6
+ version = "0.0.1"
7
+ description = "Library to run cardiac EP simulations"
8
+ authors = [{name = "Henrik Finsberg", email = "henriknf@simula.no"}]
9
+ license = {text = "MIT"}
10
+ readme = "README.md"
11
+ requires-python = ">=3.8"
12
+ keywords = ["cardiac", "electrophysiology"]
13
+ dependencies = [
14
+ "numpy",
15
+ "scipy",
16
+ "scifem>=0.2.14",
17
+ "pint",
18
+ ]
19
+
20
+ [project.urls]
21
+ Homepage = "https://finsberg.github.io/fenicsx-beat"
22
+ Documentation = "https://finsberg.github.io/fenicsx-beat"
23
+ Source = "https://github.com/finsberg/fenicsx-beat"
24
+ Tracker = "https://github.com/finsberg/fenicsx-beat/issues"
25
+
26
+ [project.optional-dependencies]
27
+ test = [
28
+ "pytest",
29
+ "pytest-cov",
30
+ ]
31
+ dev = [
32
+ "pre-commit",
33
+ ]
34
+ pypi = [
35
+ "twine",
36
+ "build"
37
+ ]
38
+ demos = [
39
+ "cardiac-geometriesx",
40
+ "fenicsx-ldrb",
41
+ "gotranx"
42
+ ]
43
+ docs = [
44
+ "jupyter-book",
45
+ "jupytext",
46
+ "fenicsx-beat[demos]",
47
+ "ipywidgets",
48
+ "imageio",
49
+ "pyvista[jupyter]",
50
+ ]
51
+ all = [
52
+ "fenicsx-beat[test]",
53
+ "fenicsx-beat[docs]",
54
+ "fenicsx-beat[pypi]",
55
+ "fenicsx-beat[dev]",
56
+ "fenicsx-beat[demos]"
57
+ ]
58
+
59
+
60
+ testpaths = [
61
+ "tests"
62
+ ]
63
+ [tool.ruff]
64
+ # Exclude a variety of commonly ignored directories.
65
+ exclude = [
66
+ "demo/*.py",
67
+ ".bzr",
68
+ ".direnv",
69
+ ".eggs",
70
+ ".git",
71
+ ".hg",
72
+ ".mypy_cache",
73
+ ".nox",
74
+ ".pants.d",
75
+ ".pytype",
76
+ ".ruff_cache",
77
+ ".svn",
78
+ ".tox",
79
+ ".venv",
80
+ "__pypackages__",
81
+ "_build",
82
+ "buck-out",
83
+ "build",
84
+ "dist",
85
+ "node_modules",
86
+ "venv",
87
+ "demos",
88
+ ]
89
+
90
+ # Same as Black.
91
+ line-length = 100
92
+
93
+ # Allow unused variables when underscore-prefixed.
94
+
95
+ # Assume Python 3.10.
96
+ target-version = "py310"
97
+
98
+
99
+ [tool.ruff.lint]
100
+ # Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
101
+ select = ["E", "F", "I"]
102
+ ignore = ["E402", "E741", "E743", "E731"]
103
+
104
+ # Allow autofix for all enabled rules (when `--fix`) is provided.
105
+ fixable = ["A", "B", "C", "D", "E", "F", "I"]
106
+ unfixable = []
107
+
108
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
109
+
110
+ [tool.ruff.lint.mccabe]
111
+ # Unlike Flake8, default to a complexity level of 10.
112
+ max-complexity = 10
113
+
114
+
115
+ [tool.ruff.lint.isort]
116
+ known-third-party = [
117
+ "basix",
118
+ "dolfinx",
119
+ "ffcx",
120
+ "ufl",
121
+ "gmsh",
122
+ "numpy",
123
+ "pytest",
124
+ ]
125
+ section-order = [
126
+ "future",
127
+ "standard-library",
128
+ "mpi",
129
+ "third-party",
130
+ "first-party",
131
+ "local-folder",
132
+ ]
133
+ [tool.ruff.lint.isort.sections]
134
+ "mpi" = ["mpi4py", "petsc4py"]
135
+
136
+
137
+ [tool.bumpversion]
138
+ allow_dirty = false
139
+ commit = true
140
+ message = "Bump version: {current_version} → {new_version}"
141
+ tag = true
142
+ sign_tags = false
143
+ tag_name = "v{new_version}"
144
+ tag_message = "Bump version: {current_version} → {new_version}"
145
+ current_version = "0.0.1"
146
+
147
+
148
+ [[tool.bumpversion.files]]
149
+ filename = "pyproject.toml"
150
+ search = 'version = "{current_version}"'
151
+ replace = 'version = "{new_version}"'
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,32 @@
1
+ from . import (
2
+ base_model,
3
+ conductivities,
4
+ ecg,
5
+ geometry,
6
+ monodomain_model,
7
+ monodomain_solver,
8
+ odesolver,
9
+ single_cell,
10
+ stimulation,
11
+ utils,
12
+ )
13
+ from .monodomain_model import MonodomainModel
14
+ from .monodomain_solver import MonodomainSplittingSolver
15
+ from .stimulation import Stimulus
16
+
17
+ __all__ = [
18
+ "monodomain_model",
19
+ "odesolver",
20
+ "base_model",
21
+ "MonodomainModel",
22
+ "monodomain_solver",
23
+ "MonodomainSplittingSolver",
24
+ "utils",
25
+ "conductivities",
26
+ "stimulation",
27
+ "geometry",
28
+ "single_cell",
29
+ "stimulation",
30
+ "ecg",
31
+ "Stimulus",
32
+ ]
@@ -0,0 +1,268 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import logging
5
+ from enum import Enum, auto
6
+ from typing import Any, Literal, NamedTuple, Sequence
7
+
8
+ from petsc4py import PETSc
9
+
10
+ import dolfinx
11
+ import dolfinx.fem.petsc
12
+ import ufl
13
+ from ufl.core.expr import Expr
14
+
15
+ from .stimulation import Stimulus
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class Status(str, Enum):
21
+ OK = auto()
22
+ NOT_CONVERGING = auto()
23
+
24
+
25
+ class Results(NamedTuple):
26
+ state: dolfinx.fem.Function
27
+ status: Status
28
+
29
+
30
+ def _transform_I_s(
31
+ I_s: Stimulus | Sequence[Stimulus] | ufl.Coefficient | None,
32
+ dZ: ufl.Measure,
33
+ ) -> list[Stimulus]:
34
+ if I_s is None:
35
+ return [Stimulus(expr=ufl.zero(), dZ=dZ)]
36
+ if isinstance(I_s, Stimulus):
37
+ return [I_s]
38
+ if isinstance(I_s, ufl.core.expr.Expr):
39
+ return [Stimulus(expr=I_s, dZ=dZ)]
40
+
41
+ # FIXME: Might need more checks here
42
+ return list(I_s)
43
+
44
+
45
+ class BaseModel:
46
+ """
47
+ Base class for models.
48
+
49
+ Parameters
50
+ ----------
51
+ time : dolfinx.fem.Constant
52
+ The current time
53
+ mesh : dolfinx.mesh.Mesh
54
+ The mesh
55
+ dx : ufl.Measure, optional
56
+ The measure for the spatial domain, by default None
57
+ params : dict, optional
58
+ Parameters for the model, by default None
59
+ I_s : Stimulus | Sequence[Stimulus] | ufl.Coefficient, optional
60
+ The stimulus, by default None
61
+ jit_options : dict, optional
62
+ JIT options, by default None
63
+ form_compiler_options : dict, optional
64
+ Form compiler options, by default None
65
+ petsc_options : dict, optional
66
+ PETSc options, by default None
67
+
68
+ """
69
+
70
+ def __init__(
71
+ self,
72
+ time: dolfinx.fem.Constant,
73
+ mesh: dolfinx.mesh.Mesh,
74
+ dx: ufl.Measure | None = None,
75
+ params: dict[str, Any] | None = None,
76
+ I_s: Stimulus | Sequence[Stimulus] | ufl.Coefficient | None = None,
77
+ jit_options: dict[str, Any] | None = None,
78
+ form_compiler_options: dict[str, Any] | None = None,
79
+ petsc_options: dict[str, Any] | None = None,
80
+ ) -> None:
81
+ self._mesh = mesh
82
+ self.time = time
83
+ self.dx = dx or ufl.dx(domain=mesh)
84
+
85
+ self.parameters = type(self).default_parameters()
86
+ if params is not None:
87
+ self.parameters.update(params)
88
+
89
+ self._I_s = _transform_I_s(I_s, dZ=self.dx)
90
+
91
+ self._setup_state_space()
92
+
93
+ self._timestep = dolfinx.fem.Constant(mesh, self.parameters["default_timestep"])
94
+ a, L = self.variational_forms(self._timestep)
95
+ self._solver = dolfinx.fem.petsc.LinearProblem(
96
+ a,
97
+ L,
98
+ u=self.state,
99
+ form_compiler_options=form_compiler_options,
100
+ jit_options=jit_options,
101
+ petsc_options=petsc_options,
102
+ )
103
+ dolfinx.fem.petsc.assemble_matrix(self._solver.A, self._solver.a) # type: ignore
104
+ self._solver.A.assemble()
105
+
106
+ @abc.abstractmethod
107
+ def _setup_state_space(self) -> None: ...
108
+
109
+ @property
110
+ @abc.abstractmethod
111
+ def state(self) -> dolfinx.fem.Function: ...
112
+
113
+ @abc.abstractmethod
114
+ def assign_previous(self) -> None: ...
115
+
116
+ @staticmethod
117
+ def default_parameters(
118
+ solver_type: Literal["iterative", "direct"] = "direct",
119
+ ) -> dict[str, Any]:
120
+ if solver_type == "iterative":
121
+ petsc_options = {
122
+ "ksp_type": "cg",
123
+ # "pc_type": "hypre",
124
+ "pc_type": "petsc_amg",
125
+ "pc_hypre_type": "boomeramg",
126
+ # "ksp_norm_type": "unpreconditioned",
127
+ # "ksp_atol": 1e-15,
128
+ # "ksp_rtol": 1e-10,
129
+ # "ksp_max_it": 10_000,
130
+ # "ksp_error_if_not_converged": False,
131
+ }
132
+ else:
133
+ petsc_options = {
134
+ "ksp_type": "preonly",
135
+ "pc_type": "lu",
136
+ "pc_factor_mat_solver_type": "mumps",
137
+ }
138
+ return {
139
+ "theta": 0.5,
140
+ "degree": 1,
141
+ "family": "Lagrange",
142
+ "default_timestep": 1.0,
143
+ "jit_options": {},
144
+ "form_compiler_options": {},
145
+ "petsc_options": petsc_options,
146
+ }
147
+
148
+ @abc.abstractmethod
149
+ def variational_forms(self, dt: Expr | float) -> tuple[ufl.Form, ufl.Form]:
150
+ """Create the variational forms corresponding to the given
151
+ discretization of the given system of equations.
152
+
153
+ Parameters
154
+ ----------
155
+ dt : Expr | float
156
+ The time step
157
+
158
+ Returns
159
+ -------
160
+ tuple[ufl.Form, ufl.Form]
161
+ The variational form and the precondition
162
+
163
+ """
164
+ ...
165
+
166
+ def _update_matrices(self):
167
+ """
168
+ Re-assemble matrix.
169
+ """
170
+ self._solver.A.zeroEntries()
171
+ dolfinx.fem.petsc.assemble_matrix(self._solver.A, self._solver.a) # type: ignore
172
+ self._solver.A.assemble()
173
+
174
+ def _update_rhs(self):
175
+ """
176
+ Re-assemble RHS vector
177
+ """
178
+ with self._solver.b.localForm() as b_loc:
179
+ b_loc.set(0)
180
+ dolfinx.fem.petsc.assemble_vector(self._solver.b, self._solver.L)
181
+ self._solver.b.ghostUpdate(
182
+ addv=PETSc.InsertMode.ADD,
183
+ mode=PETSc.ScatterMode.REVERSE,
184
+ )
185
+
186
+ def step(self, interval):
187
+ """
188
+ Perform a single time step.
189
+
190
+ Parameters
191
+ ----------
192
+ interval : tuple[float, float]
193
+ The time interval (T0, T)
194
+
195
+ """
196
+
197
+ # timer = dolfin.Timer("PDE Step")
198
+
199
+ # Extract interval and thus time-step
200
+ (t0, t1) = interval
201
+ dt = t1 - t0
202
+ theta = self.parameters["theta"]
203
+ t = t0 + theta * dt
204
+ self.time.value = t
205
+
206
+ # Update matrix and linear solvers etc as needed
207
+ timestep_unchanged = abs(dt - float(self._timestep)) < 1.0e-12
208
+ if not timestep_unchanged:
209
+ self._timestep.value = dt
210
+ self._update_matrices()
211
+
212
+ self._update_rhs()
213
+ # Solve linear system and update ghost values in the solution
214
+
215
+ self._solver.solver.solve(self._solver.b, self.state.x.petsc_vec)
216
+ self.state.x.scatter_forward()
217
+
218
+ def _G_stim(self, w):
219
+ return sum([i.expr * w * i.dz for i in self._I_s])
220
+
221
+ def solve(
222
+ self,
223
+ interval: tuple[float, float],
224
+ dt: float | None = None,
225
+ ) -> Results:
226
+ """
227
+ Solve on the given time interval.
228
+
229
+ Parameters
230
+ ----------
231
+ interval : tuple[float, float]
232
+ The time interval (T0, T)
233
+ dt : float, optional
234
+ The time step, by default None
235
+
236
+ Returns
237
+ -------
238
+ Results
239
+ The results of the solution
240
+
241
+ """
242
+
243
+ # Initial set-up
244
+ # Solve on entire interval if no interval is given.
245
+ (T0, T) = interval
246
+ if dt is None:
247
+ dt = T - T0
248
+ t0 = T0
249
+ t1 = T0 + dt
250
+
251
+ # Step through time steps until at end time
252
+ while True:
253
+ logger.info("Solving on t = (%g, %g)" % (t0, t1))
254
+ self.step((t0, t1))
255
+
256
+ # Yield solutions
257
+ # yield (t0, t1), self.solution_fields()
258
+
259
+ # Break if this is the last step
260
+ if (t1 + dt) > (T + 1e-12):
261
+ break
262
+
263
+ self.assign_previous()
264
+
265
+ t0 = t1
266
+ t1 = t0 + dt
267
+
268
+ return Results(state=self.state, status=Status.OK)