fortran66のブログ

fortran について書きます。Amazonのアソシエイトとして収入を得ています。

【メモ帳】Ubuntu24.04 での gfortran-15 共存

gfortran-15

gfortran-15 では、do concurrent(integer::i = 1:10**5) という局所変数の定義や、F_C_STRING(STRING [, ASIS]) のような、Fortrran 文字列を、尻に NULL のついた番兵法の C 文字列に直す関数などが使えるようです。Ubuntu 24.04 のデフォルトでは 14 までしか入らないので、無理に入れ込む必要があります。

最近、コンパイラの Fortran2023 対応がじわじわ進んできたので、ちょっと真面目に勉強する必要があるかもしれません。でも flang 系は相変わらず対応がノロいし、こういう細かい話はその都度 AI に聞けばいいっか〜的な気分もありです。

% ./a.out         
          10 Hello     
          11 Hello     
           6 Hello
PROGRAM test_fc_string
    USE ISO_C_BINDING
    IMPLICIT NONE
    
    CHARACTER(LEN=10) :: f_str = "Hello     "
    CHARACTER(KIND=C_CHAR, LEN=:), ALLOCATABLE :: c_str
    
    ! 末尾の空白を取り除き、Null文字を付与 ("Hello\0" になる)
    c_str = F_C_STRING(f_str, asis = .true.)
    print *, len(f_str), f_str
    print *, len(c_str), c_str  
    !
    c_str = F_C_STRING(f_str)
    print *, len(c_str), c_str
END PROGRAM test_fc_string

Ubuntu apt add-apt-repository

sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt update


### 必要に応じてC/C++コンパイラも15にする場合
sudo apt install gfortran-15
sudo update-alternatives --install /usr/bin/gfortran gfortran /usr/bin/gfortran-15 100
sudo apt install gcc-15 g++-15
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-15 100

### 登録されているバージョンから好きなものを対話的に選ぶ
sudo update-alternatives --config gcc
sudo update-alternatives --config g++
sudo update-alternatives --config gfortran

### 選択肢に 13, 14 も入れる
sudo update-alternatives --install /usr/bin/gfortran gfortran /usr/bin/gfortran-13 50
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 50
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 50

sudo update-alternatives --install /usr/bin/gfortran gfortran /usr/bin/gfortran-14 60
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 60
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 60

【ニュース】Intel Fortran 2026.0 出る! なんと Fortran 2023 の三項演算子が実装さるw

OneAPI 統合

Base Toolkit と HPC Toolkit に分かれていて、ウザかったのが統一されました。

With the 2026.0 Release, the Intel® oneAPI Base Toolkit (Base Kit) and Intel® oneAPI HPC Toolkit have been combined into the Intel® oneAPI Toolkit.

www.intel.com

なお、Linux の apt upgrade では、なぜか Fortran が入らないので

sudo apt install intel-oneapi-toolkit

を実行する必要があります。

Fortran 新機能

リリースノートを見ると、Fortran 標準文法に関しては、ファイル出力数値フォーマット、coarray の NOTIFY の改善、そして F2023 規格制定の時も物議を醸した C にあるような三項演算子が入っています。

そもそも三項演算子は、評価順が左側から決まるシングルスレッド前提のクソみたいな文法だし、読みやすいわけでもなく、なんでこんなものを入れるのか思っておりました。アレイ・プロセッサやベクトル・プロセッサの頃から、Fortran はとりあえず全部一度に(並列に)計算して、要らないのを捨てるスタイルで、捨てるものばっかりの時は PACK/UNPACK で要るものだけ gather/scatter すればいいという思想ですし。

しかし擁護派の人が出してきたメリットが、サブルーチンの引数などを与えるのに IF 文分岐より見やすくなるというもので、まぁ確かにそのような用途に限定して使えばメリットもあるかなと思えました。

つまり計算のマトモな部分に使う文法要素ではなく、計算機的なウザい枝葉末端の整理向きに限定すべきものなのかと思えてきました。まぁ想定外に乱用されてパフォーマンスが落ちそうな気がしますが。

