Postgresのstored functionで2次元配列を引数にし,Pythonから呼び出す

How to call a Postgres stored function with an matrix as an argument from Python

あまりサンプルがなかったので,簡単なものを作成してみた。その2。
ここを参考にした。

  • Postgres 16
  • Python 12

二次元配列で数字のリストを渡して,ストアドファンクションで合計を計算し,返す

Postgresql


drop function if exists matfunc;
create function matfunc(mat integer[][])
returns integer as
$$
declare 
  res integer := 0;
  i integer;
  j integer;
begin

for i in array_lower(mat,1)..array_upper(mat,1) loop
  for j in array_lower(mat,2)..array_upper(mat,2) loop
    res := res + mat[i][j];
  end loop;
end loop;
return res;
end;
$$
language plpgsql;

select matfunc(ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]]);

Python

import psycopg2

try:
    ps_connection = psycopg2.connect(user="username",
                                     password="password",
                                     host="127.0.0.1",
                                     port="5432",
                                     database="testdatabase")

    cursor = ps_connection.cursor()

    lst =[ [1,2,3],[4,5,6]]
    # call stored procedure
    cursor.execute('select matfunc(%s)', (lst,))

    result = cursor.fetchall()
    for row in result:
        print("sum = ", row[0], )

except (Exception, psycopg2.DatabaseError) as error:
    print("Error while connecting to PostgreSQL", error)

finally:
    # closing database connection.
    if ps_connection:
        cursor.close()
        ps_connection.close()
        print("PostgreSQL connection is closed")

Postgresのstored functionで配列を引数にし,Pythonから呼び出す

How to call a Postgres stored function with an array as an argument from Python

あまりサンプルがなかったので,簡単なものを作成してみた。

  • Postgres 16
  • Python 12

配列で数字のリストを渡して,ストアドファンクションで合計を計算し,返す

Postgresql


drop function if exists testfunc;
create function testfunc(ary integer[])
returns integer as
$$
declare tmp integer;
begin
select sum(s) into tmp from unnest(ary) as s;
return tmp;
end;
$$
language plpgsql;

select testfunc(ARRAY[1,2,3]);

Python

import psycopg2

try:
    ps_connection = psycopg2.connect(user="username",
                                     password="password",
                                     host="127.0.0.1",
                                     port="5432",
                                     database="testdatabase")

    cursor = ps_connection.cursor()

    lst = [1,2,3]
    # call stored procedure
    cursor.execute('select testfunc(%s)', (lst,))

    result = cursor.fetchall()
    for row in result:
        print("sum = ", row[0], )

except (Exception, psycopg2.DatabaseError) as error:
    print("Error while connecting to PostgreSQL", error)

finally:
    # closing database connection.
    if ps_connection:
        cursor.close()
        ps_connection.close()
        print("PostgreSQL connection is closed")

WordPressのプラグインで認証付きダウンローダー

S3に入れて置いたデータをwordpressのプラグインで認証付きのダウンローダを作れないかとの検討

プラグインは便利なので、Wordpressにログインした人のみがダウンロードできるようにしたい

こんな感じでできました

