What is the Julia equivalent of "USE" in Fortran90?

Hi everyone,

recently I’ve been working on a project of rule-based translator from Fortran to Julia. The project is 60% completed, but now I encountered conceptual problems and I need to decide whether to include certain features in my translator.

In Fortran90 there is a keyword USE, which is often written in the header part inside a function so that the lines below can have access to the members of the module declared after this keyword. There can be multiple lines of USE inside a Fortran function.

If I want to translate USE module_A into Julia, what would be the best practice?

Thank you !

You’re probably looking for include

USE gives one access to a module and the public elements in it. The equivalent in julia is using. You may need to include a local file first to access the modules defined in that file.

1 Like

hmmm… not exactly. Consider the following excerpt:

subroutine amendFreep
    use m_optimization
    use m_atomproperties
    implicit none
  ...
end subroutine amendFreep

where m_optimization and m_atomproperties are declared in other files. If I use using kw in Julia, the above function would be translated into

function amendfreep()
    using m_optimization
    using m_atomproperties
...
end

The interpreter gives me an error:

ERROR: syntax: "using" expression not at top level
Stacktrace:
 [1] top-level scope at none:0

If I use include, the entire piece of code for the two modules (translated) would be inserted inside the translated Julia function. That would be redundant …

using statements are not valid inside functions in Julia, you can only import modules into other modules. Usually all using statements go on top of your script/package, so you can use everything that’s exported in the whole module. It probably doesn’t make much sense to try to translate Fortran to Julia one-to-one, because they are very different languages and this might to lead to code that’s unidiomatic and hard to maintain in Julia. I would suggest trying to get a bit more familiar with the language first before tackling something like this.

2 Likes

reading and reviewing Fortran code is painful, because the code often gets vorbose. For example:
FORTRAN77/90

do i=1,gn_inequivalentsites(istr)
    nni=gn_neighbors(i,istr)
    do jj=1,nni
        j=gneighborlist(jj,i,istr)
        dxstr(jj,i,idisp,cart,istr)= &
        gxyz(1,j,istr)-gxyz(1,i,istr)
        dystr(jj,i,idisp,cart,istr)= &
        gxyz(2,j,istr)-gxyz(2,i,istr)
        dzstr(jj,i,idisp,cart,istr)= &
        gxyz(3,j,istr)-gxyz(3,i,istr)
        !diststr(jj,i,idisp,cart,istr)=distance(i,j)
        dist2str(jj,i,idisp,cart,istr)=dxstr(jj,i,idisp,cart,istr)**2 + &
        dystr(jj,i,idisp,cart,istr)**2 + dzstr(jj,i,idisp,cart,istr)**2
        diststr(jj,i,idisp,cart,istr)=sqrt(dist2str(jj,i,idisp,cart,istr))
        !dist2str(jj,i,idisp,cart,istr)=distance2(i,j)
        dist3str(jj,i,idisp,cart,istr)= &
        diststr(jj,i,idisp,cart,istr)*dist2str(jj,i,idisp,cart,istr)
    enddo
enddo

I’ve managed to translate that into Julia (with the translator I wrote) :

for i = 1 : gn_inequivalentsites[istr]
    nni = gn_neighbors[i,istr]
    for jj = 1 : nni #ORIGINAL_FILE_LINE[11340]
        j = gneighborlist[jj,i,istr]
        dxstr[jj,i,idisp,cart,istr] = gxyz[1,j,istr]-gxyz[1,i,istr]
        dystr[jj,i,idisp,cart,istr] = gxyz[2,j,istr]-gxyz[2,i,istr]
        dzstr[jj,i,idisp,cart,istr] = gxyz[3,j,istr]-gxyz[3,i,istr]
        dist2str[jj,i,idisp,cart,istr] = dxstr[jj,i,idisp,cart,istr]^2 + dystr[jj,i,idisp,cart,istr]^2 + dzstr[jj,i,idisp,cart,istr]^2 #ORIGINAL_FILE_LINE[11350]
        diststr[jj,i,idisp,cart,istr] = sqrt(dist2str[jj,i,idisp,cart,istr])
        dist3str[jj,i,idisp,cart,istr] = diststr[jj,i,idisp,cart,istr]*dist2str[jj,i,idisp,cart,istr]
    end #for
