F2Py กับอาร์เรย์รูปร่างที่จัดสรรและสันนิษฐาน


18

ฉันต้องการใช้f2pyกับ Fortran ที่ทันสมัย โดยเฉพาะอย่างยิ่งฉันพยายามทำให้ตัวอย่างพื้นฐานต่อไปนี้ทำงาน นี่คือตัวอย่างที่มีประโยชน์น้อยที่สุดที่ฉันสามารถสร้างได้

! alloc_test.f90
subroutine f(x, z)
  implicit none

! Argument Declarations !
  real*8, intent(in) ::  x(:)
  real*8, intent(out) :: z(:)

! Variable Declarations !
  real*8, allocatable :: y(:)
  integer :: n

! Variable Initializations !
  n = size(x)
  allocate(y(n))

! Statements !
  y(:) = 1.0
  z = x + y

  deallocate(y)
  return
end subroutine f

ทราบว่ามีการสรุปจากรูปร่างของพารามิเตอร์การป้อนข้อมูลn xโปรดทราบว่าyมีการจัดสรรและยกเลิกการจัดสรรภายในเนื้อหาของรูทีนย่อย

เมื่อฉันรวบรวมสิ่งนี้ด้วย f2py

f2py -c alloc_test.f90 -m alloc

และเรียกใช้ใน Python

from alloc import f
from numpy import ones
x = ones(5)
print f(x)

ฉันได้รับข้อผิดพลาดดังต่อไปนี้

ValueError: failed to create intent(cache|hide)|optional array-- must have defined dimensions but got (-1,)

ดังนั้นฉันไปและสร้างและแก้ไขpyfไฟล์ด้วยตนเอง

f2py -h alloc_test.pyf -m alloc alloc_test.f90

เป็นต้นฉบับ

python module alloc ! in 
    interface  ! in :alloc
        subroutine f(x,z) ! in :alloc:alloc_test.f90
            real*8 dimension(:),intent(in) :: x
            real*8 dimension(:),intent(out) :: z
        end subroutine f
    end interface 
end python module alloc

ดัดแปลง

python module alloc ! in 
    interface  ! in :alloc
        subroutine f(x,z,n) ! in :alloc:alloc_test.f90
            integer, intent(in) :: n
            real*8 dimension(n),intent(in) :: x
            real*8 dimension(n),intent(out) :: z
        end subroutine f
    end interface 
end python module alloc

ตอนนี้ก็ทำงาน แต่ค่าของการส่งออกอยู่เสมอz 0บางพิมพ์แก้ปัญหาเผยให้เห็นว่าnมีค่าภายในย่อย0 fฉันคิดว่าฉันขาดf2pyเวทมนต์ส่วนหัวเพื่อจัดการสถานการณ์นี้อย่างเหมาะสม

โดยทั่วไปแล้ววิธีที่ดีที่สุดในการเชื่อมโยงรูทีนย่อยด้านบนเข้ากับ Python คืออะไร? ฉันต้องการอย่างยิ่งที่จะไม่ต้องแก้ไขรูทีนย่อยเอง


Matt คุณคุ้นเคยกับแนวทางปฏิบัติที่ดีที่สุดของ Ondrej Certik โดยเฉพาะอย่างยิ่งส่วนInterfacing with Pythonหรือไม่? เราได้พูดคุยถึงปัญหาการเชื่อมต่อที่คล้ายกันสำหรับ PyClaw และยังไม่สามารถแก้ไขได้ในตอนนี้ :)
Aron Ahmadia

คำตอบ:


23

