Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Using Type as an array

Averkov__Igor
Beginner
2,415 Views
Hello!
Please help me to resolve a problem.

I have function, which accept an array as input data:
[fortran]real function MixtureDensity( MassFrac  )
  real, intent(in) :: MassFrac(4)  ! - mass fractions of H2, CO, H2O, CO2
   ....
end function[/fortran]

I have declared the structure:
[fortran]type TSubstContent
  real H2
  real CO
  real H2O
  real CO2
end type[/fortran]

This structure has identical construction as an array required in the function MixtureDensity. For some reasons I do not want to declare MassFrac as an array. The problem is: how to pass MassFrac of type TSubstContent to the function MixtureDensity:
[fortran]type(TSubstContent)  MassFrac

call MixtureDensity( MassFrac  )  ! - error by compilation[/fortran]

0 Kudos
1 Solution
dannycat
New Contributor I
2,415 Views

The way I would get round this is to map any array over the structure elements eg.

type Arr

sequence

union

map

real(4) :: MassData(4)

end map

map

real(4) :: h2

real(4) :: CO

real(4) :: H2O

real(4) :: CO2

end map

end union

end type

Type(Arr) :: MassFracArray

Then you can call your function MixtureDensity(MassFracArray%MassData)

View solution in original post

0 Kudos
13 Replies
John_B__Walter
Beginner
2,415 Views
I'm just guessing because you have provided very little information.

You say the error is in compilation, but I would expect the error would be in the link stage where it would complain about an unsatisfied external.

You really should make the two parts agree. If you want to use TSubstContent in the calling program,
you should use it in the subroutine definition. Otherwise, even though the memory layout is the same, the linker which connects the various routines into an executable program will not see them as matching, and
complain that it doesn't know anything about MixtureDensity with an argument of TSubstContent.
0 Kudos
Steven_L_Intel1
Employee
2,415 Views
I think I understand what is being asked for. Basically, you want a variable of type TSubstContent to be treated as if it were an array of four REAL values.

First of all, the Fortran standard does not guarantee that the storage of the derived type is laid out the same way an array would be. A compiler is allowed to reorder the components and vary their spacing any way it wants to. You could add SEQUENCE to the type and this would disable reordering, but does not guarantee spacing. You could add BIND(C) and this would specify the same memory layout as the "companion C processor" would use in a struct, which in most cases would be closest to what you want.

But you'd still get an error because the type of the actual argument doesn't match the type of the dummy argument.

I could give you several ways around this, but I feel obligated to ask what the "some reasons" are and why the function can't have its argument declared as the derived type.
0 Kudos
onkelhotte
New Contributor II
2,415 Views
You explain your error yourself:

-There is a function, which accepts a realarray as input data
- The function is called with a type structure as argument

It doesnt matter if your structure consists of 4 real values. When you dont want to declare MassFrac as an array, you have to create a seperate array with has the values, something like this:
[bash]real MassFracArray(4)
MassFracArray(1) = MassFrac%H2
MassFracArray(2) = MassFrac%CO
MassFracArray(3) = MassFrac%H2O
MassFracArray(4) = MassFrac%CO2
call MixtureDensity(MassFracArray)[/bash]
Markus
0 Kudos
Averkov__Igor
Beginner
2,415 Views

Steve Lionel (Intel) said:
"Basically, you want a variable of type TSubstContent to be treated as if it were an array of four REAL values"

- that is exactly, what i mean (thanks for understanding)

Steve Lionel (Intel) said:
"but I feel obligated to ask what the "some reasons" are and why the function can't have its argument declared as the derived type"

Inside many functions like the MixtureDensity, mentioned above, operating with arrays let to get all the power and comfort in coding as well perfomance (cycles do - end do, vectorized operations and many instrinc functions for arrays). But outside or in some other subroutines (and especially by debugging) there is a need to treat array as a collection (structure) of named elements. Yes i could declare named constants:
[fortran]  integer, parameter :: H2  = 1
  integer, parameter :: CO = 2
  integer, parameter :: H2O = 3
  integer, parameter :: CO2 = 4[/fortran]
so it let me to operate with separate elements very simple using the form:
[bash]MassFrac(H2) = 0.23[/bash]
it is a way (i do not need to remember what index stays for H2). But i would like to see in debuging the values of named elements (variables inspector) - and that is not possible for arrays (indexes of array are always integer).

