Coding conventions in MARBL¶
MARBL is written in Fortran. A few constructs commonly used in MARBL may be unfamiliar to scientific programmers.
Object-oriented programming features¶
Object-oriented programming constructs permit the definition of classes that both contain data and methods which can perform operations on that data.
Example of an Object-oriented Class¶
The class used to time blocks of code inside MARBL, marbl_internal_timers_type
, is object-oriented:
!*****************************************************************************
! Internal timer types
type :: marbl_single_timer_type
character(len=char_len) :: name
logical :: is_running
logical :: is_threaded
real(r8) :: cur_start
real(r8) :: cumulative_runtime
contains
procedure :: init => init_single_timer
end type marbl_single_timer_type
type, public :: marbl_internal_timers_type
integer :: num_timers
type(marbl_single_timer_type), allocatable :: individual_timers(:)
contains
procedure :: add => add_new_timer
procedure :: start => start_timer
procedure :: stop => stop_timer
procedure :: extract => extract_timer_data
procedure :: setup => setup_timers
procedure :: reset => reset_timers
procedure :: shutdown => shutdown_timers
end type marbl_internal_timers_type
This type includes several methods, such as the start()
routine, referenced to the subroutine start_timer
.
How to Call an Object-oriented Subroutine¶
A subroutine inside a class is referenced just like any other member, via the %
character.
For example, MARBL times the call to the subroutine marbl_compute_carbonate_chemistry()
from
the subroutine marbl_interior_tendency_compute()
(part of marbl_interior_tendency_mod.F90
).
The timer calls look like this:
subroutine marbl_interior_tendency_compute( &
.
.
.
type(marbl_internal_timers_type), intent(inout) :: marbl_timers
.
.
.
call marbl_timers%start(marbl_timer_indices%carbonate_chem_id, &
marbl_status_log)
call compute_carbonate_chemistry(domain, temperature, pressure, &
salinity, tracer_local(:, :), marbl_tracer_indices, carbonate, &
ph_prev_col(:), ph_prev_alt_co2_col(:), marbl_status_log)
call marbl_timers%stop(marbl_timer_indices%carbonate_chem_id, &
marbl_status_log)
Example of a Subroutine Inside an Object-oriented Class¶
The subroutine header looks like this:
subroutine start_timer(self, id, marbl_status_log)
class(marbl_internal_timers_type), intent(inout) :: self
integer, intent(in) :: id
type(marbl_log_type), intent(inout) :: marbl_status_log
One key thing to note here is the use of self
, the first argument to the subroutine.
In this case, self
stands for the particular instance of an object of type marbl_internal_timers_type
.
The subroutine is actually part of this object, which lets it access members of the class without explicitly passing them through the interface.
So this subroutine can change members of self%individual_timers(:)
(e.g. individual_timers(:)%cur_start
).
Associate construct¶
The associate
construct allows complex variables or expression to be denoted by a simple name or “alias.”
The association between the name and the underlying variable is terminated at the end of the associate block.
If we look closer at the start_timer
routine, we see an example:
associate(timer => self%individual_timers(id))
if (timer%is_running) then
log_message = 'Timer has already been started!'
call marbl_status_log%log_error(log_message, subname)
return
end if
timer%is_running = .true.
.
.
.
timer%cur_start = get_time()
end associate
In this case, timer
replaces all instances of the more complicated expression self%individual_timers(id)
.
The assocation is terminated at end associate
.