"Exchange"ではwaitRetryとjdgRetryを無限ループにして次に進むノードが空くのを待ちましたが、これでは追突防止はできても交差点制御はできません。2台のAgentが同じ交差点への侵入を待っていた場合、進行の優先順位を決める必要があるためです。そこで本モデルではMvifTo(条件付きMoveTo)という新作クラスを使っています。"Go-Round"のMvifToはかなり複雑なので、まずは追突防止のために作った最初のMvifToを紹介します。下図にそのロジックを示しますが、waitブロックとmoveToブロックの両方のPropertiesを同時に見られるようにキャプチャ画面を加工してあります。
基本はフローチャートでMvifToブロックを数珠繋ぎにして、インスタンス変数として移動先のノードdesNodeとともに前後のMvifToブロックprevMvifとnextMvifを指定します。インスタンス変数としてはこの他にbooleanのopenがあり、Agentが占有(false)しているか非占有(true)かを示します。Agentがwaitブロックに入った時にnextMvif.openがtrueであればDelayは解除されてAgentは次のmoveToブロックに進みます。falseであればwaitに留りますが、先行するAgentがnextMvifのwaitから出て行く時にそのopenをtrueにするとともに後続AgentのDelayを解除します。AgentがmoveToブロックに入るとnextMvif.openをfalseにします。
"Go-Round"のMvifToは以下のようになっています。このMvifToにはシミュレーション開始時にAgentを生成するためのsrcAmr、initSetの両ブロックと、初期位置に戻るとAgentを消滅させるslctSinkブロックが追加されています。機能としては①追突防止に加え、②交差点制御、③初期位置前後での制御、④前方2つのノードが空いているかどうかの確認があります。フロアでの入替え作業では、前後2つのノードが空いていないとかご台車が通路上で衝突するので④が必要になります。それぞれの機能に応じて、prevMvifとnextMvifのどちらかが無かったり、複数あったりします。両者の数を識別するのが新たに追加したインスタンス変数modeFlgです。
MvifToタブ画面でパラメータmodeFlgをクリックすると、Propertiesの一番下のDescription(コメント欄)にmodeFlgの意味が表示されます(上図にも挿入図として掲載)。例えば、modeFlgが1の場合はprevMvifがありませんが、インスタンス変数のprevMvifを指定しないとエラーになるのでダミーで自分自身selfを指定します。2の場合はprevMvifが2つあるので、インスタンス変数prevMvif、prevMvif2として指定します。3の場合はprevMvif3も指定します。以下、nextMvifについても同様です。modeFlgが0は①、2と3と6が②、1と4が③、2と8と9と10が④のためのものです。2は②と④の両方で使われ、5と7は"Go-Round"では使いません。
①の0は最初に作成したMvifToと同じ動作をします。②の2は田の字右側中段の交差点mvifTo58で、上側のnode35と下側のnode45の2方向から合流してnode58に進行します。waitブロックのOn exit:の処理として、prevMvif2よりもprevMvifのDelay解除を優先するとしています。②の3は田の字中央のmvifTo54で、node16、node26、node55の3方向からの合流です。この場合はprevMvif、prevMvif2、prevMvif3の優先順位でDelayを解除します。②の6は田の字上下段の真ん中mvifTo14とmvifTo24で、2方向に分岐します。分岐方向が違う2台が連続して来た場合に干渉しないように、両方の分岐先が空いていることを進行条件にしています。
③の1は初期位置からの移動で、nodeInitから上下方向への移動mvifTo11とmvifTo21が該当します。③の4は初期位置への移動mvifToInitで「3.1 Mainロジック」に登場したものです。④の8と9はAisleRunクラスの中で使われており、nextMvifとnextMvif2として前方2つのMvifToインスタンスを指定し、両方が空いていることを進行条件としています。一方、④の2と10はAisleRunを出た後に使われます。8と9は前方2つが空いていることを条件としているので、逆にそこを出た後は後方2つのMvifToインスタンスのDelayを解除する必要があるという訳です。
AisleRunクラスはフロアにおけるかご台車の入替えを制御します。そのロジックは下図のようになっており、slct1、slct11、pushpull1、putget1、mvifToVia2という5つのブロックを一纏りとするユニットが5つ並んでいます。aisleRun1~6の各インスタンスには5台のかご台車が留置されており、各ユニットがそれぞれの留置かご台車に対応します。slct1では留置かご台車が入替え対象かどうかを判断し、slct11ではデュアルモードであればpushpull1に、そうでなければputget1に進みます。pushpull1のクラスPushpullは"Exchange"のPushpullクラスと同じですが、「隣に移動」がないので最初と最後の旋回を両方とも実行します。
一方、putget1のクラスPutGetは下図の通り複雑ですが、赤字で入れた注釈を見ていただければAMRが動作する通りにフローチャートが構成されていることが分ると思います。注釈にあるRBPはかご台車のことです。RBP分離のところでSplitクラスのブロックが登場しますが、このSplitクラスではAgentがオリジナルとコピーに分離します。オリジナルはAmrCart、コピーはかご台車だけのExchCartにして、On exit original:でAmrCartのかご台車を非表示にすれば、留置かご台車がない場所でかご台車を分離・放置することが可能になります。放置されたExchCartインスタンスは、再度AMRと結合する時にSinkに入って消滅します。
普通にコードでプログラミングする場合、ユニットを5つ並べるという馬鹿なことはしないでしょう。ループの中にユニットを入れてループを5回繰返すのが当り前です。AnyLogicでもそういう建付けにすることは可能ですが、PutGetではMvifToで述べた衝突防止のことも考える必要があります。ユニットを5つ並べるというプログラミングの常識に外れたことをしているのはAnyLogicだからではなく、入替え対象の留置位置で入替え動作をさせる制御と、衝突防止のための進行制御を両立させる方法として、これが一番分りやすいと判断したためです。
衝突防止のための制御が不要であればフローチャートがシンプルになるという例が、以下にロジックを示すCartPoolクラスです。waitReturnブロックの役割は「3.1 Mainロジック」で説明した通りですが、ここではRestrictedAreaStartクラスのRaStartブロックとRestrictedAreaEndクラスのRaEndブロックに注目して下さい。両ブロックに挟まれた部分には右側のCapacity (max allowed):で指定した台数しかAgentが入れません。ここではCapacityが1なので、プールに入れるAgentは1台だけで衝突することはありません。旋回の必要もないので、Agentは台車を返す位置and/or持出す位置に移動するだけです。
本章で最後に紹介するモデルは"CarryingRBPs_Elevator"です。最初に紹介した"Exchange"と同様に荷物が載ったかご台車と空のかご台車を総入替えしますが、経路の途中にエレベータがあります。実は駄待ち狐がAnyLogicで最初に作ったモデルは、かご台車をエレベータで運ぶというものでした。そのモデルでは作業員がかご台車を押して透明人間のすれ違いをしていました。エレベータへの積込みは作業員が押込むだけで、プールにかご台車を並べる際は作業員の動きとは関係なくかご台車が整列していました。その一方、入替えが終るとトラックが到着して、作業員がかご台車をトラックに積込んでトラックが出て行くというおまけを付けていました。
モデル作成の仕上げとして、このエレベータによるフロア間移送に衝突回避を盛込むことにしました。フロア内の搬送に"Go-Round"と同じようにMvifToを使うことも考えましたが、荷溜りとエレベータ前が1本の通路だと正面衝突回避のためにずっと待たなければなりません。往路と復路を分けた2本にすると、今度は車線横断右左折をする必要があります。結局フロア内搬送は"Go-Round"方式ではなく"Exchange"方式を使うことにしました。一方、エレベータへの積込み作業には"Go-Round"のCartPoolクラスを使っています。本"Elevator"では、最初のエレベータモデル、"Exchange"、"Go-Round"が融合されているという訳です。
"Elevator"の環境モデルは1階と2階の2フロア構成です。環境モデルの作図は1階と2階を異なるレベルにして重ね書きします。下図の右下隅、赤い矢印を付けた"Levels"を(AnyLogic上で)クリックすると、ポップアップでレベル構成が表示されます。ここではLevel1とLevel2の2つだけですが、"Create a new level"をクリックしてレベルを追加することも可能です。これだけで3D表示にすると2階建ての倉庫が表示されます。1階に2階の床面よりも高い立体を置いてもその通りに表示されるので、エレベータ塔を作図するのも簡単です。ロジックでエレベータのかごを1階から2階に移動させると、これもその通りに3Dアニメーション表示されます。
Mainロジックは下図のようになっており、Elevator、2階のAmrCart、1階のAmrCartがそれぞれ独立のAgentとして動作するので、背景色で区分された3つのフローチャートが並置されています。異種Agent間の連携については次項で述べるので、ここではそれぞれのフローチャートについて説明します。まずElevatorですが、srcElvで1階に生成され、mvTo2Fで2階に上昇します。2階に着くとドアを開け、AMRによる荷積みを待ち、ドアを閉めます。その後、1階に下降して荷出し・荷積みを待ち、以後これを繰返します。最後の荷出しが終ればjdgElvStopでループを抜け、elvStopでシミュレーション終了まで待機します。
次にAmrCartですが、1階と2階でほぼ同じフローチャートになっています。entLp1等のEnterクラスと、exit1等のExitクラスが多数使われていますが、これはConnectorが錯綜して訳が分からなくなるのを防ぐ便法です。実際には、ExitブロックとEnterブロックが下図の色付きの破線で示されるように繋がってます。1つのExitに対して2つのEnterが接続されているところは条件分岐になっており、Exit側のPropertiesにその条件が記載されています。因みに背景色も破線も作図で追加したもので、背景色はlevel1、破線はlevel2にあります。
PortLoopクラスは"Exchange"のSidleLoopクラスとほば同じで、loop1とloop2の組合せで2階の荷溜りとエレベータ前の間でかご台車を入替えます。StowLoopクラスは"Go-Round"のCartPoolクラスとほぼ同じで、loop3とloop4の組合せで2階におけるエレベータへの荷積みと荷出しを行います。loop5とloop6の組合せ、loop7とloop8の組合せによって、1階でも2階と同じことをします。エレベータへの荷積みと荷出しは1台のAMRで行っており、あまり効率的とは言えませんが、狭いエレベータ内で2台のAMRを同時に働かせるのは不可能だと判断しました。
Elevator、2階AmrCart、1階AmrCartはそれぞれ独立のAgentとしてMainロジックが構成されていますが、Elevatorと2階AmrCart、Elevatorと1階AmrCartは連携して動作させる必要があります。複数のAgentを連携して動作させること自体は例えばMvifToクラスで行っており、先行するAgentがnextMvifのwaitから出て行く時に後続AgentのDelayを解除します。ElevatorとAmrCartは異種のAgentなので何か特別な手段が必要かというとそんなことはなく、MvifToと同じようにして異種Agentを操作することができます。
以下に示す2枚のMainロジック画面で、上はElevatorのOpenDoor2Fブロックをクリックした状態、下は2階AmrCartのmvToInit2ブロックをクリックした状態です。OpenDoor2FではOn exit:の最後の行でwaitElv2F.stopDelayForAll();を実行しており、2階AmrCartのwaitElv2Fブロックを操作しています。一方、mvToInit2ではOn exit:の最後の行でwaitStow2F.stopDelayForAll();を実行しており、ElevatorのwaitStow2Fブロックを操作しています。異種Agentのフローチャート中のブロックであってもこのように操作できるので、異種Agent間でタイミングを合せた動作が可能になります。
ElevatorとAmrCartの連携でもう一つ必要なことは、フロー間でかご台車を引渡すことです。1階AmrCartフローのStowLoopクラスでエレベータに積込まれたかご台車は、Elevatorフローでエレベータとともに上昇し、2階AmrCartフローのStowLoopクラスでエレベータから出されます。この動作のためのトリックとして、3D ObjectのElevatorに予めかご台車が仕込んであります。例えばOpenDoor2FブロックのOn enter:の場合、Elevatorのかご台車を非表示にしてloop4(2階AmrCartのStowLoop)のかご台車を表示にすることで、かご台車の扱いはElevatorフローから2階AmrCartフローに渡されます。
本章のモデリングシミュレーションで何を特許出願したかというと、モデリングシミュレーション(AnyLogic)とデジタルツイン(ROS)は相補的なものだということです。デジタルツイン構築の前の概念設計にモデリングシミュレーションが役立つのはもちろん、デジタルツインで得られたパラメータをモデリングシミュレーションにフィードバックすることで概念レベルでの運用改善を効率的に行うことができます。詳細は特開2024-054509に記載の通りですが、この出願も「VI. グループメンバーの弁当注文取り纏め」で出願した特開2019-113973と同様に審査請求はしていません。
ただ、特開2019-113973は審査請求期限(出願日から3年以内)を過ぎているので絶対に特許として認められることはありませんが、特開2024-054509は今後審査請求をして特許になる可能性が残っています。駄待ち狐が特開2024-054509を出願したのは、AnyLogicの導入を決して認めようとしなかった職場の開発技術者に「それでもモデリングシミュレーションは必要だ!!」ということを訴えたかったからです。それともう一つ、単に「儲けられる」特許ではなく、技術的にも格調の高い発明を人生最後の特許出願にしたかったのです。
ブロックフローチャートであってもAnyLogicのモデリングがれっきとしたプログラミングであるとご納得いただけましたでしょうか? AnyLogicはローコードではなく、Javaのクラスをブロックに仮託したハイパーコードなのだと思います。インデントも色付けもないFORTRANコードが今のプログラマーにはとても読めた代物ではないように、あるいはplain textよりもHTMLの方が表現力が豊かなように、AnyLogicはJavaの可読性を高めるツールだと思います。モデリングシミュレーションという限られた分野ではありますが、逆に応用分野を限定したがゆえに3Dアニメーションの早送り/スロー再生を自由自在に操れるということだと思います。
AnyLogicの駄待ち狐あるあるについては「1. プログラム作成の背景」で述べてしまったので、ここではもう少し広い視野に立った雌伏50年について語ります。駄待ち狐自作の人生訓は「人生で成功するには才能か努力のどちらかがあれば十分である。しかし、両方揃っても運がなければ成功は覚束ない」です。ここで言う「人生の成功」は世の中の名声を得る、もっと平たく言えば会社で出世するという意味です。この人生訓は30年くらい前に会得したものですが、その後に「運の偶然を必然に変える力がコネである」を付加えました。「コネ」は今で言う親ガチャを筆頭とする人脈、あるいはひたすらにヒラメを演じて手に入れる依怙贔屓です。
駄待ち狐は今や「人生での成功」とは無縁の世界に生きています。とはいえ、こうやって誰が読むとも知れない、と言うよりは誰も読みはしない駄文を書連ねているのは、せめても自分の人生を世間に向けて発信したいということなのです。「駄待ち狐のサイト」はプログラミングに仮託した自分史です。「凡人の自分史ほどつまらないものはない」という考えとの折合いを付けるため、そしてプログラミングのキーワードで検索してくれる人がいるかもしれないというささやかな希望に基いて、自分が作成した色々なプログラムを紹介しているのです。