Steve Lionel (Intel) said:
"You could add SEQUENCE to the type and this would disable reordering, but does not guarantee spacing."

Array sections (include that from derived types) and common arrays often have different spacing, but it makes not a problem, when they are as operands in a same vectorized expression

John B. Walter said:
"If you want to use TSubstContent in the calling program,
you should use it in the subroutine definition"

Making TSubstContent as a type of input data in functions have many disadvantages: no more vectorizing, not possible to use cycles (do - end do) and some others

onkelhotte
Thanks, your solution is correct (i new that) but i hope there is a more natural way
0 Kudos
dannycat
New Contributor I
2,416 Views

The way I would get round this is to map any array over the structure elements eg.

type Arr

sequence

union

map

real(4) :: MassData(4)

end map

map

real(4) :: h2

real(4) :: CO

real(4) :: H2O

real(4) :: CO2

end map

end union

end type

Type(Arr) :: MassFracArray

Then you can call your function MixtureDensity(MassFracArray%MassData)

0 Kudos
Steven_L_Intel1
Employee
2,415 Views
Be aware that UNION and MAP are extensions to the standard. I had not thought that we allowed them in a TYPE, but apparently we do.
0 Kudos
Averkov__Igor
Beginner
2,415 Views

dannycat

Using combination of sequence - union - map seems to be the right solution. Thank you very much for answer .

By the way dous using of sequence or union or map decrease compilator's possibility to make perfomance code, optimization and so on?

Steve Lionel (Intel) said:
"I could give you several ways around this"

If you give some more ways I will thank you. I think some ways could be connected with using of pointers so as it would be done in C. But this way is not organic with fortran and probably will decrease optimization possibility.

0 Kudos
Steven_L_Intel1
Employee
2,415 Views
You could use pointers, or you could use TRANSFER if the arguments were read and not written.
0 Kudos
IanH
Honored Contributor III
2,415 Views
Quoting io77
...Yes i could declare named constants:
  1. integer,parameter::H2=1
  2. integer,parameter::CO=2
  3. integer,parameter::H2O=3
  4. integer,parameter::CO2=4
[fortran]  integer, parameter :: H2  = 1
  integer, parameter :: CO = 2
  integer, parameter :: H2O = 3
  integer, parameter :: CO2 = 4[/fortran]
so it let me to operate with separate elements very simple using the form:
[bash]MassFrac(H2) = 0.23[/bash]
it is a way (i do not need to remember what index stays for H2). But i would like to see in debuging the values of named elements (variables inspector) - and that is not possible for arrays (indexes of array are always integer).

The last bit is not necessarily true - see pic below. The watch window shows an array reference that involves a parameter.



Debugging information, including value, is available for parameters that are referenced in a program unit. Make sure that you have the /debug-parameters:all option (Under the property Fortran > Debugging > Information for PARAMETER constants) set appropriately. Unfortunately (Dear Intel Compiler Developers - it would be nice if this was "fixed" in some way - this use case is quite common in my experience) while "used" means "used", "all" doesn't really mean "all" - you do need to have that reference somewhere in that program unit. The reference can be pretty irrelevant though - in the example I've used an otherwise pointless ONLY clause in a dummy subroutine.

So... now we have a reasonable work around for displaying array elements - why are you tracking the species concentrations in individual components again?

The use of arrays for things that are array-like in nature is the natural fortran way, particularly since F90 with all those nice intrinsic array operations. If you are tracking anything more than one or two species, then chemical species concentrations are a classic case of an an array-like thing. Using arrays wins hands down from the point of view of code clarity and maintainability - think about how easy it becomes to add and remove species as your model evolves.

I would completely avoid the non-standard MAP/UNION thing. It may solve the problem that you've posed, but it is just asking for trouble down the track. I have similar reservations about using TRANSFER or POINTERS (fractionally less evil because they are at least standard fortran features) - why obfuscate your code, make it harder to maintain, make it harder for the processor to work out what your intent is, make future readers of your code wonder what sort of substances your were consuming when your designed this software, etc, etc, when you fundamentally don't need to?