end #for

The extensive use of for loops in Fortran makes it hard to investigate. In Julia there might be chances to use broadcast.

I have rotated my screen to vertical to read Fortran !!!

I’ve tried and found that the rule-based translation can work for almost 90% of the .f90 code I have.

Example:
Original Fortran code:

subroutine setupfilenames
    !-------------------------------------------------------------c
    !
    !     Andy Duff, Feb 2008
    !
    !-------------------------------------------------------------c
    use m_filenames
    use m_geometry
    use m_datapoints
    use m_optimization

    implicit none

    logical num,ensureminimum_tocopy,freeenergy_tocopy,vasprun_tocopy,exist
    integer i,j,ifile,istruc,nlimits,optimizeforce_tocopy
    integer limits(10),step(10)
    character*80 line
    character*80 tmp,tmp2,string,string2,string3,string4
    character*80 poscarfiles_tocopy,outcarfiles_tocopy
    real(8) weights_tocopy,double

    crystalstrucfile='crystal_struc.xyz'
    settingsfile='settings'
    fitdbse='fitdbse'
    lookuptablefile='lookuptablefile'

    !Check if there is a fitdbse file
    inquire(file=trim(fitdbse),exist=exist)
    if (exist.eqv..false.) then
       call createfitdbse
    endif

    !Read in list of poscar files
    open(unit=1,file=trim(fitdbse),status='old')
    read(1,*) nposcarfiles
    if (nposcarfiles.lt.1) then
        print *,'Error: no. of poscarfiles must be greater than'
        print *,'or equal to 1'
        stop
    endif
    !Scan through fitdbse once to see how many ionic structures we have to fit
    !to (this will not necessarily equal the number of VASP files)
    nstruct=0
    do ifile=1,nposcarfiles

        read(1,'(A80)') line
        call readFitdbseLine(line,tmp,tmp2,string,string4,double)
        call checkifnumber(string,num)
        if (num.eqv..true.) then
           call getnumconfigs(string,limits,nlimits,step)
        !  print *,'string=',string
        !  print *,'limits=',limits
        !  print *,'nlimits=',nlimits
        !  print *,'step=',step
        !  stop
           do i=1,nlimits,2
              do j=limits(i),limits(i+1),step(i)
                 nstruct=nstruct+1
              enddo
           enddo
        else
           nstruct=nstruct+1
        endif
    enddo

    rewind(1)
    read(1,*)
    allocate(poscarfiles(nstruct),outcarfiles(nstruct), &
        strucnames(nstruct),optimizeforce(nstruct), &
        ensureminimum(nstruct),weights(nstruct), &
        rlxstruc
    use m_filenames
    use m_geometry
    use m_datapoints
    use m_optimization

    implicit none

    logical num,ensureminimum_tocopy,freeenergy_tocopy,vasprun_tocopy,exist
    integer i,j,ifile,istruc,nlimits,optimizeforce_tocopy
    integer limits(10),step(10)
    character*80 line
    character*80 tmp,tmp2,string,string2,string3,string4
    character*80 poscarfiles_tocopy,outcarfiles_tocopy
    real(8) weights_tocopy,double

    crystalstrucfile='crystal_struc.xyz'
    settingsfile='settings'
    fitdbse='fitdbse'
    lookuptablefile='lookuptablefile'

    !Check if there is a fitdbse file
    inquire(file=trim(fitdbse),exist=exist)
    if (exist.eqv..false.) then
       call createfitdbse
    endif

    !Read in list of poscar files
    open(unit=1,file=trim(fitdbse),status='old')
    read(1,*) nposcarfiles
    if (nposcarfiles.lt.1) then
        print *,'Error: no. of poscarfiles must be greater than'
        print *,'or equal to 1'
        stop
    endif
    !Scan through fitdbse once to see how many ionic structures we have to fit
    !to (this will not necessarily equal the number of VASP files)
    nstruct=0
    do ifile=1,nposcarfiles

        read(1,'(A80)') line
        call readFitdbseLine(line,tmp,tmp2,string,string4,double)
        call checkifnumber(string,num)
        if (num.eqv..true.) then
           call getnumconfigs(string,limits,nlimits,step)
        !  print *,'string=',string
        !  print *,'limits=',limits
        !  print *,'nlimits=',nlimits
        !  print *,'step=',step
        !  stop
           do i=1,nlimits,2
              do j=limits(i),limits(i+1),step(i)
                 nstruct=nstruct+1
              enddo
           enddo
        else
           nstruct=nstruct+1
        endif
    enddo

    rewind(1)rgy(nstruct),nconfig(nstruct), &
        vasprun(nstruct))
    istruc=1

    print *
    print *,'Initializing Fitting database'
    print *,'-----------------------------'
    print *,'File | Configs to fit | Quantity to fit | Weights'

    forcefit=.false.
    do ifile=1,nposcarfiles

       !New parsing code to improve transferability:
       read(1,'(A80)') line
       call readFitdbseLine(line,poscarfiles(istruc),outcarfiles(istruc),string,string4,weights(istruc))

       string2=poscarfiles(istruc)
       if (string2(1:7).eq."vasprun") then
          vasprun(istruc)=.true.
          outcarfiles(istruc)=poscarfiles(istruc)
          write(string3,'(i10)') nconfig(istruc)
          strucnames(istruc)=trim(poscarfiles(istruc))//trim(string3)
       else
          print *,'ERROR: vasprun files in fitdbse file must start with the word vasprun.'
          print *,'Stopping.'
          stop
          !The following is a relic from when POSCAR/OUTCAR combinations were allowed
          vasprun(istruc)=.false.
       endif

       !second columns: n=no ionic relaxation and read in one ionic
       !config from file; r=relax structure using potential; numerical
       !sequence (e.g, 1 or 1,2 or 1-5 or 1-5,6-10): read in those
       !ionic configurations from the file.
       call checkifnumber(string(1:1),num)
       if (num.eqv..false.) then
          nconfig(istruc)=1
          !VASP file has only one ionic config to be read in. Should the ionic
          !positions be relaxed (according to the potential) during optimization?
          if (string(1:1).eq.'N'.or.string(1:1).eq.'n') then
              rlxstruc(istruc)=0 !Do not relax structure using potential
          elseif (string(1:1).eq.'R'.or.string(1:1).eq.'r') then
              rlxstruc(istruc)=1 !Relax structure using potential
          else
              write(*,'(A26,A1,A37)') 'ERROR: Unexpected letter: ',string(1:1), &
                              ', in 3rd column of fitdbse. STOPPING.'
              stop
          endif
       endif

       !If the third column contains an 'F' of 'f' then set
       !optimizeforce=.true. for this file, which signals that the code
       !should optimize w.r.t the forces from this outcar file;
       !otherwise set optimizeforce=.false. and therefore optimize
       !w.r.t the energy from this file.
       if (string4(1:2).eq.'fo'.or.string4(1:2).eq.'Fo'.or. &
           string4(1:2).eq.'fO'.or.string4(1:2).eq.'FO') then
           optimizeforce(istruc)=1
           forcefit=.true.
      !elseif(snglechar2(1:1).eq.'G'.or.snglechar2(1:1).eq.'g') then
      !    optimizeforce(istruc)=2
      !    forcefit=.true.
       else
           optimizeforce(istruc)=0
          !if(snglechar2(1:1).eq.'M'.or.snglechar2(1:1).eq.'m') then
          !    ensureminimum(istruc)=.true.
          !else
           ensureminimum(istruc)=.false.
          !endif
           if (string4(1:2).eq.'fr'.or.string4(1:2).eq.'Fr'.or. &
               string4(1:2).eq.'fR'.or.string4(1:2).eq.'FR') then
               freeenergy(istruc)=.true.
           elseif (string4(1:2).eq.'e0'.or.string4(1:2).eq.'E0') then
               freeenergy(istruc)=.false.
           else
               print *,'ERROR: Incorrect entry in 3rd column of fitdbse file.'
               print *,'Expecting E0, FR or FO. Stopping'
               stop
           endif
       endif

       if (optimizeforce(istruc).gt.0) then
          string2='Force'
       else
          if (freeenergy(istruc)) then
             string2='Free energy'
          else
             string2='Energy (E0)'
          endif
       endif
       if (vasprun(istruc).eqv..false.) then
          print *,trim(poscarfiles(istruc)),' ',trim(outcarfiles(istruc)), &
                  ' ',trim(string),' ',trim(string2),' ',weights(istruc)
       else
          print *,trim(poscarfiles(istruc)),' ',trim(string),' ', &
                  trim(string2),' ',weights(istruc)
       endif


       if (num.eqv..true.) then
          rlxstruc(istruc)=0
          vasprun_tocopy=vasprun(istruc)
          poscarfiles_tocopy=poscarfiles(istruc)
          outcarfiles_tocopy=outcarfiles(istruc)
          optimizeforce_tocopy=optimizeforce(istruc)
          ensureminimum_tocopy=ensureminimum(istruc)
          freeenergy_tocopy=freeenergy(istruc)
          weights_tocopy=weights(istruc)
          call getnumconfigs(string,limits,nlimits,step)
          do i=1,nlimits,2
             !print *,'i=',i,' going from: ',limits(i),'to ',limits(i+1)
             do j=limits(i),limits(i+1),step(i)
                vasprun(istruc)=vasprun_tocopy
                poscarfiles(istruc)=poscarfiles_tocopy
                outcarfiles(istruc)=outcarfiles_tocopy
                rlxstruc(istruc)=0
                optimizeforce(istruc)=optimizeforce_tocopy
                ensureminimum(istruc)=ensureminimum_tocopy
                freeenergy(istruc)=freeenergy_tocopy
                weights(istruc)=weights_tocopy
                nconfig(istruc)=j
                if (nconfig(istruc).lt.10) then
                   write(string3,'(I1)') nconfig(istruc)
                elseif (nconfig(istruc).lt.100) then
                   write(string3,'(I2)') nconfig(istruc)
                elseif (nconfig(istruc).lt.1000) then
                   write(string3,'(I3)') nconfig(istruc)
                elseif (nconfig(istruc).lt.10000) then
                   write(string3,'(I4)') nconfig(istruc)
                endif
                strucnames(istruc)=trim(poscarfiles_tocopy)//"_"//trim(string3)
                istruc=istruc+1
             enddo
          enddo
       else
          istruc=istruc+1
       endif

     enddo
     write(*,'(A13,I5,A40)') ' (fitting to ',nstruct,' atomic configurations across all files)'
     close(unit=1)

