Reference Guide  2.5.0
function_space.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2017-2024, Science and Technology Facilities Council.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are met:
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice,
14 # this list of conditions and the following disclaimer in the documentation
15 # and/or other materials provided with the distribution.
16 #
17 # * Neither the name of the copyright holder nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 # -----------------------------------------------------------------------------
34 # Authors R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab
35 # Modified I. Kavcic, Met Office
36 # J. Henrichs, Bureau of Meteorology
37 
38 '''This module contains the FunctionSpace object and related constants.
39 '''
40 
41 from psyclone.errors import InternalError, FieldNotFoundError, GenerationError
42 from psyclone.domain.lfric.lfric_constants import LFRicConstants
43 
44 
45 class FunctionSpace():
46  '''
47  Manages the name of a function space. If it is an any_space or
48  any_discontinuous_space then its name is mangled such that it is unique
49  within the scope of an Invoke.
50 
51  :param str name: original name of function space to create a \
52  mangled name for.
53  :param kernel_args: object encapsulating all arguments to the kernel, \
54  one or more of which are on this function space.
55  :type kernel_args: :py:class:`psyclone.dynamo0p3.DynKernelArguments`
56 
57  :raises InternalError: if an unrecognised function space is encountered.
58 
59  '''
60 
61  def __init__(self, name, kernel_args):
62  self._orig_name_orig_name = name
63  self._kernel_args_kernel_args = kernel_args
64  self._mangled_name_mangled_name = None
65  self._short_name_short_name = None
66 
67  const = LFRicConstants()
68  # Check whether the function space name is a valid name
69  if self._orig_name_orig_name not in const.VALID_FUNCTION_SPACE_NAMES:
70  raise InternalError(
71  f"Unrecognised function space '{self._orig_name}'. The "
72  f"supported spaces are {const.VALID_FUNCTION_SPACE_NAMES}.")
73 
74  if self._orig_name_orig_name not in const.VALID_ANY_SPACE_NAMES + \
75  const.VALID_ANY_DISCONTINUOUS_SPACE_NAMES:
76  # We only need to name-mangle any_space and
77  # any_discontinuous_space spaces
78  self._short_name_short_name = self._orig_name_orig_name
79  self._mangled_name_mangled_name = self._orig_name_orig_name
80  else:
81  # Create short names for any_*_spaces used for mangled names
82  self._short_name_short_name = self._shorten_fs_name_shorten_fs_name()
83  # We do not construct the name-mangled name at this point
84  # as the full list of kernel arguments may still be under
85  # construction.
86 
87  @property
88  def orig_name(self):
89  '''
90  Returns the name of this function space as declared in the
91  kernel meta-data.
92 
93  :returns: original name of this function space.
94  :rtype: str
95 
96  '''
97  return self._orig_name_orig_name
98 
99  @property
100  def short_name(self):
101  '''
102  Returns the short name of this function space (original name for a
103  valid LFRic function space and condensed name for any_*_spaces).
104 
105  :returns: short name of this function space.
106  :rtype: str
107 
108  '''
109  return self._short_name_short_name
110 
111  @property
112  def mangled_name(self):
113  '''
114  Returns the mangled name of this function space such that it is
115  unique within the scope of an invoke. If the mangled name has not
116  been generated then we do that the first time we are called.
117 
118  :returns: mangled name of this function space.
119  :rtype: str
120 
121  '''
122  if self._mangled_name_mangled_name:
123  return self._mangled_name_mangled_name
124  # Cannot use kernel_args.field_on_space(x) here because that
125  # routine itself requires the mangled name in order to identify
126  # whether the space is present in the kernel call.
127  self._mangled_name_mangled_name = self._mangle_fs_name_mangle_fs_name()
128  return self._mangled_name_mangled_name
129 
130  def _mangle_fs_name(self):
131  '''
132  Constructs the mangled version of a function-space name given a list
133  of kernel arguments if the argument's function space is any_*_space
134  (if the argument's function space is one of the valid LFRic function
135  spaces then the mangled name is the original name, set in the class
136  initialisation). The mangled name is the short name of the function
137  space combined with the argument's name.
138 
139  :returns: mangled name of this function space.
140  :rtype: str
141 
142  :raises InternalError: if a function space to create the mangled \
143  name for is not one of 'any_space' or \
144  'any_discontinuous_space' spaces.
145  :raises FieldNotFoundError: if no kernel argument was found on \
146  the specified function space.
147 
148  '''
149  # First check that the the function space is one of any_*_space
150  # spaces and then proceed with name-mangling.
151  const = LFRicConstants()
152  if self._orig_name_orig_name not in const.VALID_ANY_SPACE_NAMES + \
153  const.VALID_ANY_DISCONTINUOUS_SPACE_NAMES:
154  raise InternalError(
155  f"_mangle_fs_name: function space '{self._orig_name}' is not "
156  f"one of {const.VALID_ANY_SPACE_NAMES} or "
157  f"{const.VALID_ANY_DISCONTINUOUS_SPACE_NAMES} spaces.")
158 
159  # List kernel arguments
160  args = self._kernel_args_kernel_args.args
161  # Mangle the function space name for any_*_space
162  for arg in args:
163  for fspace in arg.function_spaces:
164  if (fspace and fspace.orig_name.lower() ==
165  self._orig_name_orig_name.lower()):
166  mngl_name = self._short_name_short_name + "_" + arg.name
167  return mngl_name
168  # Raise an error if there are no kernel arguments on this
169  # function space
170  raise FieldNotFoundError(f"No kernel argument found for function "
171  f"space '{self._orig_name}'")
172 
173  def _shorten_fs_name(self):
174  '''
175  Creates short names for any_*_spaces to be used for mangled names
176  from the condensed keywords and function space IDs.
177 
178  :returns: short name of this function space.
179  :rtype: str
180 
181  :raises InternalError: if a function space to create the short \
182  name for is not one of 'any_space' or \
183  'any_discontinuous_space' spaces.
184 
185  '''
186  # Create a start for the short name and check whether the function
187  # space is one of any_*_space spaces
188  const = LFRicConstants()
189  if self._orig_name_orig_name in const.VALID_ANY_SPACE_NAMES:
190  start = "a"
191  elif self._orig_name_orig_name in const.VALID_ANY_DISCONTINUOUS_SPACE_NAMES:
192  start = "ad"
193  else:
194  raise InternalError(
195  f"_shorten_fs_name: function space '{self._orig_name}' is not "
196  f"one of {const.VALID_ANY_SPACE_NAMES} or "
197  f"{const.VALID_ANY_DISCONTINUOUS_SPACE_NAMES} spaces.")
198 
199  # Split name string to find any_*_space ID and create a short name as
200  # "<start>" + "spc" + "ID"
201  fslist = self._orig_name_orig_name.split("_")
202  self._short_name_short_name = start + "spc" + fslist[-1]
203  return self._short_name_short_name
204 
205  @property
206  def map_name(self):
207  ''':returns: a dofmap name for the supplied FunctionSpace.
208  :rtype: str
209  '''
210  return "map_" + self.mangled_namemangled_name
211 
212  @property
213  def cbanded_map_name(self):
214  ''':returns: the name of a column-banded dofmap for this FunctionSpace.
215  :rtype: str
216  '''
217  return "cbanded_map_" + self.mangled_namemangled_name
218 
219  @property
221  ''':returns: the name of a CMA indirection dofmap for the supplied \
222  FunctionSpace.
223  :rtype: str
224  '''
225  return "cma_indirection_map_" + self.mangled_namemangled_name
226 
227  @property
228  def ndf_name(self):
229  ''':returns: a ndf name for this FunctionSpace object.
230  :rtype: str
231  '''
232  return "ndf_" + self.mangled_namemangled_name
233 
234  @property
235  def undf_name(self):
236  ''':returns: a undf name for this FunctionSpace object.
237  :rtype: str
238  '''
239  return "undf_" + self.mangled_namemangled_name
240 
241  def get_basis_name(self, qr_var=None, on_space=None):
242  '''
243  Returns a name for the basis function on this FunctionSpace. If
244  the name of an associated quadrature object is supplied then this
245  is appended to the returned name. Similarly, if the function space
246  at which the basis is to be evaluated is supplied then this is
247  also appended to the name.
248 
249  :param string qr_var: the name of the Quadrature Object for which the \
250  basis functions are required
251  :param on_space: the function space at which the basis functions \
252  will be evaluated
253  :type on_space: :py:class:`psyclone.domain.lfric.FunctionSpace`
254  :returns: name for the Fortran array holding the basis function
255  :rtype: str
256 
257  '''
258  name = "_".join(["basis", self.mangled_namemangled_name])
259  if qr_var:
260  name += "_" + qr_var
261  if on_space:
262  name += "_on_" + on_space.mangled_name
263  return name
264 
265  def get_diff_basis_name(self, qr_var=None, on_space=None):
266  '''
267  Returns a name for the differential basis function on this
268  FunctionSpace. If the name of an associated quadrature object is
269  supplied then this is appended to the returned name. Similarly, if the
270  function space at which the basis is to be evaluated is supplied then
271  this is also appended to the name.
272 
273  :param str qr_var: the name of the Quadrature Object for which the \
274  differential basis functions are required.
275  :param on_space: the function space at which the differential basis \
276  functions will be evaluated
277  :type on_space: :py:class:`psyclone.dynamo0p3.domain.lfric.\
278  FunctionSpace`
279  :returns: name for the Fortran array holding the differential basis \
280  function
281  :rtype: str
282 
283  '''
284  name = "diff_basis_" + self.mangled_namemangled_name
285  if qr_var:
286  name += "_" + qr_var
287  if on_space:
288  name += "_on_" + on_space.mangled_name
289  return name
290 
291  def get_operator_name(self, operator_name, qr_var=None, on_space=None):
292  '''
293  Returns the name of the specified operator (basis or differential
294  basis) for this FunctionSpace.
295 
296  :param str operator_name: name (type) of the operator.
297  :param str qr_var: the name of the Quadrature Object for which the \
298  operator is required.
299  :param on_space: the function space at which the operator is required.
300  :type on_space: :py:class:`psyclone.domain.lfric.FunctionSpace`
301 
302  :returns: name for the Fortran arry holding the named operator \
303  for the specified function space.
304  :rtype: str
305 
306  '''
307  if operator_name == "gh_basis":
308  return self.get_basis_nameget_basis_name(qr_var=qr_var, on_space=on_space)
309  if operator_name == "gh_diff_basis":
310  return self.get_diff_basis_nameget_diff_basis_name(qr_var=qr_var, on_space=on_space)
311 
312  const = LFRicConstants()
313  raise GenerationError(
314  f"Unsupported name '{operator_name}' found. Expected one of "
315  f"{const.VALID_METAFUNC_NAMES}")
316 
317  def field_on_space(self, arguments):
318  '''Returns the corresponding argument if the supplied list of
319  arguments contains a field that exists on this space. Otherwise this
320  function returns None.
321 
322  :param arguments: list of arguments to be tested.
323  :type arguments: :py:class:`psyclone.dynamo0p3.DynKernelArguments`
324 
325  :returns: the argument from the supplied list of arguments that \
326  contains a field that exists on this space or None.
327  :rtype: :py:class:`psyclone.dynamo0p3.DynKernelArgument` or None
328 
329  '''
330  if self.mangled_namemangled_name in arguments.unique_fs_names:
331  for arg in arguments.args:
332  # First, test that argument is a field as some argument
333  # objects won't have function spaces, e.g. scalars
334  if arg.is_field and \
335  arg.function_space.orig_name == self.orig_nameorig_name:
336  return arg
337  return None
338 
339  def cma_on_space(self, arguments):
340  '''Returns the corresponding argument if the supplied list of
341  arguments contains a cma operator that maps to/from this FunctionSpace.
342  Otherwise this function returns None.
343 
344  :param arguments: list of arguments to be tested.
345  :type arguments: :py:class:`psyclone.dynamo0p3.DynKernelArguments`
346 
347  :returns: the argument from the supplied list of arguments that \
348  contains a field that exists on this space or None.
349  :rtype: :py:class:`psyclone.dynamo0p3.DynKernelArgument` or None
350 
351  '''
352  if self.mangled_namemangled_name in arguments.unique_fs_names:
353  for arg in arguments.args:
354  # First, test that arg is a CMA op as some argument objects
355  # won't have function spaces, e.g. scalars
356  if arg.argument_type == "gh_columnwise_operator" and \
357  self.orig_nameorig_name in [arg.function_space_to.orig_name,
358  arg.function_space_from.orig_name]:
359  return arg
360  return None
361 
362  @property
363  def has_scalar_basis(self):
364  ''':returns: True if this function space has scalar basis functions.
365  :rtype: bool
366  '''
367  const = LFRicConstants()
368  return self.orig_nameorig_name.lower() in const.SCALAR_BASIS_SPACE_NAMES
369 
370  @property
371  def has_vector_basis(self):
372  ''':returns: True if this function space has vector basis functions.
373  :rtype: bool
374  '''
375  const = LFRicConstants()
376  return self.orig_nameorig_name.lower() in const.VECTOR_BASIS_SPACE_NAMES
377 
378  @property
380  ''':returns: True if this function space has scalar differential
381  basis functions.
382  :rtype: bool
383  '''
384  const = LFRicConstants()
385  return self.orig_nameorig_name.lower() in const.SCALAR_DIFF_BASIS_SPACE_NAMES
386 
387  @property
389  ''':returns: True if this function space has vector differential
390  basis functions.
391  :rtype: bool
392  '''
393  const = LFRicConstants()
394  return self.orig_nameorig_name.lower() in const.VECTOR_DIFF_BASIS_SPACE_NAMES
def get_operator_name(self, operator_name, qr_var=None, on_space=None)
def get_basis_name(self, qr_var=None, on_space=None)
def get_diff_basis_name(self, qr_var=None, on_space=None)