end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

rhel7 の els(延長サポート)と、rhel7.7→7.9 でのリリースバージョンアップ

rhel 7 (Red Hat Enterprise Linux)のサポート以下のようですが、 els の利用条件を理解していませんでしたので、調べてみました。

  • 標準サポート? ( Maintenance Support ) : ~2024/6
  • 延長サポート? ( els : Extended life cycle support) : ~2026/6

els のサポート対象パッケージは、コアパッケージのみ

https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/package_manifest/_package_lists_base_repository

els の利用対象verは、rhel7.9のみ

rhel7.7→7.9のマイナーバージョンアップ方法

$ cat /etc/redhat-release 
Red Hat Enterprise Linux Server release 7.7 (Maipo)

$ sudo yum clean all
$ sudo yum check-update | grep redhat-release
redhat-release-eula.noarch           7.8-0.el7                rhel-7-server-rpms
redhat-release-server.x86_64         7.9-6.el7_9              rhel-7-server-rpms

$ sudo yum update redhat-release*

$ cat /etc/redhat-release 
Red Hat Enterprise Linux Server release 7.9 (Maipo)

ちなみに、以下のコマンドで、 全パッケージがrhel7.9関連にアップグレードされます。

$ sudo yum update

その他、カーネルのみのバージョンアップは以下

$ sudo yum update kernel*

サーバへのelsライセンス(サブスクリプション)の適用

通常のアクティベーションと同様のようです

$ subscription-manager attach --pool=????

tortoisegit for winで「fatal: unsafe repository」エラー

windows11から rhel7 on virtualbox に gitのリポジトリを作成し、 tortoisegit for win を使用して、このリポジトリを操作していますが、 以下のようなエラーが発生するようになりました。

fatal: unsafe repository
('//192.168.56.108/end0tknr/dev/SpringVuePy' is owned by someone else)
To add an exeption for this directory, call:

    git  config --global --add safe.directory
    '%(prefix)///192.168.56.108/end0tknr/dev/SpringVuePy'

Set the environment variable
GIT_TEST_DEBUG_UNSAGE_DIRECTORIES=true and run again
for more information.

どうやら、windows側のgitをバージョンアップしたことがきっかけらしい。

なので、上記のメッセージに記載されていた以下を windowsのpower shellで実行し、解消

PS> git  config --global --add safe.directory '%(prefix)///192.168.56.108/end0tknr/dev/SpringVuePy'

whoisとopensslコマンドで、dnsやssl証明書の有効期限を確認 by python

whoisとopensslコマンドで、dnsやssl証明書の有効期限を確認 - end0tknr's kipple - web写経開発

以前、記載したperl版の上記entryを pythonで書いてみました。

#!/usr/local/bin/python3
# -*- coding: utf-8 -*-

import datetime
import dateutil.parser
import os
import re
import subprocess
import sys

HOSTNAMES = [
    "www.xxxx-xxxx.com" ]
TLDS = {
    ""              :['.com',  '.org',  '.net'],
    "whois.jprs.jp" :['.co.jp','.gr.jp','.ne.jp','.or.jp','.jp'] }

TIMEOUT     = 10;
SSL_PORT    = 443;
WHOIS_CMD   = "/usr/bin/whois";
OPENSSL_CMD = "/usr/bin/openssl";

def main():

    disp_strs = "\t".join([
        "%-30s" % ("HOSTNAME"),
        "DNS_START","DNS_LAST",
        "SSL_START","SSL_LAST" ])
    print( disp_strs )
    
    for hostname in  HOSTNAMES:
        (dns_start,dns_last) = get_whois_expire( hostname )
        (ssl_start,ssl_last) = get_ssl_expire( hostname )

        disp_strs = "\t".join([
            "%-30s" % (hostname),
            dns_start,dns_last,
            ssl_start,ssl_last ])
        
        print( disp_strs )
        

def get_ssl_expire( hostname ):
    print( hostname )
        
def get_whois_expire( hostname ):

    (domain_tmp,whois_host) = extract_domain(hostname)

    cmd = None
    if whois_host:
        cmd = " ".join([WHOIS_CMD,"-h",whois_host, domain_tmp])
    else:
        cmd = " ".join([WHOIS_CMD,domain_tmp])

    (stdout,stderr,return_code) = exec_subprocess(cmd)
    if not stdout:
        return None

    re_compiles = [
        re.compile("Updated Date: (.+)"),
        re.compile("\[Last Update?d?\]\s+([^\s]+)"),
        re.compile("Registry Expiry Date: (.+)"),
        re.compile("\[State\]\s+Connected\s+\(([^\s]+)\)"),
        re.compile("\[Expires on\]\s+([^\s]+)") ]

    stdout_lines = stdout.decode().split("\n")
    start_date = None
    last_date  = None
    for stdout_line in stdout_lines:

        for i, re_compile in enumerate(re_compiles):
            re_result  = re_compile.search( stdout_line )
            if not re_result:
                continue

            if i == 0:
                start_date = parse_date( re_result.group(1) )
                break
            elif i == 1:
                start_date = parse_date( re_result.group(1) )
                break
            elif i == 2:
                last_date = parse_date( re_result.group(1) )
                break
            elif i in [3,4]:
                last_date = parse_date( re_result.group(1) )
                break
    return (start_date,last_date)


def parse_date( org_str ):
    parsed_date = dateutil.parser.parse(org_str)
    return parsed_date.strftime("%Y-%m-%d")

