From fac84b2ae8ca31847b1d84ff0fc8b19ff25ba492 Mon Sep 17 00:00:00 2001 From: Sebastian Blauth Date: Tue, 24 Jan 2023 10:40:51 +0100 Subject: [PATCH 1/2] Change the datatype of ksp option objects to be a (list of) dictionaries. This means, that the mapping from key to value is more obvious. Command line options without a value recieve the value `None` in the dictionary. --- cashocs/_constraints/constrained_problems.py | 30 +++++------ cashocs/_database/database.py | 4 +- cashocs/_database/parameter_database.py | 6 +-- cashocs/_forms/shape_form_handler.py | 21 ++++---- .../optimal_control_problem.py | 10 ++-- cashocs/_optimization/optimization_problem.py | 36 ++++++------- .../shape_optimization_problem.py | 10 ++-- .../_pde_problems/control_gradient_problem.py | 24 ++++----- cashocs/_pde_problems/hessian_problems.py | 22 ++++---- .../_pde_problems/shape_gradient_problem.py | 34 ++++++------- cashocs/_typing.py | 4 +- cashocs/_utils/__init__.py | 2 - cashocs/_utils/helpers.py | 40 +-------------- cashocs/_utils/linalg.py | 51 ++++++++++--------- cashocs/geometry/mesh_handler.py | 35 ++++++------- cashocs/geometry/mesh_testing.py | 21 ++++---- cashocs/geometry/quality.py | 21 ++++---- cashocs/nonlinear_solvers/newton_solver.py | 9 ++-- cashocs/nonlinear_solvers/picard_solver.py | 8 ++- cashocs/space_mapping/optimal_control.py | 6 +-- cashocs/space_mapping/shape_optimization.py | 6 +-- .../demo_iterative_solvers.py | 46 ++++++++--------- tests/test_config.py | 18 +++---- tests/test_exceptions.py | 18 +++---- 24 files changed, 217 insertions(+), 265 deletions(-) diff --git a/cashocs/_constraints/constrained_problems.py b/cashocs/_constraints/constrained_problems.py index 7afcb874..75ff0bfc 100644 --- a/cashocs/_constraints/constrained_problems.py +++ b/cashocs/_constraints/constrained_problems.py @@ -56,11 +56,9 @@ def __init__( constraint_list: Union[List[_typing.Constraint], _typing.Constraint], config: Optional[io.Config] = None, initial_guess: Optional[List[fenics.Function]] = None, - ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] - ] = None, + ksp_options: Optional[Union[_typing.KspOption, List[_typing.KspOption]]] = None, adjoint_ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] + Union[_typing.KspOption, List[_typing.KspOption]] ] = None, ) -> None: """Initializes self. @@ -88,10 +86,10 @@ def __init__( initial_guess: List of functions that act as initial guess for the state variables, should be valid input for :py:func:`fenics.assign`. Defaults to ``None``, which means a zero initial guess. - ksp_options: A list of strings corresponding to command line options for + ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the state systems. If this is ``None``, then the direct solver mumps is used (default is ``None``). - adjoint_ksp_options: A list of strings corresponding to command line options + adjoint_ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the adjoint systems. If this is ``None``, then the same options as for the state systems are used (default is ``None``). @@ -274,11 +272,9 @@ def __init__( riesz_scalar_products: Optional[Union[ufl.Form, List[ufl.Form]]] = None, control_constraints: Optional[List[List[Union[float, fenics.Function]]]] = None, initial_guess: Optional[List[fenics.Function]] = None, - ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] - ] = None, + ksp_options: Optional[Union[_typing.KspOption, List[_typing.KspOption]]] = None, adjoint_ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] + Union[_typing.KspOption, List[_typing.KspOption]] ] = None, control_bcs_list: Optional[ Union[ @@ -322,10 +318,10 @@ def __init__( initial_guess: List of functions that act as initial guess for the state variables, should be valid input for :py:func:`fenics.assign`. Defaults to ``None``, which means a zero initial guess. - ksp_options: A list of strings corresponding to command line options for + ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the state systems. If this is ``None``, then the direct solver mumps is used (default is ``None``). - adjoint_ksp_options: A list of strings corresponding to command line options + adjoint_ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the adjoint systems. If this is ``None``, then the same options as for the state systems are used (default is ``None``). @@ -443,11 +439,9 @@ def __init__( config: Optional[io.Config] = None, shape_scalar_product: Optional[ufl.Form] = None, initial_guess: Optional[List[fenics.Function]] = None, - ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] - ] = None, + ksp_options: Optional[Union[_typing.KspOption, List[_typing.KspOption]]] = None, adjoint_ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] + Union[_typing.KspOption, List[_typing.KspOption]] ] = None, ) -> None: """Initializes self. @@ -483,10 +477,10 @@ def __init__( initial_guess: List of functions that act as initial guess for the state variables, should be valid input for :py:func:`fenics.assign`. Defaults to ``None``, which means a zero initial guess. - ksp_options: A list of strings corresponding to command line options for + ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the state systems. If this is ``None``, then the direct solver mumps is used (default is ``None``). - adjoint_ksp_options: A list of strings corresponding to command line options + adjoint_ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the adjoint systems. If this is ``None``, then the same options as for the state systems are used (default is ``None``). diff --git a/cashocs/_database/database.py b/cashocs/_database/database.py index ffb51beb..f7c0d0a3 100644 --- a/cashocs/_database/database.py +++ b/cashocs/_database/database.py @@ -44,8 +44,8 @@ def __init__( config: io.Config, states: List[fenics.Function], adjoints: List[fenics.Function], - state_ksp_options: _typing.KspOptions, - adjoint_ksp_options: _typing.KspOptions, + state_ksp_options: List[_typing.KspOption], + adjoint_ksp_options: List[_typing.KspOption], cost_functional_list: List[_typing.CostFunctional], state_forms: List[ufl.Form], bcs_list: List[List[fenics.DirichletBC]], diff --git a/cashocs/_database/parameter_database.py b/cashocs/_database/parameter_database.py index 336f5a38..71802555 100644 --- a/cashocs/_database/parameter_database.py +++ b/cashocs/_database/parameter_database.py @@ -19,7 +19,7 @@ from __future__ import annotations -from typing import Dict, TYPE_CHECKING +from typing import Dict, List, TYPE_CHECKING from cashocs import _exceptions from cashocs import _utils @@ -37,8 +37,8 @@ def __init__( self, function_db: function_database.FunctionDatabase, config: io.Config, - state_ksp_options: _typing.KspOptions, - adjoint_ksp_options: _typing.KspOptions, + state_ksp_options: List[_typing.KspOption], + adjoint_ksp_options: List[_typing.KspOption], ) -> None: """Initializes the database. diff --git a/cashocs/_forms/shape_form_handler.py b/cashocs/_forms/shape_form_handler.py index d9631d2b..9baf1ed9 100644 --- a/cashocs/_forms/shape_form_handler.py +++ b/cashocs/_forms/shape_form_handler.py @@ -20,7 +20,7 @@ from __future__ import annotations import itertools -from typing import List, Optional, Tuple, TYPE_CHECKING, Union +from typing import List, Optional, Tuple, TYPE_CHECKING import fenics import numpy as np @@ -37,6 +37,7 @@ if TYPE_CHECKING: import ufl.core.expr + from cashocs import _typing from cashocs import io from cashocs._database import database from cashocs._optimization import shape_optimization @@ -84,7 +85,7 @@ def __init__( self.A_mu_matrix = fenics.PETScMatrix() # pylint: disable=invalid-name self.b_mu = fenics.PETScVector() - self.options_mu: List[List[Union[str, int, float]]] = [] + self.options_mu: _typing.KspOption = {} self._setup_mu_computation() @@ -98,14 +99,14 @@ def _setup_mu_computation(self) -> None: self.inhomogeneous_mu = True - self.options_mu = [ - ["ksp_type", "cg"], - ["pc_type", "hypre"], - ["pc_hypre_type", "boomeramg"], - ["ksp_rtol", 1e-16], - ["ksp_atol", 1e-50], - ["ksp_max_it", 100], - ] + self.options_mu = { + "ksp_type": "cg", + "pc_type": "hypre", + "pc_hypre_type": "boomeramg", + "ksp_rtol": 1e-16, + "ksp_atol": 1e-50, + "ksp_max_it": 100, + } phi = fenics.TrialFunction(self.cg_function_space) psi = fenics.TestFunction(self.cg_function_space) diff --git a/cashocs/_optimization/optimal_control/optimal_control_problem.py b/cashocs/_optimization/optimal_control/optimal_control_problem.py index c3899e7d..3c23c20f 100755 --- a/cashocs/_optimization/optimal_control/optimal_control_problem.py +++ b/cashocs/_optimization/optimal_control/optimal_control_problem.py @@ -71,11 +71,9 @@ def __init__( riesz_scalar_products: Optional[Union[List[ufl.Form], ufl.Form]] = None, control_constraints: Optional[List[List[Union[float, fenics.Function]]]] = None, initial_guess: Optional[List[fenics.Function]] = None, - ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] - ] = None, + ksp_options: Optional[Union[_typing.KspOption, List[_typing.KspOption]]] = None, adjoint_ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] + Union[_typing.KspOption, List[_typing.KspOption]] ] = None, desired_weights: Optional[List[float]] = None, control_bcs_list: Optional[ @@ -118,10 +116,10 @@ def __init__( initial_guess: List of functions that act as initial guess for the state variables, should be valid input for :py:func:`fenics.assign`. Defaults to ``None``, which means a zero initial guess. - ksp_options: A list of strings corresponding to command line options for + ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the state systems. If this is ``None``, then the direct solver mumps is used (default is ``None``). - adjoint_ksp_options: A list of strings corresponding to command line options + adjoint_ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the adjoint systems. If this is ``None``, then the same options as for the state systems are used (default is ``None``). diff --git a/cashocs/_optimization/optimization_problem.py b/cashocs/_optimization/optimization_problem.py index f50b242f..f15eb998 100755 --- a/cashocs/_optimization/optimization_problem.py +++ b/cashocs/_optimization/optimization_problem.py @@ -87,11 +87,9 @@ def __init__( adjoints: Union[List[fenics.Function], fenics.Function], config: Optional[io.Config] = None, initial_guess: Optional[Union[List[fenics.Function], fenics.Function]] = None, - ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] - ] = None, + ksp_options: Optional[Union[_typing.KspOption, List[_typing.KspOption]]] = None, adjoint_ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] + Union[_typing.KspOption, List[_typing.KspOption]] ] = None, desired_weights: Optional[List[float]] = None, temp_dict: Optional[Dict] = None, @@ -120,10 +118,10 @@ def __init__( initial_guess: List of functions that act as initial guess for the state variables, should be valid input for :py:func:`fenics.assign`. Defaults to ``None``, which means a zero initial guess. - ksp_options: A list of strings corresponding to command line options for + ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the state systems. If this is ``None``, then the direct solver mumps is used (default is ``None``). - adjoint_ksp_options: A list of strings corresponding to command line options + adjoint_ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the adjoint systems. If this is ``None``, then the same options as for the state systems are used (default is ``None``). @@ -264,17 +262,15 @@ def _parse_cost_functional_form( def _parse_optional_inputs( self, initial_guess: Optional[Union[List[fenics.Function], fenics.Function]], - ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] - ], + ksp_options: Optional[Union[_typing.KspOption, List[_typing.KspOption]]], adjoint_ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] + Union[_typing.KspOption, List[_typing.KspOption]] ], desired_weights: Optional[Union[List[float], float]], ) -> Tuple[ Optional[List[fenics.Function]], - _typing.KspOptions, - _typing.KspOptions, + List[_typing.KspOption], + List[_typing.KspOption], Optional[List[float]], ]: """Initializes the optional input parameters. @@ -282,9 +278,9 @@ def _parse_optional_inputs( Args: initial_guess: List of functions that act as initial guess for the state variables, should be valid input for :py:func:`fenics.assign`. - ksp_options: A list of strings corresponding to command line options for + ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the state systems. - adjoint_ksp_options: A list of strings corresponding to command line options + adjoint_ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the adjoint systems. desired_weights: A list of values for scaling the cost functional terms. If this is supplied, the cost functional has to be given as list of @@ -298,20 +294,18 @@ def _parse_optional_inputs( parsed_initial_guess = _utils.enlist(initial_guess) if ksp_options is None: - parsed_ksp_options: _typing.KspOptions = [] - option: List[List[Union[str, int, float]]] = copy.deepcopy( - _utils.linalg.direct_ksp_options - ) + parsed_ksp_options: List[_typing.KspOption] = [] + option = copy.deepcopy(_utils.linalg.direct_ksp_options) for _ in range(self.state_dim): parsed_ksp_options.append(option) else: - parsed_ksp_options = _utils.check_and_enlist_ksp_options(ksp_options) + parsed_ksp_options = _utils.enlist(ksp_options) - parsed_adjoint_ksp_options: _typing.KspOptions = ( + parsed_adjoint_ksp_options: List[_typing.KspOption] = ( parsed_ksp_options[:] if adjoint_ksp_options is None - else _utils.check_and_enlist_ksp_options(adjoint_ksp_options) + else _utils.enlist(adjoint_ksp_options) ) if desired_weights is None: diff --git a/cashocs/_optimization/shape_optimization/shape_optimization_problem.py b/cashocs/_optimization/shape_optimization/shape_optimization_problem.py index f10ba7e9..27c48d99 100755 --- a/cashocs/_optimization/shape_optimization/shape_optimization_problem.py +++ b/cashocs/_optimization/shape_optimization/shape_optimization_problem.py @@ -83,11 +83,9 @@ def __init__( config: Optional[io.Config] = None, shape_scalar_product: Optional[ufl.Form] = None, initial_guess: Optional[List[fenics.Function]] = None, - ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] - ] = None, + ksp_options: Optional[Union[_typing.KspOption, List[_typing.KspOption]]] = None, adjoint_ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] + Union[_typing.KspOption, List[_typing.KspOption]] ] = None, desired_weights: Optional[List[float]] = None, temp_dict: Optional[Dict] = None, @@ -124,10 +122,10 @@ def __init__( initial_guess: List of functions that act as initial guess for the state variables, should be valid input for :py:func:`fenics.assign`. Defaults to ``None``, which means a zero initial guess. - ksp_options: A list of strings corresponding to command line options for + ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the state systems. If this is ``None``, then the direct solver mumps is used (default is ``None``). - adjoint_ksp_options: A list of strings corresponding to command line options + adjoint_ksp_options: A list of dicts corresponding to command line options for PETSc, used to solve the adjoint systems. If this is ``None``, then the same options as for the state systems are used (default is ``None``). diff --git a/cashocs/_pde_problems/control_gradient_problem.py b/cashocs/_pde_problems/control_gradient_problem.py index f9fc18ab..947411f4 100755 --- a/cashocs/_pde_problems/control_gradient_problem.py +++ b/cashocs/_pde_problems/control_gradient_problem.py @@ -24,7 +24,7 @@ from __future__ import annotations import copy -from typing import List, TYPE_CHECKING, Union +from typing import List, TYPE_CHECKING import fenics @@ -33,6 +33,7 @@ from cashocs._pde_problems import pde_problem if TYPE_CHECKING: + from cashocs import _typing from cashocs._database import database from cashocs._pde_problems import adjoint_problem as ap from cashocs._pde_problems import state_problem as sp @@ -71,19 +72,18 @@ def __init__( gradient_method: str = self.config.get("OptimizationRoutine", "gradient_method") - option: List[List[Union[str, int, float]]] = [] if gradient_method.casefold() == "direct": - option = copy.deepcopy(_utils.linalg.direct_ksp_options) + option: _typing.KspOption = copy.deepcopy(_utils.linalg.direct_ksp_options) elif gradient_method.casefold() == "iterative": - option = [ - ["ksp_type", "cg"], - ["pc_type", "hypre"], - ["pc_hypre_type", "boomeramg"], - ["pc_hypre_boomeramg_strong_threshold", 0.7], - ["ksp_rtol", gradient_tol], - ["ksp_atol", 1e-50], - ["ksp_max_it", 250], - ] + option = { + "ksp_type": "cg", + "pc_type": "hypre", + "pc_hypre_type": "boomeramg", + "pc_hypre_boomeramg_strong_threshold": 0.7, + "ksp_rtol": gradient_tol, + "ksp_atol": 1e-50, + "ksp_max_it": 250, + } self.riesz_ksp_options = [] for _ in range(len(self.db.function_db.gradient)): diff --git a/cashocs/_pde_problems/hessian_problems.py b/cashocs/_pde_problems/hessian_problems.py index 8ed05b4e..8c017778 100755 --- a/cashocs/_pde_problems/hessian_problems.py +++ b/cashocs/_pde_problems/hessian_problems.py @@ -23,7 +23,7 @@ from __future__ import annotations -from typing import List, TYPE_CHECKING, Union +from typing import List, TYPE_CHECKING import fenics import numpy as np @@ -127,16 +127,16 @@ def __init__( self.bcs_list_ad = self.adjoint_form_handler.bcs_list_ad - option: List[List[Union[str, int, float]]] = [ - ["ksp_type", "cg"], - ["pc_type", "hypre"], - ["pc_hypre_type", "boomeramg"], - ["pc_hypre_boomeramg_strong_threshold", 0.7], - ["ksp_rtol", 1e-16], - ["ksp_atol", 1e-50], - ["ksp_max_it", 100], - ] - self.riesz_ksp_options: _typing.KspOptions = [] + option: _typing.KspOption = { + "ksp_type": "cg", + "pc_type": "hypre", + "pc_hypre_type": "boomeramg", + "pc_hypre_boomeramg_strong_threshold": 0.7, + "ksp_rtol": 1e-16, + "ksp_atol": 1e-50, + "ksp_max_it": 100, + } + self.riesz_ksp_options: List[_typing.KspOption] = [] for _ in range(len(self.db.function_db.controls)): self.riesz_ksp_options.append(option) diff --git a/cashocs/_pde_problems/shape_gradient_problem.py b/cashocs/_pde_problems/shape_gradient_problem.py index 1fd3c7eb..db0dbd47 100755 --- a/cashocs/_pde_problems/shape_gradient_problem.py +++ b/cashocs/_pde_problems/shape_gradient_problem.py @@ -78,15 +78,15 @@ def __init__( if gradient_method.casefold() == "direct": self.ksp_options = copy.deepcopy(_utils.linalg.direct_ksp_options) elif gradient_method.casefold() == "iterative": - self.ksp_options = [ - ["ksp_type", "cg"], - ["pc_type", "hypre"], - ["pc_hypre_type", "boomeramg"], - ["pc_hypre_boomeramg_strong_threshold", 0.7], - ["ksp_rtol", gradient_tol], - ["ksp_atol", 1e-50], - ["ksp_max_it", 250], - ] + self.ksp_options = { + "ksp_type": "cg", + "pc_type": "hypre", + "pc_hypre_type": "boomeramg", + "pc_hypre_boomeramg_strong_threshold": 0.7, + "ksp_rtol": gradient_tol, + "ksp_atol": 1e-50, + "ksp_max_it": 250, + } if ( self.config.getboolean("ShapeGradient", "use_p_laplacian") @@ -229,14 +229,14 @@ def __init__( if gradient_method.casefold() == "direct": self.ksp_options = copy.deepcopy(_utils.linalg.direct_ksp_options) elif gradient_method.casefold() == "iterative": - self.ksp_options = [ - ["ksp_type", "cg"], - ["pc_type", "hypre"], - ["pc_hypre_type", "boomeramg"], - ["ksp_rtol", 1e-16], - ["ksp_atol", 1e-50], - ["ksp_max_it", 100], - ] + self.ksp_options = { + "ksp_type": "cg", + "pc_type": "hypre", + "pc_hypre_type": "boomeramg", + "ksp_rtol": 1e-16, + "ksp_atol": 1e-50, + "ksp_max_it": 100, + } def solve(self) -> None: """Solves the p-Laplace problem for computing the shape gradient.""" diff --git a/cashocs/_typing.py b/cashocs/_typing.py index 5a4799df..6375ef6e 100644 --- a/cashocs/_typing.py +++ b/cashocs/_typing.py @@ -17,7 +17,7 @@ """Type hints for cashocs.""" -from typing import List, Tuple, Union +from typing import Dict, Tuple, Union import fenics @@ -44,7 +44,7 @@ fenics.Measure, ] -KspOptions = List[List[List[Union[str, int, float]]]] +KspOption = Dict[str, Union[int, float, str, None]] Constraint = Union[constraints.EqualityConstraint, constraints.InequalityConstraint] CostFunctional = Union[ diff --git a/cashocs/_utils/__init__.py b/cashocs/_utils/__init__.py index fef01a3f..09b0a336 100644 --- a/cashocs/_utils/__init__.py +++ b/cashocs/_utils/__init__.py @@ -31,7 +31,6 @@ from cashocs._utils.forms import summation from cashocs._utils.helpers import check_and_enlist_bcs from cashocs._utils.helpers import check_and_enlist_control_constraints -from cashocs._utils.helpers import check_and_enlist_ksp_options from cashocs._utils.helpers import create_function_list from cashocs._utils.helpers import enlist from cashocs._utils.helpers import optimization_algorithm_configuration @@ -49,7 +48,6 @@ "max_", "min_", "moreau_yosida_regularization", - "check_and_enlist_ksp_options", "check_and_enlist_control_constraints", "check_and_enlist_bcs", "enlist", diff --git a/cashocs/_utils/helpers.py b/cashocs/_utils/helpers.py index 62fd4f53..fc2fb45f 100644 --- a/cashocs/_utils/helpers.py +++ b/cashocs/_utils/helpers.py @@ -20,15 +20,12 @@ from __future__ import annotations import configparser -from typing import cast, List, Optional, TYPE_CHECKING, TypeVar, Union +from typing import cast, List, Optional, TypeVar, Union import fenics from cashocs import _exceptions -if TYPE_CHECKING: - from cashocs import _typing - T = TypeVar("T") @@ -115,41 +112,6 @@ def check_and_enlist_control_constraints( ) -def check_and_enlist_ksp_options( - ksp_options: Union[List[List[Union[str, int, float]]], _typing.KspOptions] -) -> _typing.KspOptions: - """Wraps ksp options into a list suitable for cashocs. - - Args: - ksp_options: The list of ksp options. - - Returns: - The wrapped list of ksp options. - - """ - if ( - isinstance(ksp_options, list) - and isinstance(ksp_options[0], list) - and isinstance(ksp_options[0][0], (str, int, float)) - ): - ksp_options = cast(List[List[Union[str, int, float]]], ksp_options) - return [ksp_options[:]] - - elif ( - isinstance(ksp_options, list) - and isinstance(ksp_options[0], list) - and isinstance(ksp_options[0][0], list) - ): - # ksp_options = cast(_typing.KspOptions, ksp_options) - return ksp_options[:] # type: ignore - else: - raise _exceptions.InputError( - "cashocs._utils.check_and_enlist_ksp_options", - "ksp_options", - "Type of ksp_options is wrong.", - ) - - def optimization_algorithm_configuration( config: configparser.ConfigParser, algorithm: Optional[str] = None ) -> str: diff --git a/cashocs/_utils/linalg.py b/cashocs/_utils/linalg.py index 2686faf5..2c6e904a 100644 --- a/cashocs/_utils/linalg.py +++ b/cashocs/_utils/linalg.py @@ -33,22 +33,22 @@ if TYPE_CHECKING: from cashocs import _typing -iterative_ksp_options: List[List[Union[str, int, float]]] = [ - ["ksp_type", "cg"], - ["pc_type", "hypre"], - ["pc_hypre_type", "boomeramg"], - ["pc_hypre_boomeramg_strong_threshold", 0.7], - ["ksp_rtol", 1e-20], - ["ksp_atol", 1e-50], - ["ksp_max_it", 1000], -] - -direct_ksp_options: List[List[Union[str, int, float]]] = [ - ["ksp_type", "preonly"], - ["pc_type", "lu"], - ["pc_factor_mat_solver_type", "mumps"], - ["mat_mumps_icntl_24", 1], -] +iterative_ksp_options: _typing.KspOption = { + "ksp_type": "cg", + "pc_type": "hypre", + "pc_hypre_type": "boomeramg", + "pc_hypre_boomeramg_strong_threshold": 0.7, + "ksp_rtol": 1e-20, + "ksp_atol": 1e-50, + "ksp_max_it": 1000, +} + +direct_ksp_options: _typing.KspOption = { + "ksp_type": "preonly", + "pc_type": "lu", + "pc_factor_mat_solver_type": "mumps", + "mat_mumps_icntl_24": 1, +} def split_linear_forms(forms: List[ufl.Form]) -> Tuple[List[ufl.Form], List[ufl.Form]]: @@ -153,7 +153,9 @@ def assemble_petsc_system( return A, b -def setup_petsc_options(ksps: List[PETSc.KSP], ksp_options: _typing.KspOptions) -> None: +def setup_petsc_options( + ksps: List[PETSc.KSP], ksp_options: List[_typing.KspOption] +) -> None: """Sets up an (iterative) linear solver. This is used to pass user defined command line type options for PETSc @@ -166,13 +168,14 @@ def setup_petsc_options(ksps: List[PETSc.KSP], ksp_options: _typing.KspOptions) from PETSc. """ - opts = fenics.PETScOptions + opts = PETSc.Options() for i in range(len(ksps)): opts.clear() - for option in ksp_options[i]: - opts.set(*option) + for key, value in ksp_options[i].items(): + val = None if value == "none" else value + opts.setValue(key, val) ksps[i].setFromOptions() @@ -180,7 +183,7 @@ def setup_petsc_options(ksps: List[PETSc.KSP], ksp_options: _typing.KspOptions) def setup_fieldsplit_preconditioner( fun: Optional[fenics.Function], ksp: PETSc.KSP, - options: List[List[Union[str, int, float]]], + options: _typing.KspOption, ) -> None: """Sets up the preconditioner for the fieldsplit case. @@ -193,7 +196,7 @@ def setup_fieldsplit_preconditioner( """ if fun is not None: - if ["pc_type", "fieldsplit"] in options: + if "pc_type" in options.keys() and options["pc_type"] == "fieldsplit": function_space = fun.function_space() if not function_space.num_sub_spaces() > 1: raise _exceptions.InputError( @@ -220,7 +223,7 @@ def solve_linear_problem( A: Optional[PETSc.Mat] = None, # pylint: disable=invalid-name b: Optional[PETSc.Vec] = None, fun: Optional[fenics.Function] = None, - ksp_options: Optional[List[List[Union[str, int, float]]]] = None, + ksp_options: Optional[_typing.KspOption] = None, rtol: Optional[float] = None, atol: Optional[float] = None, ) -> PETSc.Vec: @@ -301,7 +304,7 @@ def assemble_and_solve_linear( A: Optional[fenics.PETScMatrix] = None, # pylint: disable=invalid-name b: Optional[fenics.PETScVector] = None, fun: Optional[fenics.Function] = None, - ksp_options: Optional[List[List[Union[str, int, float]]]] = None, + ksp_options: Optional[_typing.KspOption] = None, rtol: Optional[float] = None, atol: Optional[float] = None, ) -> PETSc.Vec: diff --git a/cashocs/geometry/mesh_handler.py b/cashocs/geometry/mesh_handler.py index 91b73597..a94298c6 100644 --- a/cashocs/geometry/mesh_handler.py +++ b/cashocs/geometry/mesh_handler.py @@ -23,7 +23,7 @@ import pathlib import subprocess # nosec B404 import tempfile -from typing import List, TYPE_CHECKING, Union +from typing import List, TYPE_CHECKING import fenics import numpy as np @@ -38,6 +38,7 @@ if TYPE_CHECKING: from cashocs import _forms + from cashocs import _typing from cashocs._database import database from cashocs._optimization.optimization_algorithms import OptimizationAlgorithm from cashocs.geometry import mesh_testing @@ -141,14 +142,14 @@ def __init__( self.mesh, self.mesh_quality_type, self.mesh_quality_measure ) - self.options_frobenius: List[List[Union[str, int, float]]] = [ - ["ksp_type", "preonly"], - ["pc_type", "jacobi"], - ["pc_jacobi_type", "diagonal"], - ["ksp_rtol", 1e-16], - ["ksp_atol", 1e-20], - ["ksp_max_it", 1000], - ] + self.options_frobenius: _typing.KspOption = { + "ksp_type": "preonly", + "pc_type": "jacobi", + "pc_jacobi_type": "diagonal", + "ksp_rtol": 1e-16, + "ksp_atol": 1e-20, + "ksp_max_it": 1000, + } self.trial_dg0 = fenics.TrialFunction(self.db.function_db.dg_function_space) self.test_dg0 = fenics.TestFunction(self.db.function_db.dg_function_space) self.search_direction_container = fenics.Function( @@ -159,14 +160,14 @@ def __init__( self._setup_decrease_computation() - self.options_prior: List[List[Union[str, int, float]]] = [ - ["ksp_type", "preonly"], - ["pc_type", "jacobi"], - ["pc_jacobi_type", "diagonal"], - ["ksp_rtol", 1e-16], - ["ksp_atol", 1e-20], - ["ksp_max_it", 1000], - ] + self.options_prior: _typing.KspOption = { + "ksp_type": "preonly", + "pc_type": "jacobi", + "pc_jacobi_type": "diagonal", + "ksp_rtol": 1e-16, + "ksp_atol": 1e-20, + "ksp_max_it": 1000, + } self.transformation_container = fenics.Function( self.db.function_db.control_spaces[0] ) diff --git a/cashocs/geometry/mesh_testing.py b/cashocs/geometry/mesh_testing.py index 41648d4e..8e3ca961 100644 --- a/cashocs/geometry/mesh_testing.py +++ b/cashocs/geometry/mesh_testing.py @@ -20,7 +20,7 @@ from __future__ import annotations import collections -from typing import List, Union +from typing import TYPE_CHECKING import fenics import numpy as np @@ -28,6 +28,9 @@ from cashocs import _loggers from cashocs import _utils +if TYPE_CHECKING: + from cashocs import _typing + class APrioriMeshTester: """A class for testing the mesh before it is modified.""" @@ -61,14 +64,14 @@ def __init__(self, mesh: fenics.Mesh): * fenics.TestFunction(dg_function_space) * dx ) - self.options_prior: List[List[Union[str, int, float]]] = [ - ["ksp_type", "preonly"], - ["pc_type", "jacobi"], - ["pc_jacobi_type", "diagonal"], - ["ksp_rtol", 1e-16], - ["ksp_atol", 1e-20], - ["ksp_max_it", 1000], - ] + self.options_prior: _typing.KspOption = { + "ksp_type": "preonly", + "pc_type": "jacobi", + "pc_jacobi_type": "diagonal", + "ksp_rtol": 1e-16, + "ksp_atol": 1e-20, + "ksp_max_it": 1000, + } def test(self, transformation: fenics.Function, volume_change: float) -> bool: r"""Check the quality of the transformation before the actual mesh is moved. diff --git a/cashocs/geometry/quality.py b/cashocs/geometry/quality.py index 9e6f1f06..e4a99e27 100644 --- a/cashocs/geometry/quality.py +++ b/cashocs/geometry/quality.py @@ -20,7 +20,7 @@ from __future__ import annotations import abc -from typing import List, Union +from typing import TYPE_CHECKING import fenics import numpy as np @@ -30,6 +30,9 @@ from cashocs import _utils from cashocs.geometry import measure +if TYPE_CHECKING: + from cashocs import _typing + def compute_mesh_quality( mesh: fenics.Mesh, @@ -390,14 +393,14 @@ def compute(self, mesh: fenics.Mesh) -> np.ndarray: jac = ufl.Jacobian(mesh) inv = ufl.JacobianInverse(mesh) - options: List[List[Union[str, int, float]]] = [ - ["ksp_type", "preonly"], - ["pc_type", "jacobi"], - ["pc_jacobi_type", "diagonal"], - ["ksp_rtol", 1e-16], - ["ksp_atol", 1e-20], - ["ksp_max_it", 1000], - ] + options: _typing.KspOption = { + "ksp_type": "preonly", + "pc_type": "jacobi", + "pc_jacobi_type": "diagonal", + "ksp_rtol": 1e-16, + "ksp_atol": 1e-20, + "ksp_max_it": 1000, + } dx = measure.NamedMeasure("dx", mesh) lhs = ( diff --git a/cashocs/nonlinear_solvers/newton_solver.py b/cashocs/nonlinear_solvers/newton_solver.py index f5813fdb..6bbbaab4 100644 --- a/cashocs/nonlinear_solvers/newton_solver.py +++ b/cashocs/nonlinear_solvers/newton_solver.py @@ -20,7 +20,7 @@ from __future__ import annotations import copy -from typing import List, Optional, Union +from typing import List, Optional, TYPE_CHECKING, Union import fenics import numpy as np @@ -30,6 +30,9 @@ from cashocs import _exceptions from cashocs import _utils +if TYPE_CHECKING: + from cashocs import _typing + class _NewtonSolver: """A Newton solver.""" @@ -49,7 +52,7 @@ def __init__( damped: bool = True, inexact: bool = True, verbose: bool = True, - ksp_options: Optional[List[List[Union[str, int, float]]]] = None, + ksp_options: Optional[_typing.KspOption] = None, A_tensor: Optional[fenics.PETScMatrix] = None, # pylint: disable=invalid-name b_tensor: Optional[fenics.PETScVector] = None, is_linear: bool = False, @@ -402,7 +405,7 @@ def newton_solve( damped: bool = True, inexact: bool = True, verbose: bool = True, - ksp_options: Optional[List[List[Union[str, int, float]]]] = None, + ksp_options: Optional[_typing.KspOption] = None, A_tensor: Optional[fenics.PETScMatrix] = None, # pylint: disable=invalid-name b_tensor: Optional[fenics.PETScVector] = None, is_linear: bool = False, diff --git a/cashocs/nonlinear_solvers/picard_solver.py b/cashocs/nonlinear_solvers/picard_solver.py index 15146709..43678df9 100644 --- a/cashocs/nonlinear_solvers/picard_solver.py +++ b/cashocs/nonlinear_solvers/picard_solver.py @@ -88,7 +88,7 @@ def picard_iteration( inner_inexact: bool = True, inner_verbose: bool = False, inner_max_its: int = 25, - ksp_options: Optional[_typing.KspOptions] = None, + ksp_options: Optional[List[_typing.KspOption]] = None, # pylint: disable=invalid-name A_tensors: Optional[List[fenics.PETScMatrix]] = None, b_tensors: Optional[List[fenics.PETScVector]] = None, @@ -217,13 +217,11 @@ def _compute_residual( def _get_linear_solver_options( j: int, - ksp_options: Optional[_typing.KspOptions], + ksp_options: Optional[List[_typing.KspOption]], # pylint: disable=invalid-name A_tensors: Optional[List[fenics.PETScMatrix]], b_tensors: Optional[List[fenics.PETScVector]], -) -> Tuple[ - Optional[List[List[Union[str, int, float]]]], fenics.PETScMatrix, fenics.PETScVector -]: +) -> Tuple[Optional[_typing.KspOption], fenics.PETScMatrix, fenics.PETScVector]: """Computes the arguments for the individual components considered in the iteration. Returns: diff --git a/cashocs/space_mapping/optimal_control.py b/cashocs/space_mapping/optimal_control.py index 98cf83d0..67013ee4 100755 --- a/cashocs/space_mapping/optimal_control.py +++ b/cashocs/space_mapping/optimal_control.py @@ -83,11 +83,9 @@ def __init__( riesz_scalar_products: Optional[Union[ufl.Form, List[ufl.Form]]] = None, control_constraints: Optional[List[List[Union[float, fenics.Function]]]] = None, initial_guess: Optional[List[fenics.Function]] = None, - ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] - ] = None, + ksp_options: Optional[Union[_typing.KspOption, List[_typing.KspOption]]] = None, adjoint_ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] + Union[_typing.KspOption, List[_typing.KspOption]] ] = None, desired_weights: Optional[List[float]] = None, ) -> None: diff --git a/cashocs/space_mapping/shape_optimization.py b/cashocs/space_mapping/shape_optimization.py index 6d79a187..78a544b3 100755 --- a/cashocs/space_mapping/shape_optimization.py +++ b/cashocs/space_mapping/shape_optimization.py @@ -92,11 +92,9 @@ def __init__( config: Optional[io.Config] = None, shape_scalar_product: Optional[ufl.Form] = None, initial_guess: Optional[List[fenics.Function]] = None, - ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] - ] = None, + ksp_options: Optional[Union[_typing.KspOption, List[_typing.KspOption]]] = None, adjoint_ksp_options: Optional[ - Union[_typing.KspOptions, List[List[Union[str, int, float]]]] + Union[_typing.KspOption, List[_typing.KspOption]] ] = None, desired_weights: Optional[List[float]] = None, ): diff --git a/demos/documented/optimal_control/iterative_solvers/demo_iterative_solvers.py b/demos/documented/optimal_control/iterative_solvers/demo_iterative_solvers.py index 7ff660b8..fd90ed4b 100755 --- a/demos/documented/optimal_control/iterative_solvers/demo_iterative_solvers.py +++ b/demos/documented/optimal_control/iterative_solvers/demo_iterative_solvers.py @@ -77,14 +77,14 @@ # The options for the state and adjoint systems are defined as follows. For the state # system we have -ksp_options = [ - ["ksp_type", "cg"], - ["pc_type", "hypre"], - ["pc_hypre_type", "boomeramg"], - ["ksp_rtol", 1e-10], - ["ksp_atol", 1e-13], - ["ksp_max_it", 100], -] +ksp_options = { + "ksp_type": "cg", + "pc_type": "hypre", + "pc_hypre_type": "boomeramg", + "ksp_rtol": 1e-10, + "ksp_atol": 1e-13, + "ksp_max_it": 100, +} # This is a list of lists, where the inner ones have either 1 or 2 entries, which # correspond to the command line options for PETSc. For a detailed documentation of the @@ -97,9 +97,9 @@ # relevant options for the command line are described under "Options Database Keys". # # ::::{note} -# For example, the first command +# For example, the first line # :::python -# ["ksp_type", "cg"], +# "ksp_type": "cg", # ::: # # can be found in [KSPSetType]( @@ -109,8 +109,8 @@ # Here, we see that the above line corresponds to using the conjugate gradient method as # krylov solver. The following two lines # :::python -# ["pc_type", "hypre"], -# ["pc_hypre_type", "boomeramg"], +# "pc_type": "hypre", +# "pc_hypre_type": "boomeramg", # ::: # # specify that we use the algebraic multigrid preconditioner BOOMERAMG from HYPRE. @@ -122,9 +122,9 @@ # https://petsc.org/release/docs/manualpages/PC/PCHYPRE/). # Finally, the last three lines # :::python -# ["ksp_rtol", 1e-10], -# ["ksp_atol", 1e-13], -# ["ksp_max_it", 100], +# "ksp_rtol": 1e-10, +# "ksp_atol": 1e-13, +# "ksp_max_it": 100, # ::: # # specify that we use a relative tolerance of 1e-10, an absolute one of 1e-13, and @@ -136,12 +136,12 @@ # the adjoint system should be solved exactly the same as the state system. This is why # we can also define PETSc options for the adjoint system, which we do with -adjoint_ksp_options = [ - ["ksp_type", "minres"], - ["pc_type", "jacobi"], - ["ksp_rtol", 1e-6], - ["ksp_atol", 1e-15], -] +adjoint_ksp_options = { + "ksp_type": "minres", + "pc_type": "jacobi", + "ksp_rtol": 1e-6, + "ksp_atol": 1e-15, +} # As can be seen, we now use a completely different solver, namely MINRES (the minimal # residual method) with a jacobi preconditioner. Finally, the tolerances for the adjoint @@ -151,12 +151,12 @@ # ::::{hint} # To verify that the options indeed are used, one can supply the option # :::python -# ['ksp_view'], +# 'ksp_view': None, # ::: # # which shows the detailed settings of the solvers, and also # :::python -# ['ksp_monitor_true_residual'], +# 'ksp_monitor_true_residual': None, # ::: # # which prints the residual of the method over its iterations. diff --git a/tests/test_config.py b/tests/test_config.py index 758c6be1..0f295b65 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -76,15 +76,15 @@ def J(y, y_d, u, geometry): @pytest.fixture def ksp_options(): - return [ - ["ksp_type", "cg"], - ["pc_type", "hypre"], - ["pc_hypre_type", "boomeramg"], - ["ksp_rtol", 0.0], - ["ksp_atol", 0.0], - ["ksp_max_it", 1], - ["ksp_monitor_true_residual"], - ] + return { + "ksp_type": "cg", + "pc_type": "hypre", + "pc_hypre_type": "boomeramg", + "ksp_rtol": 0.0, + "ksp_atol": 0.0, + "ksp_max_it": 1, + "ksp_monitor_true_residual": None, + } @pytest.fixture diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index cf5d2924..e1365e2b 100755 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -83,15 +83,15 @@ def J(y, y_d, u, geometry): @pytest.fixture def ksp_options(): - return [ - ["ksp_type", "cg"], - ["pc_type", "hypre"], - ["pc_hypre_type", "boomeramg"], - ["ksp_rtol", 0.0], - ["ksp_atol", 0.0], - ["ksp_max_it", 1], - ["ksp_monitor_true_residual"], - ] + return { + "ksp_type": "cg", + "pc_type": "hypre", + "pc_hypre_type": "boomeramg", + "ksp_rtol": 0.0, + "ksp_atol": 0.0, + "ksp_max_it": 1, + "ksp_monitor_true_residual": None, + } @pytest.fixture From 6a2b0f6aaa4895549f9728393b25ea0fbb57feb1 Mon Sep 17 00:00:00 2001 From: Sebastian Blauth Date: Tue, 24 Jan 2023 10:47:17 +0100 Subject: [PATCH 2/2] Add feature to changelog --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0a4d61e3..2097a095 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -41,6 +41,8 @@ of the maintenance releases, please take a look at * The BFGS method can now be used in a damped fashion. This ensures that the inverse Hessian approximation stays positive definite. +* The options for defining parameters which are to be supplied to PETSc KSP objects have changed their datatype: They are now given by (lists of) dictionaries instead of nested lists. For options without a value in the command line (e.g. the option :bash:`-ksp_view`) have a value of :python:`None` in the dictionary (so :python:`'ksp_view': None` can be used inside the dictionary to supply the aforementioned option). + * Changed configuration file parameters * Section Output