ฉันไม่คุ้นเคยกับ f2py internals มาก แต่ฉันคุ้นเคยกับการปิด Fortran F2py เพียงดำเนินการบางอย่างหรือทั้งหมดด้านล่างโดยอัตโนมัติ

  1. ก่อนอื่นคุณต้องส่งออกไปยัง C โดยใช้โมดูล iso_c_binding ดังที่อธิบายไว้ที่นี่:

    http://fortran90.org/src/best-practices.html#interfacing-with-c

    ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้เขียนหลักของหน้า fortran90.org นี่เป็นแพลตฟอร์มเดียวและคอมไพเลอร์อย่างอิสระในการเรียก Fortran จาก C. นี่คือ F2003 ดังนั้นทุกวันนี้จึงไม่มีเหตุผลที่จะใช้วิธีอื่น

  2. คุณสามารถส่งออก / โทรอาเรย์เท่านั้นตามที่ระบุไว้เต็มความยาว (รูปร่างที่ชัดเจน) นั่นคือ:

    integer(c_int), intent(in) :: N
    real(c_double), intent(out) :: mesh(N)

    แต่ไม่คิดว่ารูปร่าง:

    real(c_double), intent(out) :: mesh(:)

    นั่นเป็นเพราะภาษา C ไม่สนับสนุนอาร์เรย์ดังกล่าวเอง มีการพูดคุยเพื่อรวมการสนับสนุนดังกล่าวใน F2008 หรือใหม่กว่า (ฉันไม่แน่ใจ) และวิธีการทำงานก็คือการสนับสนุนโครงสร้างข้อมูล C บางอย่างเนื่องจากคุณต้องพกข้อมูลรูปร่างเกี่ยวกับอาร์เรย์

    ใน Fortran คุณควรใช้รูปร่างสมมติเป็นหลักเฉพาะในกรณีพิเศษที่คุณควรใช้รูปร่างที่ชัดเจนดังที่อธิบายไว้ที่นี่:

    http://fortran90.org/src/best-practices.html#arrays

    ซึ่งหมายความว่าคุณต้องเขียน wrapper อย่างง่าย ๆ รอบรูทีนย่อยสมมติของคุณซึ่งจะห่อสิ่งต่าง ๆ ลงในอาร์เรย์รูปร่างที่ชัดเจนตามลิงก์แรกของฉันด้านบน

  3. เมื่อคุณมีลายเซ็น C เพียงเรียกจาก Python ในแบบที่คุณต้องการฉันใช้ Cython แต่คุณสามารถใช้ ctype หรือ C / API ด้วยมือ

  4. deallocate(y)งบไม่จำเป็นต้อง Fortran deallocates โดยอัตโนมัติ

    http://fortran90.org/src/best-practices.html#allocatable-arrays

  5. real*8ไม่ควรใช้ แต่ควรreal(dp):

    http://fortran90.org/src/best-practices.html#floating-point-numbers

  6. คำสั่งy(:) = 1.0กำหนด 1.0 ด้วยความแม่นยำเดียวดังนั้นตัวเลขที่เหลือจะถูกสุ่ม! นี่เป็นข้อผิดพลาดทั่วไป:

    http://fortran90.org/src/gotchas.html#floating-point-numbers

    y(:) = 1.0_dpคุณจำเป็นต้องใช้

  7. แทนที่จะเขียนว่าy(:) = 1.0_dpคุณก็สามารถเขียนy = 1ที่มัน คุณสามารถกำหนดจำนวนเต็มให้กับจำนวนจุดลอยตัวโดยไม่สูญเสียความแม่นยำและคุณไม่จำเป็นต้องใส่ข้อมูลซ้ำซ้อน(:)ลงไป ง่ายกว่ามาก

  8. แทน

    y = 1
    z = x + y

    เพียงแค่ใช้

    z = x + 1

    และไม่ต้องกังวลกับyอาเรย์เลย

  9. คุณไม่ต้องการคำสั่ง "return" ในตอนท้ายของรูทีนย่อย

  10. สุดท้ายคุณควรใช้โมดูลและใส่implicit noneระดับโมดูลและคุณไม่จำเป็นต้องทำซ้ำในแต่ละรูทีนย่อย

    มิฉะนั้นมันดูดีสำหรับฉัน นี่คือรหัสตามคำแนะนำ 1-10 ด้านบน ::

    module test
    use iso_c_binding, only: c_double, c_int
    implicit none
    integer, parameter :: dp=kind(0.d0)
    
    contains
    
    subroutine f(x, z)
    real(dp), intent(in) ::  x(:)
    real(dp), intent(out) :: z(:)
    z = x + 1
    end subroutine
    
    subroutine c_f(n, x, z) bind(c)
    integer(c_int), intent(in) :: n
    real(c_double), intent(in) ::  x(n)
    real(c_double), intent(out) :: z(n)
    call f(x, z)
    end subroutine
    
    end module

    มันแสดงให้เห็นถึงรูทีนย่อยที่เรียบง่ายเช่นเดียวกับ C wrapper

    เท่าที่ f2py อาจพยายามเขียนเสื้อคลุมนี้ให้คุณและล้มเหลว ฉันก็ไม่แน่ใจเหมือนกันว่ามันใช้iso_c_bindingโมดูลหรือไม่ ดังนั้นด้วยเหตุผลทั้งหมดนี้ฉันจึงชอบพันสิ่งด้วยมือ จากนั้นมันชัดเจนว่าเกิดอะไรขึ้น