def extract_domain( hostname ):

    for whois_host, tlds in TLDS.items():
        for tld in tlds :
            re_compile = re.compile("([^\.]+)"+tld + "$",re.IGNORECASE)
            re_result  = re_compile.search( hostname )
            if not re_result:
                continue

            return (re_result.group(1)+tld, whois_host)

    return ("","")



# cf. https://qiita.com/fetaro/items/a3b3bd4ea197b600ac45
def exec_subprocess(cmd: str, raise_error=True):
    child = subprocess.Popen( cmd,
                              shell=True,
                              stdout=subprocess.PIPE,
                              stderr=subprocess.PIPE )
    stdout, stderr = child.communicate()
    rt = child.returncode
    if rt != 0 and raise_error:
        print("ERROR",stderr,file=sys.stderr)
        return (None,None,None)

    return stdout, stderr, rt


def get_ssl_expire( host ):

    cmd = " ".join(
        ["echo |",
         "%s s_client -connect %s:%s -showcerts" % (OPENSSL_CMD,host,SSL_PORT),
         "2> /dev/null | ",
         "%s x509 -dates -noout 2> /dev/null" % (OPENSSL_CMD)])
    
    (stdout,stderr,return_code) = exec_subprocess(cmd)
    stdout_lines = stdout.decode().split("\n")

    re_compiles = [ re.compile("^notBefore\=(.+)"),
                    re.compile("^notAfter\=(.+)" ) ]
    
    start_date = None
    last_date  = None
    for stdout_line in stdout_lines:
        for i, re_compile in enumerate(re_compiles):
            re_result  = re_compile.search( stdout_line )
            if not re_result:
                continue
            date_str = re_result.group(1)
            date_str = parse_date( date_str )
            if i == 0:
                start_date = date_str
            else:
                last_date = date_str
                
    return (start_date,last_date)

if __name__ == '__main__':
    main()

ImageMagick for win + Ghostscript + perl で、PDFやMP4等からサムネイルとインデックスページ作成

https://end0tknr.hateblo.jp/entry/20221208/1670487848

上記にある先日のentry の続きです。

きちんと、テストしていませんが、いかのような感じかと思います。

#!perl

# pdf等の画像や動画fileをtraverse検索し、thumb nailを作成した上で
# それらにアクセスする為のindexページを作成します

use utf8;
use strict;
use warnings;
use Data::Dumper;
use Encode;
use File::Basename;
use File::Path;
use Math::Round;
use Time::Piece;

my $BASE_DIRS = ["./html_1"];
my $ORG_FILES_DIR = "catalog";
my $THUMBS_DIR    = "thumb";
my $CATALOG_HTML  = "catalogs_tbl.html";
my $MAGICK_CMD = "magick convert -resize 240x240 %s[0] %s";
my $ORG_ENCODE = "cp932";

# cf nginx/conf/mime.types
my $IMG_OR_MOVIE_EXTS = [
    "pdf",
    "mp4","mpeg","mpg","mov","flv","m4v","wmv","avi",
    "gif","jpeg","jpg","png","svg","svgz","tif","tiff","bmp"];
my $html_tbl_1 =<<EOF;
<table>
  <thead>
    <tr>
      <th></th>
      <th class="sort" data-sort="path"  >ファイル名</th>
      <th class="sort" data-sort="mbyte" >サイズ</th>
      <th class="sort" data-sort="update">更新日時</th>
    </tr>
  </thead>
  <tbody class="list">
EOF
my $html_tbl_2 =<<EOF;
  </tbody>
</table>
EOF
my $html_tbody_tr =<<EOF;
    <tr>
      <td>
        <a href="%s"><img src="%s" /></a>
      </td>
      <td class="path">
        <div class="parent_dir">%s</div>
        <a href="%s"><div class="filename">%s</div></a>
      </td>
      <td class="mbyte">%s MB</td>
      <td class="update">%s</td>
    </tr>
EOF


main();

sub main {
    
    for my $base_dir ( @$BASE_DIRS ){
        my $file_base_dir = "$base_dir/$ORG_FILES_DIR";
        my $file_paths = find_img_or_movie_files( $file_base_dir );

        my $thumb_infos    = [];
        my $thumb_base_dir = "$base_dir/$THUMBS_DIR";
        
        for my $file_path ( @$file_paths ){

            my $thumb_path = create_thumb_nail($file_path,
                                               $file_base_dir,
                                               $thumb_base_dir);
            if (not $thumb_path){
                print STDERR "fail create_thumb_nail() $file_path\n";
                next;
            }

            my $thumb_info = get_file_info( $file_path );
            
            $thumb_info->{"parent_dir"} = dirname($file_path);
            $thumb_info->{"parent_dir"} =~ s/$file_base_dir//;
            $thumb_info->{"parent_dir"} =~ s/^\///;
            
            $file_path =~ s/$base_dir\///;
            $thumb_info->{"org_path"}  = $file_path;
            
            $thumb_path =~ s/$base_dir\///;
            $thumb_info->{"thumb_path"} = $thumb_path;
            push(@$thumb_infos, $thumb_info);
        }

        my $html_tbl = create_index_page( $thumb_infos );
        my $tbl_html_path = "$base_dir/$CATALOG_HTML";
        open my $fh, '>', $tbl_html_path or die "fail open $tbl_html_path $!";
        print $fh Encode::encode('utf-8',$html_tbl);

        close $fh;
    }
}


