1+ from collections .abc import Callable
2+ from typing import Optional
3+
14import dolfinx
5+ import numpy as np
6+ import ufl
27from dolfinx import fem
8+ from packaging import version
39
410
511def as_fenics_constant (
@@ -17,8 +23,8 @@ def as_fenics_constant(
1723 Raises:
1824 TypeError: if the value is not a float, an int or a dolfinx.Constant
1925 """
20- if isinstance (value , ( float , int ) ):
21- return fem .Constant (mesh , dolfinx .default_scalar_type (value ))
26+ if isinstance (value , float | int ):
27+ return fem .Constant (mesh , dolfinx .default_scalar_type (float ( value ) ))
2228 elif isinstance (value , fem .Constant ):
2329 return value
2430 else :
@@ -27,7 +33,251 @@ def as_fenics_constant(
2733 )
2834
2935
30- from packaging import version
36+ def as_mapped_function (
37+ value : Callable ,
38+ function_space : Optional [fem .functionspace ] = None ,
39+ t : Optional [fem .Constant ] = None ,
40+ temperature : Optional [fem .Function | fem .Constant | ufl .core .expr .Expr ] = None ,
41+ ) -> ufl .core .expr .Expr :
42+ """Maps a user given callable function to the mesh, time or temperature within
43+ festim as needed
44+
45+ Args:
46+ value: the callable to convert
47+ function_space: the function space of the domain, optional
48+ t: the time, optional
49+ temperature: the temperature, optional
50+
51+ Returns:
52+ The mapped function
53+ """
54+
55+ # Extract the input variable names in the callable function `value`
56+ arguments = value .__code__ .co_varnames
57+
58+ kwargs = {}
59+ if "t" in arguments :
60+ kwargs ["t" ] = t
61+ if "x" in arguments :
62+ x = ufl .SpatialCoordinate (function_space .mesh )
63+ kwargs ["x" ] = x
64+ if "T" in arguments :
65+ kwargs ["T" ] = temperature
66+
67+ return value (** kwargs )
68+
69+
70+ def as_fenics_interp_expr_and_function (
71+ value : Callable ,
72+ function_space : dolfinx .fem .function .FunctionSpace ,
73+ t : Optional [fem .Constant ] = None ,
74+ temperature : Optional [fem .Function | fem .Constant | ufl .core .expr .Expr ] = None ,
75+ ) -> tuple [fem .Expression , fem .Function ]:
76+ """Takes a user given callable function, maps the function to the mesh, time or
77+ temperature within festim as needed. Then creates the fenics interpolation
78+ expression and function objects
79+
80+ Args:
81+ value: the callable to convert
82+ function_space: The function space to interpolate function over
83+ t: the time, optional
84+ temperature: the temperature, optional
85+
86+ Returns:
87+ fenics interpolation expression, fenics function
88+ """
89+
90+ mapped_function = as_mapped_function (
91+ value = value , function_space = function_space , t = t , temperature = temperature
92+ )
93+
94+ fenics_interpolation_expression = fem .Expression (
95+ mapped_function ,
96+ get_interpolation_points (function_space .element ),
97+ )
98+
99+ fenics_object = fem .Function (function_space )
100+ fenics_object .interpolate (fenics_interpolation_expression )
101+
102+ return fenics_interpolation_expression , fenics_object
103+
104+
105+ class Value :
106+ """
107+ A class to handle input values from users and convert them to a relevent fenics
108+ object
109+
110+ Args:
111+ input_value: The value of the user input
112+
113+ Attributes:
114+ input_value : The value of the user input
115+ fenics_interpolation_expression : The expression of the user input that is used
116+ to update the `fenics_object`
117+ fenics_object : The value of the user input in fenics format
118+ explicit_time_dependent : True if the user input value is explicitly time
119+ dependent
120+ temperature_dependent : True if the user input value is temperature dependent
121+
122+ """
123+
124+ input_value : (
125+ float
126+ | int
127+ | fem .Constant
128+ | np .ndarray
129+ | fem .Expression
130+ | ufl .core .expr .Expr
131+ | fem .Function
132+ )
133+
134+ ufl_expression : ufl .core .expr .Expr
135+ fenics_interpolation_expression : fem .Expression
136+ fenics_object : fem .Function | fem .Constant | ufl .core .expr .Expr
137+ explicit_time_dependent : bool
138+ temperature_dependent : bool
139+
140+ def __init__ (self , input_value ):
141+ self .input_value = input_value
142+
143+ self .ufl_expression = None
144+ self .fenics_interpolation_expression = None
145+ self .fenics_object = None
146+
147+ def __repr__ (self ) -> str :
148+ return str (self .input_value )
149+
150+ @property
151+ def input_value (self ):
152+ return self ._input_value
153+
154+ @input_value .setter
155+ def input_value (self , value ):
156+ if value is None :
157+ self ._input_value = value
158+ elif isinstance (
159+ value ,
160+ float
161+ | int
162+ | fem .Constant
163+ | np .ndarray
164+ | fem .Expression
165+ | ufl .core .expr .Expr
166+ | fem .Function ,
167+ ):
168+ self ._input_value = value
169+ elif callable (value ):
170+ self ._input_value = value
171+ else :
172+ raise TypeError (
173+ "Value must be a float, int, fem.Constant, np.ndarray, fem.Expression,"
174+ f" ufl.core.expr.Expr, fem.Function, or callable not { value } "
175+ )
176+
177+ @property
178+ def explicit_time_dependent (self ) -> bool :
179+ """Returns true if the value given is time dependent"""
180+ if self .input_value is None :
181+ return False
182+ if isinstance (self .input_value , fem .Constant | ufl .core .expr .Expr ):
183+ return False
184+ if callable (self .input_value ):
185+ arguments = self .input_value .__code__ .co_varnames
186+ return "t" in arguments
187+ else :
188+ return False
189+
190+ @property
191+ def temperature_dependent (self ) -> bool :
192+ """Returns true if the value given is temperature dependent"""
193+ if self .input_value is None :
194+ return False
195+ if isinstance (self .input_value , fem .Constant | ufl .core .expr .Expr ):
196+ return False
197+ if callable (self .input_value ):
198+ arguments = self .input_value .__code__ .co_varnames
199+ return "T" in arguments
200+ else :
201+ return False
202+
203+ def convert_input_value (
204+ self ,
205+ function_space : Optional [dolfinx .fem .function .FunctionSpace ] = None ,
206+ t : Optional [fem .Constant ] = None ,
207+ temperature : Optional [fem .Function | fem .Constant | ufl .core .expr .Expr ] = None ,
208+ up_to_ufl_expr : Optional [bool ] = False ,
209+ ):
210+ """Converts a user given value to a relevent fenics object depending
211+ on the type of the value provided
212+
213+ Args:
214+ function_space: the function space of the fenics object, optional
215+ t: the time, optional
216+ temperature: the temperature, optional
217+ up_to_ufl_expr: if True, the value is only mapped to a function if the input
218+ is callable, not interpolated or converted to a function, optional
219+ """
220+ if isinstance (
221+ self .input_value , fem .Constant | fem .Function | ufl .core .expr .Expr
222+ ):
223+ self .fenics_object = self .input_value
224+
225+ elif isinstance (self .input_value , fem .Expression ):
226+ self .fenics_interpolation_expression = self .input_value
227+
228+ elif isinstance (self .input_value , float | int ):
229+ self .fenics_object = as_fenics_constant (
230+ value = self .input_value , mesh = function_space .mesh
231+ )
232+
233+ elif callable (self .input_value ):
234+ args = self .input_value .__code__ .co_varnames
235+ # if only t is an argument, create constant object
236+ if "t" in args and "x" not in args and "T" not in args :
237+ if not isinstance (self .input_value (t = float (t )), float | int ):
238+ raise ValueError (
239+ "self.value should return a float or an int, not "
240+ + f"{ type (self .input_value (t = float (t )))} "
241+ )
242+
243+ self .fenics_object = as_fenics_constant (
244+ value = self .input_value (t = float (t )), mesh = function_space .mesh
245+ )
246+
247+ elif up_to_ufl_expr :
248+ self .fenics_object = as_mapped_function (
249+ value = self .input_value ,
250+ function_space = function_space ,
251+ t = t ,
252+ temperature = temperature ,
253+ )
254+
255+ else :
256+ self .fenics_interpolation_expression , self .fenics_object = (
257+ as_fenics_interp_expr_and_function (
258+ value = self .input_value ,
259+ function_space = function_space ,
260+ t = t ,
261+ temperature = temperature ,
262+ )
263+ )
264+
265+ def update (self , t : float ):
266+ """Updates the value
267+
268+ Args:
269+ t: the time
270+ """
271+ if callable (self .input_value ):
272+ arguments = self .input_value .__code__ .co_varnames
273+
274+ if isinstance (self .fenics_object , fem .Constant ) and "t" in arguments :
275+ self .fenics_object .value = float (self .input_value (t = t ))
276+
277+ elif isinstance (self .fenics_object , fem .Function ):
278+ if self .fenics_interpolation_expression is not None :
279+ self .fenics_object .interpolate (self .fenics_interpolation_expression )
280+
31281
32282# Check the version of dolfinx
33283dolfinx_version = dolfinx .__version__
0 commit comments