เท่าที่ฉันรู้ f2py ไม่พึ่งพาการผูก ISO C (เป้าหมายหลักคือรหัส Fortran 77 และ Fortran 90)
Aron Ahmadia

ฉันรู้ว่าฉันโง่เล็กน้อยyแต่ฉันต้องการที่จะทำบางสิ่งบางอย่างได้รับการจัดสรร (รหัสที่แท้จริงของฉันมีการจัดสรรที่ไม่สำคัญ) ฉันไม่รู้เกี่ยวกับประเด็นอื่น ๆ อีกมากมาย ดูเหมือนว่าฉันควรไปดูคู่มือปฏิบัติที่ดีที่สุดของ Fortran90 บางอย่าง .... ขอบคุณสำหรับคำตอบที่ละเอียด
MRocklin

โปรดทราบว่าการใช้คอมไพเลอร์ของ Fortran ในวันนี้คุณห่อ F77 ด้วยวิธีเดียวกัน --- โดยการเขียนตัวห่อหุ้ม iso_c_binding อย่างง่ายและเรียกรูทีนย่อย f77 ดั้งเดิมจากนั้น
OndřejČertík

6

สิ่งที่คุณต้องทำคือ:

!alloc_test.f90
subroutine f(x, z, n)
  implicit none

! Argument Declarations !
  integer :: n
  real*8, intent(in) ::  x(n)
  real*8, intent(out) :: z(n)

! Variable Declarations !
  real*8, allocatable :: y(:)

! Variable Initializations !
  allocate(y(n))

! Statements !
  y(:) = 1.0
  z = x + y

  deallocate(y)
  return
end subroutine f

แม้ว่าขนาดของอาร์เรย์ x และ z จะถูกส่งผ่านเป็นอาร์กิวเมนต์ชัดเจน แต่ f2py ทำให้อาร์กิวเมนต์ n เป็นทางเลือก ต่อไปนี้เป็น docstring ของฟังก์ชั่นตามที่มันดูเหมือนว่าหลาม:

Type:       fortran
String Form:<fortran object>
Docstring:
f - Function signature:
  z = f(x,[n])
Required arguments:
  x : input rank-1 array('d') with bounds (n)
Optional arguments:
  n := len(x) input int
Return objects:
  z : rank-1 array('d') with bounds (n)

การนำเข้าและโทรจากไพ ธ อน:

from alloc import f
from numpy import ones
x = ones(5)
print f(x)

ให้เอาต์พุตต่อไปนี้:

[ 2.  2.  2.  2.  2.]

มีวิธีที่จะใช้การแสดงออกที่ไม่สำคัญเป็นขนาดหรือไม่? ยกตัวอย่างเช่นฉันผ่านและต้องการที่จะได้รับอาร์เรย์ของขนาดn 2 ** nจนถึงตอนนี้ฉันต้องผ่าน 2 ** n เพื่อเป็นข้อโต้แย้งแยกกัน
Alleo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.