sub create_index_page {
    my ( $thumb_infos ) = @_;

    my @html_tbl = ($html_tbl_1);

    for my $thumb_info ( @$thumb_infos ){
        my $org_path   = $thumb_info->{org_path};
        my $thumb_path = $thumb_info->{thumb_path};
        my $parent_dir = $thumb_info->{parent_dir};
        my $basename   = $thumb_info->{basename};
        my $m_byte     = $thumb_info->{"m_byte"};
        my $update     = $thumb_info->{update};

        my $html_tr = sprintf($html_tbody_tr,
                              Encode::decode($ORG_ENCODE,$org_path),
                              Encode::decode($ORG_ENCODE,$thumb_path),
                              Encode::decode($ORG_ENCODE,$parent_dir),
                              Encode::decode($ORG_ENCODE,$org_path),
                              Encode::decode($ORG_ENCODE,$basename),
                              $m_byte,
                              $update);
        push(@html_tbl,$html_tr);
    }
    push(@html_tbl,$html_tbl_2);

    return join("\n",@html_tbl);
}

sub get_file_info {
    my ($file_path) = @_;
        
    my @file_stat = stat $file_path;
    my $m_byte = Math::Round::nearest(0.1,$file_stat[7] / 1024 / 1024);
        
    my $time_piece = localtime($file_stat[9]);
    my $update = sprintf("%04d/%02d/%02d %02d:%02d",
                         $time_piece->year,
                         $time_piece->mon,
                         $time_piece->mday,
                         $time_piece->hour,
                         $time_piece->minute);
    
    my $file_info = {"basename"=> basename($file_path),
                     "m_byte"  => $m_byte,
                     "update"  => $update   };
  return $file_info;
}

sub create_thumb_nail {
    my ($org_file_path,$org_base_dir,$thumb_base_dir) = @_;

    my $new_path = $org_file_path;
    $new_path =~ s/^$org_base_dir\///;
    $new_path =~ s/\//_/go;
    $new_path = "$thumb_base_dir/$new_path.png";

    my $magick_cmd = sprintf($MAGICK_CMD,$org_file_path, $new_path);

    open my $fh, '-|', $magick_cmd or die "fail open $magick_cmd $!";
    while (my $line = <$fh> ) {
        print STDERR "$line\n";
    }
    close($fh);

    return $new_path;
}

sub find_img_or_movie_files {
    my ($dir) = @_;

    my @ret_files;
    opendir my $dh, $dir or die "fail opendir $dir $!";

    while (my $file_or_dir = readdir $dh ) {
        
        if ($file_or_dir eq "." or $file_or_dir eq ".."){
            next;
        }
        my $tmp_path = "$dir/$file_or_dir";
        if (-d $tmp_path ){
            my $tmp_ret_files = find_img_or_movie_files( $tmp_path );
            push(@ret_files, @$tmp_ret_files );
            next;
        }

        if ( is_img_or_movie_file($tmp_path) ){
            push(@ret_files, $tmp_path );
        }
    }
    return \@ret_files;
}

sub parse_extension {
    my ( $org_path ) = @_;

    $org_path =~ /[^\.]+\.([^\.]+)$/o;
    if ( not length($1) ){
        return "";
    }
    my $ext = lc $1;
    return $ext;
}

sub is_img_or_movie_file {
    my ( $org_path ) = @_;

    my $ext = parse_extension( $org_path );

    for my $tmp_ext ( @$IMG_OR_MOVIE_EXTS ){
        if ( $ext eq $tmp_ext ){
            return 1;
        }
    }
    return 0;
}

↑こうかくと、↓このようなhtmlが表示されます

<table>
  <thead>
    <tr>
      <th></th>
      <th class="sort" data-sort="path"  >ファイル名</th>
      <th class="sort" data-sort="mbyte" >サイズ</th>
      <th class="sort" data-sort="update">更新日時</th>
    </tr>
  </thead>
  <tbody class="list">

    <tr>
      <td>
    <a href="catalog/PDF内に動画テスト.pdf"><img src="thumb/PDF内に動画テスト.pdf.png" /></a>
      </td>
      <td class="path">
    <div class="parent_dir"></div>
    <a href="catalog/PDF内に動画テスト.pdf"><div class="filename">PDF内に動画テスト.pdf</div></a>
      </td>
      <td class="mbyte">1.9 MB</td>
      <td class="update">2022/12/07 09:28</td>
    </tr>

    <tr>
      <td>
    <a href="catalog/カタログ_RUSTIC_TILEWALL.pdf"><img src="thumb/カタログ_RUSTIC_TILEWALL.pdf.png" /></a>
      </td>
      <td class="path">
    <div class="parent_dir"></div>
    <a href="catalog/カタログ_RUSTIC_TILEWALL.pdf"><div class="filename">カタログ_RUSTIC_TILEWALL.pdf</div></a>
      </td>
      <td class="mbyte">20.9 MB</td>
      <td class="update">2022/12/10 11:25</td>
    </tr>

    <tr>
      <td>
    <a href="catalog/メンテナンス読本.PDF"><img src="thumb/メンテナンス読本.PDF.png" /></a>
      </td>
      <td class="path">
    <div class="parent_dir"></div>
    <a href="catalog/メンテナンス読本.PDF"><div class="filename">メンテナンス読本.PDF</div></a>
      </td>
      <td class="mbyte">9.2 MB</td>
      <td class="update">2022/12/07 08:46</td>
    </tr>

    <tr>
      <td>
    <a href="catalog/動画/ZOOM背景と、らいおん.MP4"><img src="thumb/動画_ZOOM背景と、らいおん.MP4.png" /></a>
      </td>
      <td class="path">
    <div class="parent_dir">動画</div>
    <a href="catalog/動画/ZOOM背景と、らいおん.MP4"><div class="filename">ZOOM背景と、らいおん.MP4</div></a>
      </td>
      <td class="mbyte">1.8 MB</td>
      <td class="update">2022/01/07 22:54</td>
    </tr>

    <tr>
      <td>
    <a href="catalog/動画/生産工程.mp4"><img src="thumb/動画_生産工程.mp4.png" /></a>
      </td>
      <td class="path">
    <div class="parent_dir">動画</div>
    <a href="catalog/動画/生産工程.mp4"><div class="filename">生産工程.mp4</div></a>
      </td>
      <td class="mbyte">26.5 MB</td>
      <td class="update">2022/12/10 11:21</td>
    </tr>

    <tr>
      <td>
    <a href="catalog/動画/生産工程_その2.mp4"><img src="thumb/動画_生産工程_その2.mp4.png" /></a>
      </td>
      <td class="path">
    <div class="parent_dir">動画</div>
    <a href="catalog/動画/生産工程_その2.mp4"><div class="filename">生産工程_その2.mp4</div></a>
      </td>
      <td class="mbyte">44.1 MB</td>
      <td class="update">2022/12/10 11:22</td>
    </tr>

    <tr>
      <td>
    <a href="catalog/外観スタイルカタログ_2211.pdf"><img src="thumb/外観スタイルカタログ_2211.pdf.png" /></a>
      </td>
      <td class="path">
    <div class="parent_dir"></div>
    <a href="catalog/外観スタイルカタログ_2211.pdf"><div class="filename">外観スタイルカタログ_2211.pdf</div></a>
      </td>
      <td class="mbyte">126.5 MB</td>
      <td class="update">2022/12/10 11:27</td>
    </tr>

  </tbody>