(If Dr Fortran is still listening - I can get an ICE out of the compiler (12.1.2) with the attached example if I uncomment the erroneous function result clause on the function statement for the density procedure.)

DebugParameters.f90
0 Kudos
Steven_L_Intel1
Employee
2,415 Views
Thanks, Ian. Got it.
0 Kudos
Averkov__Igor
Beginner
2,415 Views
IanH wrote:
So... now we have a reasonable work around for displaying array elements
.. "all" doesn't really mean "all" - you do need to have that reference somewhere in that program unit. The reference can be pretty irrelevant though - in the example I've used an otherwise pointless ONLY clause in a dummy subroutine

This problem could be resolved as followes (adopted from pascal). Let it would be possible to declare:
[fortran]type TSubstIndexes = ( H2, H2O, CO, CO2, O2, N2, Al, Ar, CH4 )
real MassFrac( TSubstIndexes )   ! - array of 9 real values, but  indexes are from TSubstIndexes
real ShortMassFrac( CO:N2 )   ! - array of 4 real values and indexes are from subrange of TSubstIndexes[/fortran]
So compilator will know for sure what Indexes to show in Watch Window for MassFrac and ShortMassFrac (no need in debuging options). So it would be possible then to write:
[fortran]program Example
   ! ....
print *,  MixtureRg(MassFrac) !  is possible
call DoSomething (MassFrac)  !  is possible too

! may be written:
do I = H2,  CH4
   MassFrac(i) = 0
end do

! or may be written:
do I = Lbound(TSubstIndexes), Ubound(TSubstIndexes)
   MassFrac(i) = 0
end do

contains

pure real function MixtureRg(MassFrac) result(Res)
  real    , intent(in) :: MassFrac(9)    
      ! here are used pure integers for indexes: MassFrac(i)
end function

subroutine DoSomething(Array)
  real    , intent(in) :: Array(:)    
    ! ...
end subroutine

end program[/fortran]

IanH wrote:
think about how easy it becomes to add and remove species as your model evolves

The examples above are simplified. Naturally the set of species can be expanded. I can explain it.

IanH wrote:
why are you tracking the species concentrations in individual components again?

Once it is by initialization (using experimental data), once it is calculation of separate reactions between two substances. So there many situations when it is needed to deel with separate elements. In such cases they should be named.

0 Kudos
Averkov__Igor
Beginner
2,415 Views
Steve Lionel (Intel) wrote:
You could use pointers, or you could use TRANSFER if the arguments were read and not written.

thanks for more suggestions. i will try them

IanH wrote:
I would completely avoid the non-standard MAP/UNION thing

It should be included in standard

dannycat
I have tried union - map and it works even without sequence.
0 Kudos
John4
Valued Contributor I
2,415 Views

Even though someone already provided a solution using the non-standard UNION/MAP, a simpler alternative is to use generics:

[fortran]interface MixtureDensity
   module procedure MixtureDensity, MixtureDensity_TSubstContent
end interface

...

contains

...

real function MixtureDensity( MassFrac  )  
  real, intent(in) :: MassFrac(4)  ! - mass fractions of H2, CO, H2O, CO2  
   ....  
end function

real function MixtureDensity_TSubstContent( MassFrac  )  
  type(TSubstContent), intent(in) :: MassFrac  ! - mass fractions of H2, CO, H2O, CO2  
  MixtureDensity_TSubstContent = MixtureDensity([MassFrac%H2, MassFrac%CO, &
                                                 MassFrac%H2O,MassFrac%CO2])
end function
[/fortran]

EDIT:

If the problem is about using loops to go through the components of the derived type, then, as suggested by Steve Lionel, use pointers:

[fortran]type TSubstContent
  real, pointer :: H2 => NULL()
  real, pointer :: CO => NULL()
  real, pointer :: H2O => NULL()
  real, pointer :: CO2 => NULL()
  real :: array(4) = 0
end type

contains

subroutine initialize(TSC)
   type(TSubstContent), target, intent(OUT) :: TSC
   TSC%H2 => TSC%array(1)
   TSC%CO => TSC%array(2)
   TSC%H2O => TSC%array(3)
   TSC%CO2 => TSC%array(4)
end subroutine
[/fortran]

That way, you can loop through the contents using the array component.

0 Kudos
Reply