Присвоение пассивных значений или констант пользовательскому типу

Итак, я работаю над набором инструментов для автоматической дифференциации на Фортране, используя перегрузку операторов. Ранее я реализовал это на С++, но мне действительно нужно заставить его работать на Фортране.

У меня есть следующий модуль, определенный в Fortran:

     module adopov
         integer        :: indexcount
         integer, parameter :: tape_size = 1000
!
!....... ADtype
         public         :: ADtype
         type ADtype
            integer     :: index      = -1
            real        :: v          = 0.0
!
         contains
            procedure        :: oo_asg
            generic, public  :: assignment(=) => oo_asg
         end type ADtype
!
!....... class tape
         public         :: ADtape
         type ADtape
            real        :: v          = 0.0
         end type ADtape
!
!....... interface(s)
         interface assignment(=)
            module procedure oo_asg
         end interface
!
         type (ADtape), dimension(tape_size)  :: tape
!
!....... definitions
         contains
!
!....... assignment
         subroutine oo_asg (x,y)
            implicit none
            class(ADtype), intent(out)  :: x
            class(ADtype), intent(in)   :: y
!
            tape(indexcount)%v          = y%v
            indexcount = indexcount + 1
            x%v     = y%v
            x%index = indexcount
         end subroutine oo_asg
!
end module adopov

В С++ у меня есть аналогичный определяемый пользователем тип, как

class ADType {
    public:
      int index;
      double v;
      ADType() : index(-1), v(0) {};
      ADType(const double&);
      ADType& operator=(const ADType&);
  };

где конструктор устанавливает начальные значения для частей index и value. Затем у меня есть конструктор для пассивных значений или констант (типа double), так что я могу определить новую переменную класса (ADType) всякий раз, когда у меня есть двойная переменная. Например, когда у меня есть:

ADType x;
x = 2.0;

сначала создается новая переменная типа ADType со значением, установленным на 2.0, скажем, var1 = 2.0, а затем (в соответствии с оператором присваивания (=), определенным в классе ADType) я назначу эту переменную x, т. е. x = var1. Весь этот процесс записывается на ленту, которая подсчитывает операции и записывает значения и индексы.

Теперь вы можете сказать: «Зачем вам это нужно?». Ну а при сопряженном методе автоматического дифференцирования с перегрузкой операторов это необходимый шаг.

То, как я делаю это на C++, заключается в том, что у меня просто есть два следующих конструктора:

ADType:: ADType(const double& x): v(x) {
  tape[indexcounter].v = x;
  indexcounter++;
};

ADType& ADType::operator=(const ADType& x) {
  if (this==&x) return *this;
  tape[indexcounter].v = v = x.v;
  indexcounter++;
  return *this;
}

но я не знаю, как реализовать конструктор для пассивных значений и констант в Фортране.


person FRJedi    schedule 10.10.2016    source источник
comment
Это ваша программа, но если вы используете строчные буквы в C++, почему бы не использовать их и в Fortran? Они более читабельны. И вам не нужно = 0.D0, = 0 прекрасно подойдет, даже если ваши переменные были двойными. И поскольку ваши переменные являются вещественными значениями по умолчанию, D вообще не нужен.   -  person Vladimir F    schedule 10.10.2016
comment
@VladimirF Спасибо за советы. Я думаю, это просто старая привычка   -  person FRJedi    schedule 10.10.2016
comment
@VladimirF только что отредактировал вопрос, чтобы сделать его более читаемым   -  person FRJedi    schedule 10.10.2016
comment
Это нормально, это ваш код, вам решать.   -  person Vladimir F    schedule 10.10.2016


Ответы (2)


У вас есть два варианта, которые вы можете использовать в комбинации.

  1. Перегрузите определенное назначение процедурой, которая соответствует наличию РЕАЛЬНОГО объекта в правой части.

    TYPE ADTYPE
      ...
    CONTAINS
      PROCEDURE        :: OO_ASG
      PROCEDURE        :: ASSIGN_FROM_REAL
      GENERIC, PUBLIC  :: ASSIGNMENT(=) => OO_ASG, ASSIGN_FROM_REAL
    END TYPE AREAL
    
    ! Users of the module shoudn't be calling this procedure 
    ! (or the specific binding, really) directly.
    PRIVATE :: ASSIGN_FROM_REAL
    
    ...
    
    SUBROUTINE ASSIGN_FROM_REAL(x,y)
      CLASS(ADTYPE), INTENT(OUT)  :: x
      REAL, INTENT(IN)   :: y
    
      ! Do what you have to do...
      ...
      x%V = y
    END SUBROUTINE ASSIGN_FROM_REAL
    
    ! Example of use...
    TYPE(ADTYPE) :: z
    z = 2.0
    
  2. Используйте конструктор структуры или перегруженный процедурный эквивалент.

    INTERFACE ADTYPE
      MODULE PROCEDURE ADTYPE_construct_from_REAL
    END INTERFACE ADTYPE
    PRIVATE :: ADTYPE_construct_from_REAL
    ...
    
    FUNCTION ADTYPE_construct_from_REAL(y) RESULT(x)
      REAL, INTENT(IN) :: y
      TYPE(ADTYPE) :: x
    
      ! Do what you have to do.
      ...
      x%V = y
    END FUNCTION ADTYPE_construct_from_REAL
    
    ! Example of use:
    TYPE(ADTYPE) :: z
    z = ADTYPE(3.0)  
    