</table>

ImageMagick for win + Ghostscript で、PDF等からサムネイル作成

ImageMagickをインストールすると、magick コマンドを利用できるようになりますが、 サムネイル作成には Ghostscript も必要らしいので、こちらもインストールした上で、 以下のように実行。

DOS> magick convert -resize 180x180 CATALOG.PDF[0] CATALOG.PNG
DOS> magick convert -resize 180x180 MOVIE.MP4[0]   CATALOG.PNG
DOS> magick convert -resize 240x240 CATALOG.PNG[0] CATALOG_2.PNG

上記で [0] とつけているのは、1ページ目のみをサムネイル化する為です

python-mip による制約充足問題

pulp for python 以外に python-mip の存在を知った為、メモ

参考url

写経結果

#!python3
# -*- coding: utf-8 -*-
# cf https://magazine.techacademy.jp/magazine/45423
# pip install mip

from mip import Model, xsum, minimize, maximize, BINARY
import numpy  as np
import pandas as pd
import sys

def main():
    problem_1()    # 100x + 100y の最大化を求める問題
    problem_2_1()  # ナップザック問題
    problem_2_2()  # ナップザック問題
    problem_3_1()
    problem_3_2()
    
# 100x + 100y の最大化を求める問題
# cf https://qiita.com/SaitoTsutomu/items/c7b43c2e02710749d117
def problem_1():
    print("## start",sys._getframe().f_code.co_name)

    m = Model()  # 数理モデル
    # 変数
    x = m.add_var("x")
    y = m.add_var("y")
    # 目的関数 (objective)
    m.objective = maximize(100 * x + 100 * y)
    # 制約条件 (subject to)
    m += x + 2 * y<=16  # 材料Aの上限
    m += 3 * x + y<=18  # 材料Bの上限

    m.verbose = 0       # ログの非表示化
    m.optimize()        # ソルバーの実行
    print("x=",x.x, "y=",y.x)
    
# ナップザック問題
# cf https://magazine.techacademy.jp/magazine/45423
def problem_2_1():
    print("## start",sys._getframe().f_code.co_name)
    
    w = [2, 1, 3, 2, 1, 4]  # 重さ
    v = [3, 2, 6, 1, 3, 8]  # 価値
    W = 10                  # 重さの限度:10kg

    r = range(len(w))

    m = Model()
    x = [ m.add_var(var_type=BINARY) for i in r ]

    # 目的関数 (objective)
    m.objective = maximize(xsum(v[i] * x[i] for i in r))

    # 制約条件 (subject to)
    m += xsum(w[i] * x[i] for i in r) <= W

    m.verbose = 0       # ログの非表示化
    m.optimize()
    selected = [i for i in r if x[i].x >= 0.99]

    max_value = sum([v[i] for i in selected])
    weights   = [w[i] for i in selected]
    print("max value=",max_value, "wights=",weights )


# ナップザック問題.
# 変数生成にadd_var_tensor()を使う場合
# cf https://kamekokamekame.net/2021/2021-12-24-article.html
def problem_2_2():
    print("## start",sys._getframe().f_code.co_name)
    
    w = [2, 1, 3, 2, 1, 4]  # 重さ
    v = [3, 2, 6, 1, 3, 8]  # 価値
    W = 10                  # 重さの限度:10kg
    r = range(len(w))

    m = Model()
    # 1次元の変数. 内容は有/無の為、type=BINARY
    x = m.add_var_tensor((len(w),),"x", var_type=BINARY)
    # 目的関数 (objective)
    m.objective = maximize(xsum(v[i] * x[i] for i in r))
    # 制約条件 (subject to)
    m += xsum(w[i] * x[i] for i in r) <= W
    
    m.verbose = 0       # ログの非表示化
    m.optimize()

    selected = [i for i in r if x[i].x >= 0.99]

    max_value = sum([v[i] for i in selected])
    weights   = [w[i] for i in selected]
    print("max value=",max_value, "wights=",weights )

