dolfinx-adjoint 0.2.0__tar.gz → 0.3.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. {dolfinx_adjoint-0.2.0/src/dolfinx_adjoint.egg-info → dolfinx_adjoint-0.3.0}/PKG-INFO +32 -16
  2. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/README.md +27 -12
  3. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/pyproject.toml +4 -14
  4. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint/__init__.py +2 -1
  5. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint/blocks/_vector.py +12 -1
  6. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint/blocks/assembly.py +4 -0
  7. dolfinx_adjoint-0.3.0/src/dolfinx_adjoint/blocks/dirichletbc.py +42 -0
  8. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint/blocks/function_assigner.py +7 -5
  9. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint/blocks/solvers.py +223 -78
  10. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint/petsc_utils.py +2 -0
  11. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint/solvers.py +153 -81
  12. dolfinx_adjoint-0.3.0/src/dolfinx_adjoint/types/__init__.py +4 -0
  13. dolfinx_adjoint-0.3.0/src/dolfinx_adjoint/types/dirichletbc.py +97 -0
  14. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint/types/function.py +11 -11
  15. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint/utils.py +1 -4
  16. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0/src/dolfinx_adjoint.egg-info}/PKG-INFO +32 -16
  17. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint.egg-info/SOURCES.txt +5 -0
  18. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint.egg-info/requires.txt +4 -3
  19. dolfinx_adjoint-0.3.0/tests/test_constant.py +27 -0
  20. dolfinx_adjoint-0.3.0/tests/test_dirichlet_bc.py +138 -0
  21. dolfinx_adjoint-0.3.0/tests/test_hessian.py +188 -0
  22. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/tests/test_poisson_mother.py +2 -2
  23. dolfinx_adjoint-0.2.0/src/dolfinx_adjoint/types/__init__.py +0 -3
  24. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/LICENSE +0 -0
  25. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/setup.cfg +0 -0
  26. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint/assembly.py +0 -0
  27. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint/blocks/__init__.py +0 -0
  28. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint.egg-info/dependency_links.txt +0 -0
  29. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/src/dolfinx_adjoint.egg-info/top_level.txt +0 -0
  30. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/tests/test_assemble_scalar.py +0 -0
  31. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/tests/test_assign.py +0 -0
  32. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/tests/test_linear_solver.py +0 -0
  33. {dolfinx_adjoint-0.2.0 → dolfinx_adjoint-0.3.0}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dolfinx-adjoint
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Automatic differentation compatible with DOLFINx
5
5
  Author-email: "Jørgen S. Dokken" <dokken@simula.no>
6
6
  License-Expression: MIT
@@ -9,6 +9,7 @@ License-File: LICENSE
9
9
  Requires-Dist: fenics-dolfinx>=0.10.0
10
10
  Requires-Dist: pyadjoint-ad>=2025.10.0
11
11
  Requires-Dist: typing_extensions; python_version < "3.11"
12
+ Requires-Dist: packaging>=24.2
12
13
  Provides-Extra: test
13
14
  Requires-Dist: pytest; extra == "test"
14
15
  Provides-Extra: dev
@@ -24,9 +25,9 @@ Requires-Dist: pyvista[all]>0.45; extra == "docs"
24
25
  Requires-Dist: networkx; extra == "docs"
25
26
  Requires-Dist: pygraphviz; extra == "docs"
26
27
  Provides-Extra: all
27
- Requires-Dist: dolfinx_adjoint[test]; extra == "all"
28
- Requires-Dist: dolfinx_adjoint[dev]; extra == "all"
29
- Requires-Dist: dolfinx_adjoint[docs]; extra == "all"
28
+ Requires-Dist: dolfinx-adjoint[test]; extra == "all"
29
+ Requires-Dist: dolfinx-adjoint[dev]; extra == "all"
30
+ Requires-Dist: dolfinx-adjoint[docs]; extra == "all"
30
31
  Dynamic: license-file
31
32
 
32
33
  # DOLFINx-Adjoint
