Fortran

Guide To Learn

Implementing the arithmetic for the Field class

In section 10.3, we wrote a function that takes the difference between two datetime instances, which we then used to redefine the arithmetic operator -. Here, we take the exact same approach, except that we need to put some more leg work in. Take a look back at listing 10.7–we need to implement procedures to define each operator that appears in equations for uv, and h. We don’t need the whole arithmetic set between Field and all other types, only those operators that we intend to use. The procedures, operand types, and arithmetic operators that we’ll implement are summarized in table 10.1.

Table 10.1 A list of procedures, operands, and operators to be implemented for the Field class

Procedure nameLeft operandRight operandOperator
field_add_fieldFieldField+
field_add_realFieldreal(:,:)+
field_sub_fieldFieldField-
field_sub_realFieldreal(:,:)-
field_mul_fieldFieldField*
field_mul_realFieldreal(:,:)*
field_div_realFieldreal/
assign_fieldFieldField=

The operability of Field instances with two-dimensional real arrays is necessary because diffx and diffy functions return two-dimensional real arrays. Note that for the division operator, we only need division of a Field with a real scalar, as we divide with grid spacing dx and dy. If we at any point decided to allow tsunami to have a spatially varying grid spacing (a common approach in both weather and ocean prediction models), we’d need to treat dx and dy as two-dimensional arrays, and we’d also need a dedicated method for that division. For the sake of this exercise, though, this is good enough. Finally, we’ll defer the implementation of assign_field until the next subsection.

Let’s look at the field_add_real procedure, for example. The simplest implementation of this method is to accept a Field instance and a two-dimensional real array, and to add the values of the array to the data component of the Field instance, as shown in the following listing.

Listing 10.8 The field_add_real procedure

pure type(Field) function field_add_real(self, x) result(res)
  class(Field), intent(in) :: self       ❶
  real(real32), intent(in) :: x(:,:)     ❷
  call from_field(res, self)             ❸
  res % data = self % data + x           ❹
end function field_add_real

❶ Since this is a type-bound method, the first argument must be of the type that we’re binding to.

❷ The second input argument is a two-dimensional real array.

❸ Copies the Field metadata from self into the resulting instance

❹ Assigns values of x to the resulting instance

We use the subroutine from_field to conveniently initialize all the internal components of the Field class. This step is important because, by default, the Field instance res comes bare-bones–with uninitialized components and the data array not being allocated. Although we could’ve done that explicitly for more transparency, we’ll need to repeat it in every method associated with a user-defined operator. I’ve thus placed this boilerplate code into a from_field subroutine in the same module in src/ch10/mod_field.f 90. Once res is initialized and its data component is allocated with the proper range, we add to it the sum of self % data and x.

Exercise 3: Implementing the addition for the Field type

Now that you know how to add a two-dimensional array to a Field instance, can you write a similar method that adds the data to two Field instances?

Hint: use the from_field subroutine to initialize metadata for the result field, like in listing 10.8.

You can find the solution to this exercise in the “Answer key” section near the end of this chapter.

Like we did in section 10.3, the last step to implementing user-defined operators for derived types is to use the generic :: operator() statement in the type definition to instruct the compiler which operators will point to which specific type-bound methods, as shown in the following listing.

Listing 10.9 Associating operators with type-bound methods

type Field
  ...
contains
  ...
  procedure, private, pass(self) :: field_add_field, &      ❶
                                    field_add_real,  &      ❶
                                    field_sub_field, &      ❶
                                    field_sub_real,  &      ❶
                                    field_mul_field, &      ❶
                                    field_mul_real,  &      ❶
                                    field_div_real          ❶
  generic :: operator(+) => field_add_field, &              ❷
                            field_add_real                  ❷
  generic :: operator(-) => field_sub_field, &              ❷
                            field_sub_real                  ❷
  generic :: operator(*) => field_mul_field, &              ❷
                            field_mul_real                  ❷
  generic :: operator(/) => field_div_real                  ❷
end type Field

❶ Specifies that these are type-bound procedures

❷ Associates arithmetic operators with the specific procedures

This is the same approach that we took in section 10.3.2 and listing 10.4, but now expanded to multiple operators and even more type-bound methods. A user-defined operator can point to multiple different type-bound methods, following the same rules as for generic procedures we covered in chapter 9.

Still missing from listing 10.9 is the implementation of custom assignment (=) that I promised a bit earlier. I’ll cover that in the following subsection.

Implementing the arithmetic for the Field class

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top