The original way for initialization to be handled in gs2 is for each module to have its own init function. This does the initialization for a module, and makes sure that dependencies are also initialized. Generically, it’s something like:
module my_mod
private
logical :: initialized = .false.
subroutine init_my_mod
use dependency, only: init_dependency
! Return if already initialized
if (initialized) return
initialized = .true.
! Initialize dependencies
call init_dependency
! Now initialize my_mod, read namelists, allocate arrays, etc
end subroutine init_my_mod
subroutine finish_my_mod
! Try to undo everything that happened in init_my_mod
end subroutine finish_my_mod
end module my_mod
This is usually fine, but it is also limited:
It obscures the dependencies between modules. It's common for init_dependency to be called from lots of other init routines. All but one of these calls will do nothing (and so should be deleted) -- without digging, it's hard to see where init_dependency is actually doing the initialization.
For testing, or coupling gs2 to other codes, or running multiple instances of gs2 in a single execution, it's important to be able to cleanly initialize and uninitialize modules. For example, one might want to run gs2 with some run_parameters, and then run gs2 again on the same grids, but with some parameters changed. This approach doesn’t support that without uninitializing everything, and starting again.
This approach isn't object oriented - it uses global variables. If my_mod really needs dependency, I want to be working with an instance of that dependency.
New Initialization Method
To address these, Edmund introduced initialization levels, copying the approach used in initializing Linux. The idea is that each module is assigned a unique level number, and you can initialize or uninitialize code by calling init(current_level, desired_level). After this call, it is guaranteed that all modules with levels up to desired_level are initialized, and all modules above this are not initialized.
For example, if the current_level=11, and the initialization levels of le_grids and collisions are 12 and 13 respectively, I would initialize collisions by calling init(current_level,collisions_level). What this would actually do is call init_le_grids followed by init_collisions (and then set current_level=13). If I then called init(current_level,le_grids_level), it would uninitialize collisions by calling finish_collisions, but leave le_grids initialized.
The levels are determined from information in the ruby script gs2_init.rb. This script lists modules and their dependencies in the form:
The code in gs2 is actually currently a hybrid of these two approaches - the initialization levels approach is used, but init functions also still call their dependencies. That is, in the example above, the subroutine init_collisions calls init_le_grids inside itself, even though the levels method guarantees that le_grids is already initialized.
This PR removes redundant calls to init functions, so that everything is initialized only through the levels approach in gs2_init.f90. This PR also tries to correct/update the dependencies generated in gs2_init.rb. It may seem obscure, but it’s necessary to get proper unit tests working, and to make the code more object oriented.
This branch passes all tests*, but these changes are most likely to affect cases not covered by the tests: Trinity or Gryfx users, people who use flags like LOWFLOW, etc. So this PR is here to
advertise that initialization levels is a thing; and
see if I can tempt people to use this branch instead of `next`, to see what breaks…
*on my laptop, we’ll see what happens in the pipeline…
Original Initialization Method
The original way for initialization to be handled in gs2 is for each module to have its own
init
function. This does the initialization for a module, and makes sure that dependencies are also initialized. Generically, it’s something like:module my_mod private logical :: initialized = .false. subroutine init_my_mod use dependency, only: init_dependency ! Return if already initialized if (initialized) return initialized = .true. ! Initialize dependencies call init_dependency ! Now initialize my_mod, read namelists, allocate arrays, etc end subroutine init_my_mod subroutine finish_my_mod ! Try to undo everything that happened in init_my_mod end subroutine finish_my_mod end module my_mod
This is usually fine, but it is also limited:
It obscures the dependencies between modules. It's common for
init_dependency
to be called from lots of otherinit
routines. All but one of these calls will do nothing (and so should be deleted) -- without digging, it's hard to see whereinit_dependency
is actually doing the initialization.For testing, or coupling gs2 to other codes, or running multiple instances of gs2 in a single execution, it's important to be able to cleanly initialize and uninitialize modules. For example, one might want to run gs2 with some
run_parameters
, and then run gs2 again on the same grids, but with some parameters changed. This approach doesn’t support that without uninitializing everything, and starting again.This approach isn't object oriented - it uses global variables. If
my_mod
really needsdependency
, I want to be working with an instance of that dependency.New Initialization Method
To address these, Edmund introduced initialization levels, copying the approach used in initializing Linux. The idea is that each module is assigned a unique level number, and you can initialize or uninitialize code by calling
init(current_level, desired_level)
. After this call, it is guaranteed that all modules with levels up todesired_level
are initialized, and all modules above this are not initialized.For example, if the
current_level=11
, and the initialization levels ofle_grids
andcollisions
are 12 and 13 respectively, I would initialize collisions by callinginit(current_level,collisions_level)
. What this would actually do is callinit_le_grids
followed byinit_collisions
(and then setcurrent_level=13
). If I then calledinit(current_level,le_grids_level)
, it would uninitialize collisions by callingfinish_collisions
, but leavele_grids
initialized.The levels are determined from information in the ruby script
gs2_init.rb
. This script lists modules and their dependencies in the form:DEPENDENCIES = [ ['module_1', ['dendency_1','dependency_2']], ['module_2', ['dendency_3','dependency_4']], ['collisions', ['le_grids', 'other_stuff']], ['le_grids', ['stuff_not_including_collisions']], ... ]
and from this, auto-generates
gs2_init.f90
.So what’s this PR?
The code in gs2 is actually currently a hybrid of these two approaches - the initialization levels approach is used, but
init
functions also still call their dependencies. That is, in the example above, the subroutineinit_collisions
callsinit_le_grids
inside itself, even though the levels method guarantees thatle_grids
is already initialized.This PR removes redundant calls to
init
functions, so that everything is initialized only through the levels approach ings2_init.f90
. This PR also tries to correct/update the dependencies generated ings2_init.rb
. It may seem obscure, but it’s necessary to get proper unit tests working, and to make the code more object oriented.This branch passes all tests*, but these changes are most likely to affect cases not covered by the tests: Trinity or Gryfx users, people who use flags like
LOWFLOW
, etc. So this PR is here toadvertise that initialization levels is a thing; and
see if I can tempt people to use this branch instead of `next`, to see what breaks…
*on my laptop, we’ll see what happens in the pipeline…