@@ -49,7 +50,7 @@ Read the [Latest Documentation here](https://scientificcomputing.github.io/dolfi
49
50
 
50
51
  ### Dependencies
51
52
 
52
- DOLFINx-Adjoint requires **DOLFINx** (>=0.10.0) and **pyadjoint-ad**.
53
+ DOLFINx-Adjoint requires **DOLFINx** (>=0.11.0) and **pyadjoint-ad**.
53
54
 
54
55
  ### via pip
55
56
 
@@ -59,6 +60,14 @@ The main way to install the package is via pip:
59
60
  python3 -m pip install dolfinx-adjoint
60
61
  ```
61
62
 
63
+ ### via conda
64
+
65
+ You can also install dolfinx-adjoint via conda which also comes with **DOLFINx**
66
+
67
+ ```bash
68
+ conda install -c conda-forge dolfinx-adjoint
69
+ ```
70
+
62
71
  ### Development Install
63
72
 
64
73
  To install the latest development version directly from the repository, use:
@@ -75,15 +84,6 @@ cd dolfinx-adjoint
75
84
  python3 -m pip install -e ".[all]"
76
85
  ```
77
86
 
78
- ### Docker
79
-
80
- A pre-built Docker image is automatically published by the CI. You can pull the nightly build which comes with DOLFINx and DOLFINx-Adjoint pre-installed:
81
-
82
- ```bash
83
- docker run -ti ghcr.io/scientificcomputing/dolfinx-adjoint:v0.2.0
84
- ```
85
-
86
- *(Note: Adjust the tag to the latest release or build).*
87
87
 
88
88
  ## Quick Start
89
89
 
@@ -94,6 +94,7 @@ Using `dolfinx_adjoint` is designed to be as close to standard `dolfinx` syntax
94
94
  import dolfinx
95
95
  from mpi4py import MPI
96
96
  import pyadjoint
97
+ import ufl
97
98
  import dolfinx_adjoint
98
99
 
99
100
  # Create mesh and function space
@@ -103,16 +104,31 @@ V = dolfinx.fem.functionspace(mesh, ("Lagrange", 1))
103
104
  # Use dolfinx_adjoint overloaded types
104
105
  # This ensures operations are tracked on the pyadjoint tape!
105
106
  f = dolfinx_adjoint.Function(V, name="Control")
107
+ f.interpolate(lambda x: x[0] + x[1]) # Initial guess for control
106
108
  uh = dolfinx_adjoint.Function(V, name="State")
107
109
 
108
- # ... Define your UFL forms ...
110
+ # Define UFL forms for a simple Poisson problem: - \Delta u = f
111
+ u, v = ufl.TrialFunction(V), ufl.TestFunction(V)
112
+ a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx
113
+ L = f * v * ufl.dx
114
+
115
+ # Set up Dirichlet boundary condition (u = 0 on boundary)
116
+ mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)
117
+ exterior_facets = dolfinx.mesh.exterior_facet_indices(mesh.topology)
118
+ exterior_dofs = dolfinx.fem.locate_dofs_topological(V, mesh.topology.dim - 1, exterior_facets)
119
+ bc = dolfinx.fem.dirichletbc(dolfinx.default_scalar_type(0.0), exterior_dofs, V)
109
120
 
110
121
  # Use overloaded solvers
111
122
  problem = dolfinx_adjoint.LinearProblem(a, L, u=uh, bcs=[bc])
112
123
  problem.solve()
113
124
 
125
+ # Define a desired temperature profile 'd' and regularization parameter 'alpha'
126
+ x = ufl.SpatialCoordinate(mesh)
127
+ d = ufl.sin(ufl.pi * x[0]) * ufl.sin(ufl.pi * x[1])
128
+ alpha = dolfinx.fem.Constant(mesh, dolfinx.default_scalar_type(1e-6))
129
+
114
130
  # Assemble the objective scalar using the overloaded assembly
115
- J_symbolic = 0.5 * ufl.inner(uh - d, uh - d) * ufl.dx
131
+ J_symbolic = 0.5 * ufl.inner(uh - d, uh - d) * ufl.dx + 0.5 * alpha * ufl.inner(f, f) * ufl.dx
116
132
  J = dolfinx_adjoint.assemble_scalar(J_symbolic)
117
133
 
118
134
  # Create a ReducedFunctional for optimization
