Search code examples
oopfortranobject-type

Dynamically choose object type


I'm trying to make flexible user interface for numerical solver. The problem is that I cannot formulate how to implement dynamic choosing of initial condition geometry (rectangular, sphere etc). I've tried to use abstract class initial_conditions_geometry as a parent with child classes rectangular and sphere which specify certain attributes and implement method in_bounds for geometry:

type    ,abstract   ::  initial_conditions_geometry
    contains
        procedure (in_bounds)   ,deferred    :: in_bounds 
end type

abstract interface 
    logical function in_bounds(this,coordinates)
        import :: rkind
        import :: initial_conditions_geometry
        class(initial_conditions_geometry)  ,intent(in) :: this
        real(rkind)     ,dimension (:)      ,intent(in) :: coordinates
    end function in_bounds
end interface

type    ,extends(initial_conditions_geometry) :: rectangular
    integer ,dimension(:,:) ,allocatable    :: rectangular_bounds
contains
    procedure   :: in_bounds => in_bounds_rectangular
end type 

type    ,extends(initial_conditions_geometry) :: sphere
    integer ,dimension(:,:) ,allocatable    :: sphere_bounds
    integer ,dimension(:)   ,allocatable    :: sphere_center
contains
    procedure   :: in_bounds => in_bounds_sphere
end type

interface rectangular
    procedure constructor_rectangular
end interface

interface sphere
    procedure constructor_sphere
end interface

Here is initial_conditions_layer class definition, which encompasses initial condition geometry and distribution:

type    :: initial_conditions_layer
    class(initial_conditions_geometry)  ,allocatable    :: layer_geometry
    type(initial_conditions_distribution)               :: distributor
end type   

interface initial_conditions_layer
    procedure   constructor
end interface

The question is: how can manage to create constuctor for initial_conditions_layer that dynamically (depending on geometry type in some text file) sets layer_geometry type (rectangular, sphere etc) ?

Edit Maybe it is useful to add some of my attempts to solve this problem. I've tried to organize constructor for initial_conditions_layer object as follows:

type(initial_conditions_layer) function constructor(dimensions,layer_number,initial_conditions_data_file_unit)
    integer ,intent(in) :: layer_number
    integer ,intent(in) :: initial_conditions_data_file_unit
    integer ,intent(in) :: dimensions
    character(len=20)   :: layer_distribution_type
    character(len=20)   :: layer_geometry_name     

    call get_layer_properties(initial_conditions_data_file_unit,layer_number,layer_geometry_name,layer_distribution_type)

    select case(layer_geometry_name)
    case('rectangular')
        allocate(rectangular::constructor%layer_geometry)
    case('sphere')
        allocate(sphere::constructor%layer_geometry)
    end select

    select type(constructor%layer_geometry)
    type is(rectangular)
        constructor%layer_geometry = rectangular()
    type is(sphere)
        constructor%layer_geometry = sphere()
    end select
end function

But this requires associate_name in select type construct. Associate_name should be a pointer to initial_conditions_geometry, but one cannot set target attribute to derived type field.


Solution

  • What you have is quite close to working, and I think you merely have a misunderstanding.

    But this requires associate_name in select type construct. Associate_name should be a pointer to initial_conditions_geometry, but one cannot set target attribute to derived type field.

    An associate-name is required in this case but, regardless of the => syntax, no pointer is involved. You could then, without other changes, have

    select type(clg => constructor%layer_geometry)
    type is(rectangular)
        clg = rectangular()
    type is(sphere)
        clg = sphere()
    end select
    

    Alternatively, in this simple case it is possible to do away with the select type construct completely, and handle things in the select case you already have.

    Using sourced allocation with each constructor:

    select case(layer_geometry_name)
    case('rectangular')
        allocate(constructor%layer_geometry, source=rectangular())
    case('sphere')
        allocate(constructor%layer_geometry, source=sphere())
    end select
    

    Or even

    select case(layer_geometry_name)
    case('rectangular')
        constructor%layer_geometry = rectangular()
    case('sphere')
        constructor%layer_geometry = sphere()
    end select
    

    if you have the compiler support for intrinsic assignment to polymorphic variables (or by using defined assignment).