end subroutine setupfilenames

Julia translation

function setupfilenames()
    #!-------------------------------------------------------------c
    #!
    #!     Andy Duff, Feb 2008
    #!
    #!-------------------------------------------------------------c

    crystalstrucfile = 'crystal_struc.xyz'
    settingsfile = 'settings'
    fitdbse = 'fitdbse' #ORIGINAL_FILE_LINE[11090]
    lookuptablefile = 'lookuptablefile'
    inquire(file = strip(fitdbse),exist = exist)
    if ( exist==false )
        call createfitdbse
    end #if
    open(unit = 1,file = strip(fitdbse),status = 'old') #ORIGINAL_FILE_LINE[11100]
    read(1,*) nposcarfiles
    if ( nposcarfiles < 1 )
        println( "error: no. of poscarfiles must be greater than" )
        println( "or equal to 1" )
        stop
    end #if
    nstruct = 0
    for ifile = 1 : nposcarfiles #ORIGINAL_FILE_LINE[11110]
        read(1,'(a80)') line
        readfitdbseline( line, tmp, tmp2, string, string4, double )
        checkifnumber( string, num )
        if ( num==true )
            getnumconfigs( string, limits, nlimits, step )
            for i = 1 : 2 : nlimits
                for j = limits[i] : step[i] : limits[i+1]
                    nstruct = nstruct+1
                end #for
            end #for
        else
            nstruct = nstruct+1
        end #if
    end #for #ORIGINAL_FILE_LINE[11130]
    rewind(1)
    read(1,*)
    istruc = 1 #ORIGINAL_FILE_LINE[11140]
    print *
    println( "initializing fitting database" )
    println( "-----------------------------" )
    println( "file | configs to fit | quantity to fit | weights" )
    forcefit = false
        for ifile = 1 : nposcarfiles
            read(1,'(a80)') line
            readfitdbseline( line, poscarfiles(istruc), outcarfiles(istruc), string, string4, weights(istruc) )
            string2 = poscarfiles[istruc]
            if ( string2(1:7) == "vasprun" )
                vasprun[istruc] = true
                outcarfiles[istruc] = poscarfiles[istruc]
                write(string3,'(i10)') nconfig(istruc)
                strucnames[istruc] = strip(poscarfiles[istruc]) * strip(string3)
            else #ORIGINAL_FILE_LINE[11160]
                println( "error: vasprun files in fitdbse file must start with the word vasprun." )
                println( "stopping." )
                stop
                vasprun[istruc] = false
            end #if
            checkifnumber( string(1:1), num )
            if ( num==false )
                nconfig[istruc] = 1
                if ( string(1:1) == 'n' || string(1:1) == 'n' )
                    rlxstruc[istruc] = 0 #do not relax structure using potential
                elseif (string(1:1) == 'r' || string(1:1) == 'r')
                    rlxstruc[istruc] = 1 #relax structure using potential #ORIGINAL_FILE_LINE[11180]
                else
                    write(*,'(a26,a1,a37)') 'error: unexpected letter: ',string(1:1), ', in 3rd column of fitdbse. stopping.'
                    stop
                end #if
            end #if
            if ( string4(1:2) == 'fo' || string4(1:2) == 'fo' ||  string4(1:2) == 'fo' || string4(1:2) == 'fo' )
                optimizeforce[istruc] = 1
                forcefit = true
                else #ORIGINAL_FILE_LINE[11200]
                    optimizeforce[istruc] = 0
                    ensureminimum[istruc] = false
                    if ( string4(1:2) == 'fr' || string4(1:2) == 'fr' ||  string4(1:2) == 'fr' || string4(1:2) == 'fr' )
                        freeenergy[istruc] = true
                    elseif (string4(1:2) == 'e0' || string4(1:2) == 'e0') #ORIGINAL_FILE_LINE[11210]
                        freeenergy[istruc] = false
                    else
                        println( "error: incorrect entry in 3rd column of fitdbse file." )
                        println( "expecting e0, fr or fo. stopping" )
                        stop
                    end #if
                end #if
                if ( optimizeforce(istruc) > 0 )
                    string2 = 'force' #ORIGINAL_FILE_LINE[11220]
                else
                    if ( freeenergy(istruc) )
                        string2 = 'free energy'
                    else
                        string2 = 'energy (e0)'
                    end #if
                end #if
                if ( vasprun(istruc)==false )
                    println( strip(poscarfiles(istruc))," ",strip(outcarfiles(istruc))," ",strip(string)," ",strip(string2)," ",weights(istruc) ) #ORIGINAL_FILE_LINE[11230]
                else
                    println( strip(poscarfiles(istruc))," ",strip(string)," ",strip(string2)," ",weights(istruc) )
                end #if
                if ( num==true )
                    rlxstruc[istruc] = 0
                    vasprun_tocopy = vasprun[istruc]
                    poscarfiles_tocopy = poscarfiles[istruc] #ORIGINAL_FILE_LINE[11240]
                    outcarfiles_tocopy = outcarfiles[istruc]
                    optimizeforce_tocopy = optimizeforce[istruc]
                    ensureminimum_tocopy = ensureminimum[istruc]
                    freeenergy_tocopy = freeenergy[istruc]
                    weights_tocopy = weights[istruc]
                    getnumconfigs( string, limits, nlimits, step )
                    for i = 1 : 2 : nlimits
                        for j = limits[i] : step[i] : limits[i+1]
                            vasprun[istruc] = vasprun_tocopy #ORIGINAL_FILE_LINE[11250]
                            poscarfiles[istruc] = poscarfiles_tocopy
                            outcarfiles[istruc] = outcarfiles_tocopy
                            rlxstruc[istruc] = 0
                            optimizeforce[istruc] = optimizeforce_tocopy
                            ensureminimum[istruc] = ensureminimum_tocopy
                            freeenergy[istruc] = freeenergy_tocopy
                            weights[istruc] = weights_tocopy
                            nconfig[istruc] = j
                            if ( nconfig(istruc) < 10 )
                                write(string3,'(i1)') nconfig(istruc) #ORIGINAL_FILE_LINE[11260]
                            elseif (nconfig(istruc) < 100)
                                write(string3,'(i2)') nconfig(istruc)
                            elseif (nconfig(istruc) < 1000)
                                write(string3,'(i3)') nconfig(istruc)
                            elseif (nconfig(istruc) < 10000)
                                write(string3,'(i4)') nconfig(istruc)
                            end #if
                            strucnames[istruc] = strip(poscarfiles_tocopy) * "_" * strip(string3)
                            istruc = istruc+1
                        end #for #ORIGINAL_FILE_LINE[11270]
                    end #for
                else
                    istruc = istruc+1
                end #if
            end #for
            write(*,'(a13,i5,a40)') ' (fitting to ',nstruct,' atomic configurations across all files)'
            close(unit = 1)