Если вы сделали вызов конструктора, который принимает двойное явное значение в исходном коде в вашем примере C++ (т.е. ADType x; x = ADType(2.0);), то у вас есть эквивалент второго из этих двух подходов - Фортран не имеет неявных преобразований между объектами производного типа.

(В вашем примере кода показано как назначение с привязкой к типу, так и автономный интерфейс в стиле Fortran 90. Не имеет смысла иметь оба - этот модуль даже не должен компилироваться.)

person IanH    schedule 10.10.2016
comment
@lanH Спасибо за ваш ответ и предложения. Сейчас я использую первый подход, чтобы избежать явного преобразования. Еще одна причина, по которой хотелось иметь неявное преобразование, заключалась в том, чтобы избежать перегрузки всех двоичных операций специальными процедурами, которые обрабатывали бы сценарии реального и производного типов (а также производного типа и реального). Это потребовало большего количества кода, но я внедрил его в модуль. В C++ я мог преобразовать «двойные» значения в производный тип на месте, а затем выполнить перегруженные операции. Любые предложения относительно того, как сделать это более эффективно в Fortran? Спасибо - person FRJedi; 10.10.2016
comment
Фортран не имеет неявных преобразований между объектами производного типа. У этого есть свои преимущества — это делает разрешение перегрузок очень простым. Просто используйте явную ссылку на процедуру или конструктор структуры. - person IanH; 10.10.2016

Вот полное рабочее предложение по вашей проблеме. Обратите внимание, что каждая переменная внутри модуля автоматически наследует атрибут save. Если вы в конечном итоге заинтересованы в параллелизме, вам, возможно, придется заключить indexcounter внутри ADtape с соответствующей процедурой с привязкой к типу для учета.

module adopov

  use, intrinsic :: ISO_C_binding, only: &
       ip => C_INT, &
       wp => C_DOUBLE

  ! Explicit typing only
  implicit none

  ! Everything is private unless stated otherwise
  private
  public :: Adtype, wp

  ! Declare derived data types
  type ADtape
     real(wp) :: v = 0.0_wp
  end type ADtape

  type, public :: ADtype
     integer(ip) :: index = -1
     real(wp)    :: v = 0.0_wp
   contains
     procedure, private :: asgn_from_type, asgn_from_real, asgn_from_int
     generic, public  :: assignment(=) => asgn_from_type, asgn_from_real, asgn_from_int
  end type ADtype

  ! Set user-defined constructor
  interface ADtype
     module procedure :: ADtype_constructor
  end interface ADtype

  ! Variables confined to the module.
  ! Please note that every variable
  ! in a module implicitly inherits the save attribute.
  integer            :: indexcount = 0 ! Your original code left this uninitialized
  integer, parameter :: TAPE_SIZE = 1000
  type (ADtape)      :: tape(TAPE_SIZE)

contains

  pure function ADtype_constructor(x, indx) result (return_value)
    real (wp),    intent(in)            :: x
    integer (ip), intent (in), optional :: indx
    type (ADtype)                       :: return_value

    return_value%v = x
    if (present(indx)) return_value%index = indx

  end function ADtype_constructor

  subroutine update_tape(float)
    real (wp), intent (in) :: float

    tape(indexcount)%v = float
    indexcount = indexcount + 1

  end subroutine update_tape

  subroutine asgn_from_type(this, y_type)
    class(ADtype), intent(out) :: this
    class(ADtype), intent(in)  :: y_type

    associate( &
         v => this%v, &
         indx => this%index &
         )

      call update_tape(y_type%v)
      v = y_type%v
      indx = indexcount
    end associate

  end subroutine asgn_from_type

  subroutine asgn_from_real(this, y_real)
    class(ADtype), intent(out) :: this
    real(wp),      intent(in)  :: y_real

    associate( &
         v => this%v, &
         indx => this%index &
         )
      call update_tape(y_real)
      v = y_real
      indx = indexcount
    end associate

  end subroutine asgn_from_real

  subroutine asgn_from_int(this, y_int)
    class(ADtype), intent(out) :: this
    integer(ip),   intent(in)  :: y_int

    associate( &
         v => this%v, &
         indx => this%index, &
         float => real(y_int, kind=wp) &
         )
      call update_tape(float)
      v = float
      indx = indexcount
    end associate

  end subroutine asgn_from_int

end module adopov

program main

  use, intrinsic :: ISO_Fortran_env, only: &
       stdout => OUTPUT_UNIT, &
       compiler_version, &
       compiler_options

  use adopov, only: &
       ADtype, wp

  ! Explicit typing only
  implicit none

  type(ADtype) :: foo, bar, woo

  ! Invoke the user-defined constructor
  foo = ADtype(42.0_wp)
  bar = ADtype(42.0_wp, -6)
  woo = foo

  print *, foo
  print *, bar
  print *, woo

  write( stdout, '(/4a/)') &
       ' This file was compiled using ', compiler_version(), &
       ' using the options ', compiler_options()

end program main

Это дает

gfortran -Wall -o main.exe adopov.f90 main.f90
./main.exe
           1   42.000000000000000     
           2   42.000000000000000     
           3   42.000000000000000     

 This file was compiled using GCC version 6.1.1 20160802 using the options -mtune=generic -march=x86-64 -Wall
person jlokimlin    schedule 14.10.2016