環境

    • s3にファイルを格納しておく
      wordpressはec2
      プラグインはショートコードでWordpressの固定ページなどに埋め込む
  • 仕様

    http://example.com/downloader?img=dir/file.png&token=session&userid=userid
    
    • img: S3のファイルパス
    • token: wordpressのセッションID
    • userid: wordpressのログインID(ログイン名ではなくWordpress内部でのログイン番号)

    データ

    以下に格納しておく
    s3://example/dir/file.png

    ショートコード

    固定ページをhttp://example.com/downloaderで作成し、以下を埋め込む

    [sample_downloader]
    

    プラグイン

    sample_downloaderディレクトリ以下にmain.phpで作成

    run($userid,$token,$img);
    }
    
    require_once( plugin_dir_path(__FILE__) . 'vendor/autoload.php' );
    add_shortcode('sample_downloader','sample_downloader_func');
    use Aws\S3\S3Client;
    
    
    class SampleDownloader{
        function run($userid,$token,$img){
            $bucket="example";
    
            // 認証
            $wp_token=wp_get_session_token();
            $wp_userid=wp_get_current_user();
            if($userid!=$wp_userid->ID || $wp_token!=$token){
                header("HTTP/1.1 401 Unauthorized");
                return "Authorized Error";
            }
    
            try{
    
                $s3Client = S3Client::factory(array(
                        'version' => 'latest',
                        'region'  => 'ap-northeast-1'
    	        ));
                
                $result = $s3Client->getObject([
                    'Bucket' => $bucket,
                    'Key' => $img
                ]);
                $body=$result["Body"];
    
    
                $fname=basename($img);
                $sfx=pathinfo($fname, PATHINFO_EXTENSION);
                header("HTTP/1.1 200 OK");
    
                // これで本体から出力されるHTMLをクリアする
    	    if (ob_get_length() > 0) {
                    ob_end_clean();
                }
    
                switch($sfx){
                    case "svg":
                        header('Content-Type: image/svg+xml');
                        break;
                    case "png":
                        header('Content-Type: image/pmg');
                        break;
                    case "jpg":
                    case "jpeg":
                        header('Content-Type: image/jpeg');
                        break;
                    case "gif":
                        header('Content-Type: image/gif');
                        break;
                    case "csv":
                        header('Content-Type: text/csv');
                        break;
                    case "json":
                        header('Content-Type: application/json');
                        break;
                    case "pdf":
                        header('Content-Type: application/pdf');
                        break;
                    case "xls":
                        header('Content-Type: application/vnd.ms-excel');
                        break;
                    case "xlsx":
                        header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
                        break;
                    case "ppt":
                        header('Content-Type: application/vnd.ms-powerpoint');
                        break;
                    case "pptx":
                        header('Content-Type: application/vnd.openxmlformats-officedocument.presentationml.presentation');
                        break;
                    case "doc":
                        header('Content-Type: application/msword');
                        break;
                    case "docx":
                        header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document');
                        break;
                    default:
                        header('Content-Type: application/octet-stream');
                }
    
                header('Content-Disposition: attachment; filename='.$fname);
                return $body;
    
            }catch(Exception $e){
                header("HTTP/1.1 500 Internal Server Error");
    	    return "Error";
            }
        }
    }
    ?>
    
    

    デプロイ

    zipファイルを作成しWordpressのプラグインでアップロード
    cd sample_downloader
    composer  require aws/aws-sdk-php
    cd ..
    zip -r sample_downloader.zip sample_downloader/*php sample_downloader/vendor
    

    ubuntuでのtimemachine

    ubuntu22.04でMac用のTimeMachineサーバを立ち上げるための設定メモ

    ネットを調べるといろいろな設定があったのですが,最終的には
    こちらが参考になりました。

    ubuntuというユーザを作成し,このユーザの権限で書き込むことにします

    /etc/samba/smb.confを修正
    globalに以下を追加

    [global]
      fruit:encoding = native
      fruit:metadata = netatalk
      idmap config * : backend = tdb
      vfs objects = catia fruit streams_xattr
    
    

    smb.confの最後にこれを追加

    [timemachine]
      path = /path/to/timemachine
      guest ok = yes
      read only = No
      force user = ubuntu
      fruit:time machine max size = 500G
      fruit:time machine = yes
    

    ubuntuでのsysstat

    悩んでしまったのでメモ

    ubuntuでsysstatを入れるには,

    sudo apt install sysstat
    

    でインストールし,

    sudo vi /etc/default/sysstat
    


    ENABLED=”true”
    にするというもの。
    その後

    sudo /etc/init.d/sysstat restart
    

    で再起動でOK
    というはずだった。。

    しかしこれだけではうまくいきません。

    $ sar
    Cannot open /var/log/sysstat/sa24: No such file or directory
    Please check if data collecting is enabled
    

    こんな感じでエラーが出るだけ。。。

    ようやく解決しました
    上記設定を行った後,これを実行し再起動するだけ

    sudo systemctl enable sysstat
    

    ずいぶん悩んでしまいました。。。

    Windows11のHyper-Vで作成したWindows10クライアント上でGPUを動かす

    概要

    Windows11のゲストOS上でGPUパススルー出来ればいいなと思ったのでメモ。

    方法

    ググるとここなどにやり方を書いているのですが、どうもうまくいかない。
    結局ここのやり方でうまくいきました。

    ゲストOS

    設定は次の通り。

    • 世代は2で作成
    • チェックポイント無効
    • メモリは固定
    • セキュアブート有効

    ファイルコピー

    • ホストOSのC:\Windows\System32\DriverStore\FileRepository\以下にあるnv_dispi.inf_amd64で始まるフォルダをゲストOSのC:\Windows\System32\HostDriverStore\FileRepository\ 以下にコピー
    • ホストOSのC:\Windows\System32\以下にある nvで始まるファイルをゲストOSのC:\Windows\System32\にコピー

    ホスト側からの設定

    ここでいったんゲストOSを停止して、管理者モードで起動したPowerShellコマンドプロンプトから以下を打ち込む

    $vm = "ENTER YOUR VM NAME HERE"
    Remove-VMGpuPartitionAdapter -VMName $vm
    Add-VMGpuPartitionAdapter -VMName $vm
    Set-VM -GuestControlledCacheTypes $true -VMName $vm
    Set-VM -LowMemoryMappedIoSpace 1Gb -VMName $vm
    Set-VM -HighMemoryMappedIoSpace 32GB -VMName $vm
    

    確認

    ゲストOSを再度起動する。デバイスマネージャーからGPUが有効になっていることを確認。
    タスクマネージャーではGPUが見られないので、Terminalなどで半透明にしたものが、透けて見えることで確認

    ベンチマーク

    FinalFantasy XV のベンチマークやってみました。

    ホストOS

    • Windows 11 Pro
    • RTX 2060
    • memory 32G
    • Core i7 12700

    ゲストOS

    • Windows 10 Pro
    • RTX 2060
    • memory 16G
    • Core i7 12700 CPU 10コア

    ベンチマークパラメタ

    • 高画質 2560×1440 ウインドウ
    • ホストOS: 5025
    • ゲストOS: 3960

    若干ゲストOS側が悪くなりましたが、まあ使えるレベルですね

    Raspberry PI4 でNextCloudを構築

    概要

    Dropbox,OneDrive,GoogleDrive,Boxなど色々アカウントを作って
    便利に使っているのですが,無料アカウントだと容量が少ないのがネックとなります。
    4つ持っていてもすぐに一杯になり,iPhone,iPadと連携して使う分にはすぐに
    いっぱいになります。

    そこで,オープンソースのNextCloudをRaspberryPIに入れて自宅でディスクストレージ
    を構築することにします。

    RaspberryPIの準備

    RaspberryPI

    先日たまたま秋葉原で見つけて購入したRaspberryPI4の4Gメモリ版を流用します。
    Windows10を入れて使っていたのですが,如何せん重いのと,リモートで使うのには
    不便なので,ここで一旦Linux化することにします。

    raspbian os

    Macを使っているのですが,公式サイトから,Raspberry Pi Imagerをダウンロードし
    インストールします。SDカードを挿して,Raspberry Pi Imagerを起動,CHOOSE OSメニューの
    otherからRaspberry Pi OS Liteを選択しインストールします。
    ちなみにSDカードは32Gのものを使いましたが,OS入れただけだと2G程度の使用量です。

    update

    おきまりのアップデートをします

    sudo apt udpate -y
    sudo apt upgrade -y
    

    リモート設定

    sshログインできるように設定します

    sudo raspi-config
    

    3 Interface OptionsからP2 SSHを選択し YESを選択

    ifconfig
    

    このコマンドでIPアドレスを調べてリモートからログインできるようになりました

    固定IPアドレス

    固定にしないと面倒なので固定IPにします

    sudo vi /etc/dhcpcd.conf
    
    interface eth0
    static ip_address=192.168.10.102/24
    static routers=192.168.10.1
    static domain_name_servers=192.168.10.1 8.8.8.8
    

    こんな感じで追加しreboot

    NextCloud

    インストール

    こちらを参考にインストールします。
    インストールにはsnapというパッケージ管理システムを利用します。このコマンド一発で,MySQLなど必要なものが全てインストールから設定まで行ってくれます。

    sudo apt install snapd
    sudo snap install nextcloud
    

    ルータの設定

    外部からアクセスする関係上,ルータに穴を開けます。
    80と443をこのRaspberryPIのIPアドレスにマッピングします

    ダイナミックDNS

    固定IPアドレスならば気にする必要ないのですが,プロバイダによってはダイナミックIPアドレスの場合があるので,SofteatherVPNをインストールした時に,設定したダイナミックDNSのホスト名を使用します。
    vpn0000000000.softether.netといった感じのやつですね。

    ユーザ追加

    管理者を追加します。sudoでやるとパスエラーが出たので,Rootになってから実行しました

    sudo su -
    nextcloud.manual-install sammy password
    

    エラーがいっぱい出ましたが,無事終了

    ERROR: ld.so: object '/usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
    Nextcloud was successfully installed
    

    Trusted Domains

    sudo su -
    nextcloud.occ config:system:get trusted_domains
    

    先ほど設定したドメイン名も

    nextcloud.occ config:system:set trusted_domains 1 --value=vpn00000000.softether.net
    
    ERROR: ld.so: object '/usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
    System config value trusted_domains => 1 set to string vpn00000000.softether.net
    

    仕上げ

    SSLの設定

    外部からアクセスするのでLet’s Encryptの無料SSLを入れます。

    sudo su -
    nextcloud.enable-https lets-encrypt
    
    In order for Let's Encrypt to verify that you actually own the
    domain(s) for which you're requesting a certificate, there are a
    number of requirements of which you need to be aware:
    
    1. In order to register with the Let's Encrypt ACME server, you must
       agree to the currently-in-effect Subscriber Agreement located
       here:
    
           https://letsencrypt.org/repository/
    
       By continuing to use this tool you agree to these terms. Please
       cancel now if otherwise.
    
    2. You must have the domain name(s) for which you want certificates
       pointing at the external IP address of this machine.
    
    3. Both ports 80 and 443 on the external IP address of this machine
       must point to this machine (e.g. port forwarding might need to be
       setup on your router).
    
    Have you met these requirements? (y/n) y
    

    yを入力します

    Please enter an email address (for urgent notices or key recovery): 
    

    メールアドレスを入力しEnter

    Please enter your domain name(s) (space-separated): 
    

    先ほど設定したダイナミックDNSのホスト名を入力

    これでとりあえずはインストール完了です。

    その他

    そのほかとして

    • Apacheの設定をhttpからhttpsへのリダイレクト
    • linuxのApplicationFireWall設定など

    いろいろセキュリティ絡みが残っているのですが,それはまた別の話。。。

    wordpressのエラー

    2,3日前に,以前作成したWordpressのサイトが,
    エラーで入れなくなったと連絡がありました。

    さくらインターネットで運用しているのですが,サポートに連絡しても
    埒が開かないので,こちらにヘルプ依頼が。

    確かに,Wordpressのログインができないようになっています。
    仕方がないので,SSHログインしてみたところ, dead.letter  が。

    この問題を解決しようとする際、以下の情報を聞かれるかもしれません。
    WordPress バーション5.8.1
    現在のテーマ: Spacious (バージョン 1.9.4)
    現在のプラグイン: Contact Form 7 (バージョン 5.5.1)
    PHP バージョン5.6.40
    
    エラー詳細
    ===============
    エラータイプ E_PARSE が wp-content/plugins/contact-form-7/admin/includes/welcome-panel.php ファイルの 9 行目で発生しました。 エラーメッセージ: syntax error, unexpected 'print' (T_PRINT), expecting identifier (T_STRING)
    

    こんなエラーが出てました。どうやらプラグインの自動アップデートでやられたようです。
    ContactForm7なのでたくさん使われているはずなのに,それほど大きな問題になっていないのはなぜ?

    調べたところどうやら,これが原因。

    さくっと,welcome-panel.phpを修正し

    Line 9: Rename function to public function _print()
    Line 223: Rename to $column->_print();
    

    動きました。

    どうやら,このサイトはphp5.6で運用していたのが原因かもしれません。
    このあとPHP7.4にアップデートしておきました。

    ジョブスケジューラのエラー

    大学のスパコンはジョブ管理システム PBSによってジョブスケジュールを行なっているのですが,
    最近変わったのか,Pythonで並列処理をしようとするとなぜかエラーが出るようになりました。

    エラー

    =>> PBS: job killed: ncpus 20.8 exceeded limit 16
    

    コード

    
    import os
    from mpi4py import MPI
    comm = MPI.COMM_WORLD
    size = comm.Get_size()
    rank = comm.Get_rank()
    name = MPI.Get_processor_name()
    
    import csv
    source=[]
    target=[]
    with open("test.csv","r",encoding="utf-8") as f:
        cdata=csv.reader(f,delimiter=",")
        for i,items in enumerate(cdata):
            if i==0:
                continue
            source.append(items[0])
            target.append(items[1])
    
    data_lst=[]
    for src,tgt in zip(source,target):
        data_lst.append((src,tgt))
    
    # http://muscle199x.blog.fc2.com/blog-entry-70.html?sp
    def split_list(lst, n):
        list_size = len(lst)
        a = list_size // n
        b = list_size % n
        return [lst[i * a + (i if i < b else b):(i + 1) * a + (i + 1 if i < b else b)] for i in range(n)]
    
    from transformers import MT5ForConditionalGeneration, MT5Model, MT5EncoderModel, T5Tokenizer
    
    tokenizer = T5Tokenizer.from_pretrained("google/mt5-base")
    model = MT5ForConditionalGeneration.from_pretrained("google/mt5-base")
    if rank == 0:
        data = split_list(data_lst, size)
    else:
        data = None
    
    def calc(my_data):
        res=[]
    
        for src,tgt in my_data:
            inputs = tokenizer.encode("summarize: " + src, return_tensors='pt', max_length=512, truncation=True)
            summary_ids = model.generate(inputs, max_length=50, min_length=10, length_penalty=5., num_beams=2)
            summary = tokenizer.decode(summary_ids[0])
            res.append((src,tgt,summary))
        return res
    
    my_data = comm.scatter(data, root=0)
    
    for r in range(comm.size):
        if rank == r:
            print("scatter.[%d] %d" % (rank, len(my_data)))
        comm.Barrier()
    
    my_res = calc(my_data)
    def flatten(l):
        try:
            return ([item for sublist in l for item in sublist])
        except:
            import traceback
            traceback.print_exc()
            print(l)
    
    # gather
    res = MPI.COMM_WORLD.gather(my_res, root=0)
    
    if rank == 0:
        res=flatten(res)
        for src,tgt,summary in res:
            print(f"source:{src}")
            print(f"target:{tgt}")
            print(f"summary:{summary}")
            print("-----")
    
    
    

    これで実行スクリプトはこんな感じ

    #!/usr/bin/bash
    #PBS -N sample
    #PBS -j oe 
    #PBS -l select=1:ncpus=64:mpiprocs=64
    #PBS -q SINGLE
    NPROCS=`cat $PBS_NODEFILE|wc -l`
    cd ${PBS_O_WORKDIR}
    mpirun -np ${NPROCS} python mt5.py
    

    解決方法

    ここにありました。

    簡単に解決するためには,Pythonコードの一番上に

    
    import os
    os.environ["MKL_NUM_THREADS"] = "1"
    os.environ["NUMEXPR_NUM_THREADS"] = "1"
    os.environ["OMP_NUM_THREADS"] = "1"
    
    

    これを入れるだけ。Numpyを呼ぶ前に呼ぶ必要があるので,シェルスクリプトで呼んでもいいですし,Pythonの一番上で環境変数に設定してやってもいいです。

    備忘録,JAISTのスパコン使い方

    Jaistには3つスパコンがあります。
    学生でもどれも自由に使えます。
    この辺りが他の大学と違うところでしょうか?

    今回はつい最近と言っても1年くらい経ちますが,リニューアルされた
    スパコンKagayakiでのPythonでのMPIの使い方。

    公式にはPythonはサポートしてません。

    Anaconda

    いろいろ試したのですが,これが一番。
    スーパーユーザ権限がないのでローカルにインストールし,パスを設定します。

    $ cat ~/.bashrc
    # >>> conda initialize >>>
    # !! Contents within this block are managed by 'conda init' !!
    __conda_setup="$('~/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
    if [ $? -eq 0 ]; then
        eval "$__conda_setup"
    else
        if [ -f "~/anaconda3/etc/profile.d/conda.sh" ]; then
            . "~/anaconda3/etc/profile.d/conda.sh"
        else
            export PATH="~/anaconda3/bin:$PATH"
        fi
    fi
    unset __conda_setup
    # <<< conda initialize <<<
    

    実行コード 

    mpi.py

    
    #!/bin/env python
    # coding:utf-8
    
    from mpi4py import MPI
    comm = MPI.COMM_WORLD
    size = comm.Get_size()
    rank = comm.Get_rank()
    name = MPI.Get_processor_name()
    print(f"size={size},rank={rank},name={name}")
    
    

    mpi.sh

    . ~/.bashrc
    if [ ! -d venv ];then
        python -m venv venv
    fi
    . venv/bin/activate
    LOG=${0%.*}.log
    pip install -r requirements.txt > $LOG
    
    mpirun -np 4 python mpi.py > mpi.log 2>&1
    

    デフォルトではCSHなのでCSHでキックコードを作成し,中ではBashを使います

    PBS_mpi.sh

    #PBS -N mpi
    #PBS -j oe 
    #PBS -l select=1
    #PBS -q SINGLE
    
    
    source /etc/profile.d/modules.csh
    
    module purge
    module load openmpi
    module load intel
    
    
    cd ${PBS_O_WORKDIR}
    
    bash mpi.sh
    

    実行

    $ qsub PBS_mpi.sh
    

    結果

    $ cat mpi.log
    size=4,rank=1,name=spcc-250
    size=4,rank=3,name=spcc-250
    size=4,rank=0,name=spcc-250
    size=4,rank=2,name=spcc-250