end #subroutine setupfilenames #subroutine setupfilenames#ORIGINAL_FILE_LINE[11280]

I think you need to decide how to convert code organisation in Julia vs Fortran. I’m not familiar with Fortran, so take it with a pinch of salt.

So one option would be to put all your fortran functions in one Julia module. In that case, if you have multiple fortran files, translate them to multiple julia files, and then in a top level file, at the top, declare a new module, and include all your files in there. In that scheme, you can then drop all the use statements in the fortran code. So you’d have code like

using XXX
include("file2.jl")
include("file2.jl")
end #include

Another option would be to translate each fortran file as a separate module. In that case, each resulting Julia file would have a module XXX ... end in it. In that case, the using statement in Julia go at the top of the module. So you’d have code like this in each of your translated files:

module XXX
using YYYY
   function foo
         ....
   end #function 
end #module 

The key thing here is that in Julia, using cannot be used inside a function, it needs to be at the module level. This is the error you see.

Option one is probably simpler, if you are translating reasonably standalone libraries.

Hope that helps.

2 Likes

It may appear to work, and even run, but you can end up with very suboptimal and non-idiomatic code, reaping little (if any) benefit from using Julia.

Instead, I would recommend calling existing Fortran code from Julia, setting up a CI framework with lots of tests, and then porting all parts gradually.

5 Likes

Thanks for the advice! I am trying to warp up the existing code by using ccall() in Julia at the same time, because I know that I need to test the correctness of machine translation anyway (if I finally make that).

The Fortran code I have is written by a group of scientists and there are publications associated to the code. The purpose of translation is not entirely for performance. I need to examine what they did to solve the problem in science :smiley:

Thanks for the reply! My thoughts are closer to the second option, but I think one can still include files within a module.

It is indeed a problem of organising the code. I have made a dictionary of function-variable-module dependencies. If the translator sees USE module_A inside a Fortran function, it will look up the dictionary and prepend module_A to all functions and variables called in the rest part of that function. It is impossible to “flatten” the dependencies, I think. If I trivialise the dependencies, the concerns in the earlier replies would become real problems…