From 44f3b2962479a82b95195ed6ebb1fa7a260e0380 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 26 Jun 2025 21:04:43 +0200 Subject: [PATCH 01/78] Retire populate_namespace_with_expr_classes --- ufl/classes.py | 342 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 329 insertions(+), 13 deletions(-) diff --git a/ufl/classes.py b/ufl/classes.py index cd162757f..860df8ca8 100644 --- a/ufl/classes.py +++ b/ufl/classes.py @@ -54,6 +54,166 @@ import ufl.tensors import ufl.variable from ufl import exproperators as __exproperators +from ufl.action import Action +from ufl.adjoint import Adjoint +from ufl.algebra import Abs, Conj, Division, Imag, Power, Product, Real, Sum +from ufl.argument import Argument, Coargument +from ufl.averaging import CellAvg, FacetAvg +from ufl.coefficient import Coefficient, Cofunction +from ufl.conditional import ( + EQ, + GE, + GT, + LE, + LT, + NE, + AndCondition, + BinaryCondition, + Condition, + Conditional, + MaxValue, + MinValue, + NotCondition, + OrCondition, +) +from ufl.constant import Constant +from ufl.constantvalue import ( + ComplexValue, + ConstantValue, + FloatValue, + Identity, + IntValue, + PermutationSymbol, + RealValue, + ScalarValue, + Zero, +) +from ufl.core.base_form_operator import BaseFormOperator +from ufl.core.expr import Expr +from ufl.core.external_operator import ExternalOperator +from ufl.core.interpolate import Interpolate +from ufl.core.multiindex import MultiIndex +from ufl.core.operator import Operator +from ufl.core.terminal import FormArgument, Terminal +from ufl.differentiation import ( + BaseFormCoordinateDerivative, + BaseFormDerivative, + BaseFormOperatorCoordinateDerivative, + BaseFormOperatorDerivative, + CoefficientDerivative, + CompoundDerivative, + CoordinateDerivative, + Curl, + Derivative, + Div, + Grad, + NablaDiv, + NablaGrad, + ReferenceCurl, + ReferenceDiv, + ReferenceGrad, + VariableDerivative, +) +from ufl.exprcontainers import ExprList, ExprMapping +from ufl.form import BaseForm, Form, FormSum, ZeroBaseForm +from ufl.geometry import ( + CellCoordinate, + CellDiameter, + CellEdgeVectors, + CellFacetJacobian, + CellFacetJacobianDeterminant, + CellFacetJacobianInverse, + CellFacetOrigin, + CellNormal, + CellOrientation, + CellOrigin, + CellRidgeJacobian, + CellRidgeJacobianDeterminant, + CellRidgeJacobianInverse, + CellRidgeOrigin, + CellVertices, + CellVolume, + Circumradius, + FacetArea, + FacetCoordinate, + FacetEdgeVectors, + FacetJacobian, + FacetJacobianDeterminant, + FacetJacobianInverse, + FacetNormal, + FacetOrientation, + FacetOrigin, + FacetRidgeJacobian, + GeometricCellQuantity, + GeometricFacetQuantity, + GeometricQuantity, + GeometricRidgeQuantity, + Jacobian, + JacobianDeterminant, + JacobianInverse, + MaxCellEdgeLength, + MaxFacetEdgeLength, + MinCellEdgeLength, + MinFacetEdgeLength, + QuadratureWeight, + ReferenceCellEdgeVectors, + ReferenceCellVolume, + ReferenceFacetEdgeVectors, + ReferenceFacetVolume, + ReferenceNormal, + ReferenceRidgeVolume, + RidgeCoordinate, + RidgeJacobian, + RidgeJacobianDeterminant, + RidgeJacobianInverse, + RidgeOrigin, + SpatialCoordinate, +) +from ufl.indexed import Indexed +from ufl.indexsum import IndexSum +from ufl.mathfunctions import ( + Acos, + Asin, + Atan, + Atan2, + BesselFunction, + BesselI, + BesselJ, + BesselK, + BesselY, + Cos, + Cosh, + Erf, + Exp, + Ln, + MathFunction, + Sin, + Sinh, + Sqrt, + Tan, + Tanh, +) +from ufl.matrix import Matrix +from ufl.referencevalue import ReferenceValue +from ufl.restriction import NegativeRestricted, PositiveRestricted, Restricted +from ufl.tensoralgebra import ( + Cofactor, + CompoundTensorOperator, + Cross, + Determinant, + Deviatoric, + Dot, + Inner, + Inverse, + Outer, + Perp, + Skew, + Sym, + Trace, + Transposed, +) +from ufl.tensors import ComponentTensor, ListTensor +from ufl.variable import Label, Variable # Collect all classes in sets automatically classified by some properties all_ufl_classes = set(ufl.core.expr.Expr._ufl_all_classes_) @@ -71,19 +231,175 @@ "ufl_classes", ] - -def populate_namespace_with_expr_classes(namespace): - """Export all Expr subclasses into the namespace under their natural name.""" - names = [] - classes = ufl.core.expr.Expr._ufl_all_classes_ - for cls in classes: - class_name = cls.__name__ - namespace[class_name] = cls - names.append(class_name) - return names - - -__all__ += populate_namespace_with_expr_classes(locals()) +__all__ += [ + "EQ", + "GE", + "GT", + "LE", + "LT", + "NE", + "Abs", + "Acos", + "Action", + "Adjoint", + "AndCondition", + "Argument", + "Asin", + "Atan", + "Atan2", + "BaseForm", + "BaseFormCoordinateDerivative", + "BaseFormDerivative", + "BaseFormOperator", + "BaseFormOperatorCoordinateDerivative", + "BaseFormOperatorDerivative", + "BesselFunction", + "BesselI", + "BesselJ", + "BesselK", + "BesselY", + "BinaryCondition", + "CellAvg", + "CellCoordinate", + "CellDiameter", + "CellEdgeVectors", + "CellFacetJacobian", + "CellFacetJacobianDeterminant", + "CellFacetJacobianInverse", + "CellFacetOrigin", + "CellNormal", + "CellOrientation", + "CellOrigin", + "CellRidgeJacobian", + "CellRidgeJacobianDeterminant", + "CellRidgeJacobianInverse", + "CellRidgeOrigin", + "CellVertices", + "CellVolume", + "Circumradius", + "Coargument", + "Coefficient", + "CoefficientDerivative", + "Cofactor", + "Cofunction", + "ComplexValue", + "ComponentTensor", + "CompoundDerivative", + "CompoundTensorOperator", + "Condition", + "Conditional", + "Conj", + "Constant", + "ConstantValue", + "CoordinateDerivative", + "Cos", + "Cosh", + "Cross", + "Curl", + "Derivative", + "Determinant", + "Deviatoric", + "Div", + "Division", + "Dot", + "Erf", + "Exp", + "Expr", + "ExprList", + "ExprMapping", + "ExternalOperator", + "FacetArea", + "FacetAvg", + "FacetCoordinate", + "FacetEdgeVectors", + "FacetJacobian", + "FacetJacobianDeterminant", + "FacetJacobianInverse", + "FacetNormal", + "FacetOrientation", + "FacetOrigin", + "FacetRidgeJacobian", + "FloatValue", + "Form", + "FormArgument", + "FormSum", + "GeometricCellQuantity", + "GeometricFacetQuantity", + "GeometricQuantity", + "GeometricRidgeQuantity", + "Grad", + "Identity", + "Imag", + "IndexSum", + "Indexed", + "Inner", + "IntValue", + "Interpolate", + "Inverse", + "Jacobian", + "JacobianDeterminant", + "JacobianInverse", + "Label", + "ListTensor", + "Ln", + "MathFunction", + "Matrix", + "MaxCellEdgeLength", + "MaxFacetEdgeLength", + "MaxValue", + "MinCellEdgeLength", + "MinFacetEdgeLength", + "MinValue", + "MultiIndex", + "NablaDiv", + "NablaGrad", + "NegativeRestricted", + "NotCondition", + "Operator", + "OrCondition", + "Outer", + "PermutationSymbol", + "Perp", + "PositiveRestricted", + "Power", + "Product", + "QuadratureWeight", + "Real", + "RealValue", + "ReferenceCellEdgeVectors", + "ReferenceCellVolume", + "ReferenceCurl", + "ReferenceDiv", + "ReferenceFacetEdgeVectors", + "ReferenceFacetVolume", + "ReferenceGrad", + "ReferenceNormal", + "ReferenceRidgeVolume", + "ReferenceValue", + "Restricted", + "RidgeCoordinate", + "RidgeJacobian", + "RidgeJacobianDeterminant", + "RidgeJacobianInverse", + "RidgeOrigin", + "ScalarValue", + "Sin", + "Sinh", + "Skew", + "SpatialCoordinate", + "Sqrt", + "Sum", + "Sym", + "Tan", + "Tanh", + "Terminal", + "Trace", + "Transposed", + "Variable", + "VariableDerivative", + "Zero", + "ZeroBaseForm", +] # Semi-automated imports of non-expr classes: From 57dcc309483d51157f7d9e288b0bfabe37eca053 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 26 Jun 2025 21:06:26 +0200 Subject: [PATCH 02/78] combine --- ufl/classes.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/ufl/classes.py b/ufl/classes.py index 860df8ca8..92dbcd2eb 100644 --- a/ufl/classes.py +++ b/ufl/classes.py @@ -222,15 +222,6 @@ terminal_classes = set(c for c in all_ufl_classes if c._ufl_is_terminal_) nonterminal_classes = set(c for c in all_ufl_classes if not c._ufl_is_terminal_) -__all__ += [ - "__exproperators", - "abstract_classes", - "all_ufl_classes", - "nonterminal_classes", - "terminal_classes", - "ufl_classes", -] - __all__ += [ "EQ", "GE", @@ -399,6 +390,12 @@ "VariableDerivative", "Zero", "ZeroBaseForm", + "__exproperators", + "abstract_classes", + "all_ufl_classes", + "nonterminal_classes", + "terminal_classes", + "ufl_classes", ] From c2a8bf2272d11698509882582ba06daf3942264c Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 26 Jun 2025 21:12:44 +0200 Subject: [PATCH 03/78] Remove populate_namespace_with_module_classes --- ufl/classes.py | 121 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 33 deletions(-) diff --git a/ufl/classes.py b/ufl/classes.py index 92dbcd2eb..25120dedc 100644 --- a/ufl/classes.py +++ b/ufl/classes.py @@ -57,8 +57,16 @@ from ufl.action import Action from ufl.adjoint import Adjoint from ufl.algebra import Abs, Conj, Division, Imag, Power, Product, Real, Sum -from ufl.argument import Argument, Coargument +from ufl.argument import ( + Argument, + Coargument, + TestFunction, + TestFunctions, + TrialFunction, + TrialFunctions, +) from ufl.averaging import CellAvg, FacetAvg +from ufl.cell import AbstractCell, Cell, TensorProductCell from ufl.coefficient import Coefficient, Cofunction from ufl.conditional import ( EQ, @@ -92,7 +100,7 @@ from ufl.core.expr import Expr from ufl.core.external_operator import ExternalOperator from ufl.core.interpolate import Interpolate -from ufl.core.multiindex import MultiIndex +from ufl.core.multiindex import FixedIndex, Index, IndexBase, MultiIndex from ufl.core.operator import Operator from ufl.core.terminal import FormArgument, Terminal from ufl.differentiation import ( @@ -114,8 +122,18 @@ ReferenceGrad, VariableDerivative, ) +from ufl.domain import AbstractDomain, Mesh, MeshView +from ufl.equation import Equation from ufl.exprcontainers import ExprList, ExprMapping +from ufl.finiteelement import AbstractFiniteElement from ufl.form import BaseForm, Form, FormSum, ZeroBaseForm +from ufl.functionspace import ( + AbstractFunctionSpace, + DualSpace, + FunctionSpace, + MixedFunctionSpace, + TensorProductFunctionSpace, +) from ufl.geometry import ( CellCoordinate, CellDiameter, @@ -171,6 +189,7 @@ ) from ufl.indexed import Indexed from ufl.indexsum import IndexSum +from ufl.integral import Integral from ufl.mathfunctions import ( Acos, Asin, @@ -194,8 +213,26 @@ Tanh, ) from ufl.matrix import Matrix +from ufl.measure import Measure, MeasureProduct, MeasureSum +from ufl.pullback import ( + AbstractPullback, + ContravariantPiola, + CovariantContravariantPiola, + CovariantPiola, + CustomPullback, + DoubleContravariantPiola, + DoubleCovariantPiola, + IdentityPullback, + L2Piola, + MixedPullback, + NonStandardPullbackException, + PhysicalPullback, + SymmetricPullback, + UndefinedPullback, +) from ufl.referencevalue import ReferenceValue from ufl.restriction import NegativeRestricted, PositiveRestricted, Restricted +from ufl.sobolevspace import DirectionalSobolevSpace, SobolevSpace from ufl.tensoralgebra import ( Cofactor, CompoundTensorOperator, @@ -215,13 +252,6 @@ from ufl.tensors import ComponentTensor, ListTensor from ufl.variable import Label, Variable -# Collect all classes in sets automatically classified by some properties -all_ufl_classes = set(ufl.core.expr.Expr._ufl_all_classes_) -abstract_classes = set(c for c in all_ufl_classes if c._ufl_is_abstract_) -ufl_classes = set(c for c in all_ufl_classes if not c._ufl_is_abstract_) -terminal_classes = set(c for c in all_ufl_classes if c._ufl_is_terminal_) -nonterminal_classes = set(c for c in all_ufl_classes if not c._ufl_is_terminal_) - __all__ += [ "EQ", "GE", @@ -230,6 +260,11 @@ "LT", "NE", "Abs", + "AbstractCell", + "AbstractDomain", + "AbstractFiniteElement", + "AbstractFunctionSpace", + "AbstractPullback", "Acos", "Action", "Adjoint", @@ -239,6 +274,7 @@ "Atan", "Atan2", "BaseForm", + "BaseForm", "BaseFormCoordinateDerivative", "BaseFormDerivative", "BaseFormOperator", @@ -250,6 +286,7 @@ "BesselK", "BesselY", "BinaryCondition", + "Cell", "CellAvg", "CellCoordinate", "CellDiameter", @@ -282,17 +319,26 @@ "Conj", "Constant", "ConstantValue", + "ContravariantPiola", "CoordinateDerivative", "Cos", "Cosh", + "CovariantContravariantPiola", + "CovariantPiola", "Cross", "Curl", + "CustomPullback", "Derivative", "Determinant", "Deviatoric", + "DirectionalSobolevSpace", "Div", "Division", "Dot", + "DoubleContravariantPiola", + "DoubleCovariantPiola", + "DualSpace", + "Equation", "Erf", "Exp", "Expr", @@ -310,26 +356,34 @@ "FacetOrientation", "FacetOrigin", "FacetRidgeJacobian", + "FixedIndex", "FloatValue", "Form", + "Form", "FormArgument", "FormSum", + "FunctionSpace", "GeometricCellQuantity", "GeometricFacetQuantity", "GeometricQuantity", "GeometricRidgeQuantity", "Grad", "Identity", + "IdentityPullback", "Imag", + "Index", + "IndexBase", "IndexSum", "Indexed", "Inner", "IntValue", + "Integral", "Interpolate", "Inverse", "Jacobian", "JacobianDeterminant", "JacobianInverse", + "L2Piola", "Label", "ListTensor", "Ln", @@ -338,19 +392,28 @@ "MaxCellEdgeLength", "MaxFacetEdgeLength", "MaxValue", + "Measure", + "MeasureProduct", + "MeasureSum", + "Mesh", + "MeshView", "MinCellEdgeLength", "MinFacetEdgeLength", "MinValue", + "MixedFunctionSpace", + "MixedPullback", "MultiIndex", "NablaDiv", "NablaGrad", "NegativeRestricted", + "NonStandardPullbackException", "NotCondition", "Operator", "OrCondition", "Outer", "PermutationSymbol", "Perp", + "PhysicalPullback", "PositiveRestricted", "Power", "Product", @@ -377,19 +440,29 @@ "Sin", "Sinh", "Skew", + "SobolevSpace", "SpatialCoordinate", "Sqrt", "Sum", "Sym", + "SymmetricPullback", "Tan", "Tanh", + "TensorProductCell", + "TensorProductFunctionSpace", "Terminal", + "TestFunction", + "TestFunctions", "Trace", "Transposed", + "TrialFunction", + "TrialFunctions", + "UndefinedPullback", "Variable", "VariableDerivative", "Zero", "ZeroBaseForm", + "ZeroBaseForm", "__exproperators", "abstract_classes", "all_ufl_classes", @@ -398,27 +471,9 @@ "ufl_classes", ] - -# Semi-automated imports of non-expr classes: - - -def populate_namespace_with_module_classes(mod, loc): - """Export the classes that submodules list in __all_classes__.""" - names = mod.__all_classes__ - for name in names: - loc[name] = getattr(mod, name) - return names - - -__all__ += populate_namespace_with_module_classes(ufl.cell, locals()) -__all__ += populate_namespace_with_module_classes(ufl.finiteelement, locals()) -__all__ += populate_namespace_with_module_classes(ufl.domain, locals()) -__all__ += populate_namespace_with_module_classes(ufl.functionspace, locals()) -__all__ += populate_namespace_with_module_classes(ufl.core.multiindex, locals()) -__all__ += populate_namespace_with_module_classes(ufl.argument, locals()) -__all__ += populate_namespace_with_module_classes(ufl.measure, locals()) -__all__ += populate_namespace_with_module_classes(ufl.integral, locals()) -__all__ += populate_namespace_with_module_classes(ufl.form, locals()) -__all__ += populate_namespace_with_module_classes(ufl.equation, locals()) -__all__ += populate_namespace_with_module_classes(ufl.pullback, locals()) -__all__ += populate_namespace_with_module_classes(ufl.sobolevspace, locals()) +# Collect all classes in sets automatically classified by some properties +all_ufl_classes = set(ufl.core.expr.Expr._ufl_all_classes_) +abstract_classes = set(c for c in all_ufl_classes if c._ufl_is_abstract_) +ufl_classes = set(c for c in all_ufl_classes if not c._ufl_is_abstract_) +terminal_classes = set(c for c in all_ufl_classes if c._ufl_is_terminal_) +nonterminal_classes = set(c for c in all_ufl_classes if not c._ufl_is_terminal_) From 441a31f372e143f3b7757fe43a5e31e8057cbba6 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 26 Jun 2025 21:13:56 +0200 Subject: [PATCH 04/78] single __all__ --- ufl/classes.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ufl/classes.py b/ufl/classes.py index 25120dedc..825a0be0e 100644 --- a/ufl/classes.py +++ b/ufl/classes.py @@ -15,9 +15,7 @@ # Modified by Anders Logg, 2009. # Modified by Kristian B. Oelgaard, 2011 # Modified by Andrew T. T. McRae, 2014 - -# This will be populated part by part below -__all__ = [] +# Modified by Paul T. Kühner, 2025 # Import all submodules, triggering execution of the ufl_type class # decorator for each Expr class. @@ -252,7 +250,7 @@ from ufl.tensors import ComponentTensor, ListTensor from ufl.variable import Label, Variable -__all__ += [ +__all__ = [ "EQ", "GE", "GT", From 9b510750e36a763e0e8c22e5f740279f8ea93d4e Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 26 Jun 2025 21:15:11 +0200 Subject: [PATCH 05/78] Tidy up --- ufl/classes.py | 38 +------------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/ufl/classes.py b/ufl/classes.py index 825a0be0e..bf8463d55 100644 --- a/ufl/classes.py +++ b/ufl/classes.py @@ -1,9 +1,6 @@ """Classes. -This file is useful for external code like tests and form compilers, -since it enables the syntax "from ufl.classes import CellFacetooBar" for getting -implementation details not exposed through the default ufl namespace. -It also contains functionality used by algorithms for dealing with groups +It contains functionality used by algorithms for dealing with groups of classes, and for mapping types to different handler functions. """ # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -17,40 +14,7 @@ # Modified by Andrew T. T. McRae, 2014 # Modified by Paul T. Kühner, 2025 -# Import all submodules, triggering execution of the ufl_type class -# decorator for each Expr class. - -import ufl.algebra -import ufl.argument -import ufl.averaging -import ufl.cell -import ufl.coefficient -import ufl.conditional -import ufl.constantvalue import ufl.core.expr -import ufl.core.multiindex -import ufl.core.operator -import ufl.core.terminal -import ufl.differentiation -import ufl.domain -import ufl.equation -import ufl.exprcontainers -import ufl.finiteelement -import ufl.form -import ufl.functionspace -import ufl.geometry -import ufl.indexed -import ufl.indexsum -import ufl.integral -import ufl.mathfunctions -import ufl.measure -import ufl.pullback -import ufl.referencevalue -import ufl.restriction -import ufl.sobolevspace -import ufl.tensoralgebra -import ufl.tensors -import ufl.variable from ufl import exproperators as __exproperators from ufl.action import Action from ufl.adjoint import Adjoint From 7663e851f7cf11dfbd298a5f4af0c52aeefc1330 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:40:30 +0200 Subject: [PATCH 06/78] Start on mypy --- ufl/algorithms/apply_derivatives.py | 40 ++++++++++++++++------------- ufl/core/expr.py | 10 ++++++++ ufl/corealg/dag_traverser.py | 3 ++- ufl/form.py | 3 +++ 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index f42d3ba1c..d44dc9b14 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -9,7 +9,7 @@ import warnings from functools import singledispatchmethod from math import pi -from typing import Union +from typing import Union, overload import numpy as np @@ -663,16 +663,14 @@ def _(self, o: Expr, fp: Expr) -> Expr: # --- Conditionals @process.register(BinaryCondition) - def _(self, o: Expr) -> Expr: + def _(self, o: BinaryCondition) -> Expr: """Differentiate a binary_condition.""" - # Should not be used anywhere... - return None + raise RuntimeError("Can not differentiate a binary_condition.") @process.register(NotCondition) - def _(self, o: Expr) -> Expr: + def _(self, o: NotCondition) -> Expr: """Differentiate a not_condition.""" - # Should not be used anywhere... - return None + raise RuntimeError("Can not differentiate a not_condition.") @process.register(Conditional) @DAGTraverser.postorder_only_children([1, 2]) @@ -1342,7 +1340,7 @@ def _(self, o: Expr, fp: Expr) -> Expr: return facet_avg(fp) @process.register(Argument) - def _(self, o: Expr) -> Expr: + def _(self, o: Argument) -> Expr: # Explicitly defining da/dw == 0 return self._process_argument(o) @@ -1350,20 +1348,26 @@ def _process_argument(self, o: Union[Argument, Coargument]) -> Zero: return self.independent_terminal(o) @process.register(Coefficient) - def _(self, o: Expr) -> Expr: + def _(self, o: Coefficient) -> Expr: return self._process_coefficient(o) - def _process_coefficient(self, o: Union[Expr]) -> Union[Expr]: + @overload + def _process_coefficient(self, o: Expr) -> Expr: ... + + @overload + def _process_coefficient(self, o: BaseForm) -> BaseForm: ... + + def _process_coefficient(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Differentiate an Expr or a BaseForm.""" # Define dw/dw := d/ds [w + s v] = v # Return corresponding argument if we can find o among w - do = self._w2v.get(o) + do = self._w2v.get(o) # type: ignore if do is not None: return do # Look for o among coefficient derivatives - dos = self._cd.get(o) + dos = self._cd.get(o) # type: ignore if dos is None: # If o is not among coefficient derivatives, return # do/dw=0 @@ -1848,7 +1852,7 @@ def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: @process.register(CoefficientDerivative) @DAGTraverser.postorder_only_children([0]) - def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: CoefficientDerivative, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to a coefficient_derivative.""" _, w, v, cd = o.ufl_operands key = (GateauxDerivativeRuleset, w, v, cd) @@ -1868,7 +1872,7 @@ def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: @process.register(BaseFormOperatorDerivative) @DAGTraverser.postorder_only_children([0]) - def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: BaseFormOperatorDerivative, f: Union[Expr, BaseForm]) -> Expr: """Apply to a base_form_operator_derivative.""" _, w, v, cd = o.ufl_operands if isinstance(f, ZeroBaseForm): @@ -1902,7 +1906,7 @@ def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: @process.register(CoordinateDerivative) @DAGTraverser.postorder_only_children([0]) - def _(self, o: Expr, f: Union[Expr, BaseForm]) -> CoordinateDerivative: + def _(self, o: CoordinateDerivative, f: Union[Expr, BaseForm]) -> CoordinateDerivative: """Apply to a coordinate_derivative.""" _, o1, o2, o3 = o.ufl_operands return CoordinateDerivative(f, o1, o2, o3) @@ -2156,7 +2160,7 @@ def _(self, o: Expr) -> Expr: @process.register(SpatialCoordinate) def _(self, o: Expr) -> Expr: """Differentiate a spatial_coordinate.""" - do = self._w2v.get(o) + do = self._w2v.get(o) # type: ignore # d x /d x => Argument(x.function_space()) if do is not None: return do @@ -2169,7 +2173,7 @@ def _(self, o: Expr) -> Expr: @process.register(ReferenceValue) def _(self, o: Expr) -> Expr: """Differentiate a reference_value.""" - do = self._cd.get(o) + do = self._cd.get(o) # type: ignore if do is not None: return do else: @@ -2244,7 +2248,7 @@ def process(self, o: Expr) -> Expr: @process.register(Expr) @process.register(BaseForm) - def _(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: Union[Expr, BaseForm]) -> Expr: """Apply to expr and base form.""" return self.reuse_if_untouched(o) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 8b8fd5b99..e13b1cb12 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -18,6 +18,7 @@ import warnings +from ufl.core.terminal import FormArgument from ufl.core.ufl_type import UFLObject, UFLType, update_ufl_type_attributes @@ -78,6 +79,10 @@ class MyOperator(Operator): Giving a list of creation and deletion counts for each typecode. """ + # TODO: not sure + # ufl_operands : tuple[FormArgument, ...] + # ufl_shape : tuple[int, ...] + # --- Each Expr subclass must define __slots__ or _ufl_noslots_ at # --- the top --- # This is to freeze member variables for objects of this class and @@ -389,6 +394,11 @@ def __round__(self, n=None): return val # For definitions see exproperators.py + + def __call__(self, other): + """Evaluate.""" + pass + def __ne__(self, other): """Negate.""" pass diff --git a/ufl/corealg/dag_traverser.py b/ufl/corealg/dag_traverser.py index e0aa5c9c2..a1ca41e77 100644 --- a/ufl/corealg/dag_traverser.py +++ b/ufl/corealg/dag_traverser.py @@ -1,9 +1,10 @@ """Base class for dag traversers.""" from functools import singledispatchmethod, wraps -from typing import Union +from typing import Optional, Union from ufl.classes import Expr +from ufl.form import BaseForm class DAGTraverser: diff --git a/ufl/form.py b/ufl/form.py index adab29dea..59b8b70d5 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -20,6 +20,7 @@ from ufl.constant import Constant from ufl.constantvalue import Zero from ufl.core.expr import Expr, ufl_err_str +from ufl.core.terminal import FormArgument from ufl.core.ufl_type import UFLType, ufl_type from ufl.domain import extract_unique_domain, sort_domains from ufl.equation import Equation @@ -83,6 +84,8 @@ def keyfunc(item): class BaseForm(metaclass=UFLType): """Description of an object containing arguments.""" + ufl_operands: tuple[FormArgument, ...] + # Slots is kept empty to enable multiple inheritance with other # classes __slots__ = () From 69e65f5a8d66f857111f47afdf3e2fbceed2bf7f Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:52:53 +0200 Subject: [PATCH 07/78] ruff --- ufl/core/expr.py | 1 - ufl/corealg/dag_traverser.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index e13b1cb12..6d7c8c910 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -18,7 +18,6 @@ import warnings -from ufl.core.terminal import FormArgument from ufl.core.ufl_type import UFLObject, UFLType, update_ufl_type_attributes diff --git a/ufl/corealg/dag_traverser.py b/ufl/corealg/dag_traverser.py index a1ca41e77..e0aa5c9c2 100644 --- a/ufl/corealg/dag_traverser.py +++ b/ufl/corealg/dag_traverser.py @@ -1,10 +1,9 @@ """Base class for dag traversers.""" from functools import singledispatchmethod, wraps -from typing import Optional, Union +from typing import Union from ufl.classes import Expr -from ufl.form import BaseForm class DAGTraverser: From 3bacb4ed9c2c0ed30d13d13c052419cdbf837916 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:09:22 +0200 Subject: [PATCH 08/78] more mypy --- ufl/algorithms/apply_derivatives.py | 6 +++--- ufl/core/expr.py | 8 ++++---- ufl/corealg/dag_traverser.py | 13 ++++++++++--- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index d44dc9b14..f836d67ae 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -1692,7 +1692,7 @@ def __init__( coefficients: ExprList, arguments: ExprList, coefficient_derivatives: ExprMapping, - outer_base_form_op: Expr, + outer_base_form_op: Union[Expr, BaseForm], compress: Union[bool, None] = True, visited_cache: Union[dict[tuple, Expr], None] = None, result_cache: Union[dict[Expr, Expr], None] = None, @@ -1726,7 +1726,7 @@ def process(self, o: Expr) -> Expr: @process.register(Interpolate) @DAGTraverser.postorder @pending_operations_recording - def _(self, i_op: Expr, dw: Expr) -> Expr: + def _(self, i_op: Interpolate, dw: Expr) -> Union[Expr, BaseForm]: """Differentiate an interpolate.""" # Interpolate rule: D_w[v](i_op(w, v*)) = i_op(v, v*), by linearity of Interpolate! if not dw: @@ -1872,7 +1872,7 @@ def _(self, o: CoefficientDerivative, f: Union[Expr, BaseForm]) -> Union[Expr, B @process.register(BaseFormOperatorDerivative) @DAGTraverser.postorder_only_children([0]) - def _(self, o: BaseFormOperatorDerivative, f: Union[Expr, BaseForm]) -> Expr: + def _(self, o: BaseFormOperatorDerivative, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to a base_form_operator_derivative.""" _, w, v, cd = o.ufl_operands if isinstance(f, ZeroBaseForm): diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 6d7c8c910..927c8048d 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -18,6 +18,7 @@ import warnings +from ufl.core.terminal import FormArgument from ufl.core.ufl_type import UFLObject, UFLType, update_ufl_type_attributes @@ -78,10 +79,6 @@ class MyOperator(Operator): Giving a list of creation and deletion counts for each typecode. """ - # TODO: not sure - # ufl_operands : tuple[FormArgument, ...] - # ufl_shape : tuple[int, ...] - # --- Each Expr subclass must define __slots__ or _ufl_noslots_ at # --- the top --- # This is to freeze member variables for objects of this class and @@ -181,6 +178,9 @@ def __init__(self): "ufl_index_dimensions", ) + ufl_operands: tuple[FormArgument, ...] + ufl_shape: tuple[int, ...] + # Each subclass of Expr is checked to have these methods in # ufl_type # FIXME: Add more and enable all diff --git a/ufl/corealg/dag_traverser.py b/ufl/corealg/dag_traverser.py index e0aa5c9c2..683659b27 100644 --- a/ufl/corealg/dag_traverser.py +++ b/ufl/corealg/dag_traverser.py @@ -1,9 +1,10 @@ """Base class for dag traversers.""" from functools import singledispatchmethod, wraps -from typing import Union +from typing import Union, overload from ufl.classes import Expr +from ufl.form import BaseForm class DAGTraverser: @@ -27,7 +28,7 @@ def __init__( self._visited_cache = {} if visited_cache is None else visited_cache self._result_cache = {} if result_cache is None else result_cache - def __call__(self, node: Expr, **kwargs) -> Expr: + def __call__(self, node: Union[Expr, BaseForm], **kwargs) -> Expr: """Perform memoised DAG traversal with ``process`` singledispatch method. Args: @@ -71,7 +72,13 @@ def process(self, o: Expr, **kwargs) -> Expr: """ raise AssertionError(f"Rule not set for {type(o)}") - def reuse_if_untouched(self, o: Expr, **kwargs) -> Expr: + @overload + def reuse_if_untouched(self, o: Expr, **kwargs) -> Expr: ... + + @overload + def reuse_if_untouched(self, o: BaseForm, **kwargs) -> BaseForm: ... + + def reuse_if_untouched(self, o: Union[Expr, BaseForm], **kwargs) -> Union[Expr, BaseForm]: """Reuse if touched. Args: From 016f2fe9ebbabb9c5b73610750cc1eb7a9ba9cc5 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:46:27 +0200 Subject: [PATCH 09/78] Ignore apply_derivates for now --- pyproject.toml | 4 ++++ ufl/algorithms/apply_derivatives.py | 18 +++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 249be78a7..555d4b1a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,3 +93,7 @@ convention = "google" disable_error_code = [ "attr-defined", ] + +[[tool.mypy.overrides]] +module = ["ufl.algorithms.apply_derivatives"] +disable_error_code = ["override", "arg-type", "union-attr", "misc", "call-arg", "return-value"] diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index f836d67ae..90e94b5dc 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -1625,7 +1625,7 @@ def _(self, o: Expr, *dfs) -> Expr: # -- Handlers for BaseForm objects -- # @process.register(Cofunction) - def _(self, o: Expr) -> Expr: + def _(self, o: Cofunction) -> Expr: """Differentiate a cofunction.""" # Same rule than for Coefficient except that we use a Coargument. # The coargument is already attached to the class (self._v) @@ -1637,7 +1637,7 @@ def _(self, o: Expr) -> Expr: return dc @process.register(Coargument) - def _(self, o: Expr) -> Expr: + def _(self, o: Coargument) -> Union[Expr, BaseForm]: """Differentiate a coargument.""" # Same rule than for Argument (da/dw == 0). dc = self._process_argument(o) @@ -1647,7 +1647,7 @@ def _(self, o: Expr) -> Expr: return dc @process.register(Matrix) - def _(self, M: Expr) -> Expr: + def _(self, M: Expr) -> BaseForm: """Differentiate a matrix.""" # Matrix rule: D_w[v](M) = v if M == w else 0 # We can't differentiate wrt a matrix so always return zero in @@ -1689,9 +1689,9 @@ class BaseFormOperatorDerivativeRuleset(GateauxDerivativeRuleset): def __init__( self, - coefficients: ExprList, - arguments: ExprList, - coefficient_derivatives: ExprMapping, + coefficients: FormArgument, + arguments: FormArgument, + coefficient_derivatives: FormArgument, outer_base_form_op: Union[Expr, BaseForm], compress: Union[bool, None] = True, visited_cache: Union[dict[tuple, Expr], None] = None, @@ -1809,12 +1809,12 @@ def process(self, o: Expr) -> Expr: @process.register(Expr) @process.register(BaseForm) - def _(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: Expr) -> Expr: """Apply to expr and base form.""" return self.reuse_if_untouched(o) @process.register(Terminal) - def _(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: Terminal) -> Expr: """Apply to a terminal.""" return o @@ -2248,7 +2248,7 @@ def process(self, o: Expr) -> Expr: @process.register(Expr) @process.register(BaseForm) - def _(self, o: Union[Expr, BaseForm]) -> Expr: + def _(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to expr and base form.""" return self.reuse_if_untouched(o) From 894df43f635772870d25da5db2d4b768ed22414b Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:05:05 +0200 Subject: [PATCH 10/78] add ignore to aply derivates --- pyproject.toml | 4 ---- ufl/algorithms/apply_derivatives.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 555d4b1a8..249be78a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,7 +93,3 @@ convention = "google" disable_error_code = [ "attr-defined", ] - -[[tool.mypy.overrides]] -module = ["ufl.algorithms.apply_derivatives"] -disable_error_code = ["override", "arg-type", "union-attr", "misc", "call-arg", "return-value"] diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 90e94b5dc..35f32148f 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -2081,7 +2081,7 @@ def apply_derivatives(expression): dexpression_dvar.append(Action(dexpr_dN, dN_dvar)) return sum(dexpression_dvar) - +# mypy: ignore-errors class CoordinateDerivativeRuleset(GenericDerivativeRuleset): """Apply AFD (Automatic Functional Differentiation) to expression. From 8338db405fef7da7cb147db7f15ce5ffb744627c Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:12:05 +0200 Subject: [PATCH 11/78] ruff --- ufl/algorithms/apply_derivatives.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 35f32148f..1974d1348 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -2081,6 +2081,7 @@ def apply_derivatives(expression): dexpression_dvar.append(Action(dexpr_dN, dN_dvar)) return sum(dexpression_dvar) + # mypy: ignore-errors class CoordinateDerivativeRuleset(GenericDerivativeRuleset): """Apply AFD (Automatic Functional Differentiation) to expression. From 79d737eeab9c9985ba722ff5492541166ebbefae Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:16:32 +0200 Subject: [PATCH 12/78] Avoid circular dependecny in typing --- ufl/core/expr.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 927c8048d..49f5230ac 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -16,9 +16,12 @@ # Modified by Anders Logg, 2008 # Modified by Massimiliano Leoni, 2016 +import typing import warnings -from ufl.core.terminal import FormArgument +if typing.TYPE_CHECKING: + from ufl.core.terminal import FormArgument + from ufl.core.ufl_type import UFLObject, UFLType, update_ufl_type_attributes @@ -178,7 +181,7 @@ def __init__(self): "ufl_index_dimensions", ) - ufl_operands: tuple[FormArgument, ...] + ufl_operands: tuple["FormArgument", ...] ufl_shape: tuple[int, ...] # Each subclass of Expr is checked to have these methods in From 78e28963dddf68dc7c3391aea9c66c02da3fcc26 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:29:51 +0200 Subject: [PATCH 13/78] All up to objects --- pyproject.toml | 5 ----- ufl/algorithms/apply_coefficient_split.py | 2 +- ufl/algorithms/apply_derivatives.py | 2 +- ufl/core/expr.py | 18 ++++++++++++++++++ ufl/core/ufl_type.py | 3 +++ ufl/exproperators.py | 8 ++++---- ufl/restriction.py | 1 + 7 files changed, 28 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 249be78a7..14106673d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,8 +88,3 @@ allowed-confusables = ["𝐚", "𝐀", "∕", "γ", "⨯", "∨"] [tool.ruff.lint.pydocstyle] convention = "google" - -[tool.mypy] -disable_error_code = [ - "attr-defined", -] diff --git a/ufl/algorithms/apply_coefficient_split.py b/ufl/algorithms/apply_coefficient_split.py index 854a5f5f4..4d428c297 100644 --- a/ufl/algorithms/apply_coefficient_split.py +++ b/ufl/algorithms/apply_coefficient_split.py @@ -133,7 +133,7 @@ def _( @process.register(Restricted) def _( self, - o: Expr, + o: Restricted, reference_value: Union[bool, None] = False, reference_grad: Union[int, None] = 0, restricted: Union[str, None] = None, diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 1974d1348..833cfed37 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -652,7 +652,7 @@ def _(self, o: Expr, nup: Expr, fp: Expr) -> Expr: @process.register(Restricted) @DAGTraverser.postorder - def _(self, o: Expr, fp: Expr) -> Expr: + def _(self, o: Restricted, fp: Expr) -> Expr: """Differentiate a restricted.""" # Restriction and differentiation commutes if isinstance(fp, ConstantValue): diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 49f5230ac..0b6b3700b 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -183,6 +183,7 @@ def __init__(self): ufl_operands: tuple["FormArgument", ...] ufl_shape: tuple[int, ...] + _ufl_typecode_: int # Each subclass of Expr is checked to have these methods in # ufl_type @@ -396,6 +397,11 @@ def __round__(self, n=None): return val # For definitions see exproperators.py + T: property + + def dx(self, *ii): + """Integral.""" + pass def __call__(self, other): """Evaluate.""" @@ -437,6 +443,10 @@ def __add__(self, other): """Add.""" pass + def __radd__(self, other): + """Add.""" + pass + def __sub__(self, other): """Subtract.""" pass @@ -449,10 +459,18 @@ def __div__(self, other): """Divide.""" pass + def __rdiv__(self, other): + """Divide.""" + pass + def __truediv__(self, other): """Divide.""" pass + def __rtruediv__(self, other): + """Divide.""" + pass + def __pow__(self, other): """Raise to a power.""" pass diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index fb4a8b8a7..09c343901 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -21,6 +21,9 @@ class UFLObject(ABC): """A UFL Object.""" + _ufl_is_abstract_: bool + _ufl_is_terminal_: bool + @abstractmethod def _ufl_hash_data_(self) -> typing.Hashable: """Return hashable data that uniquely defines this object.""" diff --git a/ufl/exproperators.py b/ufl/exproperators.py index bcb04490c..427f411c6 100644 --- a/ufl/exproperators.py +++ b/ufl/exproperators.py @@ -221,7 +221,7 @@ def _radd(self, o): return Sum(o, self) -Expr.__radd__ = _radd +Expr.__radd__ = _radd # type: ignore def _sub(self, o): @@ -267,8 +267,8 @@ def _rdiv(self, o): return Division(o, self) -Expr.__rdiv__ = _rdiv -Expr.__rtruediv__ = _rdiv +Expr.__rdiv__ = _rdiv # type: ignore +Expr.__rtruediv__ = _rdiv # type: ignore def _pow(self, o): @@ -448,4 +448,4 @@ def _dx(self, *ii): return d.__getitem__((Ellipsis,) + ii) -Expr.dx = _dx +Expr.dx = _dx # type: ignore diff --git a/ufl/restriction.py b/ufl/restriction.py index d6c1188e6..b4d231125 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -24,6 +24,7 @@ class Restricted(Operator): """Restriction.""" __slots__ = () + _side: str def __new__(cls, expression): """Create a new Restricted.""" From e8331e895d54fe34307fb4833aeee65b54be4bb9 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:35:01 +0200 Subject: [PATCH 14/78] Fix objects --- ufl/objects.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/ufl/objects.py b/ufl/objects.py index c88af62b9..3b1761c53 100644 --- a/ufl/objects.py +++ b/ufl/objects.py @@ -16,8 +16,24 @@ i, j, k, l = indices(4) # noqa: E741 p, q, r, s = indices(4) -for integral_type, measure_name in integral_type_to_measure_name.items(): - globals()[measure_name] = Measure(integral_type) +# Former: +# for integral_type, measure_name in integral_type_to_measure_name.items(): +# globals()[measure_name] = Measure(integral_type) +dx = Measure("cell") +ds = Measure("exterior_facet") +dS = Measure("interior_facet") +dr = Measure("ridge") +dP = Measure("vertex") +dc = Measure("custom") +dC = Measure("cutcell") +dI = Measure("interface") +dO = Measure("overlap") +ds_b = Measure("exterior_facet_bottom") +ds_t = Measure("exterior_facet_top") +ds_v = Measure("exterior_facet_vert") +dS_h = Measure("interior_facet_horiz") +dS_v = Measure("interior_facet_vert") + # TODO: Firedrake hack, remove later ds_tb = ds_b + ds_t # type: ignore # noqa: F821 From 3c1ff0d83dca303a2e930baf78509cfde3127e19 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:36:36 +0200 Subject: [PATCH 15/78] ruff --- ufl/objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ufl/objects.py b/ufl/objects.py index 3b1761c53..8a82f58f8 100644 --- a/ufl/objects.py +++ b/ufl/objects.py @@ -10,7 +10,7 @@ from ufl.cell import Cell from ufl.core.multiindex import indices -from ufl.measure import Measure, integral_type_to_measure_name +from ufl.measure import Measure # Default indices i, j, k, l = indices(4) # noqa: E741 @@ -36,10 +36,10 @@ # TODO: Firedrake hack, remove later -ds_tb = ds_b + ds_t # type: ignore # noqa: F821 +ds_tb = ds_b + ds_t # type: ignore # Default measure dX including both uncut and cut cells -dX = dx + dC # type: ignore # noqa: F821 +dX = dx + dC # type: ignore # Create objects for builtin known cell types vertex = Cell("vertex") From c56751099f9dfa8c37403933686f858ab95be7f4 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:44:19 +0200 Subject: [PATCH 16/78] Revert unfixable --- ufl/algorithms/apply_derivatives.py | 35 +++++++++++++---------------- ufl/corealg/dag_traverser.py | 13 +++-------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 833cfed37..96244826e 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -6,10 +6,12 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +# mypy: ignore-errors + import warnings from functools import singledispatchmethod from math import pi -from typing import Union, overload +from typing import Union import numpy as np @@ -1351,13 +1353,7 @@ def _process_argument(self, o: Union[Argument, Coargument]) -> Zero: def _(self, o: Coefficient) -> Expr: return self._process_coefficient(o) - @overload - def _process_coefficient(self, o: Expr) -> Expr: ... - - @overload - def _process_coefficient(self, o: BaseForm) -> BaseForm: ... - - def _process_coefficient(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _process_coefficient(self, o: Union[Expr]) -> Union[Expr]: """Differentiate an Expr or a BaseForm.""" # Define dw/dw := d/ds [w + s v] = v @@ -1637,7 +1633,7 @@ def _(self, o: Cofunction) -> Expr: return dc @process.register(Coargument) - def _(self, o: Coargument) -> Union[Expr, BaseForm]: + def _(self, o: Expr) -> Expr: """Differentiate a coargument.""" # Same rule than for Argument (da/dw == 0). dc = self._process_argument(o) @@ -1689,10 +1685,10 @@ class BaseFormOperatorDerivativeRuleset(GateauxDerivativeRuleset): def __init__( self, - coefficients: FormArgument, - arguments: FormArgument, - coefficient_derivatives: FormArgument, - outer_base_form_op: Union[Expr, BaseForm], + coefficients: ExprList, + arguments: ExprList, + coefficient_derivatives: ExprMapping, + outer_base_form_op: Expr, compress: Union[bool, None] = True, visited_cache: Union[dict[tuple, Expr], None] = None, result_cache: Union[dict[Expr, Expr], None] = None, @@ -1726,7 +1722,7 @@ def process(self, o: Expr) -> Expr: @process.register(Interpolate) @DAGTraverser.postorder @pending_operations_recording - def _(self, i_op: Interpolate, dw: Expr) -> Union[Expr, BaseForm]: + def _(self, i_op: Interpolate, dw: Expr) -> Expr: """Differentiate an interpolate.""" # Interpolate rule: D_w[v](i_op(w, v*)) = i_op(v, v*), by linearity of Interpolate! if not dw: @@ -1809,12 +1805,12 @@ def process(self, o: Expr) -> Expr: @process.register(Expr) @process.register(BaseForm) - def _(self, o: Expr) -> Expr: + def _(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to expr and base form.""" return self.reuse_if_untouched(o) @process.register(Terminal) - def _(self, o: Terminal) -> Expr: + def _(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to a terminal.""" return o @@ -1852,7 +1848,7 @@ def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: @process.register(CoefficientDerivative) @DAGTraverser.postorder_only_children([0]) - def _(self, o: CoefficientDerivative, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to a coefficient_derivative.""" _, w, v, cd = o.ufl_operands key = (GateauxDerivativeRuleset, w, v, cd) @@ -1872,7 +1868,7 @@ def _(self, o: CoefficientDerivative, f: Union[Expr, BaseForm]) -> Union[Expr, B @process.register(BaseFormOperatorDerivative) @DAGTraverser.postorder_only_children([0]) - def _(self, o: BaseFormOperatorDerivative, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to a base_form_operator_derivative.""" _, w, v, cd = o.ufl_operands if isinstance(f, ZeroBaseForm): @@ -1906,7 +1902,7 @@ def _(self, o: BaseFormOperatorDerivative, f: Union[Expr, BaseForm]) -> Union[Ex @process.register(CoordinateDerivative) @DAGTraverser.postorder_only_children([0]) - def _(self, o: CoordinateDerivative, f: Union[Expr, BaseForm]) -> CoordinateDerivative: + def _(self, o: Expr, f: Union[Expr, BaseForm]) -> CoordinateDerivative: """Apply to a coordinate_derivative.""" _, o1, o2, o3 = o.ufl_operands return CoordinateDerivative(f, o1, o2, o3) @@ -2082,7 +2078,6 @@ def apply_derivatives(expression): return sum(dexpression_dvar) -# mypy: ignore-errors class CoordinateDerivativeRuleset(GenericDerivativeRuleset): """Apply AFD (Automatic Functional Differentiation) to expression. diff --git a/ufl/corealg/dag_traverser.py b/ufl/corealg/dag_traverser.py index 683659b27..e0aa5c9c2 100644 --- a/ufl/corealg/dag_traverser.py +++ b/ufl/corealg/dag_traverser.py @@ -1,10 +1,9 @@ """Base class for dag traversers.""" from functools import singledispatchmethod, wraps -from typing import Union, overload +from typing import Union from ufl.classes import Expr -from ufl.form import BaseForm class DAGTraverser: @@ -28,7 +27,7 @@ def __init__( self._visited_cache = {} if visited_cache is None else visited_cache self._result_cache = {} if result_cache is None else result_cache - def __call__(self, node: Union[Expr, BaseForm], **kwargs) -> Expr: + def __call__(self, node: Expr, **kwargs) -> Expr: """Perform memoised DAG traversal with ``process`` singledispatch method. Args: @@ -72,13 +71,7 @@ def process(self, o: Expr, **kwargs) -> Expr: """ raise AssertionError(f"Rule not set for {type(o)}") - @overload - def reuse_if_untouched(self, o: Expr, **kwargs) -> Expr: ... - - @overload - def reuse_if_untouched(self, o: BaseForm, **kwargs) -> BaseForm: ... - - def reuse_if_untouched(self, o: Union[Expr, BaseForm], **kwargs) -> Union[Expr, BaseForm]: + def reuse_if_untouched(self, o: Expr, **kwargs) -> Expr: """Reuse if touched. Args: From 36a91263484c4e4c4f882c2a67012a52155afe32 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 29 Jun 2025 20:30:26 +0200 Subject: [PATCH 17/78] Remove coment --- ufl/objects.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/ufl/objects.py b/ufl/objects.py index 8a82f58f8..bf46414ce 100644 --- a/ufl/objects.py +++ b/ufl/objects.py @@ -16,9 +16,6 @@ i, j, k, l = indices(4) # noqa: E741 p, q, r, s = indices(4) -# Former: -# for integral_type, measure_name in integral_type_to_measure_name.items(): -# globals()[measure_name] = Measure(integral_type) dx = Measure("cell") ds = Measure("exterior_facet") dS = Measure("interior_facet") From f6358b9ff45804d70aa0bb5074b1ed5719f0fceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20T=2E=20K=C3=BChner?= <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 30 Jun 2025 11:17:53 +0200 Subject: [PATCH 18/78] Apply suggestions from code review Remove no longer necessary `type: ignore`'s --- ufl/algorithms/apply_derivatives.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 96244826e..b3e687725 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -1358,12 +1358,12 @@ def _process_coefficient(self, o: Union[Expr]) -> Union[Expr]: # Define dw/dw := d/ds [w + s v] = v # Return corresponding argument if we can find o among w - do = self._w2v.get(o) # type: ignore + do = self._w2v.get(o) if do is not None: return do # Look for o among coefficient derivatives - dos = self._cd.get(o) # type: ignore + dos = self._cd.get(o) if dos is None: # If o is not among coefficient derivatives, return # do/dw=0 @@ -2156,7 +2156,7 @@ def _(self, o: Expr) -> Expr: @process.register(SpatialCoordinate) def _(self, o: Expr) -> Expr: """Differentiate a spatial_coordinate.""" - do = self._w2v.get(o) # type: ignore + do = self._w2v.get(o) # d x /d x => Argument(x.function_space()) if do is not None: return do @@ -2169,7 +2169,7 @@ def _(self, o: Expr) -> Expr: @process.register(ReferenceValue) def _(self, o: Expr) -> Expr: """Differentiate a reference_value.""" - do = self._cd.get(o) # type: ignore + do = self._cd.get(o) if do is not None: return do else: From 7baa497bb76997fd7a99051753fff808f1f32929 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 1 Jul 2025 15:32:29 +0200 Subject: [PATCH 19/78] Some more --- ufl/algorithms/apply_derivatives.py | 18 ++++++++---------- ufl/core/expr.py | 5 ++++- ufl/core/ufl_type.py | 4 ++-- ufl/corealg/dag_traverser.py | 11 +++++++++-- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index b3e687725..c154ef2a8 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -6,8 +6,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -# mypy: ignore-errors - import warnings from functools import singledispatchmethod from math import pi @@ -322,7 +320,7 @@ def _(self, o: Expr) -> Expr: @process.register(Indexed) @DAGTraverser.postorder - def _(self, o: Expr, Ap: Expr, ii: Expr) -> Expr: + def _(self, o: Indexed, Ap: Expr, ii: MultiIndex) -> Expr: """Differentiate an indexed.""" # Propagate zeros if isinstance(Ap, Zero): @@ -343,7 +341,7 @@ def _(self, o: Expr) -> Expr: @process.register(ComponentTensor) @DAGTraverser.postorder - def _(self, o: Expr, Ap: Expr, ii: Expr) -> Expr: + def _(self, o: ComponentTensor, Ap: Expr, ii: MultiIndex) -> Expr: """Differentiate a component_tensor.""" if isinstance(Ap, Zero): op = self.independent_operator(o) @@ -769,7 +767,7 @@ def _(self, o: Expr) -> Expr: return Do @process.register(JacobianInverse) - def _(self, o: Expr) -> Expr: + def _(self, o: JacobianInverse) -> Expr: """Differentiate a jacobian_inverse.""" # grad(K) == K_ji rgrad(K)_rj if is_cellwise_constant(o): @@ -831,7 +829,7 @@ def _(self, o: Expr) -> Expr: # --- Rules for values or derivatives in reference frame @process.register(ReferenceValue) - def _(self, o: Expr) -> Expr: + def _(self, o: ReferenceValue) -> Expr: """Differentiate a reference_value.""" # grad(o) == grad(rv(f)) -> K_ji*rgrad(rv(f))_rj f = o.ufl_operands[0] @@ -1604,7 +1602,7 @@ def _(self, o: Expr, o0: Expr) -> Expr: @process.register(BaseFormOperator) @DAGTraverser.postorder - def _(self, o: Expr, *dfs) -> Expr: + def _(self, o: BaseFormOperator, *dfs) -> Expr: """Differentiate a base_form_operator. If d_coeff = 0 => BaseFormOperator's derivative is taken wrt a @@ -1633,7 +1631,7 @@ def _(self, o: Cofunction) -> Expr: return dc @process.register(Coargument) - def _(self, o: Expr) -> Expr: + def _(self, o: Coargument) -> Expr: """Differentiate a coargument.""" # Same rule than for Argument (da/dw == 0). dc = self._process_argument(o) @@ -1643,7 +1641,7 @@ def _(self, o: Expr) -> Expr: return dc @process.register(Matrix) - def _(self, M: Expr) -> BaseForm: + def _(self, M: Matrix) -> BaseForm: """Differentiate a matrix.""" # Matrix rule: D_w[v](M) = v if M == w else 0 # We can't differentiate wrt a matrix so always return zero in @@ -1735,7 +1733,7 @@ def _(self, i_op: Interpolate, dw: Expr) -> Expr: @process.register(ExternalOperator) @DAGTraverser.postorder @pending_operations_recording - def external_operator(self, N: Expr, *dfs) -> Expr: + def external_operator(self, N: ExternalOperator, *dfs) -> Expr: """Differentiate an external_operator.""" result: tuple[Expr, ...] = () for i, df in enumerate(dfs): diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 0b6b3700b..c25dfd5b1 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -125,7 +125,7 @@ def __init__(self): # A reference to the UFL class itself. This makes it possible to # do type(f)._ufl_class_ and be sure you get the actual UFL class # instead of a subclass from another library. - _ufl_class_ = None + _ufl_class_: type # The handler name. This is the name of the handler function you # implement for this type in a multifunction. @@ -138,6 +138,8 @@ def __init__(self): # Type trait: If the type is a literal. _ufl_is_literal_ = None + _ufl_is_terminal_: bool = False + # Type trait: If the type is classified as a 'terminal modifier', # for form compiler use. _ufl_is_terminal_modifier_ = None @@ -184,6 +186,7 @@ def __init__(self): ufl_operands: tuple["FormArgument", ...] ufl_shape: tuple[int, ...] _ufl_typecode_: int + ufl_free_indices: tuple[int, ...] # Each subclass of Expr is checked to have these methods in # ufl_type diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 09c343901..69f2dd7c5 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -269,7 +269,7 @@ def update_ufl_type_attributes(cls): def ufl_type( is_abstract=False, - is_terminal=None, + is_terminal=False, is_scalar=False, is_index_free=False, is_shaping=False, @@ -437,4 +437,4 @@ class UFLType(type): _ufl_is_abstract_ = True # Type trait: If the type is terminal. - _ufl_is_terminal_ = None + _ufl_is_terminal_: bool = False diff --git a/ufl/corealg/dag_traverser.py b/ufl/corealg/dag_traverser.py index e0aa5c9c2..d342f52c4 100644 --- a/ufl/corealg/dag_traverser.py +++ b/ufl/corealg/dag_traverser.py @@ -1,9 +1,10 @@ """Base class for dag traversers.""" from functools import singledispatchmethod, wraps -from typing import Union +from typing import Union, overload from ufl.classes import Expr +from ufl.form import BaseForm class DAGTraverser: @@ -71,7 +72,13 @@ def process(self, o: Expr, **kwargs) -> Expr: """ raise AssertionError(f"Rule not set for {type(o)}") - def reuse_if_untouched(self, o: Expr, **kwargs) -> Expr: + @overload + def reuse_if_untouched(self, o: Expr, **kwargs) -> Expr: ... + + @overload + def reuse_if_untouched(self, o: BaseForm, **kwargs) -> BaseForm: ... + + def reuse_if_untouched(self, o: Union[Expr | BaseForm], **kwargs) -> Union[Expr | BaseForm]: """Reuse if touched. Args: From 48f12fa3082847aaf109d99d800c48b6cee79a1a Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 1 Jul 2025 15:39:46 +0200 Subject: [PATCH 20/78] Move to per line ignore --- ufl/algorithms/apply_derivatives.py | 72 +++++++++++++++-------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index c154ef2a8..7b3c7fd03 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -837,7 +837,7 @@ def _(self, o: ReferenceValue) -> Expr: raise ValueError("ReferenceValue can only wrap a terminal") domain = extract_unique_domain(f, expand_mesh_sequence=False) if isinstance(domain, MeshSequence): - element = f.ufl_function_space().ufl_element() + element = f.ufl_function_space().ufl_element() # type: ignore if element.num_sub_elements != len(domain): raise RuntimeError(f"{element.num_sub_elements} != {len(domain)}") # Get monolithic representation of rgrad(o); o might live in a mixed space. @@ -879,7 +879,7 @@ def _(self, o: ReferenceValue) -> Expr: dofoffset += ndof return as_tensor(np.asarray(components).reshape(rgrad.ufl_shape[:-1] + self._var_shape)) else: - if isinstance(f.ufl_element().pullback, PhysicalPullback): + if isinstance(f.ufl_element().pullback, PhysicalPullback): # type: ignore # TODO: Do we need to be more careful for immersed things? return ReferenceGrad(o) else: @@ -1356,12 +1356,12 @@ def _process_coefficient(self, o: Union[Expr]) -> Union[Expr]: # Define dw/dw := d/ds [w + s v] = v # Return corresponding argument if we can find o among w - do = self._w2v.get(o) + do = self._w2v.get(o) # type: ignore if do is not None: return do # Look for o among coefficient derivatives - dos = self._cd.get(o) + dos = self._cd.get(o) # type: ignore if dos is None: # If o is not among coefficient derivatives, return # do/dw=0 @@ -1624,10 +1624,10 @@ def _(self, o: Cofunction) -> Expr: # Same rule than for Coefficient except that we use a Coargument. # The coargument is already attached to the class (self._v) # which `self.coefficient` relies on. - dc = self._process_coefficient(o) + dc = self._process_coefficient(o) # type: ignore if dc == 0: # Convert ufl.Zero into ZeroBaseForm - return ZeroBaseForm(o.arguments() + self._v) + return ZeroBaseForm(o.arguments() + self._v) # type: ignore return dc @process.register(Coargument) @@ -1637,10 +1637,10 @@ def _(self, o: Coargument) -> Expr: dc = self._process_argument(o) if dc == 0: # Convert ufl.Zero into ZeroBaseForm - return ZeroBaseForm(o.arguments() + self._v) + return ZeroBaseForm(o.arguments() + self._v) # type: ignore return dc - @process.register(Matrix) + @process.register(Matrix) # type: ignore def _(self, M: Matrix) -> BaseForm: """Differentiate a matrix.""" # Matrix rule: D_w[v](M) = v if M == w else 0 @@ -1727,7 +1727,7 @@ def _(self, i_op: Interpolate, dw: Expr) -> Expr: # i_op doesn't depend on w: # -> It also covers the Hessian case since Interpolate is linear, # e.g. D_w[v](D_w[v](i_op(w, v*))) = D_w[v](i_op(v, v*)) = 0 (since w not found). - return ZeroBaseForm(i_op.arguments() + self._v) + return ZeroBaseForm(i_op.arguments() + self._v) # type: ignore return i_op._ufl_expr_reconstruct_(expr=dw) @process.register(ExternalOperator) @@ -1761,7 +1761,7 @@ def external_operator(self, N: ExternalOperator, *dfs) -> Expr: "Frechet derivative of external operators need to be provided!" ) result += (extop,) - return sum(result) + return sum(result) # type: ignore class DerivativeRuleDispatcher(DAGTraverser): @@ -1802,38 +1802,38 @@ def process(self, o: Expr) -> Expr: return super().process(o) @process.register(Expr) - @process.register(BaseForm) + @process.register(BaseForm) # type: ignore def _(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to expr and base form.""" return self.reuse_if_untouched(o) @process.register(Terminal) - def _(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: Terminal) -> Terminal: """Apply to a terminal.""" return o @process.register(Derivative) - def _(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: Derivative) -> Expr: """Apply to a derivative.""" raise NotImplementedError(f"Missing derivative handler for {type(o).__name__}.") @process.register(Grad) @DAGTraverser.postorder - def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Expr: + def _(self, o: Grad, f: Union[Expr, BaseForm]) -> Expr: """Apply to a grad.""" gdim = o.ufl_shape[-1] key = (GradRuleset, gdim) dag_traverser = self._dag_traverser_cache.setdefault(key, GradRuleset(gdim)) - return dag_traverser(f) + return dag_traverser(f) # type: ignore @process.register(ReferenceGrad) @DAGTraverser.postorder - def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: ReferenceGrad, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to a reference_grad.""" tdim = o.ufl_shape[-1] key = (ReferenceGradRuleset, tdim) dag_traverser = self._dag_traverser_cache.setdefault(key, ReferenceGradRuleset(tdim)) - return dag_traverser(f) + return dag_traverser(f) # type: ignore @process.register(VariableDerivative) @DAGTraverser.postorder_only_children([0]) @@ -1842,11 +1842,11 @@ def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: _, op = o.ufl_operands key = (VariableRuleset, op) dag_traverser = self._dag_traverser_cache.setdefault(key, VariableRuleset(op)) - return dag_traverser(f) + return dag_traverser(f) # type: ignore @process.register(CoefficientDerivative) @DAGTraverser.postorder_only_children([0]) - def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: CoefficientDerivative, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to a coefficient_derivative.""" _, w, v, cd = o.ufl_operands key = (GateauxDerivativeRuleset, w, v, cd) @@ -1854,23 +1854,23 @@ def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: # operations dag_traverser = self._dag_traverser_cache.setdefault( key, - GateauxDerivativeRuleset(w, v, cd), + GateauxDerivativeRuleset(w, v, cd), # type: ignore ) # If f has been seen by the traverser, it immediately returns # the cached value. - mapped_expr = dag_traverser(f) + mapped_expr = dag_traverser(f) # type: ignore # Need to account for pending operations that have been stored # in other integrands - self.pending_operations += dag_traverser.pending_operations + self.pending_operations += dag_traverser.pending_operations # type: ignore return mapped_expr @process.register(BaseFormOperatorDerivative) @DAGTraverser.postorder_only_children([0]) - def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: + def _(self, o: BaseFormOperatorDerivative, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to a base_form_operator_derivative.""" _, w, v, cd = o.ufl_operands if isinstance(f, ZeroBaseForm): - (arg,) = v.ufl_operands + (arg,) = v.ufl_operands # type: ignore arguments = f.arguments() # derivative(F, u, du) with `du` a Coefficient # is equivalent to taking the action of the derivative. @@ -1883,19 +1883,19 @@ def _(self, o: Expr, f: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: key = (BaseFormOperatorDerivativeRuleset, w, v, cd, f) # We need to go through the dag first to record the pending operations dag_traverser = self._dag_traverser_cache.setdefault( - key, - BaseFormOperatorDerivativeRuleset(w, v, cd, f), + key, # type: ignore + BaseFormOperatorDerivativeRuleset(w, v, cd, f), # type: ignore ) # If f has been seen by the traverser, it immediately returns # the cached value. - mapped_expr = dag_traverser(f) - mapped_f = dag_traverser._process_coefficient(f) + mapped_expr = dag_traverser(f) # type: ignore + mapped_f = dag_traverser._process_coefficient(f) # type: ignore if mapped_f != 0: # If dN/dN needs to return an Argument in N space # with N a BaseFormOperator. return mapped_f # Need to account for pending operations that have been stored in other integrands - self.pending_operations += dag_traverser.pending_operations + self.pending_operations += dag_traverser.pending_operations # type: ignore return mapped_expr @process.register(CoordinateDerivative) @@ -1914,7 +1914,7 @@ def _(self, o: Expr, f: Union[Expr, BaseForm]) -> BaseFormCoordinateDerivative: @process.register(Indexed) @DAGTraverser.postorder - def _(self, o: Expr, Ap: Expr, ii: Expr) -> Union[Expr, BaseForm]: + def _(self, o: Indexed, Ap: Expr, ii: MultiIndex) -> Union[Expr, BaseForm]: """Apply to an indexed.""" # Reuse if untouched if Ap is o.ufl_operands[0]: @@ -2154,7 +2154,7 @@ def _(self, o: Expr) -> Expr: @process.register(SpatialCoordinate) def _(self, o: Expr) -> Expr: """Differentiate a spatial_coordinate.""" - do = self._w2v.get(o) + do = self._w2v.get(o) # type: ignore # d x /d x => Argument(x.function_space()) if do is not None: return do @@ -2167,7 +2167,7 @@ def _(self, o: Expr) -> Expr: @process.register(ReferenceValue) def _(self, o: Expr) -> Expr: """Differentiate a reference_value.""" - do = self._cd.get(o) + do = self._cd.get(o) # type: ignore if do is not None: return do else: @@ -2208,7 +2208,8 @@ def _(self, o: Expr) -> Expr: # d (grad_X(x))/d x => grad_X(Argument(x.function_space()) for w, v in zip(self._w, self._v): if extract_unique_domain(o) == extract_unique_domain(w) and isinstance( - v.ufl_operands[0], FormArgument + v.ufl_operands[0], # type: ignore + FormArgument, ): return ReferenceGrad(v) return self.independent_terminal(o) @@ -2241,7 +2242,7 @@ def process(self, o: Expr) -> Expr: return super().process(o) @process.register(Expr) - @process.register(BaseForm) + @process.register(BaseForm) # type: ignore def _(self, o: Union[Expr, BaseForm]) -> Union[Expr, BaseForm]: """Apply to expr and base form.""" return self.reuse_if_untouched(o) @@ -2285,7 +2286,8 @@ def _(self, o: Expr, f: Expr) -> Expr: _, w, v, cd = o.ufl_operands key = (CoordinateDerivativeRuleset, w, v, cd) dag_traverser = self._dag_traverser_cache.setdefault( - key, CoordinateDerivativeRuleset(w, v, cd) + key, + CoordinateDerivativeRuleset(w, v, cd), # type: ignore ) return dag_traverser(f) From 38633e7418ba201d21d055fb114b831384e17a07 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 1 Jul 2025 15:50:09 +0200 Subject: [PATCH 21/78] Revert default type changes --- ufl/core/expr.py | 4 ++-- ufl/core/ufl_type.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index c25dfd5b1..6d5e51354 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -125,7 +125,7 @@ def __init__(self): # A reference to the UFL class itself. This makes it possible to # do type(f)._ufl_class_ and be sure you get the actual UFL class # instead of a subclass from another library. - _ufl_class_: type + _ufl_class_: type = None # type: ignore # The handler name. This is the name of the handler function you # implement for this type in a multifunction. @@ -138,7 +138,7 @@ def __init__(self): # Type trait: If the type is a literal. _ufl_is_literal_ = None - _ufl_is_terminal_: bool = False + _ufl_is_terminal_: bool # Type trait: If the type is classified as a 'terminal modifier', # for form compiler use. diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 69f2dd7c5..3acd2ea9d 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -269,7 +269,7 @@ def update_ufl_type_attributes(cls): def ufl_type( is_abstract=False, - is_terminal=False, + is_terminal=None, is_scalar=False, is_index_free=False, is_shaping=False, @@ -437,4 +437,4 @@ class UFLType(type): _ufl_is_abstract_ = True # Type trait: If the type is terminal. - _ufl_is_terminal_: bool = False + _ufl_is_terminal_: bool = None # type: ignore From b285953bb57035bd4724271a1a97a7abfb788bf9 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 1 Jul 2025 15:52:20 +0200 Subject: [PATCH 22/78] Last? --- ufl/corealg/dag_traverser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/corealg/dag_traverser.py b/ufl/corealg/dag_traverser.py index d342f52c4..e0f3e35cb 100644 --- a/ufl/corealg/dag_traverser.py +++ b/ufl/corealg/dag_traverser.py @@ -78,7 +78,7 @@ def reuse_if_untouched(self, o: Expr, **kwargs) -> Expr: ... @overload def reuse_if_untouched(self, o: BaseForm, **kwargs) -> BaseForm: ... - def reuse_if_untouched(self, o: Union[Expr | BaseForm], **kwargs) -> Union[Expr | BaseForm]: + def reuse_if_untouched(self, o: Union[Expr, BaseForm], **kwargs) -> Union[Expr, BaseForm]: """Reuse if touched. Args: From afe157a90d11b19f965936217e7b33cba518ac43 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 3 Jul 2025 10:04:42 +0200 Subject: [PATCH 23/78] Remove duplicates --- ufl/core/expr.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index dc89c45aa..9df7f3755 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -474,14 +474,6 @@ def __rdiv__(self, other): """Divide.""" pass - def __truediv__(self, other): - """Divide.""" - pass - - def __rtruediv__(self, other): - """Divide.""" - pass - def __pow__(self, other): """Raise to a power.""" pass From 4589c6f48e94937872c41cc022474c9ea996a63a Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 3 Jul 2025 11:14:49 +0200 Subject: [PATCH 24/78] Remove changes to UFLObject --- ufl/core/ufl_type.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 3acd2ea9d..1f51b0145 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -21,9 +21,6 @@ class UFLObject(ABC): """A UFL Object.""" - _ufl_is_abstract_: bool - _ufl_is_terminal_: bool - @abstractmethod def _ufl_hash_data_(self) -> typing.Hashable: """Return hashable data that uniquely defines this object.""" @@ -419,7 +416,7 @@ class UFLType(type): _ufl_handler_name_ = "ufl_type" # A global array of all Expr and BaseForm subclasses, indexed by typecode - _ufl_all_classes_: list[UFLObject] = [] + _ufl_all_classes_: list[UFLType] = [] # A global set of all handler names added _ufl_all_handler_names_: set[str] = set() From e7052f78a2b69a7c3ad08c2755102a6353ba8bfa Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 3 Jul 2025 11:19:33 +0200 Subject: [PATCH 25/78] Simplify --- ufl/objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ufl/objects.py b/ufl/objects.py index bf46414ce..a6d37a606 100644 --- a/ufl/objects.py +++ b/ufl/objects.py @@ -33,10 +33,10 @@ # TODO: Firedrake hack, remove later -ds_tb = ds_b + ds_t # type: ignore +ds_tb = ds_b + ds_t # Default measure dX including both uncut and cut cells -dX = dx + dC # type: ignore +dX = dx + dC # Create objects for builtin known cell types vertex = Cell("vertex") From 05a50034410fef4565bb6832cf5bf92ee49aaa81 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:23:35 +0200 Subject: [PATCH 26/78] Start with is_terminal --- ufl/core/ufl_type.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 1f51b0145..0cc5a9fc6 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -266,7 +266,7 @@ def update_ufl_type_attributes(cls): def ufl_type( is_abstract=False, - is_terminal=None, + is_terminal=False, is_scalar=False, is_index_free=False, is_shaping=False, @@ -313,7 +313,12 @@ def _ufl_type_decorator_(cls): cls._ufl_class_ = cls set_trait(cls, "is_abstract", is_abstract, inherit=False) - set_trait(cls, "is_terminal", is_terminal, inherit=True) + # because we have no real inheritance yet + # cls._ufl_is_terminal_ = get_base_attr(cls, "_ufl_is_terminal_") + cls._ufl_is_terminal_ = ( + get_base_attr(cls, "_ufl_is_terminal_") if not is_terminal else is_terminal + ) + set_trait(cls, "is_literal", is_literal, inherit=True) set_trait(cls, "is_terminal_modifier", is_terminal_modifier, inherit=True) set_trait(cls, "is_shaping", is_shaping, inherit=True) @@ -409,6 +414,8 @@ class UFLType(type): Equip UFL types with some ufl specific properties. """ + # TODO: do not attach to every type + # ---- global ---- # A global counter of the number of typecodes assigned. _ufl_num_typecodes_ = 0 @@ -429,9 +436,12 @@ class UFLType(type): # typecode _ufl_obj_del_counts_: list[int] = [] + # ---- per type ---- # Type trait: If the type is abstract. An abstract class cannot # be instantiated and does not need all properties specified. - _ufl_is_abstract_ = True + _ufl_is_abstract_: bool = True # Type trait: If the type is terminal. - _ufl_is_terminal_: bool = None # type: ignore + _ufl_is_terminal_: bool = False + + # TODO: complete list of properties currently set in `ufl_type`. From 629d8050b659256d36e69040d233e3ad202da4c9 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 18:01:17 +0200 Subject: [PATCH 27/78] Move _ufl_all_classes_ to new UFLRegistry --- test/test_classcoverage.py | 5 +++-- ufl/classes.py | 12 ++++++------ ufl/core/ufl_type.py | 32 +++++++++++++++++++++++++++----- ufl/corealg/multifunction.py | 7 +++---- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/test/test_classcoverage.py b/test/test_classcoverage.py index 717f05dcf..4738c6ec4 100755 --- a/test/test_classcoverage.py +++ b/test/test_classcoverage.py @@ -115,6 +115,7 @@ Tanh, all_ufl_classes, ) +from ufl.core.ufl_type import UFLRegistry has_repr = set() has_dict = set() @@ -725,8 +726,8 @@ def testAll(self): ic, dc = Expr.ufl_disable_profiling() constructed = set() - unused = set(Expr._ufl_all_classes_) - for cls in Expr._ufl_all_classes_: + unused = set(UFLRegistry().all_classes) + for cls in UFLRegistry().all_classes: tc = cls._ufl_typecode_ if ic[tc]: constructed.add(cls) diff --git a/ufl/classes.py b/ufl/classes.py index bf8463d55..fb331c9d6 100644 --- a/ufl/classes.py +++ b/ufl/classes.py @@ -14,7 +14,6 @@ # Modified by Andrew T. T. McRae, 2014 # Modified by Paul T. Kühner, 2025 -import ufl.core.expr from ufl import exproperators as __exproperators from ufl.action import Action from ufl.adjoint import Adjoint @@ -65,6 +64,7 @@ from ufl.core.multiindex import FixedIndex, Index, IndexBase, MultiIndex from ufl.core.operator import Operator from ufl.core.terminal import FormArgument, Terminal +from ufl.core.ufl_type import UFLRegistry from ufl.differentiation import ( BaseFormCoordinateDerivative, BaseFormDerivative, @@ -434,8 +434,8 @@ ] # Collect all classes in sets automatically classified by some properties -all_ufl_classes = set(ufl.core.expr.Expr._ufl_all_classes_) -abstract_classes = set(c for c in all_ufl_classes if c._ufl_is_abstract_) -ufl_classes = set(c for c in all_ufl_classes if not c._ufl_is_abstract_) -terminal_classes = set(c for c in all_ufl_classes if c._ufl_is_terminal_) -nonterminal_classes = set(c for c in all_ufl_classes if not c._ufl_is_terminal_) +all_ufl_classes = set(UFLRegistry().all_classes) +abstract_classes = set(c for c in all_ufl_classes if c._ufl_is_abstract_) # type: ignore +ufl_classes = set(c for c in all_ufl_classes if not c._ufl_is_abstract_) # type: ignore +terminal_classes = set(c for c in all_ufl_classes if c._ufl_is_terminal_) # type: ignore +nonterminal_classes = set(c for c in all_ufl_classes if not c._ufl_is_terminal_) # type: ignore diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 0cc5a9fc6..4ee1c8148 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -141,7 +141,7 @@ def check_type_traits_consistency(cls): # Check for consistency in global type collection sizes Expr = core.expr.Expr assert Expr._ufl_num_typecodes_ == len(Expr._ufl_all_handler_names_) - assert Expr._ufl_num_typecodes_ == len(Expr._ufl_all_classes_) + assert Expr._ufl_num_typecodes_ == len(UFLRegistry().all_classes) assert Expr._ufl_num_typecodes_ == len(Expr._ufl_obj_init_counts_) assert Expr._ufl_num_typecodes_ == len(Expr._ufl_obj_del_counts_) @@ -252,7 +252,7 @@ def update_ufl_type_attributes(cls): cls._ufl_typecode_ = UFLType._ufl_num_typecodes_ UFLType._ufl_num_typecodes_ += 1 - UFLType._ufl_all_classes_.append(cls) + UFLRegistry().register_class(cls) # Determine handler name by a mapping from "TypeName" to "type_name" cls._ufl_handler_name_ = camel2underscore(cls.__name__) @@ -408,6 +408,31 @@ def _ufl_expr_rbinop_(self, other): return _ufl_type_decorator_ +class UFLRegistry: + """Maintains global informations of the registered types.""" + + _instance: typing.Optional[UFLRegistry] = None + _all_classes: list[type] + + def __new__(cls) -> UFLRegistry: + """Create singleton UFLRegistry.""" + if cls._instance is None: + cls._instance = super().__new__(cls) + cls._instance._all_classes = [] + return cls._instance + + @property + def all_classes(self) -> list[type]: # list[UFLType] + """Return list of all Expr and BaseForm subclasses, indexed by typecode.""" + return self._all_classes + + def register_class(self, c: type) -> None: + """Register an UFLType with the registry.""" + assert c not in self.all_classes + self._all_classes.append(c) + print(f"appending {c}") + + class UFLType(type): """Base class for all UFL types. @@ -422,9 +447,6 @@ class UFLType(type): # Set the handler name for UFLType _ufl_handler_name_ = "ufl_type" - # A global array of all Expr and BaseForm subclasses, indexed by typecode - _ufl_all_classes_: list[UFLType] = [] - # A global set of all handler names added _ufl_all_handler_names_: set[str] = set() diff --git a/ufl/corealg/multifunction.py b/ufl/corealg/multifunction.py index 0042213fb..ec964ba74 100644 --- a/ufl/corealg/multifunction.py +++ b/ufl/corealg/multifunction.py @@ -9,8 +9,7 @@ from inspect import signature -from ufl.core.expr import Expr -from ufl.core.ufl_type import UFLType +from ufl.core.ufl_type import UFLRegistry, UFLType def get_num_args(function): @@ -56,13 +55,13 @@ def __init__(self): algorithm_class = type(self) cache_data = MultiFunction._handlers_cache.get(algorithm_class) if not cache_data: - handler_names = [None] * len(Expr._ufl_all_classes_) + handler_names = [None] * len(UFLRegistry().all_classes) # Iterate over the inheritance chain for each Expr # subclass (NB! This assumes that all UFL classes inherits # from a single Expr subclass and that the first # superclass is always from the UFL Expr hierarchy!) - for classobject in Expr._ufl_all_classes_: + for classobject in UFLRegistry().all_classes: for c in classobject.mro(): # Register classobject with handler for the first # encountered superclass From 602fdc0c67c2e9f7e7575835295b9962fdb44870 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 18:11:37 +0200 Subject: [PATCH 28/78] Remove duplicate info _ufl_num_typecods_ --- ufl/algorithms/apply_geometry_lowering.py | 3 ++- ufl/core/ufl_type.py | 22 +++++++++++----------- ufl/corealg/map_dag.py | 6 +++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ufl/algorithms/apply_geometry_lowering.py b/ufl/algorithms/apply_geometry_lowering.py index 472c7b650..bc38efaba 100644 --- a/ufl/algorithms/apply_geometry_lowering.py +++ b/ufl/algorithms/apply_geometry_lowering.py @@ -43,6 +43,7 @@ ) from ufl.compound_expressions import cross_expr, determinant_expr, inverse_expr from ufl.core.multiindex import Index, indices +from ufl.core.ufl_type import UFLRegistry from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction, memoized_handler from ufl.domain import extract_unique_domain @@ -58,7 +59,7 @@ def __init__(self, preserve_types=()): """Initialise.""" MultiFunction.__init__(self) # Store preserve_types as boolean lookup table - self._preserve_types = [False] * Expr._ufl_num_typecodes_ + self._preserve_types = [False] * UFLRegistry().number_registered_classes for cls in preserve_types: self._preserve_types[cls._ufl_typecode_] = True diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 4ee1c8148..f9b419a38 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -140,10 +140,10 @@ def check_type_traits_consistency(cls): """Execute a variety of consistency checks on the ufl type traits.""" # Check for consistency in global type collection sizes Expr = core.expr.Expr - assert Expr._ufl_num_typecodes_ == len(Expr._ufl_all_handler_names_) - assert Expr._ufl_num_typecodes_ == len(UFLRegistry().all_classes) - assert Expr._ufl_num_typecodes_ == len(Expr._ufl_obj_init_counts_) - assert Expr._ufl_num_typecodes_ == len(Expr._ufl_obj_del_counts_) + assert UFLRegistry().number_registered_classes == len(Expr._ufl_all_handler_names_) + assert UFLRegistry().number_registered_classes == len(UFLRegistry().all_classes) + assert UFLRegistry().number_registered_classes == len(Expr._ufl_obj_init_counts_) + assert UFLRegistry().number_registered_classes == len(Expr._ufl_obj_del_counts_) # Check that non-abstract types always specify num_ops if not cls._ufl_is_abstract_: @@ -249,9 +249,8 @@ def update_global_expr_attributes(cls): def update_ufl_type_attributes(cls): """Update UFL type attributes.""" # Determine integer typecode by incrementally counting all types - cls._ufl_typecode_ = UFLType._ufl_num_typecodes_ - UFLType._ufl_num_typecodes_ += 1 - + # TODO: improve this implict post increment + cls._ufl_typecode_ = UFLRegistry().number_registered_classes UFLRegistry().register_class(cls) # Determine handler name by a mapping from "TypeName" to "type_name" @@ -430,7 +429,11 @@ def register_class(self, c: type) -> None: """Register an UFLType with the registry.""" assert c not in self.all_classes self._all_classes.append(c) - print(f"appending {c}") + + @property + def number_registered_classes(self) -> int: + """Return number of registered classes.""" + return len(self._all_classes) class UFLType(type): @@ -441,9 +444,6 @@ class UFLType(type): # TODO: do not attach to every type # ---- global ---- - # A global counter of the number of typecodes assigned. - _ufl_num_typecodes_ = 0 - # Set the handler name for UFLType _ufl_handler_name_ = "ufl_type" diff --git a/ufl/corealg/map_dag.py b/ufl/corealg/map_dag.py index 7cd6d11ee..73696a2ca 100644 --- a/ufl/corealg/map_dag.py +++ b/ufl/corealg/map_dag.py @@ -7,7 +7,7 @@ # # Modified by Massimiliano Leoni, 2016 -from ufl.core.expr import Expr +from ufl.core.ufl_type import UFLRegistry from ufl.corealg.multifunction import MultiFunction from ufl.corealg.traversal import cutoff_unique_post_traversal, unique_post_traversal @@ -73,8 +73,8 @@ def map_expr_dags(function, expressions, compress=True, vcache=None, rcache=None handlers = function._handlers # Optimization else: # Regular function: no skipping supported - cutoff_types = [False] * Expr._ufl_num_typecodes_ - handlers = [function] * Expr._ufl_num_typecodes_ + cutoff_types = [False] * UFLRegistry().number_registered_classes + handlers = [function] * UFLRegistry().number_registered_classes # Create visited set here to share between traversal calls visited = set() From eb97e88d05f780c549f5161432b608b690e28476 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 18:15:56 +0200 Subject: [PATCH 29/78] Remove unused _ufl_all_handler_names_ --- ufl/core/ufl_type.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index f9b419a38..4889db103 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -140,7 +140,6 @@ def check_type_traits_consistency(cls): """Execute a variety of consistency checks on the ufl type traits.""" # Check for consistency in global type collection sizes Expr = core.expr.Expr - assert UFLRegistry().number_registered_classes == len(Expr._ufl_all_handler_names_) assert UFLRegistry().number_registered_classes == len(UFLRegistry().all_classes) assert UFLRegistry().number_registered_classes == len(Expr._ufl_obj_init_counts_) assert UFLRegistry().number_registered_classes == len(Expr._ufl_obj_del_counts_) @@ -255,7 +254,6 @@ def update_ufl_type_attributes(cls): # Determine handler name by a mapping from "TypeName" to "type_name" cls._ufl_handler_name_ = camel2underscore(cls.__name__) - UFLType._ufl_all_handler_names_.add(cls._ufl_handler_name_) # Append space for counting object creation and destriction of # this this type. @@ -444,12 +442,6 @@ class UFLType(type): # TODO: do not attach to every type # ---- global ---- - # Set the handler name for UFLType - _ufl_handler_name_ = "ufl_type" - - # A global set of all handler names added - _ufl_all_handler_names_: set[str] = set() - # A global array of the number of initialized objects for each # typecode _ufl_obj_init_counts_: list[int] = [] @@ -459,6 +451,8 @@ class UFLType(type): _ufl_obj_del_counts_: list[int] = [] # ---- per type ---- + # Set the handler name for UFLType + _ufl_handler_name_ = "ufl_type" # Type trait: If the type is abstract. An abstract class cannot # be instantiated and does not need all properties specified. _ufl_is_abstract_: bool = True From 421be2e6588054fbb12ea25cbc1b63eb46f8840e Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 18:32:05 +0200 Subject: [PATCH 30/78] Move profiling info to registry, finalizes global states in UFLType --- test/test_classcoverage.py | 11 +++++----- ufl/core/expr.py | 11 ++++------ ufl/core/ufl_type.py | 41 +++++++++++++++++++++----------------- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/test/test_classcoverage.py b/test/test_classcoverage.py index 4738c6ec4..08610af86 100755 --- a/test/test_classcoverage.py +++ b/test/test_classcoverage.py @@ -723,15 +723,14 @@ def testAll(self): # e = action(b) # --- Check which classes have been created - ic, dc = Expr.ufl_disable_profiling() - + Expr.ufl_disable_profiling() + ic = UFLRegistry().object_tracking constructed = set() unused = set(UFLRegistry().all_classes) - for cls in UFLRegistry().all_classes: - tc = cls._ufl_typecode_ - if ic[tc]: + for cls in ic.keys(): + if ic[cls][0] > 0: constructed.add(cls) - if cls._ufl_is_abstract_: + else: unused.remove(cls) if unused: diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 9df7f3755..f3ce9cbaf 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -22,7 +22,7 @@ if typing.TYPE_CHECKING: from ufl.core.terminal import FormArgument -from ufl.core.ufl_type import UFLObject, UFLType, update_ufl_type_attributes +from ufl.core.ufl_type import UFLObject, UFLRegistry, UFLType, update_ufl_type_attributes class Expr(metaclass=UFLType): @@ -228,27 +228,24 @@ def __init__(self): def _ufl_profiling__init__(self): """Replacement constructor with object counting.""" Expr._ufl_regular__init__(self) - Expr._ufl_obj_init_counts_[self._ufl_typecode_] += 1 + UFLRegistry().register_object_creation(type(self)) def _ufl_profiling__del__(self): """Replacement destructor with object counting.""" - Expr._ufl_obj_del_counts_[self._ufl_typecode_] -= 1 + UFLRegistry().register_object_destruction(type(self)) @staticmethod def ufl_enable_profiling(): """Turn on the object counting mechanism and reset counts to zero.""" Expr.__init__ = Expr._ufl_profiling__init__ setattr(Expr, "__del__", Expr._ufl_profiling__del__) - for i in range(len(Expr._ufl_obj_init_counts_)): - Expr._ufl_obj_init_counts_[i] = 0 - Expr._ufl_obj_del_counts_[i] = 0 + UFLRegistry().reset_object_tracking() @staticmethod def ufl_disable_profiling(): """Turn off the object counting mechanism. Return object init and del counts.""" Expr.__init__ = Expr._ufl_regular__init__ delattr(Expr, "__del__") - return (Expr._ufl_obj_init_counts_, Expr._ufl_obj_del_counts_) # === Abstract functions that must be implemented by subclasses === diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 4889db103..394881deb 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -139,10 +139,7 @@ def check_has_slots(cls): def check_type_traits_consistency(cls): """Execute a variety of consistency checks on the ufl type traits.""" # Check for consistency in global type collection sizes - Expr = core.expr.Expr assert UFLRegistry().number_registered_classes == len(UFLRegistry().all_classes) - assert UFLRegistry().number_registered_classes == len(Expr._ufl_obj_init_counts_) - assert UFLRegistry().number_registered_classes == len(Expr._ufl_obj_del_counts_) # Check that non-abstract types always specify num_ops if not cls._ufl_is_abstract_: @@ -255,11 +252,6 @@ def update_ufl_type_attributes(cls): # Determine handler name by a mapping from "TypeName" to "type_name" cls._ufl_handler_name_ = camel2underscore(cls.__name__) - # Append space for counting object creation and destriction of - # this this type. - UFLType._ufl_obj_init_counts_.append(0) - UFLType._ufl_obj_del_counts_.append(0) - def ufl_type( is_abstract=False, @@ -411,11 +403,15 @@ class UFLRegistry: _instance: typing.Optional[UFLRegistry] = None _all_classes: list[type] + # TODO: profiling should be maintained in an own profiling class/registry + _obj_tracking: dict[type, tuple[int, int]] + def __new__(cls) -> UFLRegistry: """Create singleton UFLRegistry.""" if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._all_classes = [] + cls._instance._obj_tracking = {} return cls._instance @property @@ -433,6 +429,25 @@ def number_registered_classes(self) -> int: """Return number of registered classes.""" return len(self._all_classes) + def register_object_creation(self, c: type) -> None: + """Profiling.""" + data = self._obj_tracking.get(c, (0, 0)) + self._obj_tracking[c] = (data[0] + 1, data[1]) + + def register_object_destruction(self, c: type) -> None: + """Profiling.""" + data = self._obj_tracking.get(c, (0, 0)) + self._obj_tracking[c] = (data[0], data[1] - 1) + + def reset_object_tracking(self) -> None: + """Profiling.""" + self._obj_tracking = {} + + @property + def object_tracking(self) -> dict[type, tuple[int, int]]: + """Profiling.""" + return self._obj_tracking + class UFLType(type): """Base class for all UFL types. @@ -440,16 +455,6 @@ class UFLType(type): Equip UFL types with some ufl specific properties. """ - # TODO: do not attach to every type - # ---- global ---- - # A global array of the number of initialized objects for each - # typecode - _ufl_obj_init_counts_: list[int] = [] - - # A global array of the number of deleted objects for each - # typecode - _ufl_obj_del_counts_: list[int] = [] - # ---- per type ---- # Set the handler name for UFLType _ufl_handler_name_ = "ufl_type" From 6058a4b29abad4a61b0401d551862908ef8f5ef8 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 18:40:30 +0200 Subject: [PATCH 31/78] Trait list complete --- ufl/core/ufl_type.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 394881deb..6724d0760 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -455,14 +455,16 @@ class UFLType(type): Equip UFL types with some ufl specific properties. """ - # ---- per type ---- - # Set the handler name for UFLType _ufl_handler_name_ = "ufl_type" - # Type trait: If the type is abstract. An abstract class cannot - # be instantiated and does not need all properties specified. _ufl_is_abstract_: bool = True - - # Type trait: If the type is terminal. _ufl_is_terminal_: bool = False - - # TODO: complete list of properties currently set in `ufl_type`. + _ufl_is_literal_: bool = False + _ufl_is_terminal_modifier_: bool = False + _ufl_is_shaping_: bool = False + _ufl_is_in_reference_frame_: bool = False + _ufl_is_restriction_: bool = False + _ufl_is_evaluation_: bool = False + _ufl_is_differential_: bool = False + _ufl_is_scalar_: bool = False + _ufl_is_index_free_: bool = False + _ufl_num_ops_: int = 0 From 4d740847c10bab7062930359be1601f7073a58f4 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 19:01:57 +0200 Subject: [PATCH 32/78] Begin to replace decorator --- ufl/algorithms/apply_derivatives.py | 6 ++-- ufl/algorithms/balancing.py | 2 +- ufl/coefficient.py | 4 +-- ufl/core/expr.py | 31 +------------------ ufl/core/ufl_type.py | 48 +++++++++++++++++++++++++++-- ufl/form.py | 5 +-- 6 files changed, 53 insertions(+), 43 deletions(-) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 7b3c7fd03..4e7d43e30 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -320,7 +320,7 @@ def _(self, o: Expr) -> Expr: @process.register(Indexed) @DAGTraverser.postorder - def _(self, o: Indexed, Ap: Expr, ii: MultiIndex) -> Expr: + def _(self, o: Indexed, Ap: Expr, ii: MultiIndex) -> Indexed: """Differentiate an indexed.""" # Propagate zeros if isinstance(Ap, Zero): @@ -903,8 +903,8 @@ def _(self, o: Expr) -> Expr: if not f._ufl_is_in_reference_frame_: raise RuntimeError("Expecting a reference frame type") while not f._ufl_is_terminal_: - (f,) = f.ufl_operands - element = f.ufl_function_space().ufl_element() + (f,) = f.ufl_operands # type: ignore + element = f.ufl_function_space().ufl_element() # type: ignore if element.num_sub_elements != len(domain): raise RuntimeError(f"{element.num_sub_elements} != {len(domain)}") # Get monolithic representation of rgrad(o); o might live in a mixed space. diff --git a/ufl/algorithms/balancing.py b/ufl/algorithms/balancing.py index 28f3fbfaa..9a366826b 100644 --- a/ufl/algorithms/balancing.py +++ b/ufl/algorithms/balancing.py @@ -20,7 +20,7 @@ from ufl.corealg.multifunction import MultiFunction modifier_precedence = { - m._ufl_handler_name_: i + m._ufl_handler_name_: i # type: ignore for i, m in enumerate( [ ReferenceValue, diff --git a/ufl/coefficient.py b/ufl/coefficient.py index 0bcf2d020..c6aa8950b 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -13,7 +13,7 @@ from ufl.argument import Argument from ufl.core.terminal import FormArgument -from ufl.core.ufl_type import ufl_type +from ufl.core.ufl_type import UFLType, ufl_type from ufl.duals import is_dual, is_primal from ufl.form import BaseForm from ufl.functionspace import AbstractFunctionSpace, MixedFunctionSpace @@ -23,7 +23,7 @@ # --- The Coefficient class represents a coefficient in a form --- -class BaseCoefficient(Counted): +class BaseCoefficient(UFLType, Counted): """UFL form argument type: Parent Representation of a form coefficient.""" # Slots are disabled here because they cause trouble in PyDOLFIN diff --git a/ufl/core/expr.py b/ufl/core/expr.py index f3ce9cbaf..16bd3623e 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -25,7 +25,7 @@ from ufl.core.ufl_type import UFLObject, UFLRegistry, UFLType, update_ufl_type_attributes -class Expr(metaclass=UFLType): +class Expr(UFLType): """Base class for all UFL expression types. *Instance properties* @@ -131,40 +131,11 @@ def __init__(self): # implement for this type in a multifunction. _ufl_handler_name_ = "expr" - # Number of operands, "varying" for some types, or None if not - # applicable for abstract types. - _ufl_num_ops_ = None - - # Type trait: If the type is a literal. - _ufl_is_literal_ = None - - _ufl_is_terminal_: bool - - # Type trait: If the type is classified as a 'terminal modifier', - # for form compiler use. - _ufl_is_terminal_modifier_ = None - # Type trait: If the type is a shaping operator. Shaping # operations include indexing, slicing, transposing, i.e. not # introducing computation of a new value. _ufl_is_shaping_ = False - # Type trait: If the type is in reference frame. - _ufl_is_in_reference_frame_ = None - - # Type trait: If the type is a restriction to a geometric entity. - _ufl_is_restriction_ = None - - # Type trait: If the type is evaluation in a particular way. - _ufl_is_evaluation_ = None - - # Type trait: If the type is a differential operator. - _ufl_is_differential_ = None - - # Type trait: If the type is purely scalar, having no shape or - # indices. - _ufl_is_scalar_ = None - # Type trait: If the type never has free indices. _ufl_is_index_free_ = False diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 6724d0760..9c7d99d72 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -17,6 +17,9 @@ from ufl.core.compute_expr_hash import compute_expr_hash from ufl.utils.formatting import camel2underscore +if typing.TYPE_CHECKING: + from ufl.core.terminal import FormArgument + class UFLObject(ABC): """A UFL Object.""" @@ -449,22 +452,61 @@ def object_tracking(self) -> dict[type, tuple[int, int]]: return self._obj_tracking -class UFLType(type): +class UFLType(ABC): """Base class for all UFL types. Equip UFL types with some ufl specific properties. """ - _ufl_handler_name_ = "ufl_type" + __slots__: tuple[str, ...] = () + + _ufl_handler_name_: str = "ufl_type" _ufl_is_abstract_: bool = True _ufl_is_terminal_: bool = False _ufl_is_literal_: bool = False + + # Type trait: If the type is classified as a 'terminal modifier', + # for form compiler use. _ufl_is_terminal_modifier_: bool = False _ufl_is_shaping_: bool = False _ufl_is_in_reference_frame_: bool = False + + # Is a restriction to a geometric entity. _ufl_is_restriction_: bool = False _ufl_is_evaluation_: bool = False _ufl_is_differential_: bool = False _ufl_is_scalar_: bool = False _ufl_is_index_free_: bool = False - _ufl_num_ops_: int = 0 + + # Number of operands, "varying" for some types, or None if not + # applicable for abstract types. + _ufl_num_ops_: typing.Optional[int] = None + + ufl_operands: tuple[FormArgument, ...] + ufl_shape: tuple[int, ...] + ufl_free_indices: tuple[int, ...] + ufl_index_dimensions: tuple + + # Each subclass of Expr is checked to have these methods in + # ufl_type + # FIXME: Add more and enable all + # _ufl_required_methods_: tuple[str, ...] = ( + # # To compute the hash on demand, this method is called. + # "_ufl_compute_hash_", + # # The data returned from this method is used to compute the + # # signature of a form + # "_ufl_signature_data_", + # # The == operator must be implemented to compare for identical + # # representation, used by set() and dict(). The __hash__ + # # operator is added by ufl_type. + # "__eq__", + # # To reconstruct an object of the same type with operands or + # # properties changed. + # "_ufl_expr_reconstruct_", # Implemented in Operator and Terminal so this should never + # fail + # "ufl_domains", + # # "ufl_cell", + # # "ufl_domain", + # # "__str__", + # # "__repr__", + # ) diff --git a/ufl/form.py b/ufl/form.py index 59b8b70d5..2f3d3b3ae 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -80,16 +80,13 @@ def keyfunc(item): return tuple(all_integrals) # integrals_dict -@ufl_type() -class BaseForm(metaclass=UFLType): +class BaseForm(UFLType): """Description of an object containing arguments.""" ufl_operands: tuple[FormArgument, ...] # Slots is kept empty to enable multiple inheritance with other # classes - __slots__ = () - _ufl_is_abstract_ = True _ufl_required_methods_: tuple[str, ...] = ( "_analyze_form_arguments", "_analyze_domains", From 8ff603cd216e0ffc3e184edb695943fefa5f993a Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:13:13 +0200 Subject: [PATCH 33/78] Set is_restriction through class hierachy --- ufl/core/ufl_type.py | 1 - ufl/restriction.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 9c7d99d72..9e244600f 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -315,7 +315,6 @@ def _ufl_type_decorator_(cls): set_trait(cls, "is_terminal_modifier", is_terminal_modifier, inherit=True) set_trait(cls, "is_shaping", is_shaping, inherit=True) set_trait(cls, "is_in_reference_frame", is_in_reference_frame, inherit=True) - set_trait(cls, "is_restriction", is_restriction, inherit=True) set_trait(cls, "is_evaluation", is_evaluation, inherit=True) set_trait(cls, "is_differential", is_differential, inherit=True) diff --git a/ufl/restriction.py b/ufl/restriction.py index b4d231125..4fc99935d 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -18,11 +18,11 @@ num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0, - is_restriction=True, ) class Restricted(Operator): """Restriction.""" + _ufl_is_restriction_ = True __slots__ = () _side: str From 8c5c91637318955628de2b3e7de251043ac72ce2 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:16:01 +0200 Subject: [PATCH 34/78] Set is_differential through class hierachy --- ufl/core/base_form_operator.py | 4 +++- ufl/core/external_operator.py | 4 +++- ufl/core/interpolate.py | 4 +++- ufl/core/ufl_type.py | 1 - ufl/differentiation.py | 3 ++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ufl/core/base_form_operator.py b/ufl/core/base_form_operator.py index 6282629d6..d146501c9 100644 --- a/ufl/core/base_form_operator.py +++ b/ufl/core/base_form_operator.py @@ -26,10 +26,12 @@ from ufl.utils.counted import Counted -@ufl_type(num_ops="varying", is_differential=True) +@ufl_type(num_ops="varying") class BaseFormOperator(Operator, BaseForm, Counted): """Base form operator.""" + _ufl_is_differential_ = True + # Slots are disabled here because they cause trouble in PyDOLFIN # multiple inheritance pattern: _ufl_noslots_ = True diff --git a/ufl/core/external_operator.py b/ufl/core/external_operator.py index 3a9304854..a6625ea38 100644 --- a/ufl/core/external_operator.py +++ b/ufl/core/external_operator.py @@ -17,10 +17,12 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(num_ops="varying", is_differential=True) +@ufl_type(num_ops="varying") class ExternalOperator(BaseFormOperator): """External operator.""" + _ufl_is_differential_ = True + # Slots are disabled here because they cause trouble in PyDOLFIN # multiple inheritance pattern: _ufl_noslots_ = True diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py index 3896d78bd..16e1d84c7 100644 --- a/ufl/core/interpolate.py +++ b/ufl/core/interpolate.py @@ -17,10 +17,12 @@ from ufl.functionspace import AbstractFunctionSpace -@ufl_type(num_ops="varying", is_differential=True) +@ufl_type(num_ops="varying") class Interpolate(BaseFormOperator): """Symbolic representation of the interpolation operator.""" + _ufl_is_differential_ = True + # Slots are disabled here because they cause trouble in PyDOLFIN # multiple inheritance pattern: _ufl_noslots_ = True diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 9e244600f..0b08a0bab 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -316,7 +316,6 @@ def _ufl_type_decorator_(cls): set_trait(cls, "is_shaping", is_shaping, inherit=True) set_trait(cls, "is_in_reference_frame", is_in_reference_frame, inherit=True) set_trait(cls, "is_evaluation", is_evaluation, inherit=True) - set_trait(cls, "is_differential", is_differential, inherit=True) set_trait(cls, "is_scalar", is_scalar, inherit=True) set_trait(cls, "is_index_free", _is_index_free, inherit=True) diff --git a/ufl/differentiation.py b/ufl/differentiation.py index c319ac716..198e72739 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -24,10 +24,11 @@ # --- Basic differentiation objects --- -@ufl_type(is_abstract=True, is_differential=True) +@ufl_type(is_abstract=True) class Derivative(Operator): """Base class for all derivative types.""" + _ufl_is_differential_ = True __slots__ = () def __init__(self, operands): From 28d11e484983bcfcefbd5bed5b3e04b747a7ac97 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:17:41 +0200 Subject: [PATCH 35/78] Set is_evaluation through class hierachy --- ufl/averaging.py | 10 ++++------ ufl/core/ufl_type.py | 1 - 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ufl/averaging.py b/ufl/averaging.py index 898a45db0..2a758ef8b 100644 --- a/ufl/averaging.py +++ b/ufl/averaging.py @@ -11,12 +11,11 @@ from ufl.core.ufl_type import ufl_type -@ufl_type( - inherit_shape_from_operand=0, inherit_indices_from_operand=0, num_ops=1, is_evaluation=True -) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0, num_ops=1) class CellAvg(Operator): """Cell average.""" + _ufl_is_evaluation_ = True __slots__ = () def __new__(cls, f): @@ -43,12 +42,11 @@ def __str__(self): return f"cell_avg({self.ufl_operands[0]})" -@ufl_type( - inherit_shape_from_operand=0, inherit_indices_from_operand=0, num_ops=1, is_evaluation=True -) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0, num_ops=1) class FacetAvg(Operator): """Facet average.""" + _ufl_is_evaluation_ = True __slots__ = () def __new__(cls, f): diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 0b08a0bab..3f5ca3382 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -315,7 +315,6 @@ def _ufl_type_decorator_(cls): set_trait(cls, "is_terminal_modifier", is_terminal_modifier, inherit=True) set_trait(cls, "is_shaping", is_shaping, inherit=True) set_trait(cls, "is_in_reference_frame", is_in_reference_frame, inherit=True) - set_trait(cls, "is_evaluation", is_evaluation, inherit=True) set_trait(cls, "is_scalar", is_scalar, inherit=True) set_trait(cls, "is_index_free", _is_index_free, inherit=True) From ddd0348ecabdea78e3d3e01dda00f40b48fbdaaf Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:20:08 +0200 Subject: [PATCH 36/78] Set is_in_reference_frame through class hierachy --- ufl/core/ufl_type.py | 1 - ufl/differentiation.py | 15 ++++++--------- ufl/referencevalue.py | 3 ++- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 3f5ca3382..f1b981f38 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -314,7 +314,6 @@ def _ufl_type_decorator_(cls): set_trait(cls, "is_literal", is_literal, inherit=True) set_trait(cls, "is_terminal_modifier", is_terminal_modifier, inherit=True) set_trait(cls, "is_shaping", is_shaping, inherit=True) - set_trait(cls, "is_in_reference_frame", is_in_reference_frame, inherit=True) set_trait(cls, "is_scalar", is_scalar, inherit=True) set_trait(cls, "is_index_free", _is_index_free, inherit=True) diff --git a/ufl/differentiation.py b/ufl/differentiation.py index 198e72739..456e73c96 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -306,12 +306,11 @@ def __str__(self): return f"grad({self.ufl_operands[0]})" -@ufl_type( - num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True -) +@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) class ReferenceGrad(CompoundDerivative): """Reference grad.""" + _ufl_is_in_reference_frame_ = True __slots__ = ("_dim",) def __new__(cls, f): @@ -389,12 +388,11 @@ def __str__(self): return f"div({self.ufl_operands[0]})" -@ufl_type( - num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True -) +@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) class ReferenceDiv(CompoundDerivative): """Reference divergence.""" + _ufl_is_in_reference_frame_ = True __slots__ = () def __new__(cls, f): @@ -526,12 +524,11 @@ def __str__(self): return f"curl({self.ufl_operands[0]})" -@ufl_type( - num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True -) +@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) class ReferenceCurl(CompoundDerivative): """Reference curl.""" + _ufl_is_in_reference_frame_ = True __slots__ = ("ufl_shape",) def __new__(cls, f): diff --git a/ufl/referencevalue.py b/ufl/referencevalue.py index 5dd82f4c3..481910180 100644 --- a/ufl/referencevalue.py +++ b/ufl/referencevalue.py @@ -10,10 +10,11 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(num_ops=1, is_index_free=True, is_terminal_modifier=True, is_in_reference_frame=True) +@ufl_type(num_ops=1, is_index_free=True, is_terminal_modifier=True) class ReferenceValue(Operator): """Representation of the reference cell value of a form argument.""" + _ufl_is_in_reference_frame_ = True __slots__ = () def __init__(self, f): From bbfd100e209f32a5bb758f6e46fac34b259d5984 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:23:00 +0200 Subject: [PATCH 37/78] Set is_shaping through class hierachy --- ufl/core/ufl_type.py | 5 ----- ufl/indexed.py | 3 ++- ufl/tensoralgebra.py | 3 ++- ufl/tensors.py | 6 ++++-- ufl/variable.py | 3 ++- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index f1b981f38..51cec6712 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -264,10 +264,6 @@ def ufl_type( is_shaping=False, is_literal=False, is_terminal_modifier=False, - is_in_reference_frame=False, - is_restriction=False, - is_evaluation=False, - is_differential=None, use_default_hash=True, num_ops=None, inherit_shape_from_operand=None, @@ -313,7 +309,6 @@ def _ufl_type_decorator_(cls): set_trait(cls, "is_literal", is_literal, inherit=True) set_trait(cls, "is_terminal_modifier", is_terminal_modifier, inherit=True) - set_trait(cls, "is_shaping", is_shaping, inherit=True) set_trait(cls, "is_scalar", is_scalar, inherit=True) set_trait(cls, "is_index_free", _is_index_free, inherit=True) diff --git a/ufl/indexed.py b/ufl/indexed.py index 4e2739f11..f4cd1bfca 100644 --- a/ufl/indexed.py +++ b/ufl/indexed.py @@ -15,10 +15,11 @@ from ufl.precedence import parstr -@ufl_type(is_shaping=True, num_ops=2, is_terminal_modifier=True) +@ufl_type(num_ops=2, is_terminal_modifier=True) class Indexed(Operator): """Indexed expression.""" + _ufl_is_shaping_ = True __slots__ = ( "_initialised", "ufl_free_indices", diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index 4b608b526..cfa1dba5a 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -86,10 +86,11 @@ def __init__(self, operands): # pass -@ufl_type(is_shaping=True, num_ops=1, inherit_indices_from_operand=0) +@ufl_type(num_ops=1, inherit_indices_from_operand=0) class Transposed(CompoundTensorOperator): """Transposed tensor.""" + _ufl_is_shaping_ = True __slots__ = () def __new__(cls, A): diff --git a/ufl/tensors.py b/ufl/tensors.py index 4be2ac8e6..c409d3408 100644 --- a/ufl/tensors.py +++ b/ufl/tensors.py @@ -18,10 +18,11 @@ # --- Classes representing tensors of UFL expressions --- -@ufl_type(is_shaping=True, num_ops="varying", inherit_indices_from_operand=0) +@ufl_type(num_ops="varying", inherit_indices_from_operand=0) class ListTensor(Operator): """Wraps a list of expressions into a tensor valued expression of one higher rank.""" + _ufl_is_shaping_ = True __slots__ = ("_initialised",) def __new__(cls, *expressions): @@ -178,10 +179,11 @@ def substring(expressions, indent): return substring(self.ufl_operands, 0) -@ufl_type(is_shaping=True, num_ops="varying") +@ufl_type(num_ops="varying") class ComponentTensor(Operator): """Maps the free indices of a scalar valued expression to tensor axes.""" + _ufl_is_shaping_ = True __slots__ = ("_initialised", "ufl_free_indices", "ufl_index_dimensions", "ufl_shape") def __new__(cls, expression, indices): diff --git a/ufl/variable.py b/ufl/variable.py index 56cf4b9be..9abdc95a3 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -65,7 +65,7 @@ def _ufl_signature_data_(self, renumbering): return ("Label", renumbering[self]) -@ufl_type(is_shaping=True, is_index_free=True, num_ops=1, inherit_shape_from_operand=0) +@ufl_type(is_index_free=True, num_ops=1, inherit_shape_from_operand=0) class Variable(Operator): """A Variable is a representative for another expression. @@ -79,6 +79,7 @@ class Variable(Operator): df = diff(f, e) """ + _ufl_is_shaping_ = True __slots__ = () def __init__(self, expression, label=None): From 0b32ec6c0d02f15dcbbce3e0366d6373f26117cf Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:25:06 +0200 Subject: [PATCH 38/78] Set is_literal through class hierachy --- ufl/constantvalue.py | 12 ++++++++---- ufl/core/ufl_type.py | 1 - 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index dde2b6cbf..2d0d89513 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -56,13 +56,14 @@ def ufl_domains(self): # TODO: Add geometric dimension/domain and Argument dependencies to Zero? -@ufl_type(is_literal=True) +@ufl_type() class Zero(ConstantValue): """Representation of a zero valued expression. Class for representing zero tensors of different shapes. """ + _ufl_is_literal_ = True __slots__ = ("ufl_free_indices", "ufl_index_dimensions", "ufl_shape") _cache: dict[tuple[int], "Zero"] = {} @@ -272,10 +273,11 @@ def imag(self): return self._value.imag -@ufl_type(wraps_type=complex, is_literal=True) +@ufl_type(wraps_type=complex) class ComplexValue(ScalarValue): """Representation of a constant, complex scalar.""" + _ufl_is_literal_ = True __slots__ = () def __getnewargs__(self): @@ -325,10 +327,11 @@ class RealValue(ScalarValue): __slots__ = () -@ufl_type(wraps_type=float, is_literal=True) +@ufl_type(wraps_type=float) class FloatValue(RealValue): """Representation of a constant scalar floating point value.""" + _ufl_is_literal_ = True __slots__ = () def __getnewargs__(self): @@ -352,10 +355,11 @@ def __repr__(self): return r -@ufl_type(wraps_type=int, is_literal=True) +@ufl_type(wraps_type=int) class IntValue(RealValue): """Representation of a constant scalar integer value.""" + _ufl_is_literal_ = True __slots__ = () _cache: dict[int, "IntValue"] = {} diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 51cec6712..4d9d8e4aa 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -307,7 +307,6 @@ def _ufl_type_decorator_(cls): get_base_attr(cls, "_ufl_is_terminal_") if not is_terminal else is_terminal ) - set_trait(cls, "is_literal", is_literal, inherit=True) set_trait(cls, "is_terminal_modifier", is_terminal_modifier, inherit=True) set_trait(cls, "is_scalar", is_scalar, inherit=True) From 99b06167b9d67823fc509fa83d9716b49bb7f09d Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:31:53 +0200 Subject: [PATCH 39/78] Set is_terminal_modifier through class hierachy --- ufl/core/ufl_type.py | 5 ----- ufl/differentiation.py | 18 ++++++++++++------ ufl/indexed.py | 3 ++- ufl/referencevalue.py | 3 ++- ufl/restriction.py | 6 ++++-- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 4d9d8e4aa..0439bd795 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -261,9 +261,6 @@ def ufl_type( is_terminal=False, is_scalar=False, is_index_free=False, - is_shaping=False, - is_literal=False, - is_terminal_modifier=False, use_default_hash=True, num_ops=None, inherit_shape_from_operand=None, @@ -307,8 +304,6 @@ def _ufl_type_decorator_(cls): get_base_attr(cls, "_ufl_is_terminal_") if not is_terminal else is_terminal ) - set_trait(cls, "is_terminal_modifier", is_terminal_modifier, inherit=True) - set_trait(cls, "is_scalar", is_scalar, inherit=True) set_trait(cls, "is_index_free", _is_index_free, inherit=True) diff --git a/ufl/differentiation.py b/ufl/differentiation.py index 456e73c96..c5921cd2c 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -258,10 +258,11 @@ def __init__(self, operands): Derivative.__init__(self, operands) -@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) +@ufl_type(num_ops=1, inherit_indices_from_operand=0) class Grad(CompoundDerivative): """Grad.""" + _ufl_is_terminal_modifier_ = True __slots__ = ("_dim",) def __new__(cls, f): @@ -306,10 +307,11 @@ def __str__(self): return f"grad({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) +@ufl_type(num_ops=1, inherit_indices_from_operand=0) class ReferenceGrad(CompoundDerivative): """Reference grad.""" + _ufl_is_terminal_modifier_ = True _ufl_is_in_reference_frame_ = True __slots__ = ("_dim",) @@ -357,10 +359,11 @@ def __str__(self): return f"reference_grad({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) +@ufl_type(num_ops=1, inherit_indices_from_operand=0) class Div(CompoundDerivative): """Div.""" + _ufl_is_terminal_modifier_ = True __slots__ = () def __new__(cls, f): @@ -388,10 +391,11 @@ def __str__(self): return f"div({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) +@ufl_type(num_ops=1, inherit_indices_from_operand=0) class ReferenceDiv(CompoundDerivative): """Reference divergence.""" + _ufl_is_terminal_modifier_ = True _ufl_is_in_reference_frame_ = True __slots__ = () @@ -493,10 +497,11 @@ def __str__(self): _curl_shapes = {(): (2,), (2,): (), (3,): (3,)} -@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) +@ufl_type(num_ops=1, inherit_indices_from_operand=0) class Curl(CompoundDerivative): """Compound derivative.""" + _ufl_is_terminal_modifier_ = True __slots__ = ("ufl_shape",) def __new__(cls, f): @@ -524,10 +529,11 @@ def __str__(self): return f"curl({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) +@ufl_type(num_ops=1, inherit_indices_from_operand=0) class ReferenceCurl(CompoundDerivative): """Reference curl.""" + _ufl_is_terminal_modifier_ = True _ufl_is_in_reference_frame_ = True __slots__ = ("ufl_shape",) diff --git a/ufl/indexed.py b/ufl/indexed.py index f4cd1bfca..48d24ce26 100644 --- a/ufl/indexed.py +++ b/ufl/indexed.py @@ -15,10 +15,11 @@ from ufl.precedence import parstr -@ufl_type(num_ops=2, is_terminal_modifier=True) +@ufl_type(num_ops=2) class Indexed(Operator): """Indexed expression.""" + _ufl_is_terminal_modifier_ = True _ufl_is_shaping_ = True __slots__ = ( "_initialised", diff --git a/ufl/referencevalue.py b/ufl/referencevalue.py index 481910180..edeab8673 100644 --- a/ufl/referencevalue.py +++ b/ufl/referencevalue.py @@ -10,10 +10,11 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(num_ops=1, is_index_free=True, is_terminal_modifier=True) +@ufl_type(num_ops=1, is_index_free=True) class ReferenceValue(Operator): """Representation of the reference cell value of a form argument.""" + _ufl_is_terminal_modifier_ = True _ufl_is_in_reference_frame_ = True __slots__ = () diff --git a/ufl/restriction.py b/ufl/restriction.py index 4fc99935d..2be38acc1 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -50,17 +50,19 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)}({self._side})" -@ufl_type(is_terminal_modifier=True) +@ufl_type() class PositiveRestricted(Restricted): """Positive restriction.""" + _ufl_is_terminal_modifier_ = True __slots__ = () _side = "+" -@ufl_type(is_terminal_modifier=True) +@ufl_type() class NegativeRestricted(Restricted): """Negative restriction.""" + _ufl_is_terminal_modifier_ = True __slots__ = () _side = "-" From 8085003744623ff3eebb5dbc55d687fb74cf10e6 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:43:10 +0200 Subject: [PATCH 40/78] Remove unused num_ops property from UFLType --- ufl/algebra.py | 15 ++++++------ ufl/averaging.py | 4 ++-- ufl/conditional.py | 10 ++++---- ufl/core/base_form_operator.py | 2 +- ufl/core/external_operator.py | 2 +- ufl/core/interpolate.py | 2 +- ufl/core/ufl_type.py | 44 ---------------------------------- ufl/differentiation.py | 30 +++++++++++------------ ufl/exprcontainers.py | 4 ++-- ufl/indexed.py | 2 +- ufl/indexsum.py | 2 +- ufl/mathfunctions.py | 6 ++--- ufl/referencevalue.py | 2 +- ufl/restriction.py | 1 - ufl/tensoralgebra.py | 26 ++++++++++---------- ufl/tensors.py | 4 ++-- ufl/variable.py | 2 +- 17 files changed, 56 insertions(+), 102 deletions(-) diff --git a/ufl/algebra.py b/ufl/algebra.py index 7603d5e81..1090193bb 100644 --- a/ufl/algebra.py +++ b/ufl/algebra.py @@ -21,7 +21,6 @@ @ufl_type( - num_ops=2, inherit_shape_from_operand=0, inherit_indices_from_operand=0, binop="__add__", @@ -104,7 +103,7 @@ def __str__(self): return " + ".join([parstr(o, self) for o in self.ufl_operands]) -@ufl_type(num_ops=2, binop="__mul__", rbinop="__rmul__") +@ufl_type(binop="__mul__", rbinop="__rmul__") class Product(Operator): """The product of two or more UFL objects.""" @@ -201,7 +200,7 @@ def __str__(self): return " * ".join((parstr(a, self), parstr(b, self))) -@ufl_type(num_ops=2, inherit_indices_from_operand=0, binop="__div__", rbinop="__rdiv__") +@ufl_type(inherit_indices_from_operand=0, binop="__div__", rbinop="__rdiv__") class Division(Operator): """Division.""" @@ -266,7 +265,7 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)} / {parstr(self.ufl_operands[1], self)}" -@ufl_type(num_ops=2, inherit_indices_from_operand=0, binop="__pow__", rbinop="__rpow__") +@ufl_type(inherit_indices_from_operand=0, binop="__pow__", rbinop="__rpow__") class Power(Operator): """Power.""" @@ -328,7 +327,7 @@ def __str__(self): return f"{parstr(a, self)} ** {parstr(b, self)}" -@ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0, unop="__abs__") +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0, unop="__abs__") class Abs(Operator): """Absolute value.""" @@ -363,7 +362,7 @@ def __str__(self): return f"|{parstr(a, self)}|" -@ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Conj(Operator): """Complex conjugate.""" @@ -398,7 +397,7 @@ def __str__(self): return f"conj({parstr(a, self)})" -@ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Real(Operator): """Real part.""" @@ -435,7 +434,7 @@ def __str__(self): return f"Re[{parstr(a, self)}]" -@ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Imag(Operator): """Imaginary part.""" diff --git a/ufl/averaging.py b/ufl/averaging.py index 2a758ef8b..907b509a1 100644 --- a/ufl/averaging.py +++ b/ufl/averaging.py @@ -11,7 +11,7 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0, num_ops=1) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class CellAvg(Operator): """Cell average.""" @@ -42,7 +42,7 @@ def __str__(self): return f"cell_avg({self.ufl_operands[0]})" -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0, num_ops=1) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class FacetAvg(Operator): """Facet average.""" diff --git a/ufl/conditional.py b/ufl/conditional.py index c20f0a678..e34f36412 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -39,7 +39,7 @@ def __bool__(self): __nonzero__ = __bool__ -@ufl_type(is_abstract=True, num_ops=2) +@ufl_type(is_abstract=True) class BinaryCondition(Condition): """Binary condition.""" @@ -232,7 +232,7 @@ def evaluate(self, x, mapping, component, index_values): return bool(a or b) -@ufl_type(num_ops=1) +@ufl_type() class NotCondition(Condition): """Not condition.""" @@ -254,7 +254,7 @@ def __str__(self): return f"!({self.ufl_operands[0]!s})" -@ufl_type(num_ops=3, inherit_shape_from_operand=1, inherit_indices_from_operand=1) +@ufl_type(inherit_shape_from_operand=1, inherit_indices_from_operand=1) class Conditional(Operator): """Conditional expression. @@ -320,7 +320,7 @@ def __str__(self): # --- Specific functions higher level than a conditional --- -@ufl_type(is_scalar=True, num_ops=1) +@ufl_type(is_scalar=True) class MinValue(Operator): """Take the minimum of two values.""" @@ -352,7 +352,7 @@ def __str__(self): return "min_value({}, {})".format(*self.ufl_operands) -@ufl_type(is_scalar=True, num_ops=1) +@ufl_type(is_scalar=True) class MaxValue(Operator): """Take the maximum of two values.""" diff --git a/ufl/core/base_form_operator.py b/ufl/core/base_form_operator.py index d146501c9..c7b45cc0c 100644 --- a/ufl/core/base_form_operator.py +++ b/ufl/core/base_form_operator.py @@ -26,7 +26,7 @@ from ufl.utils.counted import Counted -@ufl_type(num_ops="varying") +@ufl_type() class BaseFormOperator(Operator, BaseForm, Counted): """Base form operator.""" diff --git a/ufl/core/external_operator.py b/ufl/core/external_operator.py index a6625ea38..110d94b1f 100644 --- a/ufl/core/external_operator.py +++ b/ufl/core/external_operator.py @@ -17,7 +17,7 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(num_ops="varying") +@ufl_type() class ExternalOperator(BaseFormOperator): """External operator.""" diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py index 16e1d84c7..acb38690f 100644 --- a/ufl/core/interpolate.py +++ b/ufl/core/interpolate.py @@ -17,7 +17,7 @@ from ufl.functionspace import AbstractFunctionSpace -@ufl_type(num_ops="varying") +@ufl_type() class Interpolate(BaseFormOperator): """Symbolic representation of the interpolation operator.""" diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 0439bd795..5aff57f63 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -71,24 +71,6 @@ def set_trait(cls, basename, value, inherit=False): setattr(cls, name, value) -def determine_num_ops(cls, num_ops, unop, binop, rbinop): - """Determine number of operands for this type.""" - # Try to determine num_ops from other traits or baseclass, or - # require num_ops to be set for non-abstract classes if it cannot - # be determined automatically - if num_ops is not None: - return num_ops - elif cls._ufl_is_terminal_: - return 0 - elif unop: - return 1 - elif binop or rbinop: - return 2 - else: - # Determine from base class - return get_base_attr(cls, "_ufl_num_ops_") - - def check_is_terminal_consistency(cls): """Check for consistency in ``is_terminal`` trait among superclasses.""" if cls._ufl_is_terminal_ is None: @@ -144,23 +126,6 @@ def check_type_traits_consistency(cls): # Check for consistency in global type collection sizes assert UFLRegistry().number_registered_classes == len(UFLRegistry().all_classes) - # Check that non-abstract types always specify num_ops - if not cls._ufl_is_abstract_: - if cls._ufl_num_ops_ is None: - msg = "Class {0.__name__} has not specified num_ops." - raise TypeError(msg.format(cls)) - - # Check for non-abstract types that num_ops has the right type - if not cls._ufl_is_abstract_: - if not (isinstance(cls._ufl_num_ops_, int) or cls._ufl_num_ops_ == "varying"): - msg = 'Class {0.__name__} has invalid num_ops value {1} (integer or "varying").' - raise TypeError(msg.format(cls, cls._ufl_num_ops_)) - - # Check that num_ops is not set to nonzero for a terminal - if cls._ufl_is_terminal_ and cls._ufl_num_ops_ != 0: - msg = "Class {0.__name__} has num_ops > 0 but is terminal." - raise TypeError(msg.format(cls)) - # Check that a non-scalar type doesn't have a scalar base class. if not cls._ufl_is_scalar_: if get_base_attr(cls, "_ufl_is_scalar_"): @@ -262,7 +227,6 @@ def ufl_type( is_scalar=False, is_index_free=False, use_default_hash=True, - num_ops=None, inherit_shape_from_operand=None, inherit_indices_from_operand=None, wraps_type=None, @@ -307,10 +271,6 @@ def _ufl_type_decorator_(cls): set_trait(cls, "is_scalar", is_scalar, inherit=True) set_trait(cls, "is_index_free", _is_index_free, inherit=True) - # Number of operands can often be determined automatically - _num_ops = determine_num_ops(cls, num_ops, unop, binop, rbinop) - set_trait(cls, "num_ops", _num_ops) - # Attach builtin type wrappers to Expr """# These are currently handled in the as_ufl implementation in constantvalue.py if wraps_type is not None: @@ -463,10 +423,6 @@ class UFLType(ABC): _ufl_is_scalar_: bool = False _ufl_is_index_free_: bool = False - # Number of operands, "varying" for some types, or None if not - # applicable for abstract types. - _ufl_num_ops_: typing.Optional[int] = None - ufl_operands: tuple[FormArgument, ...] ufl_shape: tuple[int, ...] ufl_free_indices: tuple[int, ...] diff --git a/ufl/differentiation.py b/ufl/differentiation.py index c5921cd2c..05b331ba0 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -36,7 +36,7 @@ def __init__(self, operands): Operator.__init__(self, operands) -@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class CoefficientDerivative(Derivative): """Derivative of form integrand w.r.t. the degrees of freedom in a discrete Coefficient.""" @@ -68,7 +68,7 @@ def __str__(self): ) -@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class CoordinateDerivative(CoefficientDerivative): """Derivative of the integrand of a form w.r.t. the SpatialCoordinates.""" @@ -82,7 +82,7 @@ def __str__(self): ) -@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class BaseFormDerivative(CoefficientDerivative, BaseForm): """Derivative of a base form w.r.t the degrees of freedom in a discrete Coefficient.""" @@ -130,7 +130,7 @@ def arg_type(x): self._coefficients = base_form_coeffs -@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class BaseFormCoordinateDerivative(BaseFormDerivative, CoordinateDerivative): """Derivative of a base form w.r.t. the SpatialCoordinates.""" @@ -143,7 +143,7 @@ def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): ) -@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class BaseFormOperatorDerivative(BaseFormDerivative, BaseFormOperator): """Derivative of a base form operator w.r.t the degrees of freedom in a discrete Coefficient.""" @@ -189,7 +189,7 @@ def argument_slots(self, outer_form=False): return argument_slots -@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class BaseFormOperatorCoordinateDerivative(BaseFormOperatorDerivative, CoordinateDerivative): """Derivative of a base form operator w.r.t. the SpatialCoordinates.""" @@ -202,7 +202,7 @@ def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): ) -@ufl_type(num_ops=2) +@ufl_type() class VariableDerivative(Derivative): """Variable Derivative.""" @@ -258,7 +258,7 @@ def __init__(self, operands): Derivative.__init__(self, operands) -@ufl_type(num_ops=1, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class Grad(CompoundDerivative): """Grad.""" @@ -307,7 +307,7 @@ def __str__(self): return f"grad({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class ReferenceGrad(CompoundDerivative): """Reference grad.""" @@ -359,7 +359,7 @@ def __str__(self): return f"reference_grad({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class Div(CompoundDerivative): """Div.""" @@ -391,7 +391,7 @@ def __str__(self): return f"div({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class ReferenceDiv(CompoundDerivative): """Reference divergence.""" @@ -424,7 +424,7 @@ def __str__(self): return f"reference_div({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class NablaGrad(CompoundDerivative): """Nabla grad.""" @@ -463,7 +463,7 @@ def __str__(self): return f"nabla_grad({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class NablaDiv(CompoundDerivative): """Nabla div.""" @@ -497,7 +497,7 @@ def __str__(self): _curl_shapes = {(): (2,), (2,): (), (3,): (3,)} -@ufl_type(num_ops=1, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class Curl(CompoundDerivative): """Compound derivative.""" @@ -529,7 +529,7 @@ def __str__(self): return f"curl({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class ReferenceCurl(CompoundDerivative): """Reference curl.""" diff --git a/ufl/exprcontainers.py b/ufl/exprcontainers.py index 5ef003894..a2db753c3 100644 --- a/ufl/exprcontainers.py +++ b/ufl/exprcontainers.py @@ -14,7 +14,7 @@ # --- Non-tensor types --- -@ufl_type(num_ops="varying") +@ufl_type() class ExprList(Operator): """List of Expr objects. For internal use, never to be created by end users.""" @@ -72,7 +72,7 @@ def index_dimensions(self): raise ValueError("A non-tensor type has no index_dimensions.") -@ufl_type(num_ops="varying") +@ufl_type() class ExprMapping(Operator): """Mapping of Expr objects. For internal use, never to be created by end users.""" diff --git a/ufl/indexed.py b/ufl/indexed.py index 48d24ce26..a1ebedd15 100644 --- a/ufl/indexed.py +++ b/ufl/indexed.py @@ -15,7 +15,7 @@ from ufl.precedence import parstr -@ufl_type(num_ops=2) +@ufl_type() class Indexed(Operator): """Indexed expression.""" diff --git a/ufl/indexsum.py b/ufl/indexsum.py index a0b9e0a1e..d486fd283 100644 --- a/ufl/indexsum.py +++ b/ufl/indexsum.py @@ -18,7 +18,7 @@ # --- Sum over an index --- -@ufl_type(num_ops=2) +@ufl_type() class IndexSum(Operator): """Index sum.""" diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index 2519c8002..1edaf87f9 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -51,7 +51,7 @@ # --- Function representations --- -@ufl_type(is_abstract=True, is_scalar=True, num_ops=1) +@ufl_type(is_abstract=True, is_scalar=True) class MathFunction(Operator): """Base class for all unary scalar math functions.""" @@ -322,7 +322,7 @@ def __init__(self, argument): MathFunction.__init__(self, "atan", argument) -@ufl_type(is_scalar=True, num_ops=2) +@ufl_type(is_scalar=True) class Atan2(Operator): """Inverse tangent with two inputs.""" @@ -388,7 +388,7 @@ def evaluate(self, x, mapping, component, index_values): return math.erf(a) -@ufl_type(is_abstract=True, is_scalar=True, num_ops=2) +@ufl_type(is_abstract=True, is_scalar=True) class BesselFunction(Operator): """Base class for all bessel functions.""" diff --git a/ufl/referencevalue.py b/ufl/referencevalue.py index edeab8673..5b2a4ad10 100644 --- a/ufl/referencevalue.py +++ b/ufl/referencevalue.py @@ -10,7 +10,7 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(num_ops=1, is_index_free=True) +@ufl_type(is_index_free=True) class ReferenceValue(Operator): """Representation of the reference cell value of a form argument.""" diff --git a/ufl/restriction.py b/ufl/restriction.py index 2be38acc1..ee1947b35 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -15,7 +15,6 @@ @ufl_type( is_abstract=True, - num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0, ) diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index cfa1dba5a..cac1f76e7 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -86,7 +86,7 @@ def __init__(self, operands): # pass -@ufl_type(num_ops=1, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class Transposed(CompoundTensorOperator): """Transposed tensor.""" @@ -117,7 +117,7 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)}^T" -@ufl_type(num_ops=2) +@ufl_type() class Outer(CompoundTensorOperator): """Outer.""" @@ -150,7 +150,7 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)} (X) {parstr(self.ufl_operands[1], self)}" -@ufl_type(num_ops=2) +@ufl_type() class Inner(CompoundTensorOperator): """Inner.""" @@ -192,7 +192,7 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)} : {parstr(self.ufl_operands[1], self)}" -@ufl_type(num_ops=2) +@ufl_type() class Dot(CompoundTensorOperator): """Dot.""" @@ -241,7 +241,7 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)} . {parstr(self.ufl_operands[1], self)}" -@ufl_type(is_index_free=True, num_ops=1) +@ufl_type(is_index_free=True) class Perp(CompoundTensorOperator): """Perp.""" @@ -274,7 +274,7 @@ def __str__(self): return f"perp({self.ufl_operands[0]})" -@ufl_type(num_ops=2) +@ufl_type() class Cross(CompoundTensorOperator): """Cross.""" @@ -313,7 +313,7 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)} x {parstr(self.ufl_operands[1], self)}" -@ufl_type(num_ops=1, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class Trace(CompoundTensorOperator): """Trace.""" @@ -342,7 +342,7 @@ def __str__(self): return f"tr({self.ufl_operands[0]})" -@ufl_type(is_scalar=True, num_ops=1) +@ufl_type(is_scalar=True) class Determinant(CompoundTensorOperator): """Determinant.""" @@ -381,7 +381,7 @@ def __str__(self): # TODO: Drop Inverse and represent it as product of Determinant and # Cofactor? -@ufl_type(is_index_free=True, num_ops=1) +@ufl_type(is_index_free=True) class Inverse(CompoundTensorOperator): """Inverse.""" @@ -424,7 +424,7 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)}^-1" -@ufl_type(is_index_free=True, num_ops=1) +@ufl_type(is_index_free=True) class Cofactor(CompoundTensorOperator): """Cofactor.""" @@ -455,7 +455,7 @@ def __str__(self): return f"cofactor({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Deviatoric(CompoundTensorOperator): """Deviatoric.""" @@ -490,7 +490,7 @@ def __str__(self): return f"dev({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Skew(CompoundTensorOperator): """Skew.""" @@ -524,7 +524,7 @@ def __str__(self): return f"skew({self.ufl_operands[0]})" -@ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Sym(CompoundTensorOperator): """Sym.""" diff --git a/ufl/tensors.py b/ufl/tensors.py index c409d3408..63c187c53 100644 --- a/ufl/tensors.py +++ b/ufl/tensors.py @@ -18,7 +18,7 @@ # --- Classes representing tensors of UFL expressions --- -@ufl_type(num_ops="varying", inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class ListTensor(Operator): """Wraps a list of expressions into a tensor valued expression of one higher rank.""" @@ -179,7 +179,7 @@ def substring(expressions, indent): return substring(self.ufl_operands, 0) -@ufl_type(num_ops="varying") +@ufl_type() class ComponentTensor(Operator): """Maps the free indices of a scalar valued expression to tensor axes.""" diff --git a/ufl/variable.py b/ufl/variable.py index 9abdc95a3..d841d31c3 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -65,7 +65,7 @@ def _ufl_signature_data_(self, renumbering): return ("Label", renumbering[self]) -@ufl_type(is_index_free=True, num_ops=1, inherit_shape_from_operand=0) +@ufl_type(is_index_free=True, inherit_shape_from_operand=0) class Variable(Operator): """A Variable is a representative for another expression. From cf23172e085e08882e12e78c3f930c8208d1c4ae Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:45:57 +0200 Subject: [PATCH 41/78] Set is_terminal through class hierachy --- ufl/core/operator.py | 2 +- ufl/core/terminal.py | 3 ++- ufl/core/ufl_type.py | 5 ----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/ufl/core/operator.py b/ufl/core/operator.py index a6da722ca..91cc084d0 100644 --- a/ufl/core/operator.py +++ b/ufl/core/operator.py @@ -12,7 +12,7 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(is_abstract=True, is_terminal=False) +@ufl_type(is_abstract=True) class Operator(Expr): """Base class for all operators, i.e. non-terminal expression types.""" diff --git a/ufl/core/terminal.py b/ufl/core/terminal.py index 47c6c16d9..8b0c75b9a 100644 --- a/ufl/core/terminal.py +++ b/ufl/core/terminal.py @@ -17,13 +17,14 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(is_abstract=True, is_terminal=True) +@ufl_type(is_abstract=True) class Terminal(Expr): """Base class for terminal objects. A terminal node in the UFL expression tree. """ + _ufl_is_terminal_ = True __slots__ = () def __init__(self): diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 5aff57f63..621359895 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -223,7 +223,6 @@ def update_ufl_type_attributes(cls): def ufl_type( is_abstract=False, - is_terminal=False, is_scalar=False, is_index_free=False, use_default_hash=True, @@ -263,10 +262,6 @@ def _ufl_type_decorator_(cls): set_trait(cls, "is_abstract", is_abstract, inherit=False) # because we have no real inheritance yet - # cls._ufl_is_terminal_ = get_base_attr(cls, "_ufl_is_terminal_") - cls._ufl_is_terminal_ = ( - get_base_attr(cls, "_ufl_is_terminal_") if not is_terminal else is_terminal - ) set_trait(cls, "is_scalar", is_scalar, inherit=True) set_trait(cls, "is_index_free", _is_index_free, inherit=True) From ddda292cfc29cf529c2ee8c11b446294f261417d Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 21:12:15 +0200 Subject: [PATCH 42/78] Remove is_abstract --- ufl/argument.py | 4 ++-- ufl/classes.py | 4 ---- ufl/coefficient.py | 1 - ufl/conditional.py | 4 ++-- ufl/constantvalue.py | 6 +++--- ufl/core/operator.py | 2 +- ufl/core/terminal.py | 4 ++-- ufl/core/ufl_type.py | 47 ++++++++++++++++++------------------------ ufl/differentiation.py | 4 ++-- ufl/geometry.py | 8 +++---- ufl/mathfunctions.py | 4 ++-- ufl/precedence.py | 17 +++++++-------- ufl/restriction.py | 1 - ufl/tensoralgebra.py | 2 +- 14 files changed, 47 insertions(+), 61 deletions(-) diff --git a/ufl/argument.py b/ufl/argument.py index 043f23dc8..2021ca999 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -15,6 +15,7 @@ # Modified by Ignacia Fierro-Piccardo 2023. import numbers +from abc import ABC from ufl.core.terminal import FormArgument from ufl.core.ufl_type import ufl_type @@ -30,11 +31,10 @@ # --- Class representing an argument (basis function) in a form --- -class BaseArgument: +class BaseArgument(ABC): """UFL value: Representation of an argument to a form.""" __slots__ = () - _ufl_is_abstract_ = True def __getnewargs__(self): """Get new args.""" diff --git a/ufl/classes.py b/ufl/classes.py index fb331c9d6..8f1e76a6e 100644 --- a/ufl/classes.py +++ b/ufl/classes.py @@ -426,16 +426,12 @@ "ZeroBaseForm", "ZeroBaseForm", "__exproperators", - "abstract_classes", "all_ufl_classes", "nonterminal_classes", "terminal_classes", - "ufl_classes", ] # Collect all classes in sets automatically classified by some properties all_ufl_classes = set(UFLRegistry().all_classes) -abstract_classes = set(c for c in all_ufl_classes if c._ufl_is_abstract_) # type: ignore -ufl_classes = set(c for c in all_ufl_classes if not c._ufl_is_abstract_) # type: ignore terminal_classes = set(c for c in all_ufl_classes if c._ufl_is_terminal_) # type: ignore nonterminal_classes = set(c for c in all_ufl_classes if not c._ufl_is_terminal_) # type: ignore diff --git a/ufl/coefficient.py b/ufl/coefficient.py index c6aa8950b..13c3cac4e 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -31,7 +31,6 @@ class BaseCoefficient(UFLType, Counted): # __slots__ = ("_count", "_ufl_function_space", "_repr", "_ufl_shape") _ufl_noslots_ = True __slots__ = () - _ufl_is_abstract_ = True def __getnewargs__(self): """Get new args.""" diff --git a/ufl/conditional.py b/ufl/conditional.py index e34f36412..bd10ffa7e 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -21,7 +21,7 @@ # is a boolean type not a float type -@ufl_type(is_abstract=True, is_scalar=True) +@ufl_type(is_scalar=True) class Condition(Operator): """Condition.""" @@ -39,7 +39,7 @@ def __bool__(self): __nonzero__ = __bool__ -@ufl_type(is_abstract=True) +@ufl_type() class BinaryCondition(Condition): """Binary condition.""" diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index 2d0d89513..70e6fc997 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -36,7 +36,7 @@ def format_float(x): # --- Base classes for constant types --- -@ufl_type(is_abstract=True) +@ufl_type() class ConstantValue(Terminal): """Constant value.""" @@ -200,7 +200,7 @@ def zero(*shape): # --- Scalar value types --- -@ufl_type(is_abstract=True, is_scalar=True) +@ufl_type(is_scalar=True) class ScalarValue(ConstantValue): """A constant scalar value.""" @@ -320,7 +320,7 @@ def __int__(self): raise TypeError("ComplexValues cannot be cast to int") -@ufl_type(is_abstract=True, is_scalar=True) +@ufl_type(is_scalar=True) class RealValue(ScalarValue): """Abstract class used to differentiate real values from complex ones.""" diff --git a/ufl/core/operator.py b/ufl/core/operator.py index 91cc084d0..9a8c9696d 100644 --- a/ufl/core/operator.py +++ b/ufl/core/operator.py @@ -12,7 +12,7 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(is_abstract=True) +@ufl_type() class Operator(Expr): """Base class for all operators, i.e. non-terminal expression types.""" diff --git a/ufl/core/terminal.py b/ufl/core/terminal.py index 8b0c75b9a..ca1d81d19 100644 --- a/ufl/core/terminal.py +++ b/ufl/core/terminal.py @@ -17,7 +17,7 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(is_abstract=True) +@ufl_type() class Terminal(Expr): """Base class for terminal objects. @@ -97,7 +97,7 @@ def __eq__(self, other): # --- Subgroups of terminals --- -@ufl_type(is_abstract=True) +@ufl_type() class FormArgument(Terminal): """An abstract class for a form argument (a thing in a primal finite element space).""" diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 621359895..086388277 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -24,6 +24,8 @@ class UFLObject(ABC): """A UFL Object.""" + _ufl_is_terminal_: bool + @abstractmethod def _ufl_hash_data_(self) -> typing.Hashable: """Return hashable data that uniquely defines this object.""" @@ -94,7 +96,7 @@ def check_abstract_trait_consistency(cls): for base in cls.mro(): if base is core.expr.Expr: break - if not issubclass(base, core.expr.Expr) and base._ufl_is_abstract_: + if not issubclass(base, core.expr.Expr) and (ABC in base.__base__): msg = ( "Base class {0.__name__} of class {1.__name__} " "is not an abstract subclass of {2.__name__}." @@ -135,26 +137,27 @@ def check_type_traits_consistency(cls): def check_implements_required_methods(cls): """Check if type implements the required methods.""" - if not cls._ufl_is_abstract_: - for attr in core.expr.Expr._ufl_required_methods_: - if not hasattr(cls, attr): - msg = "Class {0.__name__} has no {1} method." - raise TypeError(msg.format(cls, attr)) - elif not callable(getattr(cls, attr)): - msg = "Required method {1} of class {0.__name__} is not callable." - raise TypeError(msg.format(cls, attr)) + # if not cls._ufl_is_abstract_: + # if ABC not in cls.__base__: + # for attr in core.expr.Expr._ufl_required_methods_: + # if not hasattr(cls, attr): + # msg = "Class {0.__name__} has no {1} method." + # raise TypeError(msg.format(cls, attr)) + # elif not callable(getattr(cls, attr)): + # msg = "Required method {1} of class {0.__name__} is not callable." + # raise TypeError(msg.format(cls, attr)) def check_implements_required_properties(cls): """Check if type implements the required properties.""" - if not cls._ufl_is_abstract_: - for attr in core.expr.Expr._ufl_required_properties_: - if not hasattr(cls, attr): - msg = "Class {0.__name__} has no {1} property." - raise TypeError(msg.format(cls, attr)) - elif callable(getattr(cls, attr)): - msg = "Required property {1} of class {0.__name__} is a callable method." - raise TypeError(msg.format(cls, attr)) + # if not cls._ufl_is_abstract_: + # for attr in core.expr.Expr._ufl_required_properties_: + # if not hasattr(cls, attr): + # msg = "Class {0.__name__} has no {1} property." + # raise TypeError(msg.format(cls, attr)) + # elif callable(getattr(cls, attr)): + # msg = "Required property {1} of class {0.__name__} is a callable method." + # raise TypeError(msg.format(cls, attr)) def attach_implementations_of_indexing_interface( @@ -201,14 +204,6 @@ def update_global_expr_attributes(cls): if cls._ufl_is_terminal_modifier_: core.expr.Expr._ufl_terminal_modifiers_.append(cls) - # Add to collection of language operators. This collection is - # used later to populate the official language namespace. - # TODO: I don't think this functionality is fully completed, check - # it out later. - if not cls._ufl_is_abstract_ and hasattr(cls, "_ufl_function_"): - cls._ufl_function_.__func__.__doc__ = cls.__doc__ - core.expr.Expr._ufl_language_operators_[cls._ufl_handler_name_] = cls._ufl_function_ - def update_ufl_type_attributes(cls): """Update UFL type attributes.""" @@ -259,7 +254,6 @@ def _ufl_type_decorator_(cls): # Store type traits cls._ufl_class_ = cls - set_trait(cls, "is_abstract", is_abstract, inherit=False) # because we have no real inheritance yet @@ -401,7 +395,6 @@ class UFLType(ABC): __slots__: tuple[str, ...] = () _ufl_handler_name_: str = "ufl_type" - _ufl_is_abstract_: bool = True _ufl_is_terminal_: bool = False _ufl_is_literal_: bool = False diff --git a/ufl/differentiation.py b/ufl/differentiation.py index 05b331ba0..fc550578c 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -24,7 +24,7 @@ # --- Basic differentiation objects --- -@ufl_type(is_abstract=True) +@ufl_type() class Derivative(Operator): """Base class for all derivative types.""" @@ -247,7 +247,7 @@ def __str__(self): # --- Compound differentiation objects --- -@ufl_type(is_abstract=True) +@ufl_type() class CompoundDerivative(Derivative): """Base class for all compound derivative types.""" diff --git a/ufl/geometry.py b/ufl/geometry.py index 06b9ce228..5bdf7c0fb 100644 --- a/ufl/geometry.py +++ b/ufl/geometry.py @@ -107,7 +107,7 @@ # --- Expression node types -@ufl_type(is_abstract=True) +@ufl_type() class GeometricQuantity(Terminal): """Geometric quantity.""" @@ -157,21 +157,21 @@ def __eq__(self, other): return isinstance(other, self._ufl_class_) and other._domain == self._domain -@ufl_type(is_abstract=True) +@ufl_type() class GeometricCellQuantity(GeometricQuantity): """Geometric cell quantity.""" __slots__ = () -@ufl_type(is_abstract=True) +@ufl_type() class GeometricFacetQuantity(GeometricQuantity): """Geometric facet quantity.""" __slots__ = () -@ufl_type(is_abstract=True) +@ufl_type() class GeometricRidgeQuantity(GeometricQuantity): """Geometric ridge quantity.""" diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index 1edaf87f9..bd43eb432 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -51,7 +51,7 @@ # --- Function representations --- -@ufl_type(is_abstract=True, is_scalar=True) +@ufl_type(is_scalar=True) class MathFunction(Operator): """Base class for all unary scalar math functions.""" @@ -388,7 +388,7 @@ def evaluate(self, x, mapping, component, index_values): return math.erf(a) -@ufl_type(is_abstract=True, is_scalar=True) +@ufl_type(is_scalar=True) class BesselFunction(Operator): """Base class for all bessel functions.""" diff --git a/ufl/precedence.py b/ufl/precedence.py index 3b404dbda..e9ad307f3 100644 --- a/ufl/precedence.py +++ b/ufl/precedence.py @@ -6,7 +6,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -import warnings # FIXME: This code is crap... @@ -90,7 +89,7 @@ def build_precedence_mapping(precedence_list): Utility function used by some external code. """ - from ufl.classes import Expr, abstract_classes, all_ufl_classes + from ufl.classes import Expr, all_ufl_classes pm = {} missing = set() @@ -102,9 +101,9 @@ def build_precedence_mapping(precedence_list): k += 1 # Check for missing classes, fill in subclasses for c in all_ufl_classes: - if c not in abstract_classes and c not in pm: + if c not in pm: b = c.__bases__[0] - while b is not Expr: + while b is not Expr and len(b.__bases__) > 0: if b in pm: pm[c] = pm[b] break @@ -119,8 +118,8 @@ def assign_precedences(precedence_list): pm, missing = build_precedence_mapping(precedence_list) for c, p in sorted(pm.items(), key=lambda x: x[0].__name__): c._precedence = p - if missing: - warnings.warn( - "Missing precedence levels for classes:\n" - + "\n".join(f" {c}" for c in sorted(missing)) - ) + # TODO: problem if this warns? + # if missing: + # warnings.warn( + # "Missing precedence levels for classes:\n" + "\n".join(f" {c}" for c in missing) + # ) diff --git a/ufl/restriction.py b/ufl/restriction.py index ee1947b35..c3b9ce318 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -14,7 +14,6 @@ @ufl_type( - is_abstract=True, inherit_shape_from_operand=0, inherit_indices_from_operand=0, ) diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index cac1f76e7..d4e4abd2e 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -42,7 +42,7 @@ # --- Classes representing compound tensor algebra operations --- -@ufl_type(is_abstract=True) +@ufl_type() class CompoundTensorOperator(Operator): """Compount tensor operator.""" From ae6a9209104290ded9ced5ff502e51f1b35ff1e7 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 21:22:49 +0200 Subject: [PATCH 43/78] Set is_scalar through class hierachy --- ufl/conditional.py | 12 +++++++++--- ufl/constantvalue.py | 8 ++++++-- ufl/core/ufl_type.py | 7 +++---- ufl/mathfunctions.py | 12 +++++++++--- ufl/tensoralgebra.py | 4 +++- 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/ufl/conditional.py b/ufl/conditional.py index bd10ffa7e..773064fed 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -21,10 +21,12 @@ # is a boolean type not a float type -@ufl_type(is_scalar=True) +@ufl_type() class Condition(Operator): """Condition.""" + _ufl_is_scalar_ = True + _ufl_is_index_free_ = True __slots__ = () def __init__(self, operands): @@ -320,10 +322,12 @@ def __str__(self): # --- Specific functions higher level than a conditional --- -@ufl_type(is_scalar=True) +@ufl_type() class MinValue(Operator): """Take the minimum of two values.""" + _ufl_is_scalar_ = True + _ufl_is_index_free_ = True __slots__ = () def __init__(self, left, right): @@ -352,10 +356,12 @@ def __str__(self): return "min_value({}, {})".format(*self.ufl_operands) -@ufl_type(is_scalar=True) +@ufl_type() class MaxValue(Operator): """Take the maximum of two values.""" + _ufl_is_scalar_ = True + _ufl_is_index_free_ = True __slots__ = () def __init__(self, left, right): diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index 70e6fc997..bf1059220 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -200,10 +200,12 @@ def zero(*shape): # --- Scalar value types --- -@ufl_type(is_scalar=True) +@ufl_type() class ScalarValue(ConstantValue): """A constant scalar value.""" + _ufl_is_scalar_ = True + _ufl_is_index_free_ = True __slots__ = ("_value",) def __init__(self, value): @@ -320,10 +322,12 @@ def __int__(self): raise TypeError("ComplexValues cannot be cast to int") -@ufl_type(is_scalar=True) +@ufl_type() class RealValue(ScalarValue): """Abstract class used to differentiate real values from complex ones.""" + _ufl_is_scalar_ = True + _ufl_is_index_free_ = True __slots__ = () diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 086388277..6fef9708c 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -217,8 +217,6 @@ def update_ufl_type_attributes(cls): def ufl_type( - is_abstract=False, - is_scalar=False, is_index_free=False, use_default_hash=True, inherit_shape_from_operand=None, @@ -247,7 +245,7 @@ def _ufl_type_decorator_(cls): return cls # is_scalar implies is_index_freeg - if is_scalar: + if cls._ufl_is_scalar_: _is_index_free = True else: _is_index_free = is_index_free @@ -257,7 +255,6 @@ def _ufl_type_decorator_(cls): # because we have no real inheritance yet - set_trait(cls, "is_scalar", is_scalar, inherit=True) set_trait(cls, "is_index_free", _is_index_free, inherit=True) # Attach builtin type wrappers to Expr @@ -408,6 +405,8 @@ class UFLType(ABC): _ufl_is_restriction_: bool = False _ufl_is_evaluation_: bool = False _ufl_is_differential_: bool = False + + # Note: is_scalar implies is_index_free _ufl_is_scalar_: bool = False _ufl_is_index_free_: bool = False diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index bd43eb432..3752349af 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -51,10 +51,12 @@ # --- Function representations --- -@ufl_type(is_scalar=True) +@ufl_type() class MathFunction(Operator): """Base class for all unary scalar math functions.""" + _ufl_is_scalar_ = True + _ufl_is_index_free_ = True # Freeze member variables for objects in this class __slots__ = ("_name",) @@ -322,10 +324,12 @@ def __init__(self, argument): MathFunction.__init__(self, "atan", argument) -@ufl_type(is_scalar=True) +@ufl_type() class Atan2(Operator): """Inverse tangent with two inputs.""" + _ufl_is_scalar_ = True + _ufl_is_index_free_ = True __slots__ = () def __new__(cls, arg1, arg2): @@ -388,10 +392,12 @@ def evaluate(self, x, mapping, component, index_values): return math.erf(a) -@ufl_type(is_scalar=True) +@ufl_type() class BesselFunction(Operator): """Base class for all bessel functions.""" + _ufl_is_scalar_ = True + _ufl_is_index_free_ = True __slots__ = "_name" def __init__(self, name, nu, argument): diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index d4e4abd2e..0f31c74ca 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -342,10 +342,12 @@ def __str__(self): return f"tr({self.ufl_operands[0]})" -@ufl_type(is_scalar=True) +@ufl_type() class Determinant(CompoundTensorOperator): """Determinant.""" + _ufl_is_scalar_ = True + _ufl_is_index_free_ = True __slots__ = () def __new__(cls, A): From b2b363fa361991e0124a269868a29e86e4471ec3 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 21:25:23 +0200 Subject: [PATCH 44/78] Set is_index_free through class hierachy --- ufl/core/ufl_type.py | 11 ----------- ufl/referencevalue.py | 3 ++- ufl/tensoralgebra.py | 9 ++++++--- ufl/variable.py | 3 ++- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 6fef9708c..2c819f08c 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -217,7 +217,6 @@ def update_ufl_type_attributes(cls): def ufl_type( - is_index_free=False, use_default_hash=True, inherit_shape_from_operand=None, inherit_indices_from_operand=None, @@ -244,19 +243,9 @@ def _ufl_type_decorator_(cls): # Don't need anything else for non Expr subclasses return cls - # is_scalar implies is_index_freeg - if cls._ufl_is_scalar_: - _is_index_free = True - else: - _is_index_free = is_index_free - # Store type traits cls._ufl_class_ = cls - # because we have no real inheritance yet - - set_trait(cls, "is_index_free", _is_index_free, inherit=True) - # Attach builtin type wrappers to Expr """# These are currently handled in the as_ufl implementation in constantvalue.py if wraps_type is not None: diff --git a/ufl/referencevalue.py b/ufl/referencevalue.py index 5b2a4ad10..11a3e518c 100644 --- a/ufl/referencevalue.py +++ b/ufl/referencevalue.py @@ -10,10 +10,11 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(is_index_free=True) +@ufl_type() class ReferenceValue(Operator): """Representation of the reference cell value of a form argument.""" + _ufl_is_index_free_ = True _ufl_is_terminal_modifier_ = True _ufl_is_in_reference_frame_ = True __slots__ = () diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index 0f31c74ca..6ae15abf9 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -241,10 +241,11 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)} . {parstr(self.ufl_operands[1], self)}" -@ufl_type(is_index_free=True) +@ufl_type() class Perp(CompoundTensorOperator): """Perp.""" + _ufl_is_index_free_ = True __slots__ = () def __new__(cls, A): @@ -383,10 +384,11 @@ def __str__(self): # TODO: Drop Inverse and represent it as product of Determinant and # Cofactor? -@ufl_type(is_index_free=True) +@ufl_type() class Inverse(CompoundTensorOperator): """Inverse.""" + _ufl_is_index_free_ = True __slots__ = () def __new__(cls, A): @@ -426,10 +428,11 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)}^-1" -@ufl_type(is_index_free=True) +@ufl_type() class Cofactor(CompoundTensorOperator): """Cofactor.""" + _ufl_is_index_free_ = True __slots__ = () def __init__(self, A): diff --git a/ufl/variable.py b/ufl/variable.py index d841d31c3..b6a724007 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -65,7 +65,7 @@ def _ufl_signature_data_(self, renumbering): return ("Label", renumbering[self]) -@ufl_type(is_index_free=True, inherit_shape_from_operand=0) +@ufl_type(inherit_shape_from_operand=0) class Variable(Operator): """A Variable is a representative for another expression. @@ -79,6 +79,7 @@ class Variable(Operator): df = diff(f, e) """ + _ufl_is_index_free_ = True _ufl_is_shaping_ = True __slots__ = () From 0bc8c1389a439a9c7deb0cf3728b42847baeba30 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 2 Jul 2025 21:42:26 +0200 Subject: [PATCH 45/78] Remove _ufl_terminal_modifiers_ --- ufl/algorithms/apply_coefficient_split.py | 9 +++---- ufl/core/expr.py | 3 +-- ufl/core/ufl_type.py | 29 ----------------------- 3 files changed, 6 insertions(+), 35 deletions(-) diff --git a/ufl/algorithms/apply_coefficient_split.py b/ufl/algorithms/apply_coefficient_split.py index 4d428c297..cf9338cce 100644 --- a/ufl/algorithms/apply_coefficient_split.py +++ b/ufl/algorithms/apply_coefficient_split.py @@ -102,8 +102,9 @@ def _( if reference_value: raise RuntimeError(f"Can not apply ReferenceValue on a ReferenceValue: got {o}") (op,) = o.ufl_operands - if not op._ufl_terminal_modifiers_: - raise ValueError(f"Must be a terminal modifier: {op!r}.") + # TODO: correct? + # if not op._ufl_is_terminal_modifier_: + # raise ValueError(f"Must be a terminal modifier: {op!r}.") return self( op, reference_value=True, @@ -121,7 +122,7 @@ def _( ) -> Expr: """Handle ReferenceGrad.""" (op,) = o.ufl_operands - if not op._ufl_terminal_modifiers_: + if not op._ufl_is_terminal_modifier_: raise ValueError(f"Must be a terminal modifier: {op!r}.") return self( op, @@ -142,7 +143,7 @@ def _( if restricted is not None: raise RuntimeError(f"Can not apply Restricted on a Restricted: got {o}") (op,) = o.ufl_operands - if not op._ufl_terminal_modifiers_: + if not op._ufl_is_terminal_modifier_: raise ValueError(f"Must be a terminal modifier: {op!r}.") return self( op, diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 16bd3623e..1b6735a93 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -22,7 +22,7 @@ if typing.TYPE_CHECKING: from ufl.core.terminal import FormArgument -from ufl.core.ufl_type import UFLObject, UFLRegistry, UFLType, update_ufl_type_attributes +from ufl.core.ufl_type import UFLRegistry, UFLType, update_ufl_type_attributes class Expr(UFLType): @@ -189,7 +189,6 @@ def __init__(self): _ufl_language_operators_: dict[str, object] = {} # List of all terminal modifier types - _ufl_terminal_modifiers_: list[UFLObject] = [] # --- Mechanism for profiling object creation and deletion --- diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 2c819f08c..9845a6fd3 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -61,18 +61,6 @@ def get_base_attr(cls, name): return None -def set_trait(cls, basename, value, inherit=False): - """Assign a trait to class with namespacing ``_ufl_basename_`` applied. - - If trait value is ``None``, optionally inherit it from the closest - base class that has it. - """ - name = "_ufl_" + basename + "_" - if value is None and inherit: - value = get_base_attr(cls, name) - setattr(cls, name, value) - - def check_is_terminal_consistency(cls): """Check for consistency in ``is_terminal`` trait among superclasses.""" if cls._ufl_is_terminal_ is None: @@ -195,16 +183,6 @@ def _inherited_ufl_index_dimensions(self): cls.ufl_index_dimensions = property(_inherited_ufl_index_dimensions) -def update_global_expr_attributes(cls): - """Update global attributres. - - Update global ``Expr`` attributes, mainly by adding *cls* to global - collections of ufl types. - """ - if cls._ufl_is_terminal_modifier_: - core.expr.Expr._ufl_terminal_modifiers_.append(cls) - - def update_ufl_type_attributes(cls): """Update UFL type attributes.""" # Determine integer typecode by incrementally counting all types @@ -302,9 +280,6 @@ def _ufl_expr_rbinop_(self, other): cls, inherit_shape_from_operand, inherit_indices_from_operand ) - # Update Expr - update_global_expr_attributes(cls) - # Apply a range of consistency checks to detect bugs in type # implementations that Python doesn't check for us, including # some checks that a static language compiler would do for us @@ -384,13 +359,9 @@ class UFLType(ABC): _ufl_is_terminal_: bool = False _ufl_is_literal_: bool = False - # Type trait: If the type is classified as a 'terminal modifier', - # for form compiler use. _ufl_is_terminal_modifier_: bool = False _ufl_is_shaping_: bool = False _ufl_is_in_reference_frame_: bool = False - - # Is a restriction to a geometric entity. _ufl_is_restriction_: bool = False _ufl_is_evaluation_: bool = False _ufl_is_differential_: bool = False From 0fbd1e9f8eaac9eda65bc7192db7c039be97b710 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 3 Jul 2025 09:05:00 +0200 Subject: [PATCH 46/78] Add custom type test case --- test/test_custom_type.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 test/test_custom_type.py diff --git a/test/test_custom_type.py b/test/test_custom_type.py new file mode 100644 index 000000000..e7e9932e9 --- /dev/null +++ b/test/test_custom_type.py @@ -0,0 +1,38 @@ +# Copyright (C) 2025 Paul T. Kühner +# +# This file is part of UFL (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +from utils import LagrangeElement + +from ufl import Mesh, triangle +from ufl.algebra import Product +from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering +from ufl.constant import Constant + + +class LabeledConstant(Constant): + def __init__(self, domain, shape=(), count=None, label: str = "c"): + Constant.__init__(self, domain, shape, count) + self._label = label + + @property + def label(self) -> str: + return self._label + + +def test(): + domain = Mesh(LagrangeElement(triangle, 1, (2,))) + a = LabeledConstant(domain, label="a") + b = LabeledConstant(domain, label="b") + + assert a.label == "a" + assert b.label == "b" + + ab = a * b + assert isinstance(ab, Product) + assert ab.ufl_operands == (a, b) + + assert apply_algebra_lowering(ab) == ab From e7634dfb85f707e2fd4aba89072ce5b8aec9dccb Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:03:53 +0200 Subject: [PATCH 47/78] Remove (unused) wraps_type --- ufl/constantvalue.py | 6 +++--- ufl/core/ufl_type.py | 15 --------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index bf1059220..fa7a9f0ac 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -275,7 +275,7 @@ def imag(self): return self._value.imag -@ufl_type(wraps_type=complex) +@ufl_type() class ComplexValue(ScalarValue): """Representation of a constant, complex scalar.""" @@ -331,7 +331,7 @@ class RealValue(ScalarValue): __slots__ = () -@ufl_type(wraps_type=float) +@ufl_type() class FloatValue(RealValue): """Representation of a constant scalar floating point value.""" @@ -359,7 +359,7 @@ def __repr__(self): return r -@ufl_type(wraps_type=int) +@ufl_type() class IntValue(RealValue): """Representation of a constant scalar integer value.""" diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 9845a6fd3..208cf4baf 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -198,7 +198,6 @@ def ufl_type( use_default_hash=True, inherit_shape_from_operand=None, inherit_indices_from_operand=None, - wraps_type=None, unop=None, binop=None, rbinop=None, @@ -224,20 +223,6 @@ def _ufl_type_decorator_(cls): # Store type traits cls._ufl_class_ = cls - # Attach builtin type wrappers to Expr - """# These are currently handled in the as_ufl implementation in constantvalue.py - if wraps_type is not None: - if not isinstance(wraps_type, type): - msg = "Expecting a type, not a {0.__name__} for the - wraps_type argument in definition of {1.__name__}." - raise TypeError(msg.format(type(wraps_type), cls)) - - def _ufl_from_type_(value): - return cls(value) - from_type_name = "_ufl_from_{0}_".format(wraps_type.__name__) - setattr(Expr, from_type_name, staticmethod(_ufl_from_type_)) - """ - # Attach special function to Expr. # Avoids the circular dependency problem of making # Expr.__foo__ return a Foo that is a subclass of Expr. From 90c35a04c2963b57f7371ccb69d32ffd462fac3f Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:06:04 +0200 Subject: [PATCH 48/78] On PR not main --- .github/workflows/fenicsx-tests.yml | 2 +- .github/workflows/tsfc-tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 47dbf597e..4f7651b38 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -5,7 +5,7 @@ name: FEniCSx integration on: pull_request: branches: - - main + # - main # Weekly build on Mondays at 8 am schedule: diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml index d6c9fee09..013dedbc8 100644 --- a/.github/workflows/tsfc-tests.yml +++ b/.github/workflows/tsfc-tests.yml @@ -4,7 +4,7 @@ name: TSFC integration on: pull_request: branches: - - main + # - main jobs: tsfc-tests: From 664cc07d659bc225738968e2c44643980facccd2 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:12:37 +0200 Subject: [PATCH 49/78] Remove (unused) unop --- ufl/algebra.py | 2 +- ufl/core/ufl_type.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/ufl/algebra.py b/ufl/algebra.py index 1090193bb..3472cb74d 100644 --- a/ufl/algebra.py +++ b/ufl/algebra.py @@ -327,7 +327,7 @@ def __str__(self): return f"{parstr(a, self)} ** {parstr(b, self)}" -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0, unop="__abs__") +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Abs(Operator): """Absolute value.""" diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 208cf4baf..ca44bb4c1 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -198,7 +198,6 @@ def ufl_type( use_default_hash=True, inherit_shape_from_operand=None, inherit_indices_from_operand=None, - unop=None, binop=None, rbinop=None, ): @@ -227,10 +226,6 @@ def _ufl_type_decorator_(cls): # Avoids the circular dependency problem of making # Expr.__foo__ return a Foo that is a subclass of Expr. """# These are currently attached in exproperators.py - if unop: - def _ufl_expr_unop_(self): - return cls(self) - setattr(Expr, unop, _ufl_expr_unop_) if binop: def _ufl_expr_binop_(self, other): try: From 634d0310ab8685aaabe523cf6730a9e82076281b Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:15:46 +0200 Subject: [PATCH 50/78] Remove (unused) binop --- ufl/algebra.py | 7 +++---- ufl/conditional.py | 8 ++++---- ufl/core/ufl_type.py | 9 --------- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/ufl/algebra.py b/ufl/algebra.py index 3472cb74d..60e0d3eaa 100644 --- a/ufl/algebra.py +++ b/ufl/algebra.py @@ -23,7 +23,6 @@ @ufl_type( inherit_shape_from_operand=0, inherit_indices_from_operand=0, - binop="__add__", rbinop="__radd__", ) class Sum(Operator): @@ -103,7 +102,7 @@ def __str__(self): return " + ".join([parstr(o, self) for o in self.ufl_operands]) -@ufl_type(binop="__mul__", rbinop="__rmul__") +@ufl_type(rbinop="__rmul__") class Product(Operator): """The product of two or more UFL objects.""" @@ -200,7 +199,7 @@ def __str__(self): return " * ".join((parstr(a, self), parstr(b, self))) -@ufl_type(inherit_indices_from_operand=0, binop="__div__", rbinop="__rdiv__") +@ufl_type(inherit_indices_from_operand=0, rbinop="__rdiv__") class Division(Operator): """Division.""" @@ -265,7 +264,7 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)} / {parstr(self.ufl_operands[1], self)}" -@ufl_type(inherit_indices_from_operand=0, binop="__pow__", rbinop="__rpow__") +@ufl_type(inherit_indices_from_operand=0, rbinop="__rpow__") class Power(Operator): """Power.""" diff --git a/ufl/conditional.py b/ufl/conditional.py index 773064fed..f9fab7ef7 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -132,7 +132,7 @@ def __bool__(self): __nonzero__ = __bool__ -@ufl_type(binop="__le__") +@ufl_type() class LE(BinaryCondition): """Less than or equal condition.""" @@ -149,7 +149,7 @@ def evaluate(self, x, mapping, component, index_values): return bool(a <= b) -@ufl_type(binop="__ge__") +@ufl_type() class GE(BinaryCondition): """Greater than or equal to condition.""" @@ -166,7 +166,7 @@ def evaluate(self, x, mapping, component, index_values): return bool(a >= b) -@ufl_type(binop="__lt__") +@ufl_type() class LT(BinaryCondition): """Less than condition.""" @@ -183,7 +183,7 @@ def evaluate(self, x, mapping, component, index_values): return bool(a < b) -@ufl_type(binop="__gt__") +@ufl_type() class GT(BinaryCondition): """Greater than condition.""" diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index ca44bb4c1..5ce288a7f 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -198,7 +198,6 @@ def ufl_type( use_default_hash=True, inherit_shape_from_operand=None, inherit_indices_from_operand=None, - binop=None, rbinop=None, ): """Decorator to apply to every subclass in the UFL ``Expr`` and ``BaseForm`` hierarchy. @@ -226,14 +225,6 @@ def _ufl_type_decorator_(cls): # Avoids the circular dependency problem of making # Expr.__foo__ return a Foo that is a subclass of Expr. """# These are currently attached in exproperators.py - if binop: - def _ufl_expr_binop_(self, other): - try: - other = Expr._ufl_coerce_(other) - except: - return NotImplemented - return cls(self, other) - setattr(Expr, binop, _ufl_expr_binop_) if rbinop: def _ufl_expr_rbinop_(self, other): try: From 11721bc9a5976c99e32ce1e8e402e68211c1e3db Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:17:22 +0200 Subject: [PATCH 51/78] Remove (unused) rbinop --- ufl/algebra.py | 12 ++++-------- ufl/core/ufl_type.py | 15 --------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/ufl/algebra.py b/ufl/algebra.py index 60e0d3eaa..eef4f0050 100644 --- a/ufl/algebra.py +++ b/ufl/algebra.py @@ -20,11 +20,7 @@ # --- Algebraic operators --- -@ufl_type( - inherit_shape_from_operand=0, - inherit_indices_from_operand=0, - rbinop="__radd__", -) +@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Sum(Operator): """Sum.""" @@ -102,7 +98,7 @@ def __str__(self): return " + ".join([parstr(o, self) for o in self.ufl_operands]) -@ufl_type(rbinop="__rmul__") +@ufl_type() class Product(Operator): """The product of two or more UFL objects.""" @@ -199,7 +195,7 @@ def __str__(self): return " * ".join((parstr(a, self), parstr(b, self))) -@ufl_type(inherit_indices_from_operand=0, rbinop="__rdiv__") +@ufl_type(inherit_indices_from_operand=0) class Division(Operator): """Division.""" @@ -264,7 +260,7 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)} / {parstr(self.ufl_operands[1], self)}" -@ufl_type(inherit_indices_from_operand=0, rbinop="__rpow__") +@ufl_type(inherit_indices_from_operand=0) class Power(Operator): """Power.""" diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 5ce288a7f..90aeaa9ae 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -198,7 +198,6 @@ def ufl_type( use_default_hash=True, inherit_shape_from_operand=None, inherit_indices_from_operand=None, - rbinop=None, ): """Decorator to apply to every subclass in the UFL ``Expr`` and ``BaseForm`` hierarchy. @@ -221,20 +220,6 @@ def _ufl_type_decorator_(cls): # Store type traits cls._ufl_class_ = cls - # Attach special function to Expr. - # Avoids the circular dependency problem of making - # Expr.__foo__ return a Foo that is a subclass of Expr. - """# These are currently attached in exproperators.py - if rbinop: - def _ufl_expr_rbinop_(self, other): - try: - other = Expr._ufl_coerce_(other) - except: - return NotImplemented - return cls(other, self) - setattr(Expr, rbinop, _ufl_expr_rbinop_) - """ - # Make sure every non-abstract class has its own __hash__ and # __eq__. Python 3 will set __hash__ to None if cls has # __eq__, but we've implemented it in a separate function and From 293607efa5045d8232e3e892d704e02779cb3c3d Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:27:32 +0200 Subject: [PATCH 52/78] Remove inherit_shape_from_operand --- ufl/algebra.py | 35 ++++++++++++++++++++++++++++++----- ufl/averaging.py | 4 ++-- ufl/conditional.py | 7 ++++++- ufl/core/ufl_type.py | 19 ++----------------- ufl/differentiation.py | 42 ++++++++++++++++++++++++++++++++++++------ ufl/restriction.py | 6 +++++- ufl/tensoralgebra.py | 21 ++++++++++++++++++--- ufl/variable.py | 7 ++++++- 8 files changed, 105 insertions(+), 36 deletions(-) diff --git a/ufl/algebra.py b/ufl/algebra.py index eef4f0050..ca819b7db 100644 --- a/ufl/algebra.py +++ b/ufl/algebra.py @@ -20,7 +20,7 @@ # --- Algebraic operators --- -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class Sum(Operator): """Sum.""" @@ -97,6 +97,11 @@ def __str__(self): """Format as a string.""" return " + ".join([parstr(o, self) for o in self.ufl_operands]) + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape + @ufl_type() class Product(Operator): @@ -322,7 +327,7 @@ def __str__(self): return f"{parstr(a, self)} ** {parstr(b, self)}" -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class Abs(Operator): """Absolute value.""" @@ -356,8 +361,13 @@ def __str__(self): (a,) = self.ufl_operands return f"|{parstr(a, self)}|" + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) + +@ufl_type(inherit_indices_from_operand=0) class Conj(Operator): """Complex conjugate.""" @@ -391,8 +401,13 @@ def __str__(self): (a,) = self.ufl_operands return f"conj({parstr(a, self)})" + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) + +@ufl_type(inherit_indices_from_operand=0) class Real(Operator): """Real part.""" @@ -428,8 +443,13 @@ def __str__(self): (a,) = self.ufl_operands return f"Re[{parstr(a, self)}]" + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape + -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class Imag(Operator): """Imaginary part.""" @@ -462,3 +482,8 @@ def __str__(self): """Format as a string.""" (a,) = self.ufl_operands return f"Im[{parstr(a, self)}]" + + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape diff --git a/ufl/averaging.py b/ufl/averaging.py index 907b509a1..729e6e657 100644 --- a/ufl/averaging.py +++ b/ufl/averaging.py @@ -11,7 +11,7 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class CellAvg(Operator): """Cell average.""" @@ -42,7 +42,7 @@ def __str__(self): return f"cell_avg({self.ufl_operands[0]})" -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class FacetAvg(Operator): """Facet average.""" diff --git a/ufl/conditional.py b/ufl/conditional.py index f9fab7ef7..6acd7a858 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -256,7 +256,7 @@ def __str__(self): return f"!({self.ufl_operands[0]!s})" -@ufl_type(inherit_shape_from_operand=1, inherit_indices_from_operand=1) +@ufl_type(inherit_indices_from_operand=1) class Conditional(Operator): """Conditional expression. @@ -318,6 +318,11 @@ def __str__(self): """Format as a string.""" return "{} ? {} : {}".format(*tuple(parstr(o, self) for o in self.ufl_operands)) + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[1].ufl_shape + # --- Specific functions higher level than a conditional --- diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 90aeaa9ae..1251145ab 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -148,9 +148,7 @@ def check_implements_required_properties(cls): # raise TypeError(msg.format(cls, attr)) -def attach_implementations_of_indexing_interface( - cls, inherit_shape_from_operand, inherit_indices_from_operand -): +def attach_implementations_of_indexing_interface(cls, inherit_indices_from_operand): """Attach implementations of indexing interface.""" # Scalar or index-free? Then we can simplify the implementation of # tensor properties by attaching them here. @@ -161,16 +159,6 @@ def attach_implementations_of_indexing_interface( cls.ufl_free_indices = () cls.ufl_index_dimensions = () - # Automate direct inheriting of shape and indices from one of the - # operands. This simplifies refactoring because a lot of types do - # this. - if inherit_shape_from_operand is not None: - - def _inherited_ufl_shape(self): - return self.ufl_operands[inherit_shape_from_operand].ufl_shape - - cls.ufl_shape = property(_inherited_ufl_shape) - if inherit_indices_from_operand is not None: def _inherited_ufl_free_indices(self): @@ -196,7 +184,6 @@ def update_ufl_type_attributes(cls): def ufl_type( use_default_hash=True, - inherit_shape_from_operand=None, inherit_indices_from_operand=None, ): """Decorator to apply to every subclass in the UFL ``Expr`` and ``BaseForm`` hierarchy. @@ -232,9 +219,7 @@ def _ufl_type_decorator_(cls): # class! This approach significantly reduces the amount of # small functions to implement across all the types but of # course it's a bit more opaque. - attach_implementations_of_indexing_interface( - cls, inherit_shape_from_operand, inherit_indices_from_operand - ) + attach_implementations_of_indexing_interface(cls, inherit_indices_from_operand) # Apply a range of consistency checks to detect bugs in type # implementations that Python doesn't check for us, including diff --git a/ufl/differentiation.py b/ufl/differentiation.py index fc550578c..7967afba7 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -36,7 +36,7 @@ def __init__(self, operands): Operator.__init__(self, operands) -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class CoefficientDerivative(Derivative): """Derivative of form integrand w.r.t. the degrees of freedom in a discrete Coefficient.""" @@ -67,8 +67,13 @@ def __str__(self): f"{self.ufl_operands[2]}, and coefficient derivatives {self.ufl_operands[3]}" ) + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape + -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class CoordinateDerivative(CoefficientDerivative): """Derivative of the integrand of a form w.r.t. the SpatialCoordinates.""" @@ -81,8 +86,13 @@ def __str__(self): f"{self.ufl_operands[2]}, and coordinate derivatives {self.ufl_operands[3]}" ) + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape + -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class BaseFormDerivative(CoefficientDerivative, BaseForm): """Derivative of a base form w.r.t the degrees of freedom in a discrete Coefficient.""" @@ -129,8 +139,13 @@ def arg_type(x): ) self._coefficients = base_form_coeffs + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape + -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class BaseFormCoordinateDerivative(BaseFormDerivative, CoordinateDerivative): """Derivative of a base form w.r.t. the SpatialCoordinates.""" @@ -142,8 +157,13 @@ def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): self, base_form, coefficients, arguments, coefficient_derivatives ) + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape + -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class BaseFormOperatorDerivative(BaseFormDerivative, BaseFormOperator): """Derivative of a base form operator w.r.t the degrees of freedom in a discrete Coefficient.""" @@ -188,8 +208,13 @@ def argument_slots(self, outer_form=False): ) return argument_slots + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape + -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class BaseFormOperatorCoordinateDerivative(BaseFormOperatorDerivative, CoordinateDerivative): """Derivative of a base form operator w.r.t. the SpatialCoordinates.""" @@ -201,6 +226,11 @@ def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): self, base_form, coefficients, arguments, coefficient_derivatives ) + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape + @ufl_type() class VariableDerivative(Derivative): diff --git a/ufl/restriction.py b/ufl/restriction.py index c3b9ce318..9f68f26bf 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -14,7 +14,6 @@ @ufl_type( - inherit_shape_from_operand=0, inherit_indices_from_operand=0, ) class Restricted(Operator): @@ -47,6 +46,11 @@ def __str__(self): """Format as a string.""" return f"{parstr(self.ufl_operands[0], self)}({self._side})" + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape + @ufl_type() class PositiveRestricted(Restricted): diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index 6ae15abf9..d2bbf4011 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -460,7 +460,7 @@ def __str__(self): return f"cofactor({self.ufl_operands[0]})" -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class Deviatoric(CompoundTensorOperator): """Deviatoric.""" @@ -494,8 +494,13 @@ def __str__(self): """Format as a string.""" return f"dev({self.ufl_operands[0]})" + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape + -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class Skew(CompoundTensorOperator): """Skew.""" @@ -528,8 +533,13 @@ def __str__(self): """Format as a string.""" return f"skew({self.ufl_operands[0]})" + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape + -@ufl_type(inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(inherit_indices_from_operand=0) class Sym(CompoundTensorOperator): """Sym.""" @@ -563,3 +573,8 @@ def __init__(self, A): def __str__(self): """Format as a string.""" return f"sym({self.ufl_operands[0]})" + + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape diff --git a/ufl/variable.py b/ufl/variable.py index b6a724007..2ca2d81c6 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -65,7 +65,7 @@ def _ufl_signature_data_(self, renumbering): return ("Label", renumbering[self]) -@ufl_type(inherit_shape_from_operand=0) +@ufl_type() class Variable(Operator): """A Variable is a representative for another expression. @@ -128,3 +128,8 @@ def __eq__(self, other): def __str__(self): """Format as a string.""" return f"var{self.ufl_operands[1].count()}({self.ufl_operands[0]})" + + @property + def ufl_shape(self): + """Return shape.""" + return self.ufl_operands[0].ufl_shape From d93de2d65e02ee146b6f9c02545d668464d84f1d Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:36:24 +0200 Subject: [PATCH 53/78] Remove inherit_indices_from_operand --- ufl/algebra.py | 84 +++++++++++++++++++-- ufl/averaging.py | 24 +++++- ufl/conditional.py | 12 ++- ufl/core/ufl_type.py | 16 +--- ufl/differentiation.py | 168 +++++++++++++++++++++++++++++++++++++---- ufl/restriction.py | 14 +++- ufl/tensoralgebra.py | 60 +++++++++++++-- ufl/tensors.py | 12 ++- 8 files changed, 343 insertions(+), 47 deletions(-) diff --git a/ufl/algebra.py b/ufl/algebra.py index ca819b7db..59fa054f0 100644 --- a/ufl/algebra.py +++ b/ufl/algebra.py @@ -20,7 +20,7 @@ # --- Algebraic operators --- -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class Sum(Operator): """Sum.""" @@ -102,6 +102,16 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + @ufl_type() class Product(Operator): @@ -200,7 +210,7 @@ def __str__(self): return " * ".join((parstr(a, self), parstr(b, self))) -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class Division(Operator): """Division.""" @@ -264,8 +274,18 @@ def __str__(self): """Format as a string.""" return f"{parstr(self.ufl_operands[0], self)} / {parstr(self.ufl_operands[1], self)}" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class Power(Operator): """Power.""" @@ -326,8 +346,18 @@ def __str__(self): a, b = self.ufl_operands return f"{parstr(a, self)} ** {parstr(b, self)}" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions -@ufl_type(inherit_indices_from_operand=0) + +@ufl_type() class Abs(Operator): """Absolute value.""" @@ -366,8 +396,18 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices -@ufl_type(inherit_indices_from_operand=0) + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + + +@ufl_type() class Conj(Operator): """Complex conjugate.""" @@ -406,8 +446,18 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices -@ufl_type(inherit_indices_from_operand=0) + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + + +@ufl_type() class Real(Operator): """Real part.""" @@ -448,8 +498,18 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices -@ufl_type(inherit_indices_from_operand=0) + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + + +@ufl_type() class Imag(Operator): """Imaginary part.""" @@ -487,3 +547,13 @@ def __str__(self): def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions diff --git a/ufl/averaging.py b/ufl/averaging.py index 729e6e657..4eae37d49 100644 --- a/ufl/averaging.py +++ b/ufl/averaging.py @@ -11,7 +11,7 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class CellAvg(Operator): """Cell average.""" @@ -41,8 +41,18 @@ def __str__(self): """Format as a string.""" return f"cell_avg({self.ufl_operands[0]})" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class FacetAvg(Operator): """Facet average.""" @@ -71,3 +81,13 @@ def evaluate(self, x, mapping, component, index_values): def __str__(self): """Format as a string.""" return f"facet_avg({self.ufl_operands[0]})" + + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions diff --git a/ufl/conditional.py b/ufl/conditional.py index 6acd7a858..cf743b772 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -256,7 +256,7 @@ def __str__(self): return f"!({self.ufl_operands[0]!s})" -@ufl_type(inherit_indices_from_operand=1) +@ufl_type() class Conditional(Operator): """Conditional expression. @@ -323,6 +323,16 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[1].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[1].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[1].ufl_index_dimensions + # --- Specific functions higher level than a conditional --- diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 1251145ab..1b6186944 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -148,7 +148,7 @@ def check_implements_required_properties(cls): # raise TypeError(msg.format(cls, attr)) -def attach_implementations_of_indexing_interface(cls, inherit_indices_from_operand): +def attach_implementations_of_indexing_interface(cls): """Attach implementations of indexing interface.""" # Scalar or index-free? Then we can simplify the implementation of # tensor properties by attaching them here. @@ -159,17 +159,6 @@ def attach_implementations_of_indexing_interface(cls, inherit_indices_from_opera cls.ufl_free_indices = () cls.ufl_index_dimensions = () - if inherit_indices_from_operand is not None: - - def _inherited_ufl_free_indices(self): - return self.ufl_operands[inherit_indices_from_operand].ufl_free_indices - - def _inherited_ufl_index_dimensions(self): - return self.ufl_operands[inherit_indices_from_operand].ufl_index_dimensions - - cls.ufl_free_indices = property(_inherited_ufl_free_indices) - cls.ufl_index_dimensions = property(_inherited_ufl_index_dimensions) - def update_ufl_type_attributes(cls): """Update UFL type attributes.""" @@ -184,7 +173,6 @@ def update_ufl_type_attributes(cls): def ufl_type( use_default_hash=True, - inherit_indices_from_operand=None, ): """Decorator to apply to every subclass in the UFL ``Expr`` and ``BaseForm`` hierarchy. @@ -219,7 +207,7 @@ def _ufl_type_decorator_(cls): # class! This approach significantly reduces the amount of # small functions to implement across all the types but of # course it's a bit more opaque. - attach_implementations_of_indexing_interface(cls, inherit_indices_from_operand) + attach_implementations_of_indexing_interface(cls) # Apply a range of consistency checks to detect bugs in type # implementations that Python doesn't check for us, including diff --git a/ufl/differentiation.py b/ufl/differentiation.py index 7967afba7..a7344636a 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -36,7 +36,7 @@ def __init__(self, operands): Operator.__init__(self, operands) -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class CoefficientDerivative(Derivative): """Derivative of form integrand w.r.t. the degrees of freedom in a discrete Coefficient.""" @@ -72,8 +72,18 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class CoordinateDerivative(CoefficientDerivative): """Derivative of the integrand of a form w.r.t. the SpatialCoordinates.""" @@ -91,8 +101,18 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class BaseFormDerivative(CoefficientDerivative, BaseForm): """Derivative of a base form w.r.t the degrees of freedom in a discrete Coefficient.""" @@ -144,8 +164,18 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions -@ufl_type(inherit_indices_from_operand=0) + +@ufl_type() class BaseFormCoordinateDerivative(BaseFormDerivative, CoordinateDerivative): """Derivative of a base form w.r.t. the SpatialCoordinates.""" @@ -162,8 +192,18 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices -@ufl_type(inherit_indices_from_operand=0) + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + + +@ufl_type() class BaseFormOperatorDerivative(BaseFormDerivative, BaseFormOperator): """Derivative of a base form operator w.r.t the degrees of freedom in a discrete Coefficient.""" @@ -213,8 +253,18 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices -@ufl_type(inherit_indices_from_operand=0) + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + + +@ufl_type() class BaseFormOperatorCoordinateDerivative(BaseFormOperatorDerivative, CoordinateDerivative): """Derivative of a base form operator w.r.t. the SpatialCoordinates.""" @@ -231,6 +281,16 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + @ufl_type() class VariableDerivative(Derivative): @@ -288,7 +348,7 @@ def __init__(self, operands): Derivative.__init__(self, operands) -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class Grad(CompoundDerivative): """Grad.""" @@ -336,8 +396,18 @@ def __str__(self): """Format as a string.""" return f"grad({self.ufl_operands[0]})" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class ReferenceGrad(CompoundDerivative): """Reference grad.""" @@ -388,8 +458,18 @@ def __str__(self): """Format as a string.""" return f"reference_grad({self.ufl_operands[0]})" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class Div(CompoundDerivative): """Div.""" @@ -420,8 +500,18 @@ def __str__(self): """Format as a string.""" return f"div({self.ufl_operands[0]})" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class ReferenceDiv(CompoundDerivative): """Reference divergence.""" @@ -453,8 +543,18 @@ def __str__(self): """Format as a string.""" return f"reference_div({self.ufl_operands[0]})" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions -@ufl_type(inherit_indices_from_operand=0) + +@ufl_type() class NablaGrad(CompoundDerivative): """Nabla grad.""" @@ -492,8 +592,18 @@ def __str__(self): """Format as a string.""" return f"nabla_grad({self.ufl_operands[0]})" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices -@ufl_type(inherit_indices_from_operand=0) + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + + +@ufl_type() class NablaDiv(CompoundDerivative): """Nabla div.""" @@ -523,11 +633,21 @@ def __str__(self): """Format as a string.""" return f"nabla_div({self.ufl_operands[0]})" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + _curl_shapes = {(): (2,), (2,): (), (3,): (3,)} -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class Curl(CompoundDerivative): """Compound derivative.""" @@ -558,8 +678,18 @@ def __str__(self): """Format as a string.""" return f"curl({self.ufl_operands[0]})" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices -@ufl_type(inherit_indices_from_operand=0) + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + + +@ufl_type() class ReferenceCurl(CompoundDerivative): """Reference curl.""" @@ -590,3 +720,13 @@ def __init__(self, f): def __str__(self): """Format as a string.""" return f"reference_curl({self.ufl_operands[0]})" + + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions diff --git a/ufl/restriction.py b/ufl/restriction.py index 9f68f26bf..04e9f3474 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -13,9 +13,7 @@ # --- Restriction operators --- -@ufl_type( - inherit_indices_from_operand=0, -) +@ufl_type() class Restricted(Operator): """Restriction.""" @@ -51,6 +49,16 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + @ufl_type() class PositiveRestricted(Restricted): diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index d2bbf4011..af6787f87 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -86,7 +86,7 @@ def __init__(self, operands): # pass -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class Transposed(CompoundTensorOperator): """Transposed tensor.""" @@ -116,6 +116,16 @@ def __str__(self): """Format as a string.""" return f"{parstr(self.ufl_operands[0], self)}^T" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + @ufl_type() class Outer(CompoundTensorOperator): @@ -314,7 +324,7 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)} x {parstr(self.ufl_operands[1], self)}" -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class Trace(CompoundTensorOperator): """Trace.""" @@ -342,6 +352,16 @@ def __str__(self): """Format as a string.""" return f"tr({self.ufl_operands[0]})" + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + @ufl_type() class Determinant(CompoundTensorOperator): @@ -460,7 +480,7 @@ def __str__(self): return f"cofactor({self.ufl_operands[0]})" -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class Deviatoric(CompoundTensorOperator): """Deviatoric.""" @@ -499,8 +519,18 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices -@ufl_type(inherit_indices_from_operand=0) + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + + +@ufl_type() class Skew(CompoundTensorOperator): """Skew.""" @@ -538,8 +568,18 @@ def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class Sym(CompoundTensorOperator): """Sym.""" @@ -578,3 +618,13 @@ def __str__(self): def ufl_shape(self): """Return shape.""" return self.ufl_operands[0].ufl_shape + + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions diff --git a/ufl/tensors.py b/ufl/tensors.py index 63c187c53..0e5bb00e6 100644 --- a/ufl/tensors.py +++ b/ufl/tensors.py @@ -18,7 +18,7 @@ # --- Classes representing tensors of UFL expressions --- -@ufl_type(inherit_indices_from_operand=0) +@ufl_type() class ListTensor(Operator): """Wraps a list of expressions into a tensor valued expression of one higher rank.""" @@ -178,6 +178,16 @@ def substring(expressions, indent): return substring(self.ufl_operands, 0) + @property + def ufl_free_indices(self): + """Return free indices.""" + return self.ufl_operands[0].ufl_free_indices + + @property + def ufl_index_dimensions(self): + """Retrun index dimensions.""" + return self.ufl_operands[0].ufl_index_dimensions + @ufl_type() class ComponentTensor(Operator): From 523283f7d5eb463a723e39d018fc295c03433952 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:39:54 +0200 Subject: [PATCH 54/78] Remove _ufl_is_scalar_ --- ufl/conditional.py | 6 +++--- ufl/constantvalue.py | 4 ++-- ufl/core/ufl_type.py | 12 +----------- ufl/mathfunctions.py | 6 +++--- ufl/tensoralgebra.py | 2 +- 5 files changed, 10 insertions(+), 20 deletions(-) diff --git a/ufl/conditional.py b/ufl/conditional.py index cf743b772..677ada1fe 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -25,7 +25,7 @@ class Condition(Operator): """Condition.""" - _ufl_is_scalar_ = True + ufl_shape = () _ufl_is_index_free_ = True __slots__ = () @@ -341,7 +341,7 @@ def ufl_index_dimensions(self): class MinValue(Operator): """Take the minimum of two values.""" - _ufl_is_scalar_ = True + ufl_shape = () _ufl_is_index_free_ = True __slots__ = () @@ -375,7 +375,7 @@ def __str__(self): class MaxValue(Operator): """Take the maximum of two values.""" - _ufl_is_scalar_ = True + ufl_shape = () _ufl_is_index_free_ = True __slots__ = () diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index fa7a9f0ac..ec74e3963 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -204,7 +204,7 @@ def zero(*shape): class ScalarValue(ConstantValue): """A constant scalar value.""" - _ufl_is_scalar_ = True + ufl_shape = () _ufl_is_index_free_ = True __slots__ = ("_value",) @@ -326,7 +326,7 @@ def __int__(self): class RealValue(ScalarValue): """Abstract class used to differentiate real values from complex ones.""" - _ufl_is_scalar_ = True + ufl_shape = () _ufl_is_index_free_ = True __slots__ = () diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 1b6186944..66db2cae6 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -116,12 +116,6 @@ def check_type_traits_consistency(cls): # Check for consistency in global type collection sizes assert UFLRegistry().number_registered_classes == len(UFLRegistry().all_classes) - # Check that a non-scalar type doesn't have a scalar base class. - if not cls._ufl_is_scalar_: - if get_base_attr(cls, "_ufl_is_scalar_"): - msg = "Non-scalar class {0.__name__} is has a scalar base class." - raise TypeError(msg.format(cls)) - def check_implements_required_methods(cls): """Check if type implements the required methods.""" @@ -152,10 +146,8 @@ def attach_implementations_of_indexing_interface(cls): """Attach implementations of indexing interface.""" # Scalar or index-free? Then we can simplify the implementation of # tensor properties by attaching them here. - if cls._ufl_is_scalar_: - cls.ufl_shape = () - if cls._ufl_is_scalar_ or cls._ufl_is_index_free_: + if cls._ufl_is_index_free_: cls.ufl_free_indices = () cls.ufl_index_dimensions = () @@ -295,8 +287,6 @@ class UFLType(ABC): _ufl_is_evaluation_: bool = False _ufl_is_differential_: bool = False - # Note: is_scalar implies is_index_free - _ufl_is_scalar_: bool = False _ufl_is_index_free_: bool = False ufl_operands: tuple[FormArgument, ...] diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index 3752349af..1ec67d95b 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -55,7 +55,7 @@ class MathFunction(Operator): """Base class for all unary scalar math functions.""" - _ufl_is_scalar_ = True + ufl_shape = () _ufl_is_index_free_ = True # Freeze member variables for objects in this class __slots__ = ("_name",) @@ -328,7 +328,7 @@ def __init__(self, argument): class Atan2(Operator): """Inverse tangent with two inputs.""" - _ufl_is_scalar_ = True + ufl_shape = () _ufl_is_index_free_ = True __slots__ = () @@ -396,7 +396,7 @@ def evaluate(self, x, mapping, component, index_values): class BesselFunction(Operator): """Base class for all bessel functions.""" - _ufl_is_scalar_ = True + ufl_shape = () _ufl_is_index_free_ = True __slots__ = "_name" diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index af6787f87..6cb08edbe 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -367,7 +367,7 @@ def ufl_index_dimensions(self): class Determinant(CompoundTensorOperator): """Determinant.""" - _ufl_is_scalar_ = True + ufl_shape = () _ufl_is_index_free_ = True __slots__ = () From e94f6f4315f94111576687e16db93ad771237f12 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:43:35 +0200 Subject: [PATCH 55/78] Remove _ufl_is_index_free_ --- ufl/conditional.py | 9 ++++++--- ufl/constantvalue.py | 6 ++++-- ufl/core/expr.py | 3 --- ufl/core/ufl_type.py | 12 ------------ ufl/mathfunctions.py | 9 ++++++--- ufl/referencevalue.py | 3 ++- ufl/tensoralgebra.py | 12 ++++++++---- ufl/variable.py | 3 ++- 8 files changed, 28 insertions(+), 29 deletions(-) diff --git a/ufl/conditional.py b/ufl/conditional.py index 677ada1fe..ac9099ac3 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -26,7 +26,8 @@ class Condition(Operator): """Condition.""" ufl_shape = () - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () __slots__ = () def __init__(self, operands): @@ -342,7 +343,8 @@ class MinValue(Operator): """Take the minimum of two values.""" ufl_shape = () - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () __slots__ = () def __init__(self, left, right): @@ -376,7 +378,8 @@ class MaxValue(Operator): """Take the maximum of two values.""" ufl_shape = () - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () __slots__ = () def __init__(self, left, right): diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index ec74e3963..3dc11f593 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -205,7 +205,8 @@ class ScalarValue(ConstantValue): """A constant scalar value.""" ufl_shape = () - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () __slots__ = ("_value",) def __init__(self, value): @@ -327,7 +328,8 @@ class RealValue(ScalarValue): """Abstract class used to differentiate real values from complex ones.""" ufl_shape = () - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () __slots__ = () diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 1b6735a93..a10c050cd 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -136,9 +136,6 @@ def __init__(self): # introducing computation of a new value. _ufl_is_shaping_ = False - # Type trait: If the type never has free indices. - _ufl_is_index_free_ = False - # --- All subclasses must define these object attributes --- # Each subclass of Expr is checked to have these properties in diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 66db2cae6..1f900485c 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -142,16 +142,6 @@ def check_implements_required_properties(cls): # raise TypeError(msg.format(cls, attr)) -def attach_implementations_of_indexing_interface(cls): - """Attach implementations of indexing interface.""" - # Scalar or index-free? Then we can simplify the implementation of - # tensor properties by attaching them here. - - if cls._ufl_is_index_free_: - cls.ufl_free_indices = () - cls.ufl_index_dimensions = () - - def update_ufl_type_attributes(cls): """Update UFL type attributes.""" # Determine integer typecode by incrementally counting all types @@ -287,8 +277,6 @@ class UFLType(ABC): _ufl_is_evaluation_: bool = False _ufl_is_differential_: bool = False - _ufl_is_index_free_: bool = False - ufl_operands: tuple[FormArgument, ...] ufl_shape: tuple[int, ...] ufl_free_indices: tuple[int, ...] diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index 1ec67d95b..166e2ed42 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -56,7 +56,8 @@ class MathFunction(Operator): """Base class for all unary scalar math functions.""" ufl_shape = () - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () # Freeze member variables for objects in this class __slots__ = ("_name",) @@ -329,7 +330,8 @@ class Atan2(Operator): """Inverse tangent with two inputs.""" ufl_shape = () - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () __slots__ = () def __new__(cls, arg1, arg2): @@ -397,7 +399,8 @@ class BesselFunction(Operator): """Base class for all bessel functions.""" ufl_shape = () - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () __slots__ = "_name" def __init__(self, name, nu, argument): diff --git a/ufl/referencevalue.py b/ufl/referencevalue.py index 11a3e518c..65ca9c12e 100644 --- a/ufl/referencevalue.py +++ b/ufl/referencevalue.py @@ -14,7 +14,8 @@ class ReferenceValue(Operator): """Representation of the reference cell value of a form argument.""" - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () _ufl_is_terminal_modifier_ = True _ufl_is_in_reference_frame_ = True __slots__ = () diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index 6cb08edbe..d13796d18 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -255,7 +255,8 @@ def __str__(self): class Perp(CompoundTensorOperator): """Perp.""" - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () __slots__ = () def __new__(cls, A): @@ -368,7 +369,8 @@ class Determinant(CompoundTensorOperator): """Determinant.""" ufl_shape = () - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () __slots__ = () def __new__(cls, A): @@ -408,7 +410,8 @@ def __str__(self): class Inverse(CompoundTensorOperator): """Inverse.""" - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () __slots__ = () def __new__(cls, A): @@ -452,7 +455,8 @@ def __str__(self): class Cofactor(CompoundTensorOperator): """Cofactor.""" - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () __slots__ = () def __init__(self, A): diff --git a/ufl/variable.py b/ufl/variable.py index 2ca2d81c6..81317e5bc 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -79,7 +79,8 @@ class Variable(Operator): df = diff(f, e) """ - _ufl_is_index_free_ = True + ufl_free_indices = () + ufl_index_dimensions = () _ufl_is_shaping_ = True __slots__ = () From b9bf6bec4d0c8bf47ac5d5631ef5a4fe62cae7e0 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:43:50 +0200 Subject: [PATCH 56/78] one more --- ufl/core/ufl_type.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 1f900485c..1ec8908d8 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -185,12 +185,6 @@ def _ufl_type_decorator_(cls): if use_default_hash: cls.__hash__ = compute_expr_hash - # NB! This function conditionally adds some methods to the - # class! This approach significantly reduces the amount of - # small functions to implement across all the types but of - # course it's a bit more opaque. - attach_implementations_of_indexing_interface(cls) - # Apply a range of consistency checks to detect bugs in type # implementations that Python doesn't check for us, including # some checks that a static language compiler would do for us From 5375569bcb604735d161330ca1a3396c35782cd6 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:51:45 +0200 Subject: [PATCH 57/78] Simplify --- ufl/core/ufl_type.py | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 1ec8908d8..d2e2ee85e 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -117,31 +117,6 @@ def check_type_traits_consistency(cls): assert UFLRegistry().number_registered_classes == len(UFLRegistry().all_classes) -def check_implements_required_methods(cls): - """Check if type implements the required methods.""" - # if not cls._ufl_is_abstract_: - # if ABC not in cls.__base__: - # for attr in core.expr.Expr._ufl_required_methods_: - # if not hasattr(cls, attr): - # msg = "Class {0.__name__} has no {1} method." - # raise TypeError(msg.format(cls, attr)) - # elif not callable(getattr(cls, attr)): - # msg = "Required method {1} of class {0.__name__} is not callable." - # raise TypeError(msg.format(cls, attr)) - - -def check_implements_required_properties(cls): - """Check if type implements the required properties.""" - # if not cls._ufl_is_abstract_: - # for attr in core.expr.Expr._ufl_required_properties_: - # if not hasattr(cls, attr): - # msg = "Class {0.__name__} has no {1} property." - # raise TypeError(msg.format(cls, attr)) - # elif callable(getattr(cls, attr)): - # msg = "Required property {1} of class {0.__name__} is a callable method." - # raise TypeError(msg.format(cls, attr)) - - def update_ufl_type_attributes(cls): """Update UFL type attributes.""" # Determine integer typecode by incrementally counting all types @@ -191,9 +166,7 @@ def _ufl_type_decorator_(cls): check_abstract_trait_consistency(cls) check_has_slots(cls) check_is_terminal_consistency(cls) - check_implements_required_methods(cls) - check_implements_required_properties(cls) - check_type_traits_consistency(cls) + assert UFLRegistry().number_registered_classes == len(UFLRegistry().all_classes) return cls From 4f9c5bb4df1a1f63a160dacddc98bf4ef94a4b34 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:57:09 +0200 Subject: [PATCH 58/78] Remove _ufl_class_ --- ufl/algorithms/apply_derivatives.py | 11 ++++------- ufl/algorithms/apply_restrictions.py | 6 +++--- ufl/algorithms/change_to_reference.py | 2 +- ufl/algorithms/check_arities.py | 2 +- ufl/algorithms/compute_form_data.py | 2 +- ufl/algorithms/estimate_degrees.py | 4 ++-- ufl/algorithms/expand_indices.py | 2 +- ufl/algorithms/transformer.py | 2 +- ufl/constantvalue.py | 2 +- ufl/core/expr.py | 13 ++----------- ufl/core/external_operator.py | 4 +--- ufl/core/operator.py | 4 ++-- ufl/core/ufl_type.py | 3 --- ufl/corealg/multifunction.py | 2 +- ufl/differentiation.py | 6 +++--- ufl/geometry.py | 8 ++++---- ufl/utils/formatting.py | 2 +- 17 files changed, 29 insertions(+), 46 deletions(-) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 4e7d43e30..c0338abe5 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -165,13 +165,11 @@ def __init__( def unexpected(self, o): """Raise error about unexpected type.""" - raise ValueError(f"Unexpected type {o._ufl_class_.__name__} in AD rules.") + raise ValueError(f"Unexpected type {type(o).__name__} in AD rules.") def override(self, o): """Raise error about overriding.""" - raise ValueError( - f"Type {o._ufl_class_.__name__} must be overridden in specialized AD rule set." - ) + raise ValueError(f"Type {type(o).__name__} must be overridden in specialized AD rule set.") # --- Some types just don't have any derivative, this is just to # --- make algorithm structure generic @@ -213,7 +211,7 @@ def process(self, o: Expr) -> Expr: def _(self, o: Expr) -> Expr: """Raise error.""" raise ValueError( - f"Missing differentiation handler for type {o._ufl_class_.__name__}. " + f"Missing differentiation handler for type {type(o).__name__}. " "Have you added a new type?" ) @@ -221,8 +219,7 @@ def _(self, o: Expr) -> Expr: def _(self, o: Expr) -> Expr: """Raise error.""" raise ValueError( - f"Unhandled derivative type {o._ufl_class_.__name__}, " - "nested differentiation has failed." + f"Unhandled derivative type {type(o).__name__}, nested differentiation has failed." ) @process.register(Label) diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index d2a4ace41..68471b5b2 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -62,7 +62,7 @@ def _ignore_restriction(self, o): def _require_restriction(self, o): """Restrict a discontinuous quantity to current side, require a side to be set.""" if self.current_restriction is None: - raise ValueError(f"Discontinuous type {o._ufl_class_.__name__} must be restricted.") + raise ValueError(f"Discontinuous type {type(o).__name__} must be restricted.") return o(self.current_restriction) def _default_restricted(self, o): @@ -81,7 +81,7 @@ def _opposite(self, o): If the current restriction is different swap the sign, require a side to be set. """ if self.current_restriction is None: - raise ValueError(f"Discontinuous type {o._ufl_class_.__name__} must be restricted.") + raise ValueError(f"Discontinuous type {type(o).__name__} must be restricted.") elif self.current_restriction == self.default_restriction: return o(self.default_restriction) else: @@ -89,7 +89,7 @@ def _opposite(self, o): def _missing_rule(self, o): """Raise an error.""" - raise ValueError(f"Missing rule for {o._ufl_class_.__name__}") + raise ValueError(f"Missing rule for {type(o).__name__}") # --- Rules for operators diff --git a/ufl/algorithms/change_to_reference.py b/ufl/algorithms/change_to_reference.py index 17b106ad7..cb0fca5f3 100644 --- a/ufl/algorithms/change_to_reference.py +++ b/ufl/algorithms/change_to_reference.py @@ -138,7 +138,7 @@ def grad(self, o): rv = True (o,) = o.ufl_operands else: - raise ValueError(f"Invalid type {o._ufl_class_.__name__}") + raise ValueError(f"Invalid type {type(o).__name__}") f = o if rv: f = ReferenceValue(f) diff --git a/ufl/algorithms/check_arities.py b/ufl/algorithms/check_arities.py index 2d51bbe1d..53839633d 100644 --- a/ufl/algorithms/check_arities.py +++ b/ufl/algorithms/check_arities.py @@ -45,7 +45,7 @@ def nonlinear_operator(self, o): for t in traverse_unique_terminals(o): if t._ufl_typecode_ == Argument._ufl_typecode_: raise ArityMismatch( - f"Applying nonlinear operator {o._ufl_class_.__name__} to " + f"Applying nonlinear operator {type(o).__name__} to " f"expression depending on form argument {t}." ) return self._et diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py index 09f98af4e..ace4435e6 100644 --- a/ufl/algorithms/compute_form_data.py +++ b/ufl/algorithms/compute_form_data.py @@ -157,7 +157,7 @@ def _check_facet_geometry(integral_data): if not ("facet" in it or "custom" in it or "interface" in it): # Not a facet integral for expr in traverse_unique_terminals(itg.integrand()): - cls = expr._ufl_class_ + cls = type(expr) if issubclass(cls, GeometricFacetQuantity): raise ValueError(f"Integral of type {it} cannot contain a {cls.__name__}.") diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index 1cc42e998..f0b7c92ca 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -126,14 +126,14 @@ def _max_degrees(self, v, *ops): def _not_handled(self, v, *args): """Apply to _not_handled.""" - raise ValueError(f"Missing degree handler for type {v._ufl_class_.__name__}") + raise ValueError(f"Missing degree handler for type {type(v).__name__}") def expr(self, v, *ops): """Apply to expr. For most operators we take the max degree of its operands. """ - warnings.warn(f"Missing degree estimation handler for type {v._ufl_class_.__name__}") + warnings.warn(f"Missing degree estimation handler for type {type(v).__name__}") return self._add_degrees(v, *ops) # Utility types with no degree concept diff --git a/ufl/algorithms/expand_indices.py b/ufl/algorithms/expand_indices.py index de994bd12..33a3368ad 100644 --- a/ufl/algorithms/expand_indices.py +++ b/ufl/algorithms/expand_indices.py @@ -88,7 +88,7 @@ def scalar_value(self, x): if s: raise ValueError(f"Free index set mismatch, these indices have no value assigned: {s}.") - return x._ufl_class_(x.value()) + return type(x)(x.value()) def conditional(self, x): """Apply to conditional.""" diff --git a/ufl/algorithms/transformer.py b/ufl/algorithms/transformer.py index e49893e3e..d75a54e5a 100644 --- a/ufl/algorithms/transformer.py +++ b/ufl/algorithms/transformer.py @@ -118,7 +118,7 @@ def visit(self, o): def undefined(self, o): """Trigger error.""" - raise ValueError(f"No handler defined for {o._ufl_class_.__name__}.") + raise ValueError(f"No handler defined for {type(o).__name__}.") def reuse(self, o): """Reuse Expr (ignore children).""" diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index 3dc11f593..27c551ea9 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -234,7 +234,7 @@ def __eq__(self, other): can still succeed. These will however not have the same hash value and therefore not collide in a dict. """ - if isinstance(other, self._ufl_class_): + if isinstance(other, type(self)): return self._value == other._value elif isinstance(other, (int, float)): # FIXME: Disallow this, require explicit 'expr == diff --git a/ufl/core/expr.py b/ufl/core/expr.py index a10c050cd..3da3f876d 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -122,11 +122,6 @@ def __init__(self): # type traits that categorize types are mostly set to None for # Expr but should be True or False for any non-abstract type. - # A reference to the UFL class itself. This makes it possible to - # do type(f)._ufl_class_ and be sure you get the actual UFL class - # instead of a subclass from another library. - _ufl_class_: type = None # type: ignore - # The handler name. This is the name of the handler function you # implement for this type in a multifunction. _ufl_handler_name_ = "expr" @@ -248,7 +243,7 @@ def ufl_domain(self): def evaluate(self, x, mapping, component, index_values): """Evaluate expression at given coordinate with given values for terminals.""" - raise ValueError(f"Symbolic evaluation of {self._ufl_class_.__name__} not available.") + raise ValueError(f"Symbolic evaluation of {type(self).__name__} not available.") def _ufl_evaluate_scalar_(self): if self.ufl_shape or self.ufl_free_indices: @@ -315,7 +310,7 @@ def __str__(self): def _ufl_err_str_(self): """Return a short string to represent this Expr in an error message.""" - return f"<{self._ufl_class_.__name__} id={id(self)}>" + return f"<{type(self).__name__} id={id(self)}>" def _simplify_indexed(self, multiindex): """Return a simplified Expr used in the constructor of Indexed(self, multiindex).""" @@ -455,10 +450,6 @@ def __getitem__(self, index): pass -# Initializing traits here because Expr is not defined in the class -# declaration -Expr._ufl_class_ = Expr # type: ignore - # Update Expr with metaclass properties (e.g. typecode or handler name) # Explicitly done here instead of using `@ufl_type` to avoid circular imports. update_ufl_type_attributes(Expr) diff --git a/ufl/core/external_operator.py b/ufl/core/external_operator.py index 110d94b1f..2210b31bc 100644 --- a/ufl/core/external_operator.py +++ b/ufl/core/external_operator.py @@ -81,9 +81,7 @@ def grad(self): def assemble(self, *args, **kwargs): """Assemble the external operator.""" - raise NotImplementedError( - f"Symbolic evaluation of {self._ufl_class_.__name__} not available." - ) + raise NotImplementedError(f"Symbolic evaluation of {type(self).__name__} not available.") def _ufl_expr_reconstruct_( self, *operands, function_space=None, derivatives=None, argument_slots=None, add_kwargs={} diff --git a/ufl/core/operator.py b/ufl/core/operator.py index 9a8c9696d..d049ca57e 100644 --- a/ufl/core/operator.py +++ b/ufl/core/operator.py @@ -31,7 +31,7 @@ def __init__(self, operands=None): def _ufl_expr_reconstruct_(self, *operands): """Return a new object of the same type with new operands.""" - return self._ufl_class_(*operands) + return type(self)(*operands) def _ufl_signature_data_(self): """Get UFL signature data.""" @@ -44,4 +44,4 @@ def _ufl_compute_hash_(self): def __repr__(self): """Default repr string construction for operators.""" # This should work for most cases - return f"{self._ufl_class_.__name__}({', '.join(map(repr, self.ufl_operands))})" + return f"{type(self).__name__}({', '.join(map(repr, self.ufl_operands))})" diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index d2e2ee85e..6f266f084 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -149,9 +149,6 @@ def _ufl_type_decorator_(cls): # Don't need anything else for non Expr subclasses return cls - # Store type traits - cls._ufl_class_ = cls - # Make sure every non-abstract class has its own __hash__ and # __eq__. Python 3 will set __hash__ to None if cls has # __eq__, but we've implemented it in a separate function and diff --git a/ufl/corealg/multifunction.py b/ufl/corealg/multifunction.py index ec964ba74..00d17c171 100644 --- a/ufl/corealg/multifunction.py +++ b/ufl/corealg/multifunction.py @@ -95,7 +95,7 @@ def __call__(self, o, *args): def undefined(self, o, *args): """Trigger error for types with missing handlers.""" - raise ValueError(f"No handler defined for {o._ufl_class_.__name__}.") + raise ValueError(f"No handler defined for {type(o).__name__}.") def reuse_if_untouched(self, o, *ops): """Reuse object if operands are the same objects. diff --git a/ufl/differentiation.py b/ufl/differentiation.py index a7344636a..e69a24e9e 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -376,7 +376,7 @@ def _ufl_expr_reconstruct_(self, op): if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: raise ValueError("Free index mismatch in Grad reconstruct.") return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) - return self._ufl_class_(op) + return type(self)(op) def evaluate(self, x, mapping, component, index_values, derivatives=()): """Get child from mapping and return the component asked for.""" @@ -438,7 +438,7 @@ def _ufl_expr_reconstruct_(self, op): if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: raise ValueError("Free index mismatch in ReferenceGrad reconstruct.") return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) - return self._ufl_class_(op) + return type(self)(op) def evaluate(self, x, mapping, component, index_values, derivatives=()): """Get child from mapping and return the component asked for.""" @@ -581,7 +581,7 @@ def _ufl_expr_reconstruct_(self, op): if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: raise ValueError("Free index mismatch in NablaGrad reconstruct.") return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) - return self._ufl_class_(op) + return type(self)(op) @property def ufl_shape(self): diff --git a/ufl/geometry.py b/ufl/geometry.py index 5bdf7c0fb..17f896e66 100644 --- a/ufl/geometry.py +++ b/ufl/geometry.py @@ -137,15 +137,15 @@ def is_cellwise_constant(self): def _ufl_signature_data_(self, renumbering): """Signature data of geometric quantities depend on the domain numbering.""" - return (self._ufl_class_.__name__,) + self._domain._ufl_signature_data_(renumbering) + return (type(self).__name__,) + self._domain._ufl_signature_data_(renumbering) def __str__(self): """Format as a string.""" - return self._ufl_class_.name + return type(self).name def __repr__(self): """Representation.""" - r = f"{self._ufl_class_.__name__}({self._domain!r})" + r = f"{type(self).__name__}({self._domain!r})" return r def _ufl_compute_hash_(self): @@ -154,7 +154,7 @@ def _ufl_compute_hash_(self): def __eq__(self, other): """Check equality.""" - return isinstance(other, self._ufl_class_) and other._domain == self._domain + return isinstance(other, type(self)) and other._domain == self._domain @ufl_type() diff --git a/ufl/utils/formatting.py b/ufl/utils/formatting.py index d3e0ad2bc..45c45c1a3 100644 --- a/ufl/utils/formatting.py +++ b/ufl/utils/formatting.py @@ -89,7 +89,7 @@ def _tree_format_expression(expression, indentation, parentheses): _tree_format_expression(o, indentation + 1, parentheses) for o in expression.ufl_operands ] - s = f"{ind}{expression._ufl_class_.__name__}\n" + s = f"{ind}{type(expression).__name__}\n" if parentheses and len(sops) > 1: s += f"{ind}(\n" s += "\n".join(sops) From 389ab1d258fdc0361cf70709a65e78788d211cac Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:12:25 +0200 Subject: [PATCH 59/78] Remove (unused) _ufl_is_shaping_ --- ufl/core/expr.py | 5 ----- ufl/core/ufl_type.py | 1 - ufl/indexed.py | 1 - ufl/tensoralgebra.py | 1 - ufl/tensors.py | 2 -- ufl/variable.py | 1 - 6 files changed, 11 deletions(-) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 3da3f876d..a51fc546e 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -126,11 +126,6 @@ def __init__(self): # implement for this type in a multifunction. _ufl_handler_name_ = "expr" - # Type trait: If the type is a shaping operator. Shaping - # operations include indexing, slicing, transposing, i.e. not - # introducing computation of a new value. - _ufl_is_shaping_ = False - # --- All subclasses must define these object attributes --- # Each subclass of Expr is checked to have these properties in diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 6f266f084..ce87cc251 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -235,7 +235,6 @@ class UFLType(ABC): _ufl_is_literal_: bool = False _ufl_is_terminal_modifier_: bool = False - _ufl_is_shaping_: bool = False _ufl_is_in_reference_frame_: bool = False _ufl_is_restriction_: bool = False _ufl_is_evaluation_: bool = False diff --git a/ufl/indexed.py b/ufl/indexed.py index a1ebedd15..a747daf07 100644 --- a/ufl/indexed.py +++ b/ufl/indexed.py @@ -20,7 +20,6 @@ class Indexed(Operator): """Indexed expression.""" _ufl_is_terminal_modifier_ = True - _ufl_is_shaping_ = True __slots__ = ( "_initialised", "ufl_free_indices", diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index d13796d18..fbd796fbd 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -90,7 +90,6 @@ def __init__(self, operands): class Transposed(CompoundTensorOperator): """Transposed tensor.""" - _ufl_is_shaping_ = True __slots__ = () def __new__(cls, A): diff --git a/ufl/tensors.py b/ufl/tensors.py index 0e5bb00e6..eb7cd7572 100644 --- a/ufl/tensors.py +++ b/ufl/tensors.py @@ -22,7 +22,6 @@ class ListTensor(Operator): """Wraps a list of expressions into a tensor valued expression of one higher rank.""" - _ufl_is_shaping_ = True __slots__ = ("_initialised",) def __new__(cls, *expressions): @@ -193,7 +192,6 @@ def ufl_index_dimensions(self): class ComponentTensor(Operator): """Maps the free indices of a scalar valued expression to tensor axes.""" - _ufl_is_shaping_ = True __slots__ = ("_initialised", "ufl_free_indices", "ufl_index_dimensions", "ufl_shape") def __new__(cls, expression, indices): diff --git a/ufl/variable.py b/ufl/variable.py index 81317e5bc..79e1405fe 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -81,7 +81,6 @@ class Variable(Operator): ufl_free_indices = () ufl_index_dimensions = () - _ufl_is_shaping_ = True __slots__ = () def __init__(self, expression, label=None): From 24a1e28b600e6e91387523a90d6e22587bc6bc43 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:14:23 +0200 Subject: [PATCH 60/78] Remove (unused) _ufl_is_restriction_ --- ufl/core/ufl_type.py | 1 - ufl/restriction.py | 1 - 2 files changed, 2 deletions(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index ce87cc251..e02a81b65 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -236,7 +236,6 @@ class UFLType(ABC): _ufl_is_terminal_modifier_: bool = False _ufl_is_in_reference_frame_: bool = False - _ufl_is_restriction_: bool = False _ufl_is_evaluation_: bool = False _ufl_is_differential_: bool = False diff --git a/ufl/restriction.py b/ufl/restriction.py index 04e9f3474..5c55db0c7 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -17,7 +17,6 @@ class Restricted(Operator): """Restriction.""" - _ufl_is_restriction_ = True __slots__ = () _side: str From 3a0657f56aa0c39de5a2908fcf0675af309fe572 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:15:29 +0200 Subject: [PATCH 61/78] Remove (unused) _ufl_is_evaluation_ --- ufl/averaging.py | 2 -- ufl/core/ufl_type.py | 1 - 2 files changed, 3 deletions(-) diff --git a/ufl/averaging.py b/ufl/averaging.py index 4eae37d49..46bdfb670 100644 --- a/ufl/averaging.py +++ b/ufl/averaging.py @@ -15,7 +15,6 @@ class CellAvg(Operator): """Cell average.""" - _ufl_is_evaluation_ = True __slots__ = () def __new__(cls, f): @@ -56,7 +55,6 @@ def ufl_index_dimensions(self): class FacetAvg(Operator): """Facet average.""" - _ufl_is_evaluation_ = True __slots__ = () def __new__(cls, f): diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index e02a81b65..2252e2285 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -236,7 +236,6 @@ class UFLType(ABC): _ufl_is_terminal_modifier_: bool = False _ufl_is_in_reference_frame_: bool = False - _ufl_is_evaluation_: bool = False _ufl_is_differential_: bool = False ufl_operands: tuple[FormArgument, ...] From c9ff72424f883681209e73e52fa9adac983147e7 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:16:24 +0200 Subject: [PATCH 62/78] Remove (unused) _ufl_is_differential_ --- ufl/core/base_form_operator.py | 2 -- ufl/core/external_operator.py | 2 -- ufl/core/interpolate.py | 2 -- ufl/core/ufl_type.py | 1 - ufl/differentiation.py | 1 - 5 files changed, 8 deletions(-) diff --git a/ufl/core/base_form_operator.py b/ufl/core/base_form_operator.py index c7b45cc0c..1b7e2b789 100644 --- a/ufl/core/base_form_operator.py +++ b/ufl/core/base_form_operator.py @@ -30,8 +30,6 @@ class BaseFormOperator(Operator, BaseForm, Counted): """Base form operator.""" - _ufl_is_differential_ = True - # Slots are disabled here because they cause trouble in PyDOLFIN # multiple inheritance pattern: _ufl_noslots_ = True diff --git a/ufl/core/external_operator.py b/ufl/core/external_operator.py index 2210b31bc..69a1331d3 100644 --- a/ufl/core/external_operator.py +++ b/ufl/core/external_operator.py @@ -21,8 +21,6 @@ class ExternalOperator(BaseFormOperator): """External operator.""" - _ufl_is_differential_ = True - # Slots are disabled here because they cause trouble in PyDOLFIN # multiple inheritance pattern: _ufl_noslots_ = True diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py index acb38690f..5e8c38858 100644 --- a/ufl/core/interpolate.py +++ b/ufl/core/interpolate.py @@ -21,8 +21,6 @@ class Interpolate(BaseFormOperator): """Symbolic representation of the interpolation operator.""" - _ufl_is_differential_ = True - # Slots are disabled here because they cause trouble in PyDOLFIN # multiple inheritance pattern: _ufl_noslots_ = True diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 2252e2285..3c3629fa6 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -236,7 +236,6 @@ class UFLType(ABC): _ufl_is_terminal_modifier_: bool = False _ufl_is_in_reference_frame_: bool = False - _ufl_is_differential_: bool = False ufl_operands: tuple[FormArgument, ...] ufl_shape: tuple[int, ...] diff --git a/ufl/differentiation.py b/ufl/differentiation.py index e69a24e9e..bd284f9e1 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -28,7 +28,6 @@ class Derivative(Operator): """Base class for all derivative types.""" - _ufl_is_differential_ = True __slots__ = () def __init__(self, operands): From 6bb684fba3e66f90fd64860069a9a3a637750ac4 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:20:56 +0200 Subject: [PATCH 63/78] Remove (unused) _ufl_is_literal_ --- ufl/constantvalue.py | 4 ---- ufl/core/ufl_type.py | 1 - 2 files changed, 5 deletions(-) diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index 27c551ea9..7f1df9fac 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -63,7 +63,6 @@ class Zero(ConstantValue): Class for representing zero tensors of different shapes. """ - _ufl_is_literal_ = True __slots__ = ("ufl_free_indices", "ufl_index_dimensions", "ufl_shape") _cache: dict[tuple[int], "Zero"] = {} @@ -280,7 +279,6 @@ def imag(self): class ComplexValue(ScalarValue): """Representation of a constant, complex scalar.""" - _ufl_is_literal_ = True __slots__ = () def __getnewargs__(self): @@ -337,7 +335,6 @@ class RealValue(ScalarValue): class FloatValue(RealValue): """Representation of a constant scalar floating point value.""" - _ufl_is_literal_ = True __slots__ = () def __getnewargs__(self): @@ -365,7 +362,6 @@ def __repr__(self): class IntValue(RealValue): """Representation of a constant scalar integer value.""" - _ufl_is_literal_ = True __slots__ = () _cache: dict[int, "IntValue"] = {} diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 3c3629fa6..6345e1f9a 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -232,7 +232,6 @@ class UFLType(ABC): _ufl_handler_name_: str = "ufl_type" _ufl_is_terminal_: bool = False - _ufl_is_literal_: bool = False _ufl_is_terminal_modifier_: bool = False _ufl_is_in_reference_frame_: bool = False From 9ed883e7650c8415c2aa1c2adfab1a8ec3571a1e Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:42:23 +0200 Subject: [PATCH 64/78] Revert "Remove (unused) _ufl_is_literal_" This reverts commit 6bb684fba3e66f90fd64860069a9a3a637750ac4. --- ufl/constantvalue.py | 4 ++++ ufl/core/ufl_type.py | 1 + 2 files changed, 5 insertions(+) diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index 7f1df9fac..27c551ea9 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -63,6 +63,7 @@ class Zero(ConstantValue): Class for representing zero tensors of different shapes. """ + _ufl_is_literal_ = True __slots__ = ("ufl_free_indices", "ufl_index_dimensions", "ufl_shape") _cache: dict[tuple[int], "Zero"] = {} @@ -279,6 +280,7 @@ def imag(self): class ComplexValue(ScalarValue): """Representation of a constant, complex scalar.""" + _ufl_is_literal_ = True __slots__ = () def __getnewargs__(self): @@ -335,6 +337,7 @@ class RealValue(ScalarValue): class FloatValue(RealValue): """Representation of a constant scalar floating point value.""" + _ufl_is_literal_ = True __slots__ = () def __getnewargs__(self): @@ -362,6 +365,7 @@ def __repr__(self): class IntValue(RealValue): """Representation of a constant scalar integer value.""" + _ufl_is_literal_ = True __slots__ = () _cache: dict[int, "IntValue"] = {} diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 6345e1f9a..3c3629fa6 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -232,6 +232,7 @@ class UFLType(ABC): _ufl_handler_name_: str = "ufl_type" _ufl_is_terminal_: bool = False + _ufl_is_literal_: bool = False _ufl_is_terminal_modifier_: bool = False _ufl_is_in_reference_frame_: bool = False From e7950173d5a2986392bff31b27d3e364bc959f29 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 12 Aug 2025 20:41:01 +0200 Subject: [PATCH 65/78] Remove use_default_hash --- ufl/algorithms/domain_analysis.py | 5 +++++ ufl/argument.py | 5 +++++ ufl/coefficient.py | 9 +++++++++ ufl/constant.py | 5 +++++ ufl/constantvalue.py | 17 +++++++++++++++++ ufl/core/base_form_operator.py | 24 +++++++++++++++--------- ufl/core/expr.py | 12 +++++------- ufl/core/external_operator.py | 5 +++++ ufl/core/interpolate.py | 5 +++++ ufl/core/multiindex.py | 5 +++++ ufl/core/terminal.py | 5 +++++ ufl/core/ufl_type.py | 13 +------------ ufl/form.py | 17 +++++++++++------ ufl/geometry.py | 5 +++++ ufl/variable.py | 5 +++++ 15 files changed, 103 insertions(+), 34 deletions(-) diff --git a/ufl/algorithms/domain_analysis.py b/ufl/algorithms/domain_analysis.py index 7b728c5fa..7a37f44e1 100644 --- a/ufl/algorithms/domain_analysis.py +++ b/ufl/algorithms/domain_analysis.py @@ -15,6 +15,7 @@ strip_coordinate_derivatives, ) from ufl.algorithms.renumbering import renumber_indices +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.form import Form from ufl.integral import Integral from ufl.protocols import id_or_none @@ -84,6 +85,10 @@ def __eq__(self, other): and self.metadata == other.metadata ) + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + def __str__(self): """Format as a string.""" s = f"IntegralData over domain({self.integral_type}, {self.subdomain_id})" diff --git a/ufl/argument.py b/ufl/argument.py index 2021ca999..3a2f16e58 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -17,6 +17,7 @@ import numbers from abc import ABC +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.terminal import FormArgument from ufl.core.ufl_type import ufl_type from ufl.duals import is_dual, is_primal @@ -185,6 +186,10 @@ def __repr__(self): """Representation.""" return self._repr + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + @ufl_type() class Coargument(BaseForm, BaseArgument): diff --git a/ufl/coefficient.py b/ufl/coefficient.py index 13c3cac4e..121d2b840 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -12,6 +12,7 @@ # Modified by Ignacia Fierro-Piccardo 2023. from ufl.argument import Argument +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.terminal import FormArgument from ufl.core.ufl_type import UFLType, ufl_type from ufl.duals import is_dual, is_primal @@ -99,6 +100,10 @@ def __eq__(self, other): return True return self._count == other._count and self._ufl_function_space == other._ufl_function_space + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + @ufl_type() class Cofunction(BaseCoefficient, BaseForm): @@ -196,6 +201,10 @@ def __eq__(self, other): return True return self._count == other._count and self._ufl_function_space == other._ufl_function_space + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + def __repr__(self): """Representation.""" return self._repr diff --git a/ufl/constant.py b/ufl/constant.py index dfc65aa06..31a6847a3 100644 --- a/ufl/constant.py +++ b/ufl/constant.py @@ -6,6 +6,7 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.terminal import Terminal from ufl.core.ufl_type import ufl_type from ufl.domain import as_domain @@ -67,6 +68,10 @@ def __eq__(self, other): and self._ufl_shape == self._ufl_shape ) + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + def _ufl_signature_data_(self, renumbering): """Signature data for constant depends on renumbering.""" return ( diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index 27c551ea9..b617342c8 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -16,6 +16,7 @@ # --- Helper functions imported here for compatibility--- from ufl.checks import is_python_scalar, is_true_ufl_scalar, is_ufl_scalar # noqa: F401 +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.expr import Expr from ufl.core.multiindex import FixedIndex, Index from ufl.core.terminal import Terminal @@ -162,6 +163,10 @@ def __eq__(self, other): else: return False + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + def __neg__(self): """Negate.""" return self @@ -243,6 +248,10 @@ def __eq__(self, other): else: return False + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + def __str__(self): """Format as a string.""" return str(self._value) @@ -446,6 +455,10 @@ def __eq__(self, other): """Check equalty.""" return isinstance(other, Identity) and self._dim == other._dim + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + # --- Permutation symbol --- @@ -490,6 +503,10 @@ def __eq__(self, other): """Check equalty.""" return isinstance(other, PermutationSymbol) and self._dim == other._dim + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + def __eps(self, x): """Get eps. diff --git a/ufl/core/base_form_operator.py b/ufl/core/base_form_operator.py index 1b7e2b789..1c540bc6d 100644 --- a/ufl/core/base_form_operator.py +++ b/ufl/core/base_form_operator.py @@ -18,6 +18,7 @@ from ufl.argument import Argument, Coargument from ufl.constantvalue import as_ufl +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type from ufl.duals import is_dual @@ -171,15 +172,20 @@ def __repr__(self): return r def __hash__(self): - """Hash code for use in dicts.""" - hashdata = ( - type(self), - tuple(hash(op) for op in self.ufl_operands), - tuple(hash(arg) for arg in self._argument_slots), - self.derivatives, - hash(self.ufl_function_space()), - ) - return hash(hashdata) + """Return hash.""" + return compute_expr_hash(self) + + # TODO: is this not needed? + # def __hash__(self): + # """Hash code for use in dicts.""" + # hashdata = ( + # type(self), + # tuple(hash(op) for op in self.ufl_operands), + # tuple(hash(arg) for arg in self._argument_slots), + # self.derivatives, + # hash(self.ufl_function_space()), + # ) + # return hash(hashdata) def __eq__(self, other): """Check for equality.""" diff --git a/ufl/core/expr.py b/ufl/core/expr.py index a51fc546e..c25e2888e 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -22,6 +22,7 @@ if typing.TYPE_CHECKING: from ufl.core.terminal import FormArgument +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.ufl_type import UFLRegistry, UFLType, update_ufl_type_attributes @@ -107,13 +108,6 @@ def __init__(self): """Initialise.""" self._hash = None - # This shows the principal behaviour of the hash function attached - # in ufl_type: - # def __hash__(self): - # if self._hash is None: - # self._hash = self._ufl_compute_hash_() - # return self._hash - # --- Type traits are added to subclasses by the ufl_type class # --- decorator --- @@ -321,6 +315,10 @@ def __eq__(self, other): """ raise NotImplementedError(self.__class__.__eq__) + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + def __len__(self): """Length of expression. Used for iteration over vector expressions.""" s = self.ufl_shape diff --git a/ufl/core/external_operator.py b/ufl/core/external_operator.py index 69a1331d3..2c4e1329a 100644 --- a/ufl/core/external_operator.py +++ b/ufl/core/external_operator.py @@ -14,6 +14,7 @@ # Modified by Nacime Bouziani, 2023 from ufl.core.base_form_operator import BaseFormOperator +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.ufl_type import ufl_type @@ -116,3 +117,7 @@ def __eq__(self, other): and self.derivatives == other.derivatives and self.ufl_function_space() == other.ufl_function_space() ) + + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py index 5e8c38858..232e428c0 100644 --- a/ufl/core/interpolate.py +++ b/ufl/core/interpolate.py @@ -11,6 +11,7 @@ from ufl.argument import Argument, Coargument from ufl.constantvalue import as_ufl from ufl.core.base_form_operator import BaseFormOperator +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.ufl_type import ufl_type from ufl.duals import is_dual from ufl.form import BaseForm @@ -93,6 +94,10 @@ def __eq__(self, other): and self.ufl_function_space() == other.ufl_function_space() ) + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + # Helper function def interpolate(expr, v): diff --git a/ufl/core/multiindex.py b/ufl/core/multiindex.py index af4ffb775..b4e3bc18e 100644 --- a/ufl/core/multiindex.py +++ b/ufl/core/multiindex.py @@ -8,6 +8,7 @@ # # Modified by Massimiliano Leoni, 2016. +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.terminal import Terminal from ufl.core.ufl_type import ufl_type from ufl.utils.counted import Counted @@ -179,6 +180,10 @@ def evaluate(self, x, mapping, component, index_values): component.append(index_values[i]) return tuple(component) + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + @property def ufl_shape(self): """Get the UFL shape. diff --git a/ufl/core/terminal.py b/ufl/core/terminal.py index ca1d81d19..93d0e5790 100644 --- a/ufl/core/terminal.py +++ b/ufl/core/terminal.py @@ -13,6 +13,7 @@ import warnings +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.expr import Expr from ufl.core.ufl_type import ufl_type @@ -93,6 +94,10 @@ def __eq__(self, other): """Default comparison of terminals just compare repr strings.""" return repr(self) == repr(other) + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + # --- Subgroups of terminals --- diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 3c3629fa6..572bfbfa4 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -14,7 +14,6 @@ from abc import ABC, abstractmethod import ufl.core as core -from ufl.core.compute_expr_hash import compute_expr_hash from ufl.utils.formatting import camel2underscore if typing.TYPE_CHECKING: @@ -128,9 +127,7 @@ def update_ufl_type_attributes(cls): cls._ufl_handler_name_ = camel2underscore(cls.__name__) -def ufl_type( - use_default_hash=True, -): +def ufl_type(): """Decorator to apply to every subclass in the UFL ``Expr`` and ``BaseForm`` hierarchy. This decorator contains a number of checks that are intended to @@ -149,14 +146,6 @@ def _ufl_type_decorator_(cls): # Don't need anything else for non Expr subclasses return cls - # Make sure every non-abstract class has its own __hash__ and - # __eq__. Python 3 will set __hash__ to None if cls has - # __eq__, but we've implemented it in a separate function and - # want to inherit/use that for all types. Allow overriding by - # setting use_default_hash=False. - if use_default_hash: - cls.__hash__ = compute_expr_hash - # Apply a range of consistency checks to detect bugs in type # implementations that Python doesn't check for us, including # some checks that a static language compiler would do for us diff --git a/ufl/form.py b/ufl/form.py index 2f3d3b3ae..77da7be5a 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -19,6 +19,7 @@ from ufl.checks import is_scalar_constant_expression from ufl.constant import Constant from ufl.constantvalue import Zero +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.expr import Expr, ufl_err_str from ufl.core.terminal import FormArgument from ufl.core.ufl_type import UFLType, ufl_type @@ -145,6 +146,10 @@ def __eq__(self, other): """ return Equation(self, other) + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + def __radd__(self, other): """Add.""" # Ordering of form additions make no difference @@ -898,6 +903,12 @@ def __eq__(self, other): else: return False + def __hash__(self): + """Hash.""" + if self._hash is None: + self._hash = hash(("ZeroBaseForm", hash(self._arguments))) + return self._hash + def __str__(self): """Format as a string.""" return "ZeroBaseForm({})".format(", ".join(str(arg) for arg in self._arguments)) @@ -905,9 +916,3 @@ def __str__(self): def __repr__(self): """Representation.""" return "ZeroBaseForm({})".format(", ".join(repr(arg) for arg in self._arguments)) - - def __hash__(self): - """Hash.""" - if self._hash is None: - self._hash = hash(("ZeroBaseForm", hash(self._arguments))) - return self._hash diff --git a/ufl/geometry.py b/ufl/geometry.py index 17f896e66..bea92791f 100644 --- a/ufl/geometry.py +++ b/ufl/geometry.py @@ -6,6 +6,7 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.terminal import Terminal from ufl.core.ufl_type import ufl_type from ufl.domain import MeshSequence, as_domain, extract_unique_domain @@ -156,6 +157,10 @@ def __eq__(self, other): """Check equality.""" return isinstance(other, type(self)) and other._domain == self._domain + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + @ufl_type() class GeometricCellQuantity(GeometricQuantity): diff --git a/ufl/variable.py b/ufl/variable.py index 79e1405fe..edc93b123 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -9,6 +9,7 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from ufl.constantvalue import as_ufl +from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.expr import Expr from ufl.core.operator import Operator from ufl.core.terminal import Terminal @@ -125,6 +126,10 @@ def __eq__(self, other): and self.ufl_operands[0] == other.ufl_operands[0] ) + def __hash__(self): + """Return hash.""" + return compute_expr_hash(self) + def __str__(self): """Format as a string.""" return f"var{self.ufl_operands[1].count()}({self.ufl_operands[0]})" From 81d8cae3bdbe426cdc787146ff9030aa11e6b350 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:19:56 +0200 Subject: [PATCH 66/78] No more type consistency checks --- ufl/core/expr.py | 8 +--- ufl/core/ufl_type.py | 108 ++++--------------------------------------- 2 files changed, 11 insertions(+), 105 deletions(-) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index c25e2888e..fece54106 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -23,9 +23,10 @@ from ufl.core.terminal import FormArgument from ufl.core.compute_expr_hash import compute_expr_hash -from ufl.core.ufl_type import UFLRegistry, UFLType, update_ufl_type_attributes +from ufl.core.ufl_type import UFLRegistry, UFLType, ufl_type +@ufl_type() class Expr(UFLType): """Base class for all UFL expression types. @@ -443,11 +444,6 @@ def __getitem__(self, index): pass -# Update Expr with metaclass properties (e.g. typecode or handler name) -# Explicitly done here instead of using `@ufl_type` to avoid circular imports. -update_ufl_type_attributes(Expr) - - def ufl_err_str(expr): """Return a UFL error string.""" if hasattr(expr, "_ufl_err_str_"): diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 572bfbfa4..4f77dce39 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -13,7 +13,6 @@ import typing from abc import ABC, abstractmethod -import ufl.core as core from ufl.utils.formatting import camel2underscore if typing.TYPE_CHECKING: @@ -50,110 +49,21 @@ def __ne__(self, other): return not self.__eq__(other) -def get_base_attr(cls, name): - """Return first non-``None`` attribute of given name among base classes.""" - for base in cls.mro(): - if hasattr(base, name): - attr = getattr(base, name) - if attr is not None: - return attr - return None - - -def check_is_terminal_consistency(cls): - """Check for consistency in ``is_terminal`` trait among superclasses.""" - if cls._ufl_is_terminal_ is None: - msg = ( - f"Class {cls.__name__} has not specified the is_terminal trait." - " Did you forget to inherit from Terminal or Operator?" - ) - raise TypeError(msg) - - base_is_terminal = get_base_attr(cls, "_ufl_is_terminal_") - if base_is_terminal is not None and cls._ufl_is_terminal_ != base_is_terminal: - msg = ( - f"Conflicting given and automatic 'is_terminal' trait for class {cls.__name__}." - " Check if you meant to inherit from Terminal or Operator." - ) - raise TypeError(msg) - - -def check_abstract_trait_consistency(cls): - """Check that the first base classes up to ``Expr`` are other UFL types.""" - for base in cls.mro(): - if base is core.expr.Expr: - break - if not issubclass(base, core.expr.Expr) and (ABC in base.__base__): - msg = ( - "Base class {0.__name__} of class {1.__name__} " - "is not an abstract subclass of {2.__name__}." - ) - raise TypeError(msg.format(base, cls, core.expr.Expr)) - - -def check_has_slots(cls): - """Check if type has __slots__ unless it is marked as exception with _ufl_noslots_.""" - if "_ufl_noslots_" in cls.__dict__: - return - - if "__slots__" not in cls.__dict__: - msg = ( - "Class {0.__name__} is missing the __slots__ " - "attribute and is not marked with _ufl_noslots_." - ) - raise TypeError(msg.format(cls)) - - # Check base classes for __slots__ as well, skipping object which is the last one - for base in cls.mro()[1:-1]: - if "__slots__" not in base.__dict__: - msg = "Class {0.__name__} is has a base class {1.__name__} with __slots__ missing." - raise TypeError(msg.format(cls, base)) - - -def check_type_traits_consistency(cls): - """Execute a variety of consistency checks on the ufl type traits.""" - # Check for consistency in global type collection sizes - assert UFLRegistry().number_registered_classes == len(UFLRegistry().all_classes) - - -def update_ufl_type_attributes(cls): - """Update UFL type attributes.""" - # Determine integer typecode by incrementally counting all types - # TODO: improve this implict post increment - cls._ufl_typecode_ = UFLRegistry().number_registered_classes - UFLRegistry().register_class(cls) - - # Determine handler name by a mapping from "TypeName" to "type_name" - cls._ufl_handler_name_ = camel2underscore(cls.__name__) - - def ufl_type(): - """Decorator to apply to every subclass in the UFL ``Expr`` and ``BaseForm`` hierarchy. - - This decorator contains a number of checks that are intended to - enforce uniform behaviour across UFL types. - - The rationale behind the checks and the meaning of the optional - arguments should be sufficiently documented in the source code - below. - """ + """Decorator to apply to every subclass in the UFL ``Expr`` and ``BaseForm`` hierarchy.""" def _ufl_type_decorator_(cls): """UFL type decorator.""" # Update attributes for UFLType instances (BaseForm and Expr objects) - update_ufl_type_attributes(cls) - if not issubclass(cls, core.expr.Expr): - # Don't need anything else for non Expr subclasses - return cls - - # Apply a range of consistency checks to detect bugs in type - # implementations that Python doesn't check for us, including - # some checks that a static language compiler would do for us - check_abstract_trait_consistency(cls) - check_has_slots(cls) - check_is_terminal_consistency(cls) + # Determine integer typecode by incrementally counting all types + cls._ufl_typecode_ = UFLRegistry().number_registered_classes + UFLRegistry().register_class(cls) + assert UFLRegistry().number_registered_classes == len(UFLRegistry().all_classes) + # Determine handler name by a mapping from "TypeName" to "type_name" + cls._ufl_handler_name_ = camel2underscore(cls.__name__) + return cls return _ufl_type_decorator_ @@ -211,7 +121,7 @@ def object_tracking(self) -> dict[type, tuple[int, int]]: return self._obj_tracking -class UFLType(ABC): +class UFLType: """Base class for all UFL types. Equip UFL types with some ufl specific properties. From 01cc31da3f997175841ee864eff87e0f0b126e0a Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:31:50 +0200 Subject: [PATCH 67/78] Move Expr properties back --- ufl/core/expr.py | 11 +++++++---- ufl/core/ufl_type.py | 40 +++++----------------------------------- 2 files changed, 12 insertions(+), 39 deletions(-) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index fece54106..457f8b8c7 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -91,6 +91,13 @@ class MyOperator(Operator): __slots__ = ("__weakref__", "_hash") # _ufl_noslots_ = True + _ufl_is_terminal_modifier_: bool = False + _ufl_is_in_reference_frame_: bool = False + + ufl_operands: tuple["FormArgument", ...] + ufl_shape: tuple[int, ...] + ufl_free_indices: tuple[int, ...] + ufl_index_dimensions: tuple # --- Basic object behaviour --- @@ -117,10 +124,6 @@ def __init__(self): # type traits that categorize types are mostly set to None for # Expr but should be True or False for any non-abstract type. - # The handler name. This is the name of the handler function you - # implement for this type in a multifunction. - _ufl_handler_name_ = "expr" - # --- All subclasses must define these object attributes --- # Each subclass of Expr is checked to have these properties in diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 4f77dce39..6390c6712 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -15,9 +15,6 @@ from ufl.utils.formatting import camel2underscore -if typing.TYPE_CHECKING: - from ufl.core.terminal import FormArgument - class UFLObject(ABC): """A UFL Object.""" @@ -129,38 +126,11 @@ class UFLType: __slots__: tuple[str, ...] = () + # TODO: ufl_handler_name type name -> remove _ufl_handler_name_: str = "ufl_type" + + # TODO: _ufl_is_terminal iff. is Cofunction or Terminal -> remove _ufl_is_terminal_: bool = False - _ufl_is_literal_: bool = False - _ufl_is_terminal_modifier_: bool = False - _ufl_is_in_reference_frame_: bool = False - - ufl_operands: tuple[FormArgument, ...] - ufl_shape: tuple[int, ...] - ufl_free_indices: tuple[int, ...] - ufl_index_dimensions: tuple - - # Each subclass of Expr is checked to have these methods in - # ufl_type - # FIXME: Add more and enable all - # _ufl_required_methods_: tuple[str, ...] = ( - # # To compute the hash on demand, this method is called. - # "_ufl_compute_hash_", - # # The data returned from this method is used to compute the - # # signature of a form - # "_ufl_signature_data_", - # # The == operator must be implemented to compare for identical - # # representation, used by set() and dict(). The __hash__ - # # operator is added by ufl_type. - # "__eq__", - # # To reconstruct an object of the same type with operands or - # # properties changed. - # "_ufl_expr_reconstruct_", # Implemented in Operator and Terminal so this should never - # fail - # "ufl_domains", - # # "ufl_cell", - # # "ufl_domain", - # # "__str__", - # # "__repr__", - # ) + # TODO:_ufl_is_literal_ iff. is Zero, ComplexValue, FloatValue or IntValue -> remove + _ufl_is_literal_: bool = False From bb66ec9940399f9b31d3b5e9921a36b79b6e9e5c Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:36:21 +0200 Subject: [PATCH 68/78] Clean up expr further --- ufl/core/expr.py | 69 ++++---------------------------------------- ufl/core/ufl_type.py | 3 ++ 2 files changed, 9 insertions(+), 63 deletions(-) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 457f8b8c7..09748836d 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -88,17 +88,17 @@ class MyOperator(Operator): # --- the top --- # This is to freeze member variables for objects of this class and # save memory by skipping the per-instance dict. - - __slots__ = ("__weakref__", "_hash") - # _ufl_noslots_ = True - _ufl_is_terminal_modifier_: bool = False - _ufl_is_in_reference_frame_: bool = False - ufl_operands: tuple["FormArgument", ...] ufl_shape: tuple[int, ...] ufl_free_indices: tuple[int, ...] ufl_index_dimensions: tuple + _ufl_is_terminal_modifier_: bool = False + _ufl_is_in_reference_frame_: bool = False + # _ufl_noslots_ = True + + __slots__ = ("__weakref__", "_hash") + # --- Basic object behaviour --- def __getnewargs__(self): @@ -116,63 +116,6 @@ def __init__(self): """Initialise.""" self._hash = None - # --- Type traits are added to subclasses by the ufl_type class - # --- decorator --- - - # Note: Some of these are modified after the Expr class definition - # because Expr is not defined yet at this point. Note: Boolean - # type traits that categorize types are mostly set to None for - # Expr but should be True or False for any non-abstract type. - - # --- All subclasses must define these object attributes --- - - # Each subclass of Expr is checked to have these properties in - # ufl_type - _ufl_required_properties_ = ( - # A tuple of operands, all of them Expr instances. - "ufl_operands", - # A tuple of ints, the value shape of the expression. - "ufl_shape", - # A tuple of free index counts. - "ufl_free_indices", - # A tuple providing the int dimension for each free index. - "ufl_index_dimensions", - ) - - ufl_operands: tuple["FormArgument", ...] - ufl_shape: tuple[int, ...] - _ufl_typecode_: int - ufl_free_indices: tuple[int, ...] - - # Each subclass of Expr is checked to have these methods in - # ufl_type - # FIXME: Add more and enable all - _ufl_required_methods_: tuple[str, ...] = ( - # To compute the hash on demand, this method is called. - "_ufl_compute_hash_", - # The data returned from this method is used to compute the - # signature of a form - "_ufl_signature_data_", - # The == operator must be implemented to compare for identical - # representation, used by set() and dict(). The __hash__ - # operator is added by ufl_type. - "__eq__", - # To reconstruct an object of the same type with operands or - # properties changed. - "_ufl_expr_reconstruct_", # Implemented in Operator and Terminal so this should never fail - "ufl_domains", - # "ufl_cell", - # "ufl_domain", - # "__str__", - # "__repr__", - ) - - # --- Global variables for collecting all types --- - - # A global dict mapping language_operator_name to the type it - # produces - _ufl_language_operators_: dict[str, object] = {} - # List of all terminal modifier types # --- Mechanism for profiling object creation and deletion --- diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 6390c6712..126c7c92d 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -124,6 +124,9 @@ class UFLType: Equip UFL types with some ufl specific properties. """ + # TODO: can we move this assignment into __new__? + _ufl_typecode_: int + __slots__: tuple[str, ...] = () # TODO: ufl_handler_name type name -> remove From 1c7b51e6da8ee1cc8e543ac528f5c4ab91f464d5 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:39:07 +0200 Subject: [PATCH 69/78] Remove more required methods --- test/test_custom_type.py | 2 ++ ufl/differentiation.py | 3 --- ufl/form.py | 9 --------- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/test/test_custom_type.py b/test/test_custom_type.py index e7e9932e9..907019385 100644 --- a/test/test_custom_type.py +++ b/test/test_custom_type.py @@ -11,8 +11,10 @@ from ufl.algebra import Product from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.constant import Constant +from ufl.core.ufl_type import ufl_type +@ufl_type() class LabeledConstant(Constant): def __init__(self, domain, shape=(), count=None, label: str = "c"): Constant.__init__(self, domain, shape, count) diff --git a/ufl/differentiation.py b/ufl/differentiation.py index bd284f9e1..e4fb18628 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -116,9 +116,6 @@ class BaseFormDerivative(CoefficientDerivative, BaseForm): """Derivative of a base form w.r.t the degrees of freedom in a discrete Coefficient.""" _ufl_noslots_ = True - _ufl_required_methods_: tuple[str, ...] = ( - CoefficientDerivative._ufl_required_methods_ + BaseForm._ufl_required_methods_ - ) def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): """Initalise.""" diff --git a/ufl/form.py b/ufl/form.py index 77da7be5a..a962ae854 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -86,14 +86,6 @@ class BaseForm(UFLType): ufl_operands: tuple[FormArgument, ...] - # Slots is kept empty to enable multiple inheritance with other - # classes - _ufl_required_methods_: tuple[str, ...] = ( - "_analyze_form_arguments", - "_analyze_domains", - "ufl_domains", - ) - def __init__(self): """Initialise.""" # Internal variables for caching form argument/coefficient data @@ -698,7 +690,6 @@ class FormSum(BaseForm): "_weights", "ufl_operands", ) - _ufl_required_methods_ = "_analyze_form_arguments" # type: ignore def __new__(cls, *args, **kwargs): """Create a new FormSum.""" From f4ba329ffc147fbe19f6787481fe9680e7c17ad8 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 12 Aug 2025 21:50:44 +0200 Subject: [PATCH 70/78] Extend custom type test to differentiation --- test/test_custom_type.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/test_custom_type.py b/test/test_custom_type.py index 907019385..8fa499bed 100644 --- a/test/test_custom_type.py +++ b/test/test_custom_type.py @@ -9,9 +9,12 @@ from ufl import Mesh, triangle from ufl.algebra import Product +from ufl.algorithms.analysis import extract_constants, has_exact_type from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.constant import Constant from ufl.core.ufl_type import ufl_type +from ufl.geometry import SpatialCoordinate +from ufl.operators import diff @ufl_type() @@ -38,3 +41,14 @@ def test(): assert ab.ufl_operands == (a, b) assert apply_algebra_lowering(ab) == ab + + x = SpatialCoordinate(domain) + + a_dx = diff(a, x) + assert a_dx == 0 + assert a_dx.ufl_shape == (2,) + + # TODO: expression does not get simplified, so can't check here for ax_dx == a + ax_dx = diff(a * x, x) + assert has_exact_type(ax_dx, LabeledConstant) + assert extract_constants(ax_dx)[0].label == "a" From 73fde954d3ddf3adaafdece0b99d3bd6c4fd4344 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:36:35 +0200 Subject: [PATCH 71/78] Ruff + revert branches --- .github/workflows/fenicsx-tests.yml | 2 +- .github/workflows/tsfc-tests.yml | 2 +- ufl/classes.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 8a38d6a52..7b63de130 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -5,7 +5,7 @@ name: FEniCSx integration on: pull_request: branches: - # - main + - main # Weekly build on Mondays at 8 am schedule: diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml index 5e152994b..2cda941a3 100644 --- a/.github/workflows/tsfc-tests.yml +++ b/.github/workflows/tsfc-tests.yml @@ -4,7 +4,7 @@ name: TSFC integration on: pull_request: branches: - # - main + - main jobs: tsfc-tests: diff --git a/ufl/classes.py b/ufl/classes.py index 0b94e4add..8f1e76a6e 100644 --- a/ufl/classes.py +++ b/ufl/classes.py @@ -434,4 +434,4 @@ # Collect all classes in sets automatically classified by some properties all_ufl_classes = set(UFLRegistry().all_classes) terminal_classes = set(c for c in all_ufl_classes if c._ufl_is_terminal_) # type: ignore -nonterminal_classes = set(c for c in all_ufl_classes if not c._ufl_is_terminal_) # type: ignore \ No newline at end of file +nonterminal_classes = set(c for c in all_ufl_classes if not c._ufl_is_terminal_) # type: ignore From d96f2a83bdee5a108a7ef10fa5c8920ea5a56094 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:45:25 +0200 Subject: [PATCH 72/78] Check without operands --- test/test_custom_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_custom_type.py b/test/test_custom_type.py index 8fa499bed..de6d84d9f 100644 --- a/test/test_custom_type.py +++ b/test/test_custom_type.py @@ -38,7 +38,7 @@ def test(): ab = a * b assert isinstance(ab, Product) - assert ab.ufl_operands == (a, b) + # assert ab.ufl_operands == (a, b) assert apply_algebra_lowering(ab) == ab From 0351ce4fe18da480e5141bbccedd4e29946de67d Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 4 Sep 2025 17:03:05 +0200 Subject: [PATCH 73/78] Ruff check --- ufl/core/ufl_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 126c7c92d..e77fb56e0 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -69,7 +69,7 @@ def _ufl_type_decorator_(cls): class UFLRegistry: """Maintains global informations of the registered types.""" - _instance: typing.Optional[UFLRegistry] = None + _instance: UFLRegistry | None = None _all_classes: list[type] # TODO: profiling should be maintained in an own profiling class/registry From 04f410ba0b044ac68b3497bce474dd4a60ec118b Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Wed, 10 Sep 2025 21:29:27 +0200 Subject: [PATCH 74/78] ruff --- test/test_apply_function_pullbacks.py | 2 +- test/test_automatic_differentiation.py | 24 +++++++++---------- test/test_degree_estimation.py | 2 +- test/test_derivative.py | 4 ++-- test/test_diff.py | 2 +- test/test_ffcforms.py | 2 +- test/test_indices.py | 4 ++-- ...mixed_function_space_with_mesh_sequence.py | 8 +++---- test/test_scratch.py | 6 ++--- test/test_signature.py | 10 ++++---- test/test_str.py | 2 +- test/test_strip_forms.py | 2 +- ufl/algorithms/domain_analysis.py | 2 +- ufl/algorithms/estimate_degrees.py | 2 +- ufl/compound_expressions.py | 2 +- ufl/exproperators.py | 2 +- ufl/formatting/ufl2unicode.py | 2 +- ufl/precedence.py | 2 +- 18 files changed, 40 insertions(+), 40 deletions(-) diff --git a/test/test_apply_function_pullbacks.py b/test/test_apply_function_pullbacks.py index 2bd43379e..58a6e9762 100755 --- a/test/test_apply_function_pullbacks.py +++ b/test/test_apply_function_pullbacks.py @@ -373,7 +373,7 @@ def test_apply_single_function_pullbacks_triangle(): J = Jacobian(domain) detJ = JacobianDeterminant(domain) Jinv = JacobianInverse(domain) - i, j, k, l = indices(4) # noqa: E741 + i, j, _k, _l = indices(4) # Contravariant H(div) Piola mapping: M_hdiv = (1.0 / detJ) * J diff --git a/test/test_automatic_differentiation.py b/test/test_automatic_differentiation.py index 7e4618130..c1f6ebc86 100755 --- a/test/test_automatic_differentiation.py +++ b/test/test_automatic_differentiation.py @@ -361,24 +361,24 @@ def _test_no_derivatives_but_still_changed(self, collection): def test_only_terminals_no_change(self, d_expr): - d, ex = d_expr + _d, ex = d_expr _test_no_derivatives_no_change(self, ex.terminals) def test_no_derivatives_no_change(self, d_expr): - d, ex = d_expr + _d, ex = d_expr _test_no_derivatives_no_change(self, ex.noncompounds) def xtest_compounds_no_derivatives_no_change( self, d_expr ): # This test fails with expand_compounds enabled - d, ex = d_expr + _d, ex = d_expr _test_no_derivatives_no_change(self, ex.compounds) def test_zero_derivatives_of_terminals_produce_the_right_types_and_shapes(self, d_expr): - d, ex = d_expr + _d, ex = d_expr _test_zero_derivatives_of_terminals_produce_the_right_types_and_shapes(self, ex) @@ -405,7 +405,7 @@ def _test_zero_derivatives_of_terminals_produce_the_right_types_and_shapes(self, def test_zero_diffs_of_terminals_produce_the_right_types_and_shapes(self, d_expr): - d, ex = d_expr + _d, ex = d_expr _test_zero_diffs_of_terminals_produce_the_right_types_and_shapes(self, ex) @@ -433,7 +433,7 @@ def _test_zero_diffs_of_terminals_produce_the_right_types_and_shapes(self, colle def test_zero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(self, d_expr): - d, ex = d_expr + _d, ex = d_expr _test_zero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(self, ex) @@ -453,7 +453,7 @@ def _test_zero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(se def test_zero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, d_expr): - d, ex = d_expr + _d, ex = d_expr _test_zero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, ex) @@ -477,7 +477,7 @@ def _test_zero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, co def test_nonzero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(self, d_expr): - d, ex = d_expr + _d, ex = d_expr _test_nonzero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(self, ex) @@ -505,7 +505,7 @@ def _test_nonzero_derivatives_of_noncompounds_produce_the_right_types_and_shapes def test_nonzero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, d_expr): - d, ex = d_expr + _d, ex = d_expr _test_nonzero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, ex) @@ -538,7 +538,7 @@ def _test_nonzero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, def test_grad_coeff(self, d_expr): - d, collection = d_expr + _d, collection = d_expr u = collection.shared_objects.u v = collection.shared_objects.v @@ -563,7 +563,7 @@ def test_grad_coeff(self, d_expr): def test_derivative_grad_coeff(self, d_expr): - d, collection = d_expr + _d, collection = d_expr u = collection.shared_objects.u v = collection.shared_objects.v @@ -586,7 +586,7 @@ def test_derivative_grad_coeff(self, d_expr): def xtest_derivative_grad_coeff_with_variation_components(self, d_expr): - d, collection = d_expr + _d, collection = d_expr v = collection.shared_objects.v w = collection.shared_objects.w diff --git a/test/test_degree_estimation.py b/test/test_degree_estimation.py index cf8e802d1..73fbbfb07 100755 --- a/test/test_degree_estimation.py +++ b/test/test_degree_estimation.py @@ -93,7 +93,7 @@ def test_total_degree_estimation(): assert estimate_total_polynomial_degree(f2**3 * v1 + f1 * v1) == 7 # Math functions of constant values are constant values - nx, ny = FacetNormal(domain) + nx, _ny = FacetNormal(domain) e = nx**2 for f in [sin, cos, tan, abs, lambda z: z**7]: assert estimate_total_polynomial_degree(f(e)) == 0 diff --git a/test/test_derivative.py b/test/test_derivative.py index 84e755dcb..8799864a5 100755 --- a/test/test_derivative.py +++ b/test/test_derivative.py @@ -657,7 +657,7 @@ def test_vector_coefficient_scalar_derivatives(self): integrand = inner(f, g) - i0, i1, i2, i3, i4 = [Index(count=c) for c in range(5)] + i0, i1, _i2, _i3, _i4 = [Index(count=c) for c in range(5)] expected = as_tensor(df[i1] * dv, (i1,))[i0] * g[i0] F = integrand * dx @@ -685,7 +685,7 @@ def test_vector_coefficient_derivatives(self): integrand = inner(f, g) - i0, i1, i2, i3, i4 = [Index(count=c) for c in range(5)] + i0, i1, i2, _i3, _i4 = [Index(count=c) for c in range(5)] expected = as_tensor(df[i2, i1] * dv[i1], (i2,))[i0] * g[i0] F = integrand * dx diff --git a/test/test_diff.py b/test/test_diff.py index bc0fb2239..2cf5512c5 100755 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -33,7 +33,7 @@ def get_variables(): @pytest.fixture def v(): - xv, vv = get_variables() + _xv, vv = get_variables() return vv diff --git a/test/test_ffcforms.py b/test/test_ffcforms.py index 689354fbe..a6db6e8e8 100755 --- a/test/test_ffcforms.py +++ b/test/test_ffcforms.py @@ -429,6 +429,6 @@ def HodgeLaplaceGradCurl(space, fspace): VectorLagrange = LagrangeElement(shape, order + 1, (3,)) domain = Mesh(LagrangeElement(shape, 1, (3,))) - [a, L] = HodgeLaplaceGradCurl( + [_a, _L] = HodgeLaplaceGradCurl( FunctionSpace(domain, MixedElement([GRAD, CURL])), FunctionSpace(domain, VectorLagrange) ) diff --git a/test/test_indices.py b/test/test_indices.py index 76c4d0e24..951a63608 100755 --- a/test/test_indices.py +++ b/test/test_indices.py @@ -245,7 +245,7 @@ def test_indexed(self): v = TestFunction(space) u = TrialFunction(space) Coefficient(space) - i, j, k, l = indices(4) # noqa: E741 + i, j, _k, _l = indices(4) a = v[i] self.assertSameIndices(a, (i,)) @@ -265,7 +265,7 @@ def test_spatial_derivative(self): space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) - i, j, k, l = indices(4) # noqa: E741 + i, j, _k, _l = indices(4) d = 2 a = v[i].dx(i) diff --git a/test/test_mixed_function_space_with_mesh_sequence.py b/test/test_mixed_function_space_with_mesh_sequence.py index a888ccaa6..280e9b1d3 100644 --- a/test/test_mixed_function_space_with_mesh_sequence.py +++ b/test/test_mixed_function_space_with_mesh_sequence.py @@ -38,10 +38,10 @@ def test_mixed_function_space_with_mesh_sequence_basic(): v = TestFunction(V) f = Coefficient(V, count=1000) g = Coefficient(V, count=2000) - u0, u1, u2 = split(u) - v0, v1, v2 = split(v) - f0, f1, f2 = split(f) - g0, g1, g2 = split(g) + u0, _u1, _u2 = split(u) + _v0, v1, _v2 = split(v) + f0, _f1, _f2 = split(f) + _g0, _g1, _g2 = split(g) dx1 = Measure("dx", mesh1) x = SpatialCoordinate(mesh1) form = x[1] * f0 * inner(grad(u0), v1) * dx1(999) diff --git a/test/test_scratch.py b/test/test_scratch.py index 97502df2b..f8a7c19da 100755 --- a/test/test_scratch.py +++ b/test/test_scratch.py @@ -194,9 +194,9 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): def test_unit_tensor(self): - E2_1, ii = unit_indexed_tensor((2,), (1,)) - E3_1, ii = unit_indexed_tensor((3,), (1,)) - E22_10, ii = unit_indexed_tensor((2, 2), (1, 0)) + _, _ = unit_indexed_tensor((2,), (1,)) + _, _ = unit_indexed_tensor((3,), (1,)) + _, _ = unit_indexed_tensor((2, 2), (1, 0)) # TODO: Evaluate and assert values diff --git a/test/test_signature.py b/test/test_signature.py index 88df7b462..1c9b7906a 100755 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -83,7 +83,7 @@ def test_terminal_hashdata_depends_on_literals(self): hashes = set() def forms(): - i, j = indices(2) + _i, j = indices(2) for d, cell in [(2, triangle), (3, tetrahedron)]: domain = Mesh(LagrangeElement(cell, 1, (d,)), ufl_id=d - 2) x = SpatialCoordinate(domain) @@ -409,7 +409,7 @@ def hashdatas(): hashes.add(hash(expr)) yield compute_multiindex_hashdata(expr, {}) - c, d, r, h = compute_unique_multiindex_hashdatas(hashdatas()) + c, d, _r, _h = compute_unique_multiindex_hashdatas(hashdatas()) assert c == 9 assert d == 9 - 1 # (1,0 is repeated, therefore -1) assert len(reprs) == 9 - 1 @@ -435,7 +435,7 @@ def hashdatas(): hashes.add(hash(expr)) yield compute_multiindex_hashdata(expr, {}) - c, d, r, h = compute_unique_multiindex_hashdatas(hashdatas()) + c, d, _r, _h = compute_unique_multiindex_hashdatas(hashdatas()) assert c == 3 + 9 + 9 assert d == 1 + 1 assert len(reprs) == 3 + 9 + 9 @@ -454,7 +454,7 @@ def hashdatas(): # and hashes changing because new indices are created each # repetition. index_numbering = {} - i, j, k, l = indices(4) # noqa: E741 + i, j, k, _l = indices(4) for expr in ( MultiIndex((i,)), MultiIndex((i,)), # r @@ -469,7 +469,7 @@ def hashdatas(): hashes.add(hash(expr)) yield compute_multiindex_hashdata(expr, index_numbering) - c, d, r, h = compute_unique_multiindex_hashdatas(hashdatas()) + c, d, _r, _h = compute_unique_multiindex_hashdatas(hashdatas()) assert c == nrep * 8 assert d == 5 assert len(reprs) == nrep * 5 diff --git a/test/test_str.py b/test/test_str.py index a2102e546..e5d43fd4f 100755 --- a/test/test_str.py +++ b/test/test_str.py @@ -100,7 +100,7 @@ def test_str_list_vector(): def test_str_list_vector_with_zero(): domain = Mesh(LagrangeElement(tetrahedron, 1, (3,))) - x, y, z = SpatialCoordinate(domain) + x, _y, _z = SpatialCoordinate(domain) v = as_vector((x, 0, 0)) assert str(v) == (f"[{x}, 0, 0]") diff --git a/test/test_strip_forms.py b/test/test_strip_forms.py index bc6e6a735..9f9857870 100644 --- a/test/test_strip_forms.py +++ b/test/test_strip_forms.py @@ -87,7 +87,7 @@ def test_strip_form_arguments_strips_data_refs(): assert sys.getrefcount(coeff_data) == MIN_REF_COUNT + 1 assert sys.getrefcount(const_data) == MIN_REF_COUNT + 1 - stripped_form, mapping = strip_terminal_data(form) + _stripped_form, mapping = strip_terminal_data(form) del form, mapping gc.collect() # This is needed to update the refcounts diff --git a/ufl/algorithms/domain_analysis.py b/ufl/algorithms/domain_analysis.py index 7a37f44e1..3fe3d925b 100644 --- a/ufl/algorithms/domain_analysis.py +++ b/ufl/algorithms/domain_analysis.py @@ -284,7 +284,7 @@ def build_integral_data(integrals): # Build list with canonical ordering, iteration over dicts # is not deterministic across python versions def keyfunc(item): - (d, itype, sid), integrals = item + (d, itype, sid), _integrals = item sid_int = tuple(-1 if i == "otherwise" else i for i in sid) return (d._ufl_sort_key_(), itype, (type(sid).__name__,), sid_int) diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index f0b7c92ca..451e40a11 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -269,7 +269,7 @@ def power(self, v, a, b): If b is a positive integer: degree(a**b) == degree(a)*b otherwise use the heuristic: degree(a**b) == degree(a) + 2. """ - f, g = v.ufl_operands + _f, g = v.ufl_operands if isinstance(g, IntValue): gi = g.value() diff --git a/ufl/compound_expressions.py b/ufl/compound_expressions.py index 6f92dd00d..bc48de105 100644 --- a/ufl/compound_expressions.py +++ b/ufl/compound_expressions.py @@ -68,7 +68,7 @@ def generic_pseudo_inverse_expr(A): def pseudo_inverse_expr(A): """Compute the Penrose-Moore pseudo-inverse of A: (A.T*A)^-1 * A.T.""" - m, n = A.ufl_shape + _m, n = A.ufl_shape if n == 1: # Simpler special case for 1d i, j, k = indices(3) diff --git a/ufl/exproperators.py b/ufl/exproperators.py index 427f411c6..e5ba1e87c 100644 --- a/ufl/exproperators.py +++ b/ufl/exproperators.py @@ -108,7 +108,7 @@ def _mult(a, b): bfi = b.ufl_free_indices afid = a.ufl_index_dimensions bfid = b.ufl_index_dimensions - fi, fid, ri, rid = merge_overlapping_indices(afi, afid, bfi, bfid) + fi, fid, ri, _rid = merge_overlapping_indices(afi, afid, bfi, bfid) # Pick out valid non-scalar products here (dot products): # - matrix-matrix (A*B, M*grad(u)) => A . B diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 5d8d5f0c5..3c7407c62 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -568,7 +568,7 @@ def transposed(self, o, a): def indexed(self, o, A, ii): """Format an indexed.""" - op0, op1 = o.ufl_operands + op0, _op1 = o.ufl_operands Aprec = precedence(op0) oprec = precedence(o) if Aprec > oprec: diff --git a/ufl/precedence.py b/ufl/precedence.py index e9ad307f3..e32a09806 100644 --- a/ufl/precedence.py +++ b/ufl/precedence.py @@ -115,7 +115,7 @@ def build_precedence_mapping(precedence_list): def assign_precedences(precedence_list): """Given a precedence list, assign ints to class._precedence.""" - pm, missing = build_precedence_mapping(precedence_list) + pm, _missing = build_precedence_mapping(precedence_list) for c, p in sorted(pm.items(), key=lambda x: x[0].__name__): c._precedence = p # TODO: problem if this warns? From b666d503168fd74ba7689b26a426482b8492e5dd Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 19 Oct 2025 12:55:42 +0200 Subject: [PATCH 75/78] Adapt to changes on main --- ufl/algorithms/apply_coefficient_split.py | 10 ++++++---- ufl/algorithms/apply_restrictions.py | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ufl/algorithms/apply_coefficient_split.py b/ufl/algorithms/apply_coefficient_split.py index b52a6ceb6..464339be7 100644 --- a/ufl/algorithms/apply_coefficient_split.py +++ b/ufl/algorithms/apply_coefficient_split.py @@ -123,8 +123,9 @@ def _( ) -> Expr: """Handle ReferenceGrad.""" (op,) = o.ufl_operands - if not op._ufl_is_terminal_modifier_: - raise ValueError(f"Must be a terminal modifier: {op!r}.") + # TODO: + # if not op._ufl_is_terminal_modifier_: + # raise ValueError(f"Must be a terminal modifier: {op!r}.") return self( op, reference_value=reference_value, @@ -144,8 +145,9 @@ def _( if restricted is not None: raise RuntimeError(f"Can not apply Restricted on a Restricted: got {o}") (op,) = o.ufl_operands - if not op._ufl_is_terminal_modifier_: - raise ValueError(f"Must be a terminal modifier: {op!r}.") + # TODO: + # if not op._ufl_is_terminal_modifier_: + # raise ValueError(f"Must be a terminal modifier: {op!r}.") return self( op, reference_value=reference_value, diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index 2533a542a..a2cbcfc12 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -106,7 +106,7 @@ def _require_restriction(self, o): return o else: raise ValueError( - f"Discontinuous type {o._ufl_class_.__name__} must be restricted." + f"Discontinuous type {type(o).__name__} must be restricted." ) elif self.current_restriction in ["+", "-"]: if r not in ["+", "-"]: @@ -168,7 +168,7 @@ def _opposite(self, o): return o else: raise ValueError( - f"Discontinuous type {o._ufl_class_.__name__} must be restricted." + f"Discontinuous type {type(o).__name__} must be restricted." ) elif self.current_restriction in ["+", "-"]: if r is None: From d09a898584ee2fb522e11da95bc8018e0200ed12 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 19 Oct 2025 12:56:22 +0200 Subject: [PATCH 76/78] Ruff --- ufl/algorithms/apply_restrictions.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index a2cbcfc12..23b4d7bce 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -105,9 +105,7 @@ def _require_restriction(self, o): if r is None: return o else: - raise ValueError( - f"Discontinuous type {type(o).__name__} must be restricted." - ) + raise ValueError(f"Discontinuous type {type(o).__name__} must be restricted.") elif self.current_restriction in ["+", "-"]: if r not in ["+", "-"]: raise ValueError( @@ -167,9 +165,7 @@ def _opposite(self, o): if r is None: return o else: - raise ValueError( - f"Discontinuous type {type(o).__name__} must be restricted." - ) + raise ValueError(f"Discontinuous type {type(o).__name__} must be restricted.") elif self.current_restriction in ["+", "-"]: if r is None: raise ValueError( From 059cee17904942bb174d7ccad018d635ac7fe8e4 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:53:07 +0200 Subject: [PATCH 77/78] Adapt to main --- ufl/core/expr.py | 2 +- ufl/core/ufl_type.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index db50260f2..c745bc1fa 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -20,7 +20,7 @@ import warnings if typing.TYPE_CHECKING: - import ufl.core.terminal + from ufl.core import FormArgument from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.ufl_type import UFLRegistry, UFLType, ufl_type diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index b082e2b08..ab0e0af85 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -21,7 +21,7 @@ class UFLObject(abc.ABC): _ufl_is_terminal_: bool - @abstractmethod + @abc.abstractmethod def _ufl_hash_data_(self) -> typing.Hashable: """Return hashable data that uniquely defines this object.""" ... From 4ab930bc18911fe25aadc6738a5e011c1ec934c5 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:55:30 +0200 Subject: [PATCH 78/78] One more --- ufl/core/expr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index c745bc1fa..09748836d 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -20,7 +20,7 @@ import warnings if typing.TYPE_CHECKING: - from ufl.core import FormArgument + from ufl.core.terminal import FormArgument from ufl.core.compute_expr_hash import compute_expr_hash from ufl.core.ufl_type import UFLRegistry, UFLType, ufl_type