# cf https://qiita.com/SaitoTsutomu/items/c7b43c2e02710749d117
# - 倉庫群から工場群への輸送量を決めたい → 変数
# - 輸送コストを最小化したい             → 目的関数
# - 各倉庫からの搬出は、供給可能量以下   → 制約
# - 各工場への搬入は、需要量以上         → 制約
def problem_3_1():
    print("## start",sys._getframe().f_code.co_name)
    
    nw = 3  # 倉庫数
    nf = 4  # 工場数
    rnd = np.random.default_rng(0)      # 乱数generator
    supply = rnd.integers(30,50, nw)    # 供給 乱数
    demand = rnd.integers(20,40, nf)    # 需要  〃
    cost   = rnd.integers(10,20,(nw,nf))# 輸送費〃

    m1 = Model()
    v1 = m1.add_var_tensor((nw, nf), "v1")
    
    expr = xsum(xsum(v) for v in cost * v1)
    m1.objective = minimize(expr)
    for i in range(nw):
        m1 += xsum(v1[i]) <= supply[i]
    for j in range(nf):
        m1 += xsum(v1[:, j]) >= demand[j]
    m1.verbose = 0
    m1.optimize()
    # 変数の値を多次元配列で取得
    print( v1.astype(float) )

def problem_3_2():
    print("## start",sys._getframe().f_code.co_name)

    nw = 3  # 倉庫数
    nf = 4  # 工場数
    rnd = np.random.default_rng(0)              # 乱数generator
    supply   = rnd.integers(30,50, nw)          # 供給 乱数
    demand   = rnd.integers(20,40, nf)          # 需要  〃
    cost = rnd.integers(10,20,(nw,nf))# 輸送費〃

    dfw = pd.DataFrame({"倉庫":["W1","W2","W3"],      "supply": supply})
    dff = pd.DataFrame({"工場":["F1","F2","F3", "F4"],"demand": demand})
    df  = pd.merge(dfw, dff, "cross").assign( cost=cost.flatten() )
    # print( df )

    m2 = Model()
    df["Var"] = m2.add_var_tensor((len(df),), "v2")
    print( df["Var"] )

    
    m2.objective = minimize(xsum(df.cost * df.Var))
    for _, gr in df.groupby('倉庫'):
        m2 += xsum(gr.Var) <= gr.supply.iloc[0]
    for _, gr in df.groupby('工場'):
        m2 += xsum(gr.Var) >= gr.demand.iloc[0]
        
    m2.verbose = 0
    m2.optimize()
    # 変数の多次元配列を取得
    df["Val"] = df.Var.astype(float)
    # 更にその中から >0 のものを選択
    print( df[df.Val > 0] )

if __name__ == '__main__':
    main()

旧ms accessのmdbファイルを pandas_access for pythonで読む

以下の通りです

$ sudo yum install mdbtools-devel
#!/usr/local/bin/python3
# -*- coding: utf-8 -*-

import pandas_access as mdb

def main():
    mdb_file = 'my_access_db.mdb'

    for tbl in mdb.list_tables(mdb_file):
        disp_tbl_rows(mdb_file, tbl)

def disp_tbl_rows(mdb_file, tbl):
    df = mdb.read_table(mdb_file, tbl)
#    df = mdb.read_table(mdb_file, tbl, encoding='cp932')

    print( df )

    for index, row in df.iterrows():
        if "USER_BUSHO" in row:
            print( row["USER_BUSHO"] )
        
if __name__ == '__main__':
    main()

ジョンソン法による 2工程フローショップのスケジューリング

上記urlからの写経です。

ジョンソン法は、特に「2工程(=taskが2コ)」にしか適用できないようですので、 利用シーンは少ないはずです。

# -*- coding: utf-8 -*-

is_complete = 9999

def main():
    jobs = [ [5,5],[6,4],[4,3],[2,8],[5,7] ]
    johnson_scheduling( jobs )
    
# ジョンソン法とは、
# 2工程(=taskが2コ)のフローショップ (≠ジョブショップ)において
# 最も時間の短い作業(task)を探し、それが
# 前工程のものであれば、そのジョブを前から割り付け
# 後工程のものであれば、そのジョブを後から割り付ける
# スケジューリング
def johnson_scheduling( jobs ):

    front = []
    back = []
    process_1 = []
    process_2 = []
    sum_time = 0

    while True:
        # 各ジョブの組から、小時間のタスクを列挙
        min_tasks = []
        for tasks in jobs:
            min_tasks.append( min(tasks) )
            
        # 上記の中から最小タスクを算出
        min_task = min( min_tasks )

        if min_task == is_complete:
            sum_time += process_1[0] #一番最初の前工程を足して終了
            break
        # 最小タスクのあるジョブ
        job_no  = min_tasks.index( min_task )
        task_no = jobs[job_no].index( min_task )

        # 前工程 → ジョブを前から割り付け
        if task_no == 0:
            front.append(job_no)
        # 後工程 → そのジョブを後から割り付け
        else:
            back.append(job_no)

        process_1.append( jobs[job_no][0] )
        process_2.append( jobs[job_no][1] )

        if sum(process_1) > sum(process_2):
            sum_time += jobs[job_no][0]
        else:
            sum_time += jobs[job_no][1]

        jobs[job_no] = [is_complete, is_complete]

    front.extend( list(reversed(back)) )
    order = ["Job No" + str(x + 1) for x in front]
    print(order)
    print("所要時間 : " + str(sum_time))