@@ -18,7 +18,7 @@ Read the [Latest Documentation here](https://scientificcomputing.github.io/dolfi
18
18
 
19
19
  ### Dependencies
20
20
 
21
- DOLFINx-Adjoint requires **DOLFINx** (>=0.10.0) and **pyadjoint-ad**.
21
+ DOLFINx-Adjoint requires **DOLFINx** (>=0.11.0) and **pyadjoint-ad**.
22
22
 
23
23
  ### via pip
24
24
 
@@ -28,6 +28,14 @@ The main way to install the package is via pip:
28
28
  python3 -m pip install dolfinx-adjoint
29
29
  ```
30
30
 
31
+ ### via conda
32
+
33
+ You can also install dolfinx-adjoint via conda which also comes with **DOLFINx**
34
+
35
+ ```bash
36
+ conda install -c conda-forge dolfinx-adjoint
37
+ ```
38
+
31
39
  ### Development Install
32
40
 
33
41
  To install the latest development version directly from the repository, use:
@@ -44,15 +52,6 @@ cd dolfinx-adjoint
44
52
  python3 -m pip install -e ".[all]"
45
53
  ```
46
54
 
47
- ### Docker
48
-
49
- A pre-built Docker image is automatically published by the CI. You can pull the nightly build which comes with DOLFINx and DOLFINx-Adjoint pre-installed:
50
-
51
- ```bash
52
- docker run -ti ghcr.io/scientificcomputing/dolfinx-adjoint:v0.2.0
53
- ```
54
-
55
- *(Note: Adjust the tag to the latest release or build).*
56
55
 
57
56
  ## Quick Start
58
57
 
@@ -63,6 +62,7 @@ Using `dolfinx_adjoint` is designed to be as close to standard `dolfinx` syntax
63
62
  import dolfinx
64
63
  from mpi4py import MPI
65
64
  import pyadjoint
65
+ import ufl
66
66
  import dolfinx_adjoint
67
67
 
68
68
  # Create mesh and function space
@@ -72,16 +72,31 @@ V = dolfinx.fem.functionspace(mesh, ("Lagrange", 1))
72
72
  # Use dolfinx_adjoint overloaded types
73
73
  # This ensures operations are tracked on the pyadjoint tape!
74
74
  f = dolfinx_adjoint.Function(V, name="Control")
75
+ f.interpolate(lambda x: x[0] + x[1]) # Initial guess for control
75
76
  uh = dolfinx_adjoint.Function(V, name="State")
76
77
 
77
- # ... Define your UFL forms ...
78
+ # Define UFL forms for a simple Poisson problem: - \Delta u = f
79
+ u, v = ufl.TrialFunction(V), ufl.TestFunction(V)
80
+ a = ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx
81
+ L = f * v * ufl.dx
82
+
83
+ # Set up Dirichlet boundary condition (u = 0 on boundary)
84
+ mesh.topology.create_connectivity(mesh.topology.dim - 1, mesh.topology.dim)
85
+ exterior_facets = dolfinx.mesh.exterior_facet_indices(mesh.topology)
86
+ exterior_dofs = dolfinx.fem.locate_dofs_topological(V, mesh.topology.dim - 1, exterior_facets)
87
+ bc = dolfinx.fem.dirichletbc(dolfinx.default_scalar_type(0.0), exterior_dofs, V)
78
88
 
79
89
  # Use overloaded solvers
80
90
  problem = dolfinx_adjoint.LinearProblem(a, L, u=uh, bcs=[bc])
81
91
  problem.solve()
82
92
 
93
+ # Define a desired temperature profile 'd' and regularization parameter 'alpha'
94
+ x = ufl.SpatialCoordinate(mesh)
95
+ d = ufl.sin(ufl.pi * x[0]) * ufl.sin(ufl.pi * x[1])
96
+ alpha = dolfinx.fem.Constant(mesh, dolfinx.default_scalar_type(1e-6))
97
+
83
98
  # Assemble the objective scalar using the overloaded assembly
84
- J_symbolic = 0.5 * ufl.inner(uh - d, uh - d) * ufl.dx
99
+ J_symbolic = 0.5 * ufl.inner(uh - d, uh - d) * ufl.dx + 0.5 * alpha * ufl.inner(f, f) * ufl.dx
85
100
  J = dolfinx_adjoint.assemble_scalar(J_symbolic)
86
101
 
87
102
  # Create a ReducedFunctional for optimization
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.0.0", "wheel"]
3
3
 
4
4
  [project]
5
5
  name = "dolfinx-adjoint"
6
- version = "0.2.0"
6
+ version = "0.3.0"
7
7
  description = "Automatic differentation compatible with DOLFINx"
8
8
  authors = [{ name = "Jørgen S. Dokken", email = "dokken@simula.no" }]
9
9
  license = "MIT"
@@ -13,6 +13,7 @@ dependencies = [
13
13
  "fenics-dolfinx>=0.10.0",
14
14
  "pyadjoint-ad>=2025.10.0",
15
15
  "typing_extensions; python_version < '3.11'",
16
+ "packaging>=24.2",
16
17
  ]
17
18
 
18
19
 
@@ -28,7 +29,7 @@ docs = [
28
29
  "networkx",
29
30
  "pygraphviz"
30
31
  ]
31
- all = ["dolfinx_adjoint[test]", "dolfinx_adjoint[dev]", "dolfinx_adjoint[docs]"]
32
+ all = ["dolfinx-adjoint[test]", "dolfinx-adjoint[dev]", "dolfinx-adjoint[docs]"]
32
33
 
33
34
  [tool.pytest.ini_options]
34
35
  addopts = ["--import-mode=importlib"]
@@ -91,21 +92,10 @@ tag = true
91
92
  sign_tags = false
92
93
  tag_name = "v{new_version}"
93
94
  tag_message = "Bump version: {current_version} → {new_version}"
94
- current_version = "0.2.0"
95
+ current_version = "0.3.0"
95
96
 
96
97
 
97
98
  [[tool.bumpversion.files]]
98
99
  filename = "pyproject.toml"
99
100
  search = 'version = "{current_version}"'
100
101
  replace = 'version = "{new_version}"'
101
-
102
-
103
- [[tool.bumpversion.files]]
104
- filename = "README.md"
105
- search = "ghcr.io/scientificcomputing/dolfinx-adjoint:v{current_version}"
106
- replace = "ghcr.io/scientificcomputing/dolfinx-adjoint:v{new_version}"
107
-
108
- [[tool.bumpversion.files]]
109
- filename = "Dockerfile"
110
- search = "ghcr.io/scientificcomputing/dolfinx-adjoint:v{current_version}"
111
- replace = "ghcr.io/scientificcomputing/dolfinx-adjoint:v{new_version}"
@@ -7,7 +7,7 @@ import pyadjoint as _pyad
7
7
 
8
8
  from .assembly import assemble_scalar, error_norm
9
9
  from .solvers import LinearProblem, NonlinearProblem
10
- from .types import Constant, Function
10
+ from .types import Constant, Function, dirichletbc
11
11
  from .types.function import assign
12
12
 
13
13
  meta = metadata("dolfinx_adjoint")
@@ -24,6 +24,7 @@ _pyad.continue_annotation()
24
24
  __all__ = [
25
25
  "Constant",
26
26
  "Function",
27
+ "dirichletbc",
27
28
  "LinearProblem",
28
29
  "NonlinearProblem",
29
30
  "assemble_scalar",
@@ -41,6 +41,15 @@ def _vector(
41
41
  Returns:
42
42
  A distributed vector.
43
43
  """
44
+ vtype: (
45
+ type[dolfinx.cpp.la.Vector_float32]
46
+ | type[dolfinx.cpp.la.Vector_float64]
47
+ | type[dolfinx.cpp.la.Vector_complex64]
48
+ | type[dolfinx.cpp.la.Vector_complex128]
49
+ | type[dolfinx.cpp.la.Vector_int8]
50
+ | type[dolfinx.cpp.la.Vector_int32]
51
+ | type[dolfinx.cpp.la.Vector_int64]
52
+ )
44
53
  if np.issubdtype(dtype, np.float32):
45
54
  vtype = dolfinx.cpp.la.Vector_float32
46
55
  elif np.issubdtype(dtype, np.float64):
@@ -72,6 +81,8 @@ def _create_vector(L: dolfinx.fem.Form, space: dolfinx.fem.FunctionSpace) -> _Sp
72
81
  """
73
82
  # Can just take the first dofmap here, since all dof maps have the same
74
83
  # index map in mixed-topology meshes
75
- dofmap = L.function_spaces[0].dofmaps(0) # type: ignore
84
+
85
+ dofmap = L.function_spaces[0].dofmaps[0] # type: ignore
86
+
76
87
  assert space._cpp_object == L.function_spaces[0], "Function space mismatch when creating vector."
77
88
  return _vector(dofmap.index_map, dofmap.index_map_bs, dtype=L.dtype, function_space=space)
@@ -129,6 +129,7 @@ class AssembleBlock(Block):
129
129
  form_compiler_options=self._form_compiler_options,
130
130
  entity_maps=self._entity_maps,
131
131
  )
132
+
132
133
  if space is None:
133
134
  # If space is not supplied infer it from the form
134
135
  assert len(dform.arguments()) == 1
@@ -143,6 +144,9 @@ class AssembleBlock(Block):
143
144
  # assemble_compiled_form(compiled_adjoint, self._cached_vectors[id(space)])
144
145
  assemble_compiled_form(compiled_adjoint, vector)
145
146
  # return a vector scaled by the scalar `adj_input`
147
+ # Safegaurd against None seeds from PyAdjoint
148
+ if adj_input is None:
149
+ adj_input = 1.0
146
150
  vector.array[:] *= vector.x.array.dtype.type(adj_input)
147
151
  vector.scatter_forward()
148
152
 
@@ -0,0 +1,42 @@
1
+ import dolfinx
2
+ import numpy as np
3
+ import numpy.typing as npt
4
+ from pyadjoint.block import Block
5
+
6
+
7
+ class DirichletBCBlock(Block):
8
+ """A block representing a DirichletBC in the adjoint framework.
9
+
10
+ Args:
11
+ value: The value of the Dirichlet BC.
12
+ dofs: An array of degree-of-freedom indices in `V` where the BC should be applied.
13
+ V: The function space associated with the Dirichlet BC.
14
+ ad_block_tag: An optional tag to identify this block in the adjoint framework.
15
+
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ value: dolfinx.fem.Function | dolfinx.fem.Constant,
21
+ dofs: npt.NDArray[np.int32],
22
+ V: dolfinx.fem.FunctionSpace | None = None,
23
+ ad_block_tag: str | None = None,
24
+ ):
25
+ super().__init__(ad_block_tag=ad_block_tag)
26
+ self._dofs = dofs
27
+ self._V = V
28
+ self.add_dependency(value)
29
+
30
+ @property
31
+ def dofs(self):
32
+ return self._dofs
33
+
34
+ @property
35
+ def V(self):
36
+ return self._V
37
+
38
+ def prepare_recompute_component(self, inputs, relevant_outputs):
39
+ return inputs[0] if inputs else None
40
+
41
+ def recompute_component(self, inputs, block_variable, idx, prepared):
42
+ return block_variable.saved_output
@@ -6,8 +6,7 @@ import ufl
6
6
  from pyadjoint import AdjFloat, Block, OverloadedType
7
7
  from ufl.formatting.ufl2unicode import ufl2unicode
8
8
 
9
- from dolfinx_adjoint.utils import function_from_vector
10
-
9
+ from ..utils import function_from_vector
11
10
  from ._vector import _vector
12
11
 
13
12
 
@@ -178,15 +177,18 @@ class FunctionAssignBlock(Block):
178
177
  def recompute_component(self, inputs, block_variable, idx, prepared):
179
178
  if self.expr is None:
180
179
  prepared = inputs[0]
181
- output = dolfinx.fem.Function(
182
- block_variable.output.function_space, name="f{block_variable.output.name}_AssignBlockRecompute"
183
- )
180
+
181
+ # We should return the exact object instance to maintain C++ memory bindings
182
+ # (especially for DirichletBCs), updating it in-place.
183
+ output = block_variable.saved_output
184
+
184
185
  try:
185
186
  if output.function_space == prepared.function_space:
186
187
  output.x.array[:] = prepared.x.array[:]
187
188
  except AttributeError:
188
189
  # Handling float value
189
190
  output.x.array[:] = prepared
191
+
190
192
  return output
191
193
 
192
194
  def __str__(self):