www.intel.com

三項演算子 実行例 (Optinal 引数)

Optional 引数は、副プログラムの頭部が本筋ではないところで重くなってしまい、見苦しくてしょうがないのですが、こういう用途には合っているかもしれません。

実行結果

   10.00000
   999.9990

プログラム

    program CondExpression
        implicit none

        call sub(10.0)
        
        call sub()
        
    contains
        subroutine sub(x0)
            import, none
            real, intent(in), optional:: x0
            real :: x
            x = (present(x0) ? x0 : 999.999) 
            print *, x
        end subroutine sub   
    end program CondExpression

カーニハンの回顧録

先日、図書館をのぞいたところ「カーニハンの UNIX 回顧録」というのがありました。パラパラ立ち読みした限りでは、薄めの味付けで歴史的に興味深いことをサラッと書いている感じでした。

【メモ帳】ニュースなど

Winteracter 無料化

Winteracter というのは、Fortran 用の GUI ライブラリで ’90 年代からあった気がしますが、販売を停止し無料化されたようです。ダウンロードには登録が必要です。

今、デモなどを見ますと、なんとなく古式ゆかしい雰囲気が漂います。

www.winteracter.uk

ネタ元: cyber.dabamos.de

gfortran 16 出る!

gcc.gnu.org

Fortran

  • Coarrays using native shared memory mulithreading on single node machines and handling Fortran 2018's TEAM feature.

  • Fortran 2003: Parameterized Derived Types support is improved. Handling of LEN parameters works but still requires a future change of representation (see PR82649).

  • Fortran 2018: Support the extensions to the IMPORT statement, the REDUCE intrinsic and the new GENERIC statement.

  • The Fortran 2023 additions to the trigonometric functions are now supported (such as the sinpi intrinsic).

  • Fortran 2023: The split intrinsic subroutine is now supported and c_f_pointer now accepts an optional lower bound as a argument.

  • The -fexternal-blas64 option has been added to call external BLAS routines with 64-bit integer arguments for MATMUL. This option is only valid for 64-bit systems and when -ffrontend-optimize is in effect.

ネタ元: fortran-lang.discourse.group

BOLT Graphics の ZEUS 続報

64bit 浮動小数点数も加速してくれるという約束手形を切っている ZEUS 君。

少し前にメールが来ていました。ネットの下馬評では、「速さからコスパに目標が変わっていて草」などという感じです。色々出す出すサギの前科もあるようなので皆さん眉に唾をつけて聞いているようです。

www.prnewswire.com

【寝言】AI につくらせた漢文学習 Web 教材デモ

漢文学習教材 Web App

戯れに Claude に、「そこな蔵人よ。麿に漢文学習アプリを作ってたもれ。オプションで白文、返点、送り仮名、読み仮名を消したり出したりできるやつ。」と頼んだところ、5分もせぬうちに作ってくれました。横書きでw さすが加速主義で伝統破壊を目指す AI よと感心させられました。

伝統的には縦書きだと言って直すように命じ、その他細々伝統に従うように頼んだところ、デモを作ってくれました。返点も AI が打ってくれましたが、一部間違っているので指摘したところ、ルールを誤解していたとかいうので、その場で考えていたのでしょうか?謎です。

いかなる仕組みで動いているのか、WEB 知識が CERN の HTML1.0 で止まっている私には皆目見当もつきません。ずいぶん前から、こういう教材があれば、とても勉強が捗るであろうと思っていましたが、AI がものの半刻ばかりで作り上げるのには驚かせられました。まさに後生恐るべし。

AI 面白すぎ。アイデア勝負の時代というのは本当のようです。

github にあげて pages で実物を動かせるように、手取り足取り教えてもらいました。 f66blog.github.io

なんか「しこうして」とかになってますがw

【メモ帳】Fortran 実行時に AI に Python スクリプトを書かせて smolvm で実行する