if __name__ == '__main__':
    main()

簡易なNFP (No Fit Polygon)アルゴリズムによる多角形の図形詰み込み問題(bin packing)

上記urlにあるネスティングツールでは

  • 大量の多角形部品を
  • 部品を回転させながら
  • GA(Genetic Algorithm、遺伝的)アルゴリズム
  • NFP (No Fit Polygon = ≒ ミンコフスキー差 ?)アルゴリズム

で、図形詰み込みを行っていますが、 少数の部品であれば、部品の回転や、GAがなくても、詰み込み可能と思い、 pythonで書いてみた。

# -*- coding: utf-8 -*-

import copy
import pyclipper
import io
import matplotlib.pyplot as plt
import numpy             as np
import re
import sys
from functools import cmp_to_key
from matplotlib.path        import Path
from matplotlib.patches     import PathPatch
from matplotlib.collections import PatchCollection
from shapely            import affinity
from shapely.geometry   import Polygon, LineString, Point

#┏━┳━┳━┳━━━┓─┐
#┃  ┃  ┃  ┃      ┃  │
#┃  ┣━┛  ┃    /┃  │
#┃  ┃      ┃  /  ┃  │
#┃  ┃      ┃/    ┃  │
#┗━┻━━━┻━━━┛─┘
co_bin   = [
    [  0, 0],[120, 0],[120,50],[0,50]]
co_parts = [
    [[ 0, 0],[ 20, 0],[ 20,50],[ 0,50]],
    [[20, 0],[ 60, 0],[ 60,50],[40,50],[40,30],[20,30]],
    [[20,30],[ 40,30],[ 40,50],[20,50]],
    [[60, 0],[100, 0],[100,30]],
    [[60, 0],[100,30],[100,50],[60,50]]
]

# cf. https://svgnest.com/
def main():
    # 領域(bin)のload
    poly_bin = Polygon( shell=co_bin, holes=[])
    
    # 部品群(parts)のload
    poly_parts = []
    for co_part in co_parts:
        poly_part = Polygon( shell=co_part, holes=[])
        # 後程、minkowski差を求める為、原点へ仮移動
        poly_part = align_to_origin( poly_part )
        poly_parts.append( poly_part )
        
    # 大きな部品から配置すると、隙間が減る(らしい)為
    poly_parts = sort_parts(poly_parts)
    
    # 配置済みの部品
    placed_parts = []
    placed_part  = Polygon(shell=[], holes=[])

    while len(poly_parts):
        for j, poly_part in enumerate(poly_parts):
            # binに内接する範囲を算出
            poly_inner   = inner_fit_rect( poly_bin,poly_part )
            if not poly_inner:
                continue
            
            new_part_pos = None
            
            if placed_part.area:
                # ミンコフスキー差により、配置済み部品群に外接する範囲を算出
                poly_mnkwski = minkowski_diff( placed_part,poly_part )
                if not poly_mnkwski:
                    continue
                #plot_polygons(poly_inner,[placed_part],poly_mnkwski)

                # inner_fit_rect()とminkowski_diff()のedge交点のうち
                # 最もbinの原点に近いものをpartの配置先とする
                new_part_pos = calc_new_part_pos_1( poly_inner, poly_mnkwski)
            else:
                new_part_pos = calc_new_part_pos_0( poly_inner )
                
            if not new_part_pos:
                continue
            # partを配置先へ移動
            poly_part = affinity.translate( poly_part,
                                            xoff=new_part_pos[0],
                                            yoff=new_part_pos[1] )
            placed_parts.append( poly_part )
            placed_part = placed_part.union( poly_part )
            poly_parts.pop(j)

            plot_polygons(poly_bin, placed_parts)
            break


def calc_new_part_pos_0( poly_inter ):
    if type(poly_inter) is Point:
        [(x_0,y_0)] = list(poly_inter.coords)
        return (x_0,y_0)
    
    if type(poly_inter) is LineString:
        tmp_coords = list( poly_inter.coords )
        tmp_coords = sort_coords( tmp_coords )
        return tmp_coords[0]

    # 最もbinの原点に近いものを算出
    tmp_coords = list( poly_inter.exterior.coords )
    tmp_coords = sort_coords( tmp_coords )
    return tmp_coords[0]


def calc_new_part_pos_1( poly_inner, poly_mnkwski):
    edges_inner   = conv_to_lines( poly_inner )
    edges_mnkwski = conv_to_lines( poly_mnkwski )
        
    cross_points = []
    for edge_inner in edges_inner:
        for edge_mnkwski in edges_mnkwski:
            cross_point = line_cross_point(edge_inner, edge_mnkwski)
            if cross_point:
                cross_points.append( cross_point )
    if len(cross_points) == 0:
        return None
    
    # 最もbinの原点に近いものを算出
    cross_points = sort_coords( cross_points )
    return cross_points[0]

def conv_to_lines(org_poly):
    org_coords = []
    if type(org_poly) is LineString:
        org_coords = org_poly.coords
    elif type(org_poly) is Polygon:
        org_coords = org_poly.exterior.coords
    else:
        return []
    
    ret_lines = []
    for k in range(len(org_coords) - 1):
        co_0 = org_coords[k]
        co_1 = org_coords[k+1]
        if co_0 == co_1:
            continue
        ret_lines.append( LineString( org_coords[k:k+2]) )
    return ret_lines

def align_to_origin(poly):
    (min_x, min_y, max_x, max_y) = ( poly.bounds )
    new_poly = affinity.translate(poly, xoff= -1 * min_x, yoff= -1 * min_y)
    return new_poly
    
