t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

Raspberry Piの構成変更をScriptコマンドで記録する

今回の記事は完全に自分の備忘録用である。

LinuxにはScriptというコマンドがあり、これを使うとコマンドラインでの入出力をテキストログに記録してくれる。
仕事では監査対応で使ったりするが、Raspberry Piの変更管理にも使えるんじゃないかと思ったのでやってみた。
構成変更をロギングしておけば将来ぶっ壊れたときに環境を再構築しやすくなる。

使い方は、script ファイル名を実行するだけ。
そのあと普通にコマンドを叩いて、おわったらexitすれば操作がファイルに記録される。

これは便利と思って早速使ってみたのだが、vimで普通に開いてみたところ制御文字がばっちり記録されてて読みづらい。
f:id:t-hom:20210801083757p:plain

このログはlessコマンドに-rオプションを付けることで綺麗に読むことができる。
less -r ~/20210801.log
f:id:t-hom:20210801083949p:plain

カラーもついてるので一瞬実際のコマンド画面かと思ったけどログだった。

さて、これをファイル出力してWindows側で保管するには制御文字を実際に消し去らないといけない。
そこで利用させていただいたのが以下の掲示板で紹介されていたコード。
unix.stackexchange.com

#!/usr/bin/perl

while (<>) {
  s/ \e[ #%()*+\-.\/]. |
    \r | # Remove extra carriage returns also
    (?:\e\[|\x9b) [ -?]* [@-~] | # CSI ... Cmd
    (?:\e\]|\x9d) .*? (?:\e\\|[\a\x9c]) | # OSC ... (ST|BEL)
    (?:\e[P^_]|[\x90\x9e\x9f]) .*? (?:\e\\|\x9c) | # (DCS|PM|APC) ... ST
    \e.|[\x80-\x9f] //xg;
    1 while s/[^\b][\b]//g;  # remove all non-backspace followed by backspace
  print;
}


ところが実際にやってみると次のとおり日本語が文字化けする。
f:id:t-hom:20210801084720p:plain

これを解決するヒントはこちらに合った。
osksn2.hep.sci.osaka-u.ac.jp

最終コード

UTF-8の処理を組み込んで、コードは最終的に次のようになった。

#!/usr/bin/perl
use utf8;
binmode STDIN, ":utf8";
binmode STDOUT, ":utf8";

while (<>) {
  s/ \e[ #%()*+\-.\/]. |
    \r | # Remove extra carriage returns also
    (?:\e\[|\x9b) [ -?]* [@-~] | # CSI ... Cmd
    (?:\e\]|\x9d) .*? (?:\e\\|[\a\x9c]) | # OSC ... (ST|BEL)
    (?:\e[P^_]|[\x90\x9e\x9f]) .*? (?:\e\\|\x9c) | # (DCS|PM|APC) ... ST
    \e.|[\x80-\x9f] //xg;
    1 while s/[^\b][\b]//g;  # remove all non-backspace followed by backspace
  print;
}

使い方

このスクリプトをformatlogという名前で保存し、chmod +x formatlogで実行権限を付け、/usr/local/binへ移動する。
(恒久的に使う為にそうしたけど、一回限りならホームフォルダのままでも良い。その場合はコマンド実行にフルパス指定が必要となる。)

これでformatlog < 入力ファイル > 出力テキストファイルというコマンドで制御コードが取り除かれたファイルが入手できるようになった。

上記の操作もScriptでロギングしたので記しておく。

Script started on 2021-08-01 08:54:50+09:00 [TERM="xterm-256color" TTY="/dev/pts/2" COLUMNS="98" LINES="23"]
pi@raspberrypi:~ $ pwd
/home/pi
pi@raspberrypi:~ $ ls
20210801.log      Arduino    Desktop    Downloads  Music     Public  Templates  formatlog
202108010854.log  Bookshelf  Documents  Health     Pictures  Stage   Videos
pi@raspberrypi:~ $ chmod +x formatlog
pi@raspberrypi:~ $ mv formatlog /usr/local/bin
mv: 'formatlog' から '/usr/local/bin/formatlog' へ移動できません: 許可がありません
pi@raspberrypi:~ $ sudo mv formatlog /usr/local/bin
pi@raspberrypi:~ $ formatlog < 20210801.log > 20210801.txt
pi@raspberrypi:~ $ ls
20210801.log  202108010854.log  Bookshelf  Documents  Health  Pictures  Stage      Videos
20210801.txt  Arduino           Desktop    Downloads  Music   Public    Templates
pi@raspberrypi:~ $ exit
exit
Script done on 2021-08-01 08:56:10+09:00 [COMMAND_EXIT_CODE="0"]

注意点

このスクリプトでは制御文字を単純に取り除いてしまうため、コマンド履歴を呼び出して編集するような操作を行った場合は少しおかしいことになる。

例えば、以下のようにsudoをつけ忘れてコマンドが失敗した場合、上矢印キーで履歴を呼び出し、Homeキーで先頭にカーソルを移動した後にsudoを入力し、Endキーで末尾に戻ってEnterで実行するということがある。

pi@raspberrypi:~ $ mv formatlog /usr/local/bin
mv: 'formatlog' から '/usr/local/bin/formatlog' へ移動できません: 許可がありません
pi@raspberrypi:~ $ sudo mv formatlog /usr/local/bin

この場合、生ログは次のように制御コードが入力される。

]0;pi@raspberrypi: ~ [01;32mpi@raspberrypi[00m:[01;34m~ $[00m mv formatlog /us/[Kr/local/bin
mv: 'formatlog' から '/usr/local/bin/formatlog' へ移動できません: 許可がありません
]0;pi@raspberrypi: ~ [01;32mpi@raspberrypi[00m:[01;34m~ $[00m mv formatlog /usr/local/bin
[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[1@s[1@u[1@d[1@o[1@ [C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C
]0;

カラーコードも含むのですごく見づらいけど、なんとなく、[1@s[1@u[1@d[1@oがsudoを示していることがよみとれる。これはコマンドより後に登場するため、単純にスクリプトで変換すると以下のように末尾にsudoが付いてしまうのだ。

pi@raspberrypi:~ $ mv formatlog /usr/local/binsudo

これを防ぐ手立ては不明なので、監査ログを作るようなシチュエーションではコマンド履歴を使わずに全部手打ちするか、生ログを残しておくのが良い。

生ログをless -r ファイル名で読むと実行したコマンドがそのまま表示されるので、作業エビデンスとしての意味合いを持たせたいなら生ログを残しておくのが吉だと思う。

以上

VBA クラスモジュールで工場建造ゲームSatisfactoryのコンベアスプリッター効率検証

最近遊んでいる工場建造ゲーム Satisfactoryで少し疑問に思うことがあったので、今回はVBAを使って検証してみた。
ゲームを題材にしてはいるものの、この記事で伝えたいことはクラスモジュールの有用性なので、ゲーム自体に興味ない方も是非ご一読いただければ幸いである。

前提の説明

このゲームではコンベアスプリッターという機材を使ってベルトコンベアを分岐させ、複数のマシンに資源を送り込むことができる。
スプリッターは資源の分配レートを指定できない為、出力経路が2本なら単純に2分割、3本なら3分割される仕組みである。
この時に使用する分岐メソッドは2タイプあり、1つはLoad Balancing Method、もう1つはOverflow Methodである。

実際のゲーム画面で説明するとこんな感じ。

モデル化したものがこちらである。

Load Balancing Methodの方は最初から資源が等配分されるのに対し、Overflow MethodはInputに近い順から配分が大きくなる。

Overflowメソッドで受け取れる資源は次のようになる。

No 受け取れる資源の量
マシン1 半分
マシン2 半分の半分
マシン3 半分の半分の半分
マシン4 残り

一見、後方のマシンの生産性が低いように思えるが、マシン1には生産効率を超えて資源が入るので稼働を続けると貯めきれなくなった分が溢れて資源を受け取れなくなる。そうすると受け取れなかった分は次のマシンに回されていくので、同じ資源を与えた場合のトータルの生産性はLoad Balancing Methodに追いつくとされている。(立ち上がりのアドバンテージによって総生産数の差は埋まらないが、このゲームではマシンは延々と動き続けるので総生産数はどのみち∞。重要なのは生産効率である。)

Overflowメソッドの場合、同じパターンで何台でも繋げられるので拡張性に優れている。

疑問:本当に生産効率は変わらないのか

稼働から一定時間が経過すると2つのメソッドの生産性はイコールになるということだが、本当にそうなのか心配になった。
生産性がイコールになるという説明は頭では理解できる。しかし直感的には資源が常に等分されるLoad Balancingの方が生産性に優れているような気がするのだ。

大規模な工場を建造する場合は失敗するとかなりの時間を失うため、ここでこの疑問をばっちり解消しておきたい。

VBAで検証:設計編

今回Load Balancingメソッドについては特に検証はしない。
資源が等しく配分される以上、検証するまでもなく全マシンの生産性は最初から最大である。

検証するのはOverflowメソッドの生産性である。

コンベアスプリッターとマシンで共通して使えるノードクラスを考え、次のプロパティを実装することにした。
プロパティはパブリック変数として簡易に実装する。

変数名 説明
Storage Double 格納できる資源の上限数
Store Double 実際に格納されている資源の数
Consume Double 1チックごとに消費する資源の数
Product Long 生産された製品の数
NextNodes Collection 資源の送出先ノード

スプリッターの場合はConsumeは0、資源の上限数は現状コンベアベルトMk.5の運搬スピードが780 Item/minなので780する。NextNodesには次のスプリッターノードとマシンノードを登録する。
マシンの場合はConsumeが90、資源の上限数は500とし、NextNodesは何も登録しない。

また、マシンの挙動としてStoreがConsumeよりも大きい場合、1チックごとにStoreからConsumeを差し引いて、代わりにProductが1増える仕組みとした。
ちなみにスプリッターも内部的には同じ挙動をするが、Consumeは0なのでStoreは減らず、単にProductだけが増え続ける。スプリッターのProduct数に意味はないが、わざわざ増やさない処理を書くのも面倒だったので単に無視することにした。

VBAで検証:コーディング

まずはクラスモジュールを挿入し、オブジェクト名をNodeとして次のコードを張り付ける。

Option Explicit
Public Storage As Double
Public Store As Double
Public Consume As Double
Public Product As Long
Public NextNodes As Collection


Public Sub Tick()
    Dim n As Node
    
    If Store > Consume Then
        Store = Store - Consume
        Product = Product + 1
    End If
    
    Dim cnt As Integer
    cnt = NextNodes.Count
    For Each n In NextNodes
        Dim ret As Double
        ret = n.TryReceive(Store / cnt)
        Store = Store - (Store / cnt)
        Store = Store + ret
        cnt = cnt - 1
    Next
End Sub

Public Function TryReceive(amount As Double) As Double
    Store = Store + amount
    Dim ret As Double
    If Storage < Store Then
        ret = Store - Storage
        Store = Storage
    Else
        ret = 0
    End If
    TryReceive = ret
End Function

Private Sub Class_Initialize()
    Set NextNodes = New Collection
End Sub


次に標準モジュールのコード。

Sub OverflowProductivityCheck()
    Const NUMBER_OF_TICKS = 100
    Dim splitter(1 To 4) As Node
    Dim machine(1 To 4) As Node
    
    Dim i As Long
    For i = 1 To 4
        Set splitter(i) = New Node
        splitter(i).Storage = 780
        splitter(i).Consume = 0
        
        Set machine(i) = New Node
        machine(i).Storage = 500
        machine(i).Consume = 90
    Next
    
    For i = 1 To 4
        splitter(i).NextNodes.Add machine(i)
        If i < 4 Then
            splitter(i).NextNodes.Add splitter(i + 1)
        End If
    Next
    
    Dim j As Integer
    For i = 1 To NUMBER_OF_TICKS
        splitter(1).TryReceive 360
        For j = 1 To 4
            splitter(j).Tick
            machine(j).Tick
        Next
    Next
    
    For i = 1 To 4
        Debug.Print machine(i).Product
    Next
End Sub

OverflowProductivityCheckプロシージャはNUMBER_OF_TICKSで指定された回数分時間を進めて、マシンごとの累計プロダクト数を出力する。

NUMBER_OF_TICKSを変えて何度か試してみたところ、Tick回数ごとの累計プロダクト数は以下のようになった。

↓マシン/Tick回数→ 5 10 50 100 10000
マシン1 5 10 50 1000 10000
マシン2 4 9 49 99 9999
マシン3 2 6 45 95 9995
マシン4 2 6 45 95 9995

この実験で、累計プロダクト数は立ち上がり時の効率による影響を受けているものの、生産性は確かにLoad Balancing Methodに追いつくということが分かった。

終わりに

本当はクラスなんて作らなくても数学的に解けないか、あるいはExcelの機能で簡単に解けないかということも考えていたのだが、結局私は文系思考から抜け出せていない為、ロジックを言語で記述すること(つまりプログラミング)で実験することにした。

私の中に最近、ロジックを式で完結に表記するのが理系、言葉で順序だてて説明するのが文系というイメージがある。そう考えると、プログラミングは文系最強の道具なのではないかと思う。

さて、今回は頭で考えてもなかなか払拭できない疑問を晴らすためにクラスモジュールを使って実際の構造を再現して実行してみた。
頭の中に描いたモデルを実際にプログラミングで使える道具にしてしまうのがクラスモジュールの強みである。

特にデータ構造を作ったとき、各ノードは何らかの機能を持っていることが普通のなので、クラスを用いることでそうしたモデルを直感的に扱うことができるようになる。

クラスなんて使わなくてもVBAは書けるので全く覚える必要はないという意見も耳にするが、それは頭の良い人が言ってることなので真に受けないほうが良い。
絵を描く天才が、直線引くのに定規なんて要ります?鉛筆だけで十分でしょと言ってるのと同じ。

私は凡人である。凡人には強力なツールが必要だ。さもなければすぐに頭がこんがらがってしまい、難しい問題に太刀打ちできない。
皆さんも凡人である。天才はこんなブログ読んでない。多分。

クラスモジュールは凡人にこそおススメしたい、VBA最強の整理整頓ツールである。

以上。

thom.hateblo.jp

VBA クラスモジュールでタートルグラフィックプログラムを作成

今回はExcel VBAのクラスモジュールを使ってタートルグラフィックを描けるオブジェクトを作成してみた。

タートルグラフィックはカメを動かして線を引いていくプログラムで、プログラミング初心者の学習等で活用されることも多い。
例えばPythonでは標準装備されているのでモジュールをインポートするだけで使用できる。

以下がタートルを使ったグラフィックのイメージ。
f:id:t-hom:20210723124407p:plain

作成したVBAバージョンの使用イメージ。タートルといいつつカメは省略しているが、同じように線は引ける。
f:id:t-hom:20210723124756p:plain

入手方法

このプログラムはGitHubにアップロードしたので、使ってみたい方は以下より、binリンク→Turtle.xlsmリンク→Downloadボタンの順で入手可能である。
github.com

コードだけ見たい方はsrc/Turtle.xlsmリンクより、各モジュールのコードが確認できる。

作り方

今回は一応このブログにもコードを掲載しておこうと思う。

描画キャンバスの準備

Sheet1の表示メニューから枠線のチェックを外し、セルの線を非表示にしておく。
f:id:t-hom:20210723125641p:plain

次にSheet1のオブジェクト名をCanvasSheetに変更する。
※シートオブジェクト名の変更方法は以下の過去記事参照
thom.hateblo.jp

次に、VBEでCanvasSheetを開き、次のコードを張り付ける。

Sub Clear()
    Dim sh As Shape
    For Each sh In Me.Shapes
        sh.Delete
    Next
End Sub

Turtleクラスの作成

クラスモジュールを挿入し、オブジェクト名をTurtleとする。
次のコードを貼り付けて完成。

Option Explicit
Private isPenDown As Boolean
Private x As Double
Private y As Double
Private degree As Double

Sub Forward(length)
    Dim new_x As Double
    Dim new_y As Double
    new_x = x + Sin(Radian(degree)) * length
    new_y = y + Cos(Radian(degree)) * length
    If isPenDown Then
        Call CanvasSheet.Shapes.AddConnector(msoConnectorStraight, x, y, new_x, new_y)
    End If
    x = new_x
    y = new_y
End Sub

Sub PenDown()
    isPenDown = True
End Sub

Sub PenUP()
    isPenDown = False
End Sub

Sub TurnLeft(d)
    degree = degree + d
End Sub

Sub TurnRight(d)
    degree = degree - d
End Sub

Private Sub Class_Initialize()
    With CanvasSheet
        With Cells(.Rows.Count \ 2, .Columns.Count \ 2)
            x = .Left + Application.UsableWidth / 2
            y = .Top + Application.UsableHeight / 2
            Application.Goto .Item(1), True
        End With
    End With
    degree = 90
    CanvasSheet.Clear
End Sub

Function Radian(degree As Double) As Double
    Radian = degree / 45 * Atn(1)
End Function

Class_Initializeで少し特殊なことをしているが、これはExcel上で0,0よりも左上には描画できないためカメが早々に壁にぶつからないようにする対応策である。
シートの中央にジャンプさせてそこから描画を開始することでマイナス方向への移動の制約はほぼ無くなる。

メインモジュール

メインモジュールはクラスを使って好きにプログラムを書くだけである。
使える命令は以下のとおり。

命令 説明
PenDown ペンを下す命令。先にこれをしておかないと線が描けない。
PenUp ペンを上げる命令。線を書かずに場所を移動したいときにペンを上げる。
Forward(距離) 進行方向に進む命令。サイズ感が分からなければ、とりあえず100くらいから初めてみると良いと思われる。
TurnRight(角度) 進行方向を右に回転させる。
TurnLeft(角度) 進行方向を左に回転させる。

以下は、星型の図形を描くサンプル。

Sub Sample_Star()
    Dim t As Turtle: Set t = New Turtle
    
    t.PenDown
    Dim i As Integer
    For i = 1 To 5
        t.Forward 100
        t.TurnRight 360 / 5 * 2
    Next
End Sub

実行すると次のようになる。
f:id:t-hom:20210723131535p:plain

セルの左上がLCB524288となっているが、これはClass_Initializeで解説したように、A1付近から初めてしまうとマイナス方向への移動で早々に壁にぶつかってしまって描けなくなる為の対策である。

いろんなサイズの星をちりばめてみた。

Sub Sample_Star2()
    Dim t As Turtle: Set t = New Turtle
    
    For j = 1 To 50
        turn = WorksheetFunction.RandBetween(1, 360)
        fwd = WorksheetFunction.RandBetween(100, 300)
        size = WorksheetFunction.RandBetween(20, 100)
        t.TurnRight CDbl(turn)
        t.Forward CDbl(fwd)
        t.TurnLeft CDbl(turn)
        
        t.PenDown
        Dim i As Integer
        For i = 1 To 5
            t.Forward size
            t.TurnRight 360 / 5 * 2
        Next
        t.PenUP
    Next
End Sub

ランダムに移動しながら星をまき散らすので実行直後に数個しか表示されてなくても画面外にスクロールしていくと次のような星の群が見つかる。
f:id:t-hom:20210723133540p:plain

終わりに

プログラミング初心者の学習に使用されることが多いタートルプログラムだが、幾何学模様が手軽に作れるのでアイデア次第では面白いアート作品になると思う。
初心者向けの教材として使ってもらっても良いし、中~上級者はクラスを改造して色々と命令を追加してみるのも良いかなと思う。

今回作ったタートルは最低限の命令しか用意していないが、一般的には線の色を変えたり、バックしたり、角度・位置を直接指定したり、円を描いたりという命令が備わってるらしい。そういうコードを追加していっても良いかもしれない。

VBA 三角関数を用いて複数のオートシェイプを円周状に並べる

今回は三角関数を用いて複数のオートシェイプを円周状に並べるプログラムを作成したので備忘禄がてらサクッとご紹介。
目的はとあるゲームのためであるが、記事の本筋ではないのでそれは最後に紹介。

作ったもの

以下のように、正方形を円形に3周分並べた。

コード

※特定の目的のための使い捨てコードなので変数宣言は一部省略している箇所があります。

Sub LayoutRectangleAsCirculer()
    Const OFFSET_X = 200
    Const OFFSET_Y = 200
    Const SIZE = 14
    Dim r As Double: r = 100
    Dim sh As Shape
    Dim degree As Integer
    For i = 1 To 3
        For degree = 0 To 350 Step 10
            θ = Radian(degree)
            x = r * Sin(θ) + OFFSET_X - SIZE / 2
            y = r * Cos(θ) + OFFSET_Y - SIZE / 2
            cnt = cnt + 1
            Set sh = Sheet1.Shapes.AddShape _
                (msoShapeRectangle, x, y, SIZE, SIZE)
            sh.Rotation = 360 - degree
        Next
        r = r + SIZE + 1
    Next
End Sub

Function Radian(degree As Integer)
    Radian = degree / 45 * Atn(1)
End Function

説明

三角関数は高校1年で習う。ただ当時は使いどころが分からずただ闇雲に覚えさせられるだけという方も多いと思う。かくいう私も今年に入ってようやく意味を理解したところである。
今回は円周上にオートシェイプを配置したいのだが、半径Rは適当に決めるとしてそこから角度ごとのX・Y座標をどうやって求めるかがポイントになる。

そこで三角関数の出番だ。三角関数を使うと、直角三角形のうち90°以外の角度が一つ分かれば、それぞれの辺の比が求まる。

具体的には、
底辺X = r * Sin(θ)
高さY = r * Cos(θ)

となる。

あとはθをループで0°~360°まで、任意のステップで変化させていけば良い。
(0°と360°は同じになるので実際には360°手前までループさせる。)

三角関数の使いどころについて改めて感じること

高校で三角関数を習ったときは、「三角形の辺の比?興味ねぇ」という感じだった。
それからどこかで「三角関数を使うと円が描ける」という話を聞いたときも、「へぇ、それで?」という感想しか持っていなかった。円なんて、コンパス使えば描けるし、コンピューターならなおのこと。ペイントでもCADでもパワポでも、円描画機能なんて大抵の作図ソフトが標準装備しているからだ。

三角関数について本格的に学んだのは今年1月頃に学習していた材料力学に登場したためだ。
(当時は木工でちゃんと耐荷重計算した机を作ろうとしてた。それ自体は結局挫折したけど、得たものは大きい。)

それから、PS4のコントローラーのアナログスティックの押し込み角度の計算に応用できることを知って、こんな記事を書いた。
thom.hateblo.jp

それでもまぁ、作図に使うことは無いだろうと思っていたんだけど、今回たまたま表題の用件があって一瞬で閃いた。
もし三角関数を知らないままだったら、円のシェイプをガイドとして配置して、その上に手で並べていたと思う。
三角関数は超便利だし、アイデアの源泉にもなるということを改めて実感した。

今回プログラムを書いた個人的な目的

実は随分前から、Satisfactoryという工場建設ゲームにハマっていて、今ちょうど円形の原子力発電所を建設しているところである。
それで、大量の水をパイプラインで運んでこないといけないんだけど、下のマップのように海は東側に偏っていて、岩にぶつからずに延々と海側へ伸ばしていけるラインは4か所しかない。

1ラインでパイプ18本。これが4か所で計72本を360°均等に振り分ける為のパイプの引き方に悩んでいた。

上記で紹介したマップは有志が作ったツールで、ゲーム自体は以下の通りファーストパーソンビューなので、極太パイプ72本を干渉させずに4か所にまとめるのがとても苦労するのだ。

それで計画を立てるために以下のような図を用意してプランを立てたという顛末である。

色分けしたとおりにパイプを引き込んで、最終的にはこんな感じになった。


以上

VBA 線が絡まないPERT図(グラフ)の自動レイアウトアルゴリズム

今回は、グラフのレイアウトを自動化するためのアルゴリズムを紹介する。
ここでいうグラフはExcelのグラフ機能のことではなく、数学のグラフ理論の方のグラフ。

知らない方は、以下のような作業工程図をイメージしてもらえば良いかと思う。
f:id:t-hom:20210526011052p:plain

マインドマップ等も一種のグラフと見做すことができ、私がグラフの自動レイアウトを知ったのはFrieve Editorというツールがきっかけだ。
www.frieve.com

このグラフ自動レイアウトは5年以上前からずっと気になっていたのだが、当時の私はどうやっても調べることができず断念していた。

ところが先日たまたま気になって検索しなおすと、Wikipediaのページに疑似コードが掲載されていることに気づいた。
ja.wikipedia.org

これだ!と思ったけど私の技術力ではこれをVBAコードに置き換えることはできなかった。
そこでまずは英語版Wikipediaからタイトルの「Force-directed graph drawing」を拾い上げて+言語名でGoogle検索したところ、Python2で書かれたコードを見つけることができた。
Simple force directed graph drawing algorithm · GitHub

実行すると、次のようにランダム配置されたノードが接続関係を保ったまま綺麗にほどかれていく様がアニメーションになっている。
f:id:t-hom:20210712121015p:plain
(アルゴリズムの仕様上、初期配置が悪いとたまに絡まったまま完了してしまうことがある。)

そこで、このコードを穴が開くほど読み込んで、なんとかVBAに移植することができた。
実コードは以下のGitHubページのbinフォルダーからxlsmファイルをダウンロードするか、src/Spring.xlsmフォルダーの各ファイルから直接閲覧できる。
github.com


移植したといっても挙動はPython版と異なり、アニメーションはせずに直接格子型のレイアウトがパッと現れておしまい。
これはパフォーマンスを心配してあえてアニメーションさせなかったんだけど、内部の管理座標はランダムで開始されて徐々に本来の座標に移動し、レイアウトが完成したところで描画している。
こればかりはソースコードを読まないと分からず、最初から定義された座標に配置してるんじゃないかと疑われるかもしれないが。

このアルゴリズムの考え方は、ざっくり言えば接続されたノード同士は引き合い接続されていないノード同士は反発しあうというもの。
f:id:t-hom:20210712123824p:plain

この法則でノードにかかる力を計算しながら移動していき、全体のつり合いがとれたところで終了すると綺麗に展開されるという手法である。

私が自分で考えを巡らせていた頃は、線の重なりを解析してそれを避ける配置を探すというアルゴリズムなのかなと思っていたので、こういう力学的な解き方があるというのは目から鱗だった。

この反発力は電磁気学からクーロンの法則が用いられ、引張力は力学からフックの法則が用いられる。
電磁気学も力学も、それ自体を目的としてコンピューターシミュレーションが行われるのは普通のことだが、コンピューター上で図形のレイアウトを行う目的で全然違う分野をまぜこぜに使っているのが逆輸入みたいでとても面白いと思う。

ちなみに力学を用いている関係で、初期配置によっては絡まったままつり合いが取れてしまうこともあって、現状100%うまくいくわけではない。まぁそうなってもボタンを押しなおすだけなので人が配置するよりもはるかに楽ではあるが、ユーザー目線だと微妙な印象は与えてしまうかなと思う。

多分これより優れたやり方はあるんだろうけど、自分が使う分には十分に満足できそうなので、今後、先日作成したネットワークダイアグラムの作成ツールに組み込めないか思案中である。

以上

VBA PERT図もどきを作図して依存関係を考慮したスケジュールを自動生成するツール

今回は久々にVBAでツールを作ったのでご紹介。

作ったもの

プロジェクト管理で使える、スケジューリングツール。

タスクリストからタスクの依存関係図(PERT図もどき)を作成して、依存関係を反映させたスケジュールを生成するための補助ツールである。
f:id:t-hom:20210526011311p:plain

動機

小規模なプロジェクトを扱う際に、Excelでガントチャートを作成する場合があるが、少しタスク間の依存関係が複雑になると単純なスケジュール表では扱いが難しい。

そこで、タスクとタスクを矢印で結んで依存関係を表すPERT図が欲しくなる。
ja.wikipedia.org

ただ、Excelでこういった作図するのはとても面倒だし、作図した後に複雑な依存関係をガントチャートのようなスケジュール表に落とし込むのも面倒だ。

そこで作図とスケジューリングが簡単に行えるようにツールを作成した。

ちなみに今回作成したツールは厳密なPERT図を作図するものではなく、依存関係を表すためだけの簡易ダイアグラムを作図するツールである。

動作イメージ

百聞は一見にしかずということで、実際に使用している動画をキャプチャーしたのでまずはこちらをご覧いただければと思う。
youtu.be

作業手順の概要

  1. TaskListシートにタスクを手入力する。
  2. Drawシートのマクロでタスクを出力する。
  3. Locateマクロ群と手作業を併用してタスクを並べる。
  4. Connectマクロ群でタスク同士を接続していく。
  5. Numberマクロでタスクに番号を振る。
  6. HolidaysシートのA列に土日以外のプロジェクト休止日(日本の祝日や会社の休業日)を追加する。
  7. Scheduleシートのマクロでスケジュールを出力する。
  8. STARTタスクのPlanned Startにプロジェクト開始日を手動更新する。

マクロボタンの説明

Plot Tasks

TaskListシートに記入されたタスクを円形のオートシェイプで出力する。

Order Node Vertical

Shiftを押しながら連続でタスクのシェイプを選択してから実行すると、選択したタスクが縦に並ぶ。

Swap Node Location

2つのシェイプを選択した状態で実行すると、位置を入れ替える。

Connect Straight

Shiftを押しながら連続でタスクのシェイプを選択してから実行すると、選択したタスクの順に矢印が接続される。

Connect Split

Shiftを押しながら連続でタスクのシェイプを選択してから実行すると、最初に選択したタスクから、各タスクに向かって矢印が接続される。

Connect Marge

Shiftを押しながら連続でタスクのシェイプを選択してから実行すると、各タスクから最後に選択したタスクに向かって矢印が接続される。

Find Disconnection

コネクターを誤ってドラッグした等で、接続が切れたコネクターを赤色に変更して目立たせる。
再度手動で接続した後に実行すると黒に戻る。
スケジュールシートではこの接続関係を使用するため、コネクターが切断されているとスケジュールを作成できない。

Number Nodes as Selection Order

Shiftを押しながら連続でタスクのシェイプを選択してから実行すると、選択した順にタスクに番号が振られる。
スケジュールシートではこの番号を使用するため、この工程は必須である。

Unnumber All Nodes

すべてのタスクから番号を除去する。
このマクロは番号の振り直しのためのリセット用である。
スケジュールシートではタスク番号を使用するため、最終的にはすべてのタスクに番号が付いていないと次の行程に進めない。

Remove All Shapes

やり直し用。すべてのシェイプが削除される。

Remove Connections

接続のやり直し用。すべてのコネクターが削除される。

Plot Schedule

Drawシートの依存関係を元にスケジュールを出力する。
Planned Startは依存元タスクのPlanned End + 1日が設定される。
Planned EndはPlanned Start + Duration(日)が設定される。
複数タスクに依存する場合のPlanned Startは依存元タスクのPlanned Endのうち最も遅いものをMAX関数で取得しており、その日付+1日が設定される。

公開場所

github.com

Excelファイルだけ欲しい方

binの中のNDT.xlsmを開いてDownloadボタンをクリック

ソースコードだけ見たい方

src/NDT.xlsmを開いて各モジュールを開く

免責事項

完全に自分用に作ったツールなので改善の要望等は受け付けていませんが、ダウンロードして改造して使う分には特に許可は不要です。ただし改造して再配布する場合、出元は示しておいてください。

MITライセンスとして公開していますのでライセンスの概要はご自身で確認お願いします。

無線LAN-SDカードとコンデジでブログ用写真を快適に…と思ったらハマった件

電子工作を初めてから写真を撮る頻度が上がったのだが、スマホで写真を撮るのを面倒に感じていた。
OneDriveで自動的にPCと同期する設定にしているが、あまり同期の頻度が良くないし逐一クラウドを経由するのも好みではない。

それで無線LAN付きのカメラで自動同期できないかと探していたんだけど、そうした機能を謳っている手ごろなカメラが見つからずに困っていたところ、無線LAN付きのSDカードの存在を知った。
東芝のFlashAirという製品は、サードパーティー製のSnowyというツールを使うことで自動でPCに写真を取り込めるらしい。

これを使えば、手持ちのコンパクトデジカメ(Nikon COOLPIX P300)に挿して無線でファイルを取得できる。

Amazonで中古(16GBが6,800円)を見つけたので購入してみた。
f:id:t-hom:20210315163908p:plain

※実はモノが届くまで中古だということに気づいてなかった。最近Googleの言語設定を英語にしたせいか、Amazonまで英語表示になってるので説明をちゃんと読んでなかった為である。

今朝届いたので早速PCから設定しようとしたんだけれど、、
f:id:t-hom:20210315163317p:plain
f:id:t-hom:20210315163600p:plain

は?

いやいや、落ち着け。多分あれだ、2013年の製品だから旧製品カテゴリーに移動させたとかそういうのだろう。

…ない。

ググってみると製品サポートがキオクシアという会社に移っているとのことで、そちらで探してみたところ、
f:id:t-hom:20210315165152p:plain

は?

まじか。買ったばっかりなのに?

一応APモード(FlashAirが無線LAN親機になるモード)で接続できなくはないけど、逐一PCの無線切り替えて繋ぎに行くなんてのはナンセンス。STAモード(FlashAirが無線子機になるモード)でメインのLANからさくっとアクセスして欲しい。

小一時間ほど凹みながらググっていたところ、どうやらCONFIGファイルをテキストエディタでいじれば設定できるらしい。

参考サイト
https://flashair-developers.github.io/website/docs/api/config.html

私の設定
f:id:t-hom:20210315171348p:plain

これでとりあえず接続できた。

次に以下よりSnowyをDLする。
emoacht.github.io

Zipを展開するとSnowyImageCopy.exeが入っているので実行。
SnowyTool.exe

Win10の場合はネットから拾ってきたプログラムということで最初に警告が出るが、詳細設定から実行するを選択することで使用可能である。(あくまで自己責任で)

SnowyTool.exeという実行ファイルもあるので何か初期設定でもするのかなと思って起動してみたけど、よく分からなかったので無視して良さげ。

SnowyImageCopy.exeの使用方法は他のサイトに譲るとして、少し戸惑った点だけ説明しておく。
まず接続先や保存先などは右上のオプションボタンから設定する。少し変わった開き方をするけれど慣れの問題なのでそこは別に問題ない。
FlashAirアドレスを指定する欄は、http://のあとにAPPNAMEで指定した名前を入れるのだが、その後スラッシュで続けて実際に写真が保存されるパスを指定しなければならない。
ルートフォルダーから自動で探してきてくれたりはしないのでそこが少し面倒である。

パスが不明なときはブラウザからhttp://のあとにAPPNAMEを指定して開くとフォルダーをたどれるので、実際に写真のあるフォルダーが開いたらブラウザのアドレス欄からそのままコピーすれば良い。
f:id:t-hom:20210315173150p:plain

自動チェック感覚は分指定かと思いきや秒指定だった。デフォルトは30秒であるが、私は10秒に設定した。
常に自動チェックをかけるというよりは、カメラを使う時だけアプリを起動し、自動チェックをオンにした状態で撮影する感じ。
撮った傍からPCにたまっていく。それと、自動削除も設定したのでPCへ取り込みが済んだらSDカードからはデータが自動削除されていく。
f:id:t-hom:20210315174647p:plain

これですこぶる快適な環境が整った。

ひとつ難点を挙げると、電池の消耗が早いことである。
まぁこればかりは仕方がない。色々試していたせいもあって早々に電池が切れてしまったけれど、普通に記事執筆のために使う分には問題ないと思う。

さて、今回は公式サポート終了品を購入してしまったために色々と設定で苦労した。
これから購入される場合は以下の製品がおススメである。

上記はSnowyのサイトで動作検証済と書かれたSDカードのうち一番新しいもので、キオクシアによるツールのサポートも続いているようだ。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。