Fortran で動的に AI にスクリプトを書かせて実行するデモ

自分でプログラムを考えるのが馬鹿らしいこの頃、Fortran ではめんどくさい作業を、自然言語のプロンプトを与えて、動的に AI に Python スクリプトを書いてもらい、それをそのまま実行することを考えます。

しかし、AI の反乱を恐れて生成されたスクリプトは隔離された環境で行うのが流行りのようなので、その波に乗ることにします。smolvm という独立 Kernel をもつ Virtual Machine を Docker に近い身軽さで起動するものが、AI 生成プログラムなどを動かす Sandbox 的隔離環境として流行っているようです。ところで smolvm は同名のものが複数あって、何が何やらわからないのですが、WSL2 の環境で動くものを AI に選ばせたところ pip で入る smolvm を指名されたので、それを用います。WSL2 のせいか色々制限があるので AI によく聞かれるのがいいと思います。

smolvm のデフォルトの vm は何も入ってない Alpine Linux の最小イメージなので、Python/numpy/matplotlib インストールしたイメージを一回作って保存し、そのイメージを保存して利用することにします。これはすこし太ったイメージなので VM 起動が少し遅くなります。

VM はメモリー的に完全に隔離されて WSL2 では、ReadOnly でも disk 共有ができなかったので、scp でコピーすることで対応します。結果を持ってくるにはこれを使わざるおえないので、まぁいいでしょう。

実行結果

以下では Fortran が Bessel 関数を計算し、バイナリファイルに配列データとして出力します。

次に、curl を使って、AI (GPT-5.4-mini) の API 呼び出しを行い、matplotlib で図を描くプログラムを作れと命じます。API 呼び出しには json が必要になりますが、Linux の jq コマンド利用で切り抜けます。ここで python スクリプトが出来上がります。OpenAI の key を環境変数にセットしておく必要があります。漏洩すると、悪いおじさんが乱用してとんでもない額の請求書がやってくるようです。

最後に、いま生成したスクリプトを smolvm で実行します。あらかじめ smolvm の設定が必要になります。なかなか面倒なので、AI に言われるまま設定する必要となります。動くまでだいぶ苦労しました。smolvm は完全独立したメモリ空間にあるので、データの受け渡しは工夫が必要です。ここでは scp によるファイルコピーで解決しました。初めは、Disk の共有で行こうとしたのですが、WSL2 では無理なようでした。ip アドレスのマスカレード処理もする必要がありましたが、AI の言うがままに行ったため説明できません。気分的にはエドガー・ポーの「アモンティリャードの酒樽」の最後のごとく「アモンティリャード」と繰り返すのみです。

なお、プログラムは主に ChatGPT と Claude の chat mode で作り、時々 Gemini にも聞きました。自力では smolvm 設定や API 呼び出しの json 処理などは到底なしえなかったと思います。

AI に与えたプロンプトと、その指示から生成したスクリプト

見た感じ、複数回実行しても毎回改行以外は同じ Python スクリプトが生成されています。

Read data.bin as raw binary float64 data using numpy.fromfile. The file contains repeated pairs x,y: x0,y0,x1,y1,... . Reshape the array to (-1, 2). Use column 0 as x and column 1 as y. Plot y vs x and save to plot.png. Use matplotlib with the gg backend. Do not use pandas. Output ONLY raw Python code, no fences, no explanation.
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

data = np.fromfile("data.bin", dtype=np.float64)
xy = data.reshape(-1, 2)
x = xy[:, 0]
y = xy[:, 1]

plt.plot(x, y)
plt.xlabel("x")
plt.ylabel("y")
plt.savefig("plot.png", dpi=150, bbox_inches="tight")

#プログラム

module ai_openai_mod
    !! Module for generating code through the OpenAI Chat Completions API.
    !! Requires: curl, jq, and the OPENAI_API_KEY environment variable.
    implicit none
    private
    public :: ai_generate_python

    character(len=*), parameter :: DEFAULT_MODEL = "gpt-5.4-mini"