def sort_parts(poly_parts):
    ret_parts = sorted(poly_parts,
                       key=lambda x: x.area,
                       reverse=True)
    return ret_parts

def sort_coords( coords ):
    def sort_coords_sub(coord_a,coord_b):
        
        if coord_a[0] < coord_b[0]:
            return -1
        if coord_a[0] > coord_b[0]:
            return 1
        if coord_a[1] < coord_b[1]:
            return -1
        if coord_a[1] > coord_b[1]:
            return 1
        return 0
    
    ret_coords = sorted( coords, key=cmp_to_key(sort_coords_sub) )
    return ret_coords

def inner_fit_rect(poly_a,poly_b):
    
    (min_x_a,min_y_a,max_x_a,max_y_a) = poly_a.bounds # bin
    (min_x_b,min_y_b,max_x_b,max_y_b) = poly_b.bounds # part
    
    # a:binより b:part が大きい場合、算出不可
    if max_x_a - min_x_a < max_x_b - min_x_b or \
       max_y_a - min_y_a < max_y_b - min_y_b:
        return None

    coords_b = poly_b.exterior.coords[0]
    min_x = min_x_a - min_x_b + coords_b[0]
    min_y = min_y_a - min_y_b + coords_b[1]
    max_x = max_x_a - max_x_b + coords_b[0]
    max_y = max_y_a - max_y_b + coords_b[1]

    poly = Polygon(
        shell=[[min_x,min_y],[max_x,min_y],[max_x,max_y],[min_x,max_y]],
        holes=[])
    return poly
    
def minkowski_diff(poly_a,poly_b):

    shell_a = list( poly_a.exterior.coords )
    shell_b = list( poly_b.exterior.coords )
    minkowskis = pyclipper.MinkowskiDiff(shell_b, shell_a)

    if len(minkowskis) == 1:
        return Polygon( shell=minkowskis[0], holes=[])
    
    tmp_poly_0 = Polygon( shell=minkowskis[0], holes=[])
    tmp_poly_1 = Polygon( shell=minkowskis[1], holes=[])

    if tmp_poly_0.area < tmp_poly_1.area:
        return tmp_poly_1
    return tmp_poly_0

    
def plot_polygons(poly_bin, poly_parts,poly_other=None):
    fig, ax = plt.subplots()
    if poly_bin:
        plot_polygon(ax,
                     poly_bin,
                     facecolor='None',
                     edgecolor='black',
                     alpha=1)
    for poly_part in poly_parts:
        plot_polygon(ax,
                     poly_part,
                     facecolor='lightblue',
                     edgecolor='blue',
                     alpha=0.2)
    if poly_other:
        plot_polygon(ax,
                     poly_other,
                     facecolor='None',
                     edgecolor='red',
                     alpha=1)
    plt.show()

# Plots a Polygon to pyplot `ax`
# cf. https://stackoverflow.com/questions/55522395
def plot_polygon(ax, poly, **kwargs):

    if poly.type == "MultiPolygon":
        for polygon_tmp in poly.geoms:
            plot_polygon(ax, polygon_tmp, **kwargs)
        return

    path = Path.make_compound_path(
        Path(np.asarray(poly.exterior.coords)[:, :2]),
        *[Path(np.asarray(ring.coords)[:, :2]) for ring in poly.interiors])

    patch = PathPatch(path, **kwargs)
    collection = PatchCollection([patch], **kwargs)
    
    ax.add_collection(collection, autolim=True)
    ax.autoscale_view()

# https://tjkendev.github.io/procon-library/python/geometry/line_cross_point.html
def line_cross_point(line_p, line_q):
    ((x0, y0), (x1,y1)) = tuple( line_p.coords )
    ((x2, y2), (x3,y3)) = tuple( line_q.coords )

    # 端点を共有する場合
    if (x0, y0) in ((x2, y2), (x3,y3)):
        return (x0, y0)
    if (x1, y1) in ((x2, y2), (x3,y3)):
        return (x1, y1)

    a0 = x1 - x0; b0 = y1 - y0
    a2 = x3 - x2; b2 = y3 - y2

    d = a0*b2 - a2*b0
    if d == 0:  # 2つの線分が平行
        return None

    # s = sn/d
    sn = b2 * (x2-x0) - a2 * (y2-y0)
    # t = tn/d
    #tn = b0 * (x2-x0) - a0 * (y2-y0)
    ret_x = x0 + a0*sn/d
    ret_y = y0 + b0*sn/d
    if x0 <= ret_x <= x1 and x2 <= ret_x <= x3 and \
       y0 <= ret_y <= y1 and y2 <= ret_y <= y3:
        return (ret_x,ret_y)
    return None


if __name__ == '__main__':
    main()

↑こう書くと、↓こう表示されます

redhat rhel-app-streams-life-cycle

https://end0tknr.hateblo.jp/entry/20211217/1639700840

redhat (rhel)のミドルウェアサポート期限は、↑こう理解していましたが、 現在は、以下のurlの通りのようです。

https://access.redhat.com/support/policy/updates/rhel-app-streams-life-cycle

pyclipper for python による Minkowski Sum / Diff

ネスティングツール(図形積み込み、bin packing)の svgnest.com では NFP ( No Fit Polygon ) を算出する際、ミンコフスキー和 or 差 を使用しています。

当初、ミンコフスキー和 or 差 の独自実装も考えましたが、 pyclipper の元である angus johnson's clipper を除くと、 余りに手間を必要としますので、単に pyclipper を利用するだけにしました。

参考url