contains
    subroutine ai_generate_python(prompt, outfile, model)
        character(len=*), intent(in)           :: prompt
        character(len=*), intent(in)           :: outfile
        character(len=*), intent(in), optional :: model
        character(len=:), allocatable :: m
        integer :: u

        m = DEFAULT_MODEL
        if (present(model)) m = model

        ! Pass the prompt through a file and let jq read it with --rawfile.
        ! jq handles escaping for quotes, backslashes, and newlines.
        open(newunit=u, file="prompt.txt", status="replace", action="write")
        write(u,'(a)') prompt
        close(u)

        call execute_command_line(                                           &
            'jq -n --arg model "' // m // '" --rawfile content prompt.txt ' // &
            '''{model: $model, messages: [{role: "user", content: $content}]}''' // &
            ' > req.json')

        call execute_command_line(                                           &
            'curl -sS https://api.openai.com/v1/chat/completions '         // &
            '-H "Authorization: Bearer $OPENAI_API_KEY" '                  // &
            '-H "Content-Type: application/json" '                         // &
            '-d @req.json | jq -r ".choices[0].message.content" > ' // outfile)
    end subroutine ai_generate_python
end module ai_openai_mod

program demo
    !! Fortran -> AI (OpenAI) -> Python -> smolvm execution.
    !! This program writes J_0(x) data as raw binary, asks the AI to generate
    !! a plotting Python script, runs it inside a disposable smolvm VM,
    !! and retrieves plot.png from the VM.
    use iso_fortran_env, only : real64
    implicit none

    integer :: u, i, ios
    real(real64) :: x, y
    character(len=*), parameter :: fbin = 'data.bin'
    character(len=*), parameter :: pys  = 'gen.py'

    ! --- 1. Generate data in Fortran ---
    ! data.bin is raw binary float64 data:
    !   x0,y0,x1,y1,x2,y2,...
    open(newunit=u, file=fbin, status='replace', action='write', &
         access='stream', form='unformatted', iostat=ios)
    if (ios /= 0) stop 'cannot open data.bin'

    do i = 0, 200
        x = 0.1_real64 * i
        y = bessel_j0(x)
        write(u) x, y
    end do
    close(u)

    ! --- 2. Ask the AI to generate a Python script ---
 AI:block
        use ai_openai_mod, only: ai_generate_python

        character(len=*), parameter :: PROMPT = &
            "Read data.bin as raw binary float64 data using numpy.fromfile. "  // &
            "The file contains repeated pairs x,y: x0,y0,x1,y1,... . "         // &
            "Reshape the array to (-1, 2). "                                   // &
            "Use column 0 as x and column 1 as y. "                            // &
            "Plot y vs x and save to plot.png. "                               // &
            "Use matplotlib with the Agg backend. Do not use pandas. "         // &
            "Output ONLY raw Python code, no fences, no explanation."

        print *, 'Asking AI...'
        call ai_generate_python(PROMPT, pys)
        print *, '...Finished'
    end block AI

    ! --- 3-10. Run the generated Python script in smolvm and retrieve the result ---
smolvm:block
        character(len=*), parameter :: vm      = 'ftvm'
        character(len=*), parameter :: sshkey  = '~/.smolvm/keys/id_ed25519'
        character(len=*), parameter :: sshopts = &
            '-o StrictHostKeyChecking=no -o BatchMode=yes'

        character(len=4096) :: cmd
        character(len=16)   :: port
        integer :: exitstat

        ! --- 3. Stop and delete any existing VM with the same name ---
        call execute_command_line( &
            'smolvm stop ' // vm // ' >/dev/null 2>&1 || true', exitstat=exitstat)

        call execute_command_line( &
            'smolvm delete ' // vm // ' >/dev/null 2>&1 || true', exitstat=exitstat)

        ! --- 4. Restore the prepared VM disk image ---
        call execute_command_line( &
            'cp ~/.smolvm/images/alpine-with-python.ext4' // &
            ' ~/.local/state/smolvm/disks/' // vm // '.ext4', &
            exitstat=exitstat)

        if (exitstat /= 0) stop 'image restore failed'

        ! --- 5. Start the VM ---
        cmd = 'smolvm create --backend firecracker --name ' // vm // &
              ' --boot-timeout 30'

        print *, trim(cmd)
        call execute_command_line(trim(cmd), exitstat=exitstat)

        if (exitstat /= 0) stop 'smolvm create failed'

        ! --- 6. Detect the forwarded SSH port ---
        call execute_command_line( &
            'smolvm list --json | jq -r ''.data.vms[] | select(.name=="' // vm // &
            '") | .ssh_port'' > .port_tmp', exitstat=exitstat)

        if (exitstat /= 0) stop 'port detection failed'

        open(newunit=u, file='.port_tmp', status='old', action='read', iostat=ios)
        if (ios /= 0) stop 'cannot read port'

        read(u,'(A)') port
        port = adjustl(port)

        close(u)

        call execute_command_line('rm -f .port_tmp', exitstat=exitstat)

        print *, 'SSH port: ', trim(port)

        ! --- 7. Copy the binary data and generated Python script to the VM ---
        cmd = 'scp -i ' // sshkey // ' -P ' // trim(port) // ' ' // sshopts // &
              ' ' // fbin // ' ' // pys // ' root@127.0.0.1:/tmp/'

        print *, trim(cmd)
        call execute_command_line(trim(cmd), exitstat=exitstat)

        if (exitstat /= 0) stop 'scp to vm failed'

        ! --- 8. Run the Python script inside the VM ---
        cmd = 'ssh -i ' // sshkey // ' -p ' // trim(port) // ' ' // sshopts // &
              ' root@127.0.0.1' // &
              ' "cd /tmp && python3 ' // pys // '"'

        print *, trim(cmd)
        call execute_command_line(trim(cmd), exitstat=exitstat)

        if (exitstat /= 0) stop 'python in vm failed'

        ! --- 9. Retrieve the generated plot from the VM ---
        cmd = 'scp -i ' // sshkey // ' -P ' // trim(port) // ' ' // sshopts // &
              ' root@127.0.0.1:/tmp/plot.png ./plot.png'

        print *, trim(cmd)
        call execute_command_line(trim(cmd), exitstat=exitstat)

        if (exitstat /= 0) stop 'scp from vm failed'

        ! --- 10. Stop and delete the VM ---
        call execute_command_line('smolvm stop ' // vm, exitstat=exitstat)

        call execute_command_line( &
            'smolvm delete ' // vm // ' >/dev/null 2>&1', exitstat=exitstat)

        ! --- 11. Remove temporary work files ---
        call execute_command_line('rm -f prompt.txt req.json .port_tmp', exitstat=exitstat)
    end block smolvm

    print *, 'done. host file: plot.png'
end program demo

【メモ帳】Linux の RAM Disk /dev/shm

execute_command_line からの python 実行時に RAM Disk 利用

昔、Fortran 2003 で導入された execute_command_line の利用例として、Fortran の中で Python スクリプトを書いてファイル出力した後、それを実行すると言うことをやりました。

fortran66.hatenablog.com

その時は Fortran と Python でのデータのやり取りを、直接スクリプト内に書き込む方式でやりましたが、numpy があれば Fortran 形式のバイナリファイルを読み書きできるので、ファイルを介したデータやり取りが比較的効率的にできます。この時、Linux だとお仕着せの RAM Disk があると言うことを AI に教えられたので、それを利用してみることにします。MacOS / Windows には残念ながらデフォルト状態で使える仕組みはないようです。

この方式は、一旦外部ファイルに書き出すのでダサい感はあるのですが、DLL とか Link path とかを気にしなくていいので、コロコロ環境が変わる Python に対するロバスト性というか、打たれ強さがあるところが利点です。また、スクリプトを内部生成するので、ファイルが Fortran プログラムだけで済むことも気楽です。ただ write 文で書き出すのが色々だるいところがありますが、今は AI 小僧に書かせればいいので昼下がりの気だるさ位で済みます。

実行結果

配列を外部ファイルに書き出して、Python に処理させた後、また配列に読み込みます。

(pyfort) $ ./a.out
 RUN: python /dev/shm/pyfor.py /dev/shm/fort_in.bin /dev/shm/fort_out.bin 10
Python read: [ 1.  2.  3.  4.  5.  6.  7.  8.  9. 10.]
 Python exit status =            0
 Fortran got back:
   10.500000000000000        20.500000000000000        30.500000000000000        40.500000000000000        50.500000000000000        60.500000000000000        70.500000000000000        80.500000000000000        90.500000000000000        100.50000000000000

ソース・プログラム

Linu ではファイル位置を /dev/shm 下におくと RAM Disk が使われるそうなので、少しだけ外部ファイル経由が軽くなります。WSL2 だとそれで動くけれど、メモリ管理の複雑さから色々微妙な模様です。

なお CLOSE 文に status = 'DELETE' 属性をつけることで、テンポラリのデータファイルや Python スクリプトを消去しています。

program pytest
    use iso_fortran_env, only : real64
    implicit none

    integer, parameter :: n = 10
    real(real64) :: arr(n), arr2(n)
    integer :: i, u, ios, exitstat
    character(len=*), parameter :: fin  = '/dev/shm/fort_in.bin'
    character(len=*), parameter :: fout = '/dev/shm/fort_out.bin'
    character(len=*), parameter :: pys  = '/dev/shm/pyfor.py'
    character(len=512) :: cmd

    do i = 1, n
        arr(i) = real(i, real64)
    end do

    !--- make data file
    open(newunit=u, file=fin, access='stream', form='unformatted', &
         status='replace', action='write', iostat=ios)
    if (ios /= 0) stop 'cannot open input binary'
    write(u) arr
    close(u)

    !--- generate Python script 
    open(newunit=u, file=pys, status='replace', action='write', iostat=ios)
    if (ios /= 0) stop 'cannot open python script'

    write(u,'(A)') 'import sys'
    write(u,'(A)') 'import numpy as np'
    write(u,'(A)') ''
    write(u,'(A)') 'fin  = sys.argv[1]'
    write(u,'(A)') 'fout = sys.argv[2]'
    write(u,'(A)') 'n    = int(sys.argv[3])'
    write(u,'(A)') ''
    write(u,'(A)') 'arr = np.fromfile(fin, dtype=np.float64, count=n)'
    write(u,'(A)') 'print("Python read:", arr)'
    write(u,'(A)') 'arr = arr * 10.0 + 0.5'
    write(u,'(A)') 'arr.tofile(fout)'
    close(u)

    !--- Python Execution
    !
    cmd = 'python ' // pys // ' ' // fin // ' ' // fout // ' 10'
    print *, 'RUN: ', trim(cmd)

    call execute_command_line(trim(cmd), exitstat=exitstat)
    print *, 'Python exit status = ', exitstat
    if (exitstat /= 0) stop 'python failed'

    !--- read results
    open(newunit=u, file=fout, access='stream', form='unformatted', &
         status='old', action='read', iostat=ios)
    if (ios /= 0) stop 'cannot open output binary'
    read(u) arr2
    print *, 'Fortran got back:'
    print *, arr2

    ! delete files
    close(u, status = 'delete')

    open(newunit = u, file = fin)
    close(u, status = 'delete')

    open(newunit = u, file = pys)
    close(u, status = 'delete')
end program pytest

【メモ帳】Tiobe 月旦評 2026年 4月号 

Fortran 13位 変わらず

評論は Rust について、天井に来た感もプロ向けには伸び代?

そろそろメジャー言語は、AI 任せに流れて、生産者の顔が見える昔ながらの一文字一文字心を込めて打ち込んだ Fortran プログラムの時代が蘇るw 虫が残っているのも手作り無農薬の証。

www.tiobe.com