# -*- coding: utf-8 -*-

import pyclipper
import io
import matplotlib.pyplot as plt
import numpy             as np
import re
import sys
from matplotlib.path        import Path
from matplotlib.patches     import PathPatch
from matplotlib.collections import PatchCollection
from shapely.geometry   import Polygon


def main():
    # parts a
    shell_a_s = [
        [[ 0,0],[50,0],[50,50],[ 0,50]],
        [[ 0,0],[50,0],[50,30],[30,50],[0,50]],
        [[20,0],[50,0],[50,50],[ 0,50],[0,20]],
        [[ 0,0],[50,0],[50,10],[10,10],[10,40],[50,40],[50,50],[0,50]]
    ]
    # parts b
    shell_b=[[0,0],[15,0],[0,15]]

    for shell_a in shell_a_s:
        
        poly_a = Polygon( shell=shell_a,      holes=[])
        poly_b = Polygon( shell=shell_b,      holes=[])
        
        # https://github.com/fonttools/pyclipper/blob/main/tests/test_pyclipper.py
        # ミンコフスキー和
        # minkowskis = pyclipper.MinkowskiSum(shell_a, shell_b, False)
        # print( poly_mnkwskis )
        # poly_mnkwski = Polygon( shell=minkowski[0], holes=[])
        # plot_polygons(poly_mnkwski, poly_a, poly_b)
        
        # ミンコフスキー差
        minkowskis = pyclipper.MinkowskiDiff(shell_b, shell_a)
        print( minkowskis )
        poly_mnkwski = Polygon( shell=minkowskis[0], holes=[])
        plot_polygons(poly_mnkwski, poly_a, poly_b)

        
def plot_polygons(poly_mnkwski, poly_a, poly_b):
    fig, ax = plt.subplots()
    for [polygon,face,edge] in [[poly_mnkwski,'None',       'red'],
                                [poly_a,      'lightgray','black'],
                                [poly_b,      'lightblue','blue' ]
                                ]:

        plot_polygon(ax, polygon,
                     facecolor=face,
                     edgecolor=edge,
                     alpha=0.2)
    plt.show()


# Plots a Polygon to pyplot `ax`
# cf. https://stackoverflow.com/questions/55522395
def plot_polygon(ax, poly, **kwargs):
    path = Path.make_compound_path(
        Path(np.asarray(poly.exterior.coords)[:, :2]),
        *[Path(np.asarray(ring.coords)[:, :2]) for ring in poly.interiors])

    patch = PathPatch(path, **kwargs)
    collection = PatchCollection([patch], **kwargs)
    
    ax.add_collection(collection, autolim=True)
    ax.autoscale_view()
    return collection


if __name__ == '__main__':
    main()

↑こう書くと、↓このように表示されます

rhel8の開発用subscription 更新

rhel8試用に伴う Red Hat サブスクリプションの有効化 - end0tknr's kipple - web写経開発

以前、記載した上記entryの関連です。

redhatの開発用ライセンスは、有効期間:1年間ですので、 以下の手順で更新しますが、たまにしか行わない作業ですので、いつも忘れています。

Step 0 - ライセンスの有効期限切れに気づく

以下のようなエラーが表示され、yum(dnf)が実行できません。

$ sudo yum install perl
Updating Subscription Management repositories.

This system is registered with an entitlement server,
but is not receiving updates. You can use subscription-manager
to assign subscriptions.

「sudo subscription-manager list」を実行しても、 有効なサブスクリプションがないことを確認できます。

$ sudo subscription-manager list
+-------------------------------------------+
    Installed Product Status
+-------------------------------------------+
Product Name:   Red Hat Enterprise Linux for x86_64
Product ID:     479
Version:        8.5
Arch:           x86_64
Status:         Not Subscribed
Status Details: Not supported by a valid subscription.
Starts:         
Ends:           

Product Name:   Red Hat CodeReady Linux Builder for x86_64
Product ID:     491
Version:        8.5
Arch:           x86_64
Status:         Not Subscribed
Status Details: Not supported by a valid subscription.
Starts:         
Ends:

Step 1 - access.redhat.com で サブスクリプション更新

https://access.redhat.com/ へ、ログインすると、 以下のような画面が表示されますので、チェック & 送信します。

その後、https://access.redhat.com/management へ、 アクセスすると、サブスクリプションが更新されたことを確認できます。

Step 2 - rhelへのサブスクリプション適用

最後に、以下のコマンドを実行し、完了です。

# subscription-manager refresh
All local data refreshed

# subscription-manager register --auto-attach --force
Registering to: subscription.rhsm.redhat.com:443/subscription
Username: end0tknr
Password: 
The system has been registered with ID: cdd7b576-0812-4a64-9c26-<ないしょ>
The registered system name is: rhel8h.a5.jp

Installed Product Current Status:
Product Name: Red Hat Enterprise Linux for x86_64
Status:       Subscribed

Product Name: Red Hat CodeReady Linux Builder for x86_64
Status:       Subscribed

# subscription-manager list
+-------------------------------------------+
    Installed Product Status
+-------------------------------------------+
Product Name:   Red Hat Enterprise Linux for x86_64
Product ID:     479
Version:        8.5
Arch:           x86_64
Status:         Subscribed
Status Details: 
Starts:         11/16/2022
Ends:           11/16/2023

Product Name:   Red Hat CodeReady Linux Builder for x86_64
Product ID:     491
Version:        8.5
Arch:           x86_64
Status:         Subscribed
Status Details: 
Starts:         11/16/2022
Ends:           11/16/2023