2025,江端さんの技術メモ

取り敢えず、192.168.0.8  の c:\Users\tomoi\rag_projectの中身でやったこと一覧

Ollamaは、どうやって知識を蓄えることができますか?

Ollama自体(正確にはOllama上で動くLLM)は「知識を蓄える(=記憶を永続化する)」機能は原則持っていません。理由と補足は以下の通りです:


🔍 なぜOllama(+LLM)は知識を蓄えないのか?

  • Ollamaが実行するのは事前学習済みの言語モデル(例:LLaMA 3やMistral)であり、内部に知識を書き込む(=学習させ直す)機能はない

  • 一時的な文脈(会話履歴など)は保持できるが、Ollama再起動や会話のリセットで消える。


✅ 知識を“蓄える”ように使いたい場合の方法

方法1: ベクトルDB+RAGで「覚えさせる」

RAG (Retrieval-Augmented Generation) という手法を使うと、ローカルLLMでも“知識を覚えたように使う”ことができます。

構成例:

要素 内容
LLM Ollama + llama3/mistral
知識DB ベクトルDB(例えば Chroma, Weaviate, FAISS など)
統合フレームワーク LangChain, LlamaIndex など
処理内容 PDF, テキスト, Web等 → 分割 → ベクトル化 → 質問に応じて該当知識を検索 → LLMに渡す

📌 つまり、「知識」を蓄えるのはLLMではなく、外部のベクトルデータベースです。


方法2: チューニング(学習)で知識そのものを埋め込む

これは本当の意味での「知識の蓄積」になりますが、難易度が高くなります。

方法

方法名 内容 難易度
LoRA(Low-Rank Adaptation) 特定の知識をモデルに微調整として埋め込む 中〜上
Full fine-tuning モデル全体を再学習 非現実的(高コスト・GPU必須)

🧠「覚えているように見せる」簡易テク

  • LangChainやLlamaIndexで、テキストやPDFファイルを渡すだけで“知識として答える”ように見せられる。

  • これにより、対話履歴を記憶しているように錯覚させることも可能です。


現在、Ollamaが動いていますが、知識を“蓄える”ようにしたいですが、その前にRAGとチューニングの違いについて教えて下さい

はい、非常に重要な質問です。
以下に「RAG」と「チューニング(LoRAなど)」の違いと使い分けを、わかりやすく解説します。


🧠 RAG(Retrieval-Augmented Generation)とは?

▶ 特徴

  • LLMの外部に知識を持たせる。

  • 質問に応じて必要な情報を検索してLLMに与える。

  • モデル自体は一切変更しない。

▶ イメージ

LLMが「図書館で調べながら答えてくれる」ような使い方

▶ メリット

  • 高速かつ安全(モデル改変なし)

  • データの更新が簡単(PDF差し替えなど)

  • ローカルでも構築しやすい

▶ デメリット

  • 検索精度に依存する(ベクトル検索や分割方法が重要)

  • 外部検索結果に基づくため、「推論能力」に制限あり


🔧 チューニング(Fine-tuning / LoRA)とは?

▶ 特徴

  • LLMのパラメータ自体を再学習する。

  • モデル内部に知識を埋め込む。

  • LoRA(Low-Rank Adaptation)を使うと軽量に実現可能。

▶ イメージ

LLMが「訓練して記憶を更新した頭脳を持っている」ような使い方

▶ メリット

  • 対話スタイルやドメイン知識を完全に内面化できる

  • オフラインでも即答できる(検索なし)

▶ デメリット

  • 訓練が難しい(GPU・スクリプト・パラメータ設計)

  • データの入れ替えが容易でない(再チューニングが必要)

  • 学習エラーで性能が落ちることもある


🔄 RAG vs チューニング 比較表

比較項目 RAG チューニング(LoRA含む)
知識の格納先 外部DB(検索) LLM本体(学習)
導入の簡単さ ◎ 簡単 △ 難易度高
データ更新 ◎ 簡単(ファイル差替え) ✕ 毎回再学習が必要
実行速度 ◯(検索+推論) ◎(即応)
精度の安定性 ◯ 安定 △ 調整による
用途 FAQ・文書検索 特定業務への最適化

🎯 どちらを使うべきか?

目的 選択すべき手法
社内文書を検索しながら答えさせたい ✅ RAG
マニュアルからFAQを生成させたい ✅ RAG
専門用語に強いAI秘書を作りたい ✅ チューニング(LoRA)
自分の書き癖や口調を覚えさせたい ✅ チューニング(LoRA)

まずは、「Ollama + RAG(ローカルでの知識追加)」の構築方法を教えて下さい。なおOllamaはWindows11で動いています

Windows 11 上で動いている Ollama に ローカル知識を与える(RAG構成) ためには、以下のステップを踏むのが最も簡単かつ安定です。

✅ 目標

「PDFやテキストをローカルに保存 → 質問 → Ollamaがその内容に基づいて答える」構成を作る。


🛠 必要なツール構成

ツール 目的
Ollama LLM本体(例:llama3)
LangChain or LlamaIndex チャットの実行+外部知識連携
ベクトルDB 知識の保存・検索(Chromaを使用)
Python スクリプト処理

🪜 手順一覧

① Python + ライブラリをインストール

  1. Python 3.10以上をインストール(公式サイト

  2. 以下をインストール:

pip install langchain llama-index chromadb ollama

③ 知識を追加する方法

  • docs/example.txt に内容を書いて保存(例:社内マニュアル、FAQなど)

  • PDFなら PyMuPDF を使って読み込めます:

pip install pymupdf
python
from langchain.document_loaders import PyMuPDFLoader
loader = PyMuPDFLoader("docs/manual.pdf")

📁 まとめ:フォルダ構成例

rag_project/
├── docs/
│ └── example.txt ←知識ソース ├── rag_ollama.py ←実行スクリプト  

✅ 必要なインストール

以下をすべてインストール済みであることを確認してください:

bash
pip install -U langchain langchain-community langchain-ollama chromadb
ollama run nomic-embed-text # 埋め込み用モデル
ollama run llama3 # LLM本体

🆕 rag_ollama.py(最新版)

from langchain_community.vectorstores import Chroma
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain_ollama import OllamaEmbeddings, OllamaLLM

# 1. 知識となるテキストを読み込み
loader = TextLoader("docs/example.txt", encoding="utf-8")
documents = loader.load()

# 2. テキストをチャンクに分割
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
texts = text_splitter.split_documents(documents)

# 3. Ollama埋め込みモデルでベクトル化
embedding = OllamaEmbeddings(model="nomic-embed-text")

# 4. ChromaでベクトルDBに保存(自動永続化)
db = Chroma.from_documents(texts, embedding, persist_directory="./chroma_db")

# 5. Ollama本体モデル(例:llama3)
llm = OllamaLLM(model="llama3")

# 6. RAG構成(Retriever + LLM)
qa = RetrievalQA.from_chain_type(llm=llm, retriever=db.as_retriever())

# 7. 対話ループ
print("終了したいときは 'exit' または 'quit' と入力してください。\n")
while True:
    query = input("質問 > ")
    if query.strip().lower() in ["exit", "quit"]:
        break
    result = qa.invoke({"query": query})
    print("答え:", result["result"])

📁 フォルダ構成例

rag_project/
                  ├── rag_ollama.py
                  ├── chroma_db/ ← 自動生成される(永続保存)
                  └── docs/
                                 └── example.txt ← 知識として使いたい文書

📌 注意点

  • .persist()不要(Chromaが自動保存)

  • import元はすべて langchain_community または langchain_ollama に変更済み

  • run() ではなく .invoke() を使用

いま実施して気が付いたのですが、RAGはText(Sample.txt)に記載されたこと以外のことはこたえられないのですか? 

ollama run llama3

ではこたえられることが、

python rag_ollama.py

ではこたえられません。

はい、その気づきはまさにRAGの本質を突いています。
ご理解のとおり:


✅ 【結論】

RAGでの回答は「与えられた知識(たとえば sample.txt)に基づくもの」に限定されます。


🔍 なぜか?

🔁 モードの違い

実行コマンド 動作内容 回答の知識源
ollama run llama3 LLMが直接回答 LLaMA3があらかじめ学習した膨大な知識(Wikipediaなど)
python rag_ollama.py RAG構成で回答 あらかじめ与えられたテキストファイルのみ

つまり、RAGは「知らないことは答えない」ように設計されています。

で、いま、このハイブリッド方法についてコーディングしているんですが、上手く動いていません。

2025,江端さんの忘備録

今、新幹線の新横浜駅にいます。
I'm at Shin-Yokohama Station waiting for the Shinkansen.

出発1時間半前です。
It's an hour and a half before departure.

正直、安堵で腑抜けているところです。
Honestly, I'm feeling utterly relieved and drained.

-----

私の勤務している会社では、会社が発行したクレジットカードを用いた新幹線チケットの購入が認められなくなりました。
At my company, we're no longer allowed to buy Shinkansen tickets using company-issued credit cards.

代わりに、「JR東海エクスプレス予約」というサービスを、会社のシステム経由で使わなければならなくなり、さらに『早割○○○』でのチケット購入が必須となりました。
Instead, we are required to use the JR Central Express Reservation system via our company system and purchase tickets under special advance-discount plans.

出張というのは、現場で何が起きるかわかないものです。
Business trips are unpredictable by nature.

人身事故がなくても、遅延や遅着は当たり前。
Delays and late arrivals are common even without personal injury accidents.

鉄道を止める規模の地震は、日本各地で週単位で発生しています。
Earthquakes large enough to halt train services occur somewhere in Japan almost every week.

加えて、豪雪・豪雨は、もはや日常レベルです。
Additionally, heavy snow and rain are frequent occurrences.

つまり、チケット予約は「怖い」のです。
In short, booking tickets is a nerve-wracking affair.

いや、チケット予約そのものはどうということはありません。
The act of booking itself isn’t the problem.

怖いのは、予測不能の事態が発生したとき、ネット経由のチケットキャンセルが、ほぼ100%の確率で失敗することを、私は経験として知っているからです。
What scares me is the near-certainty that online ticket cancellations will fail during unforeseen events—something I know from painful experience.

キャンセルができなかった場合の「損金処理」の始末が、地獄のように面倒くさい。
Handling the accounting for unused tickets when cancellation fails is a nightmare.

これだけで、2日分の通常業務を吹き飛ばすくらいの時間がかかります。
It can eat up the equivalent of two full days’ worth of regular work.

なぜ、このようなことになるのか。
Why does this happen?

―― システム(のUI:ユーザーインターフェース)が、絶望的にアホだからです。
Because the system UI—the user interface—is hopelessly stupid.

-----

ANAのチケット予約システムも含めて、大抵の予約システムのUIはアホで、
Including ANA's flight reservation system, most booking UIs are simply idiotic.

特にキャンセルや解約に関しては、
Especially when it comes to cancellations and contract terminations,

―― 意図的に手続を妨害しているとしか思えないUI設計
—The UI seems deliberately designed to obstruct the process.

になっています。
That's how it feels.

キャンセルや解約の手続きは、トップページの先頭に、ワンボタンで配置すべきです。
Cancellation and termination options should be a one-click button at the top of the homepage.

(実際、私は町内会のホームページをリメイクしたとき、トップページに脱会用PDFのリンクを貼るようにデザインしました)
(When I redesigned our neighborhood association website, I made sure to put a link to the withdrawal form PDF right on the homepage.)

-----

最近思うのですが、ホームページのデザインにも、製作者の名前(×法人名、○個人名)を表示するべきではないでしょうか。
Lately, I think that website designs should include the actual designer’s name, not the company, but the individual.

アホなUIを設計したWebデザイナーは、業界から嘲笑され、顧客から憎悪される
A web designer who builds a foolish UI should be ridiculed by the industry and hated by users—

――これくらいのフィードバックがない限り、我が国のUIデザインは、これからも"アホ"を続けることになります。
—Without that level of feedback, Japanese UI design will remain forever idiotic.

というか、新幹線の予約システムも、異なるシステムを5つくらい作ればいいんです(5つの会社に発注する)。
Honestly, they should build five separate Shinkansen reservation systems, each developed by a different company.

そうして、アホなUIのWebは競争的に淘汰されるようになればいい。
That way, the dumb ones would be weeded out through competition.

(……と書きながら、排他制御やセキュアなデータ管理を考えると、それが現実的に難しいことも理解してはいるのですが)
(Though I say that, I also understand the practical challenges like exclusive access controls and secure data management.)

-----

今、私が新横浜駅の新幹線ホームで安堵して腑抜けている理由は、
Now, the reason I'm standing limp with relief on the Shinkansen platform at Shin-Yokohama Station

―― JR東海エクスプレス予約と私のPASMOとの紐付けが成功していることを、改札で確認できたからです。
—I confirmed at the gate that my JR Express Reservation was successfully linked to my PASMO card.

この紐付け作業のために、私は弊社のシステム部門の方を1時間以上拘束し、マンツーマンのテレビ会議で説明を受けながら操作を行いました。
To complete this linking process, I spent over an hour with a system department colleague in a one-on-one video call, receiving instructions step-by-step.

ちなみに、私は現役のシステムエンジニアですよ?
Just so you know—I’m a current, active system engineer.

そんな私が、機密情報を扱うシステム部門の人とタッグを組まなければ「紐付け」が完了しないシステムって、一体何なの?
If I have to team up with someone who manages confidential data to complete this process, what kind of system would that be?

そして今日は、その「紐付け」後、最初の運用日です。
And today is the first day I’m using the system post-linkage.

―― この紐付け、絶対に失敗している。そして私は大声で叫びながら、窓口やコールセンターに助けを求めるが、誰一人としてロクな回答ができず、キャンセルもできないまま、新幹線の発車時刻が過ぎていく……
—I had foreseen the worst: the link fails, I scream at the counter and call centers, no one can help, I can't cancel, and the train departs without me.

そういう最悪の未来を予知したがゆえに、私は「1時間半前の新横浜駅到着」を選んだのです。
That foresight is precisely why I arrived at Shin-Yokohama Station 90 minutes early.

だから、自動改札機から座席案内のタグが無事に出てきたとき、私は泣きそうになるくらい嬉しかったのです。
So when the seat tag came out of the automatic gate, I was on the verge of tears with joy.

-----

システムエンジニアとして、いま私の社会システムへの信頼度は、最大値を振り切ってマイナスに突入しています。
As a system engineer, my trust in our societal systems is currently off the scale, in the negative direction.

現在の私の唯一の希望は、生成AIによる「窓口システム」です。
My only hope now lies in AI-generated front-desk systems.

電話のたらい回しが、生成AIによって完全にワンストップで処理されるなら、
If generative AI can eliminate phone-based runarounds and offer accurate one-stop service,

―― もう、人間なんぞ消えてなくなってもいい
—Then let humanity vanish already.

と、私は言い切ります。
I say that without hesitation.

2025,江端さんの技術メモ

word で、本来 "1. 序論 1.1 背景 現在、日本の多くの行政区において...... "と表示されるところが、"1."、"1.1 "が表示されなくなりました。 ナビゲーションだけでなく、本文にも表示されていません

見出し1, 見出し2、が全部表示されなくなったが、これで直った。

心臓に悪いから、こういうのは止めて欲しい。

2025,江端さんの技術メモ

以前、

クローンのSDカードが動かん (The replica SD card doesn't work.) → 動きました(It works) writable: UNEXPECTED INCONSISTENCY; RUN fsck MANUALLY,

に記載しましたが、SDカードのサイズがほんの少しだけ小さいだけ、という理由で、ラズパイのSDカードのクローンを作るのに失敗したお話をしました。

で、あの時は、SDカード→SDカードの場合に成功したのですが、今回は、ハードディスクに格納したimgファイル→SDカードのケースで対応できませんでした。

📝補足:なぜ少し足りないだけでもダメ?

.img ファイルは「SDカード全体の構造」を再現する形式なので、たとえ未使用部分があってもその全体サイズ分を丸ごと書き込む必要があります。そのため、数百MBでも足りないと完全に書き込めません

そこで、今回は、この対応を試みて、1日がかりで成功しました。128GBのimgファイルのコピーって、もの凄く時間がかかる(2時間以上)ので、そっちでも苦労しました。

が、この方法で、なんとか成功できたようなので、自分用にメモを残しておきます(抜粋のみ)。

 

🔧 対処法 2:img ファイルを縮小する(上級者向け)

方法:Linuxまたは仮想マシン上で pishrink.sh を使う

sudo ./pishrink.sh shajyou_6001_20250221.img shajyou_6001_20250221_shrink.img

これにより、未使用領域を削除して圧縮済み .img を作成できます。これで書き込み先のSDカードがそれよりわずかに小さい場合にも書き込めるようになります。

※ Windows単体では難しいですが、WSLやUbuntu仮想マシンがあるなら対応可能です。

私は、WindowsのHDDにimgファイルもってきて、WSLから実施しました。

ただ、pishrink.sh はダウンロードする必要があります。https://github.com/Drewsif/PiShrink/blob/master/pishrink.sh

また、その途中でparted  のインストールも必要になりました。

sudo apt update
sudo apt install parted

こうして、

sudo ./pishrink.sh shajyou_6001_20250221.img shajyou_6001_20250221_shrink.img

を実施しました

で、その結果以下のようなものが作成されました。

約1/3に圧縮されていますので、128GBのSDカードには軽く入る計算になります。

さて、次に、SDカードへの書き込み(クローンの作成)に入ります

✅ おすすめ:Raspberry Pi Imager(公式ツール)

【手順】

  1. Raspberry Pi Imager をダウンロード・インストール
     → 公式サイト: https://www.raspberrypi.com/software/

  2. SDカードをPCに挿入

  3. Raspberry Pi Imagerを起動し、以下を選択:

    • 「OSを選ぶ」→ 一番下の「カスタムイメージを使う」→ shajyou_6001_20250221_shrink.img を選択

    • 「ストレージを選ぶ」→ SDカードを選択

  4. 「書き込む」をクリック → 書き込み完了まで待つ

書き込み→検証で1時間くらいはかかるので、諦めて待ってください。

-----

最後に、このクローンのSDカードで、ラズパイが立ち上がるのかを確認するのですが、かなりドキドキします。

私の場合、初回のブート時に、

てな画面がでてきて、自動的に再起動がかかりましたが、2度目の起動でログイン画面になり、ホッとしました。

最初は、USB関連を抜いて追いた方が良いかもしれません(キーボードとマウスはしょうがないとしても)。

以上

P.S. さて、これからあちこち(PCやらNASやら)にコピーしたimgファイルの削除です。imgファイルだけで128GBの容量を取られるので、本当にシャレになりません。

2025,江端さんの技術メモ

GStreamerをC言語でフレームレートの設定ができなかったので、色々試していましたが、

caps = gst_caps_from_string("video/x-raw, width=640, height=360,framerate=5/1")は動きません(少なくとも私のコードでは駄目だった)。

これね、GStreamerのコマンドと同じ内容になるように並べなければあらないようです。

gst-launch-1.0 rtspsrc location=rtsp://cam:Cam12345@192.168.0.10/Src/MediaInput/stream_1 latency=0 ! rtph264depay ! avdec_h264 ! videoconvert ! videoscale ! video/x-raw,width=640,height=360 ! videorate ! video/x-raw,framerate=1/1 ! x264enc bitrate=1700 speed-preset=ultrafast tune=zerolatency key-int-max=1 ! mpegtsmux ! srtserversink uri = "srt://192.168.101.10:38091" latency=500

これを忠実にコーディングする必要があるようです。

で、今のコードに入っていなかったのが"video/x-raw,framerate=1/1" です。

なので、capsfilter2 やら caps2 のコードを追加する必要がありました。
という訳で、以下のような追加をすることで動くようになりました。

ーーーーー

GstElement *videoscale, *capsfilter, *videorate, *x264enc, *mpegtsmux, *srtserversink;
GstElement *capsfilter2;

GstCaps *caps;
GstCaps *caps2;

ーーーーー

capsfilter = gst_element_factory_make("capsfilter", "capsfilter");
videorate = gst_element_factory_make("videorate", "videorate");
capsfilter2 = gst_element_factory_make("capsfilter", "capsfilter2");

ーーーーー

if (!pipeline || !rtspsrc || !rtph264depay || !avdec_h264 || !videoconvert || !videoscale ||
!capsfilter || !videorate || !capsfilter2 || !x264enc || !mpegtsmux || !srtserversink) {
g_printerr("Not all elements could be created.\n");
abc_log_message("Not all elements could be created.");
return -1;
}

ーーーーー

caps = gst_caps_from_string("video/x-raw,width=640,height=360");
g_object_set(capsfilter, "caps", caps, NULL);
gst_caps_unref(caps);
caps2 = gst_caps_from_string("video/x-raw,framerate=10/1");
g_object_set(capsfilter2, "caps", caps2, NULL);
gst_caps_unref(caps2);

ーーーーー

gst_bin_add_many(GST_BIN(pipeline), rtspsrc, rtph264depay, avdec_h264, videoconvert, videoscale,
capsfilter, videorate, capsfilter2, x264enc, mpegtsmux, srtserversink, NULL);

if (!gst_element_link_many(rtph264depay, avdec_h264, videoconvert, videoscale, capsfilter, videorate,
capsfilter2, x264enc, mpegtsmux, srtserversink, NULL)) {
g_printerr("Elements could not be linked.\n");
abc_log_message("Elements could not be linked.");
gst_object_unref(pipeline);
return -1;
}

ーーーーー

もっと簡単にできると思ったんだけどなぁ・・・結構、手間かかったなぁ。

2025,江端さんの技術メモ

Go言語を使ったマルチエージェントシミュレーションが、他の言語よりもメリットがある点とデメリットがある点について論じて下さい

一つ一つが、大変納得した内容だったので、私のメモ用に記録しておきます。

 

Go言語を用いたマルチエージェントシミュレーション(MAS)の利点と欠点について、他の言語(C++, Python, Juliaなど)と比較しながら論じる。

1. Go言語を使うメリット

(1) 並行処理の容易さ

Goはgoroutineを用いた並行処理が特徴であり、エージェントごとにgoroutineを割り当てることで効率的な並列処理が可能である。他の言語ではスレッド管理(C++のstd::threadやPythonのthreading)が必要になるが、Goではシンプルな記述で実装できる。

例:

func agent(id int) {
    for {
        fmt.Printf("Agent %d is moving...\n", id)
        time.Sleep(time.Millisecond * 500)
    }
}

func main() {
    for i := 0; i < 10; i++ {
        go agent(i) // 各エージェントをgoroutineで実行
    }
    select {} // メインスレッドを終了させない
}

このようにGoの並行処理はシンプルかつ軽量であり、大量のエージェントを扱うシミュレーションに適している。


(2) シンプルなコードとメンテナンス性

Goはシンプルな文法強力な標準ライブラリを持つため、コードの可読性が高く、開発者間での共有が容易である。
C++ではテンプレートやマルチスレッド処理が複雑になりやすく、PythonではGIL(Global Interpreter Lock)の影響で並列処理が制限される。一方、GoはGILの問題がなく、コードの記述量も比較的少ないため、長期的な開発に向いている。


(3) 高速なコンパイルと実行

Goはコンパイルが非常に速く、バイナリを直接実行できるため、デバッグやテストのサイクルを短縮できる。C++のように長時間のコンパイルが不要で、Pythonのようなインタープリタ型の遅延もない。


(4) クロスプラットフォームの容易さ

Goは静的バイナリを生成できるため、Windows, Linux, macOS などの異なる環境での実行が容易である。C++ではコンパイル時にライブラリ依存の問題が生じやすく、Pythonでは環境設定(仮想環境やパッケージ管理)が面倒になりがちだが、Goでは1つのバイナリで解決できる。


(5) クラウドやWebとの親和性

GoはもともとGoogleが開発した言語であり、クラウド環境(GCP, AWS)やWebとの親和性が高い。
シミュレーション結果をWeb API経由でリアルタイム表示したり、分散処理の一部をクラウド上で実行する際に、Goの標準ライブラリだけでHTTPサーバを簡単に実装できるのは大きなメリット。


2. Go言語を使うデメリット

(1) 高速計算ライブラリが少ない

PythonやC++には、最適化された数値計算ライブラリ(NumPy, SciPy, Eigen, OpenMPなど)が豊富にあるのに対し、Goは数値計算や線形代数のライブラリが少ない
そのため、大量の行列演算や物理シミュレーションを行う場合、Go単体では計算効率が劣る可能性がある。

対策:

  • Goで数値計算を行う場合は、gonum ライブラリを使用する。
  • 必要に応じて、GoからC/C++(BLAS, LAPACK)を呼び出す。

(2) GPUサポートが弱い

C++はCUDAやOpenCLを利用してGPUによる並列計算が可能であり、PythonもTensorFlowやCuPyを通じてGPUを活用できる。しかし、Goは公式にはGPUを直接サポートしていないため、CUDAを使う場合はCGO経由でC++のライブラリを呼び出す必要がある

対策:

  • gorgonia(Go向けのニューラルネットワークライブラリ)を利用すると、一部のGPU計算が可能。
  • CUDA/OpenCLを直接利用したい場合は、GoからC++のラッパーを呼び出す。

(3) 研究事例が少ない

PythonやC++は、数多くの学術論文や研究事例があり、豊富なサンプルコードが存在する。一方で、Goは学術分野ではあまり使われておらず、論文や研究事例が少ない
特に、MAS関連のライブラリやフレームワークが未成熟なため、開発者が独自に実装する必要がある。

対策:

  • MASの一般的なアルゴリズム(Boidモデル、Cellular Automatonなど)をGoで実装する。
  • 他言語のライブラリと組み合わせて使用する。

(4) ガベージコレクションの影響

Goのガベージコレクション(GC)は自動的にメモリを管理するが、大規模なシミュレーションではGCの影響で予期しないタイミングで一時的な遅延が発生する可能性がある。
C++のように明示的なメモリ管理ができないため、リアルタイム性を求めるシミュレーションでは不利になることがある。

対策:

  • メモリ割り当てを最小限に抑える設計を行う(オブジェクトの再利用)。
  • sync.Pool を活用してメモリ管理のオーバーヘッドを減らす。

3. 結論

Go言語を使ったMASは、並行処理の容易さ・コードのシンプルさ・クラウドとの親和性に優れており、大規模エージェントシミュレーションの実装がしやすい
しかし、GPUサポートの弱さ・計算ライブラリの不足・研究事例の少なさといった課題もあり、特に高度な数値計算が必要なシミュレーションではPythonやC++の方が適している

Go言語が適しているケース:

  • 大規模なエージェント数をgoroutineで管理
  • ネットワーク・クラウド連携が必要
  • シンプルな設計を重視

PythonやC++の方が適しているケース:

  • 高度な数値計算(線形代数、最適化)
  • GPUを活用したシミュレーション
  • 既存のライブラリや研究事例を活用

最適なアプローチ:
Go単体でMASを実装するよりも、計算部分をPython/C++で処理し、Goは並列制御やネットワーク管理に使うというハイブリッドアプローチが有効となる。


昨日、

PrumeMobileを使った人流の見える化

について記載したんだけど、これ簡単に言うと『goroutineを止めて、サブルーチンにした』というものです。

300万のエージェントが、メインルーチンの情報変更に上手く連動しなくなると、"goroutineの自己破壊命令が届かない"という問題がボロボロでてきたからです。

まあ、それでも、『数十万人分、361万行の移動データを、全部表示して正常終了した』というGo言語の凄さには、相変らず感心しているんですけどね(昨夜、PC回しながら寝たのですが、今朝、全部のオブジェクトが消えて、正常終了していました)。

2025,江端さんの技術メモ

起動方法
$go run server22-1.go

http://localhost:8080

$go run pm_proxy_single_socket.go

 

// server22-1.go

/*
このプログラムは、WebSocket を利用したリアルタイム位置情報サーバーである。
- クライアントから送信された位置情報を受信し、処理後にレスポンスを返す。
- 位置情報はチャネルを介して他のクライアントと共有される。
- `sync.Mutex` を使用してスレッド安全な処理を実現。
- WebSocket の接続エラー時には適切にログを出力し、安定した通信を確保。
- HTML を提供し、ブラウザ上で WebSocket 通信を確認可能。
*/
package main

import (
	"flag"
	"html/template"
	"log"
	"net/http"
	"sync"

	"github.com/gorilla/websocket"
)

// GetLoc GetLoc
type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
	//Address string  `json:"address"`
}

// var addr = flag.String("addr", "localhost:8080", "http service address")
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト

var upgrader = websocket.Upgrader{} // use default options

var chan2_1 = make(chan GetLoc)

var maxid = 0

var mutex sync.Mutex

func echo2(w http.ResponseWriter, r *http.Request) {
	c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()

	//mutex := new(sync.Mutex)

	for {
		//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
		//_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)

		//mutex.Lock()  // ここに置くとデッドロックしてしまう

		gl := new(GetLoc)

		err := c.ReadJSON(&gl) // クライアントからのメッセージの受信

		mutex.Lock()

		// 原因不明の対処処理
		if gl.ID == 0 && gl.Lat < 0.01 && gl.Lng < 0.01 {
			mutex.Unlock()
			break
		} else if gl.ID < -1 { // 受理できないメッセージとして返信する
			//条件分岐 (変なIDが付与されているメッセージは潰す)
			//if (gl.ID > maxid) || (gl.ID < -1) { // 受理できないメッセージとして返信する

			gl.ID = -1
			gl.Lat = -999
			gl.Lng = -999
			err2 := c.WriteJSON(gl)
			if err2 != nil {
				log.Println("write1:", err2)
				mutex.Unlock()
				break
			}
		} else { // それ以外は転送する
			/*
				log.Printf("echo2 after c.WriteJSON(gl) ID:%d", gl.ID)
				log.Printf("echo2 after c.WriteJSON(gl) Lat:%f", gl.Lat)
				log.Printf("echo2 after c.WriteJSON(gl) Lng:%f", gl.Lng)
			*/
			if err != nil {
				log.Println("read:", err)
				mutex.Unlock()
				break
			}
			//fmt.Printf("echo2 before chan2_1 <- *gl\n")
			chan2_1 <- *gl
			//fmt.Printf("echo2 after chan2_1 <- *gl\n")

			//で、ここで受けとる
			//gl2 := new(GetLoc)
			//fmt.Printf("echo2 before gl2 := <-chan2_1\n")
			gl2 := <-chan2_1
			maxid = gl2.ID // ID最大値の更新
			/*
				log.Printf("echo2 after gl2 := <-chan2_1 ID:%d", gl2.ID)
				log.Printf("echo2 after gl2 := <-chan2_1 Lat:%f", gl2.Lat)
				log.Printf("echo2 after gl2 := <-chan2_1 Lng:%f", gl2.Lng)
			*/
			//fmt.Printf("echo2 before err2 := c.WriteJSON(gl2)\n")
			err2 := c.WriteJSON(gl2)
			//fmt.Printf("echo2 after err2 := c.WriteJSON(gl2)\n")
			if err2 != nil {
				log.Println("write2:", err2)
				mutex.Unlock()
				break
			}
			//fmt.Printf("end of echo2\n")

		}

		mutex.Unlock()
	}
}

func echo(w http.ResponseWriter, r *http.Request) {

	c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()

	/*	ここでロックして待つ */

	for {

		//fmt.Printf("echo before gl := <-chan2_1\n")
		gl := <-chan2_1
		//fmt.Printf("echo after gl := <-chan2_1\n")

		//fmt.Printf("echo before err = c.WriteJSON(gl) gl2.id = %d\n", gl.ID)
		//fmt.Printf("echo before err = c.WriteJSON(gl) gl2.lat = %f\n", gl.Lat)
		//fmt.Printf("echo before err = c.WriteJSON(gl) gl2.lng= %f\n", gl.Lng)
		err = c.WriteJSON(gl)
		if err != nil {
			log.Println("WriteJSON1:", err)
		}
		//fmt.Printf("echo after err = c.WriteJSON(gl)\n")

		//fmt.Printf("echo before err = c.RreadJSON(gl)\n")
		gl2 := new(GetLoc)
		err2 := c.ReadJSON(&gl2)
		//fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.id = %d\n", gl2.ID)
		//fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.lat = %f\n", gl2.Lat)
		//fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.lng= %f\n", gl2.Lng)
		if err2 != nil {
			log.Println("ReadJSON:", err2)
		}
		// ここからチャネルで返す
		//fmt.Printf("echo before chan2_1 <- *gl2 gl2.id = %d\n", gl2.ID)
		//fmt.Printf("echo before chan2_1 <- *gl2 gl2.lat = %f\n", gl2.Lat)
		//fmt.Printf("echo before chan2_1 <- *gl2 gl2.lng = %f\n", gl2.Lng)
		chan2_1 <- *gl2
		//fmt.Printf("echo after chan2_1 <- *gl2\n")
		//fmt.Printf("end of echo\n")
	}

}

func home(w http.ResponseWriter, r *http.Request) {
	homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}

func smartphone(w http.ResponseWriter, r *http.Request) {
	smartphoneTemplate.Execute(w, "ws://"+r.Host+"/echo2")
}

func main() {
	flag.Parse()
	log.SetFlags(0)

	http.HandleFunc("/echo2", echo2)           // echo関数を登録 (サーバとして必要)
	http.HandleFunc("/echo", echo)             // echo関数を登録 (サーバとして必要)
	http.HandleFunc("/", home)                 // home関数を登録
	http.HandleFunc("/smartphone", smartphone) // smartphone関数を登録
	log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}

var smartphoneTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script> 

function obj(id, lat, lng){
	this.id = id;
	this.lat = lat;
	this.lng = lng;
}

function random(min, max){
	return  Math.random()*(max-min) + min;
}

// var personal_id;

var lat = 35.654543;
var lng = 139.795534;  

window.addEventListener("load", function(evt) {
    var output = document.getElementById("output");
    var input = document.getElementById("input");
    var ws;
    var print = function(message) {
        var d = document.createElement("div");
        d.textContent = message;
        output.appendChild(d);
	};

	var personal_id = 0;

	
	///// 起動時のボタン
	// disabled属性を削除
	document.getElementById("open").removeAttribute("disabled");
	document.getElementById("open").style.color = "black";

	// disabled属性を設定 (closeボタンを非活性化)
	document.getElementById("close").setAttribute("disabled", true);
	document.getElementById("close").style.color = "White";	

	// disabled属性を設定 (sendボタンを非活性化)
	document.getElementById("send").setAttribute("disabled", true);
	document.getElementById("send").style.color = "White";	


		
	document.getElementById("open").onclick = function(evt) {
		console.log("document.getElementById open");

		// disabled属性を設定 (openボタンを非活性化)
		document.getElementById("open").setAttribute("disabled", true);
		document.getElementById("open").style.color = "White";		

		// disabled属性を削除
		document.getElementById("send").removeAttribute("disabled");
		document.getElementById("send").style.color = "black";	

		// disabled属性を削除
		document.getElementById("close").removeAttribute("disabled");
		document.getElementById("close").style.color = "black";	

		////////// 削除2
		// ws = new WebSocket("{{.}}");
		////////// 削除2終り


		////////// 削除1
		//var send_obj = new obj(0, 35.654543,139.795534);  // 最初は"0"でエントリ

		//console.log("open:send_obj");	
		//console.log(send_obj.id);	
		//console.log(send_obj.lat);
		//console.log(send_obj.lng);		

		//var json_obj = JSON.stringify(send_obj);
		//ws.send(json_obj);
		/////////// 削除1終り


        if (ws) {
            return false;
        }
		
		
		////////// 追加2
		ws = new WebSocket("{{.}}");
		////////// 追加2終り

		
        ws.onopen = function(evt) {
			print("OPEN");
		
			//ws = new WebSocket("{{.}}");
			
			////////// 追加1			
			var send_obj = new obj(0, 35.654543,139.795534);  // 最初は"0"でエントリ

			console.log("open:send_obj");	
			console.log(send_obj.id);	
			console.log(send_obj.lat);
			console.log(send_obj.lng);		

			var json_obj = JSON.stringify(send_obj);
			ws.send(json_obj);
			/////////// 追加1終り

		}
		
        ws.onclose = function(evt) {

			print("CLOSE");
            ws = null;
        }

		ws.onmessage = function(evt) {  // 受信したメッセージはここに飛んでくる
			print("RESPONSE: " + evt.data);  // jsonメッセージの内容を表示
			// データをJSON形式に変更
			var obj = JSON.parse(evt.data);

			personal_id = obj.id; // IDの取得(何回も取る必要はないが)
			console.log("personal_id");			
			console.log(personal_id);

			
			if ((Math.abs(obj.lat) > 90.0) || (Math.abs(obj.lng) > 180.0)){ // 異常な座標が入った場合は、マーカーを消去する
				console.log("before ws.close()");
				ws.close();
				console.log("after ws.close()");
			}
		}
		
        ws.onerror = function(evt) {
            print("ERROR: " + evt.data);
        }
        return false;
    };
	
	document.getElementById("send").onclick = function(evt) {

		console.log("document.getElementById send");

		// disabled属性を設定 (openボタンを非活性化)
		document.getElementById("open").setAttribute("disabled", true);
		document.getElementById("open").style.color = "White";	
	
		// disabled属性を削除
		document.getElementById("send").removeAttribute("disabled");
		document.getElementById("send").style.color = "black";	

		// disabled属性を削除
		document.getElementById("close").removeAttribute("disabled");
		document.getElementById("close").style.color = "black";	
	
		if (!ws) {
			console.log("return false send");
			return false;			
		}

		lat += random(0.5, -0.5) * 0.00001 * 10 * 5;
		lng += random(0.5, -0.5) * 0.00002 * 10 * 5

		
		//var send_obj = new obj(personal_id, 35.654543,139.795534);  // idでエントリ
		var send_obj = new obj(personal_id, lat, lng);  // idでエントリ

		console.log("send:send_obj");	
		console.log(send_obj.id);	
		console.log(send_obj.lat);
		console.log(send_obj.lng);		

		var json_obj = JSON.stringify(send_obj);
		ws.send(json_obj);		

		/*
        print("SEND: " + input.value);
        ws.send(input.value);
		return false;
		*/

		return false;	
    };

	document.getElementById("close").onclick = function(evt) {
		console.log(" document.getElementById close");

		// disabled属性を削除
		document.getElementById("open").removeAttribute("disabled");
		document.getElementById("open").style.color = "black";

		// disabled属性を設定 (closeボタンを非活性化)
		document.getElementById("close").setAttribute("disabled", true);
		document.getElementById("close").style.color = "White";	

		// disabled属性を設定 (sendボタンを非活性化)
		document.getElementById("send").setAttribute("disabled", true);
		document.getElementById("send").style.color = "White";			


        if (!ws) {
            return false;
		}
	
		var send_obj = new obj(personal_id, 999.9, 999.9);  // 最初は"0"でエントリ

		console.log("close:send_obj");
		console.log(send_obj.id);		
		console.log(send_obj.lat);
		console.log(send_obj.lng);		

		var json_obj = JSON.stringify(send_obj);
		ws.send(json_obj);

        //ws.close();  // これはws.onmessageの方で実施
        return false;
    };
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server, 
"Send" to send a message to the server and "Close" to close the connection. 
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<!-- <p><input id="input" type="text" value="Hello world!"> -->
<button id="send">Send</button>
<button id="close">Close</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))

var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>PruneMobile</title>

	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.css"/>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.js"></script>

	<script src="http://kobore.net/PruneCluster.js"></script>           <!-- これ、いずれローカルホストから取れるように換える -->
	<link rel="stylesheet" href="http://kobore.net/examples.css"/>      <!-- これも、いずれローカルホストから取れるように換える -->

	<!-- goのテンプレートのローカルって、どこになるんだろう? -->

</head>
<body>
<div id="map"></div>

<script>

	ws = new WebSocket("{{.}}"); // websocketの確立

	/*
	var print = function(message) {
		var d = document.createElement("div");
		d.textContent = message;
		output.appendChild(d);
	};
	*/

	// 引数にはミリ秒を指定。(例:5秒の場合は5000)
	function sleep(a){
  		var dt1 = new Date().getTime();
  		var dt2 = new Date().getTime();
  		while (dt2 < dt1 + a){
			dt2 = new Date().getTime();
		}
  		return;
	}

    var map = L.map("map", {
        attributionControl: false,
        zoomControl: false
	}).setView(new L.LatLng(35.36716428833585, 139.62724520774287), 16); // 富岡		
	//}).setView(new L.LatLng(33.58973407765046, 130.41048227121925), 16); // 中州
    //}).setView(new L.LatLng(35.654543, 139.795534), 18); 豊洲
    // }).setView(new L.LatLng(35.598563, 139.475528), 18); 広袴

    L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        detectRetina: true,
        maxNativeZoom: 18
    }).addTo(map);

    var leafletView = new PruneClusterForLeaflet(1,1);  // (120,20)がデフォルト

	ws.onopen = function (event) {
	}

	var markers = [];

	//var helicopterIcon = L.icon({ iconUrl: 'http://sintef-9012.github.io/PruneCluster/examples/helicopter.png', iconSize: [48, 48] });
    //var airplaneIcon = L.icon({ iconUrl: 'http://sintef-9012.github.io/PruneCluster/examples/airplane.png', iconSize: [48, 48] });

	// 受信すると、勝手にここに飛んでくる
	ws.onmessage = function (event) {
		// データをJSON形式に変更
		var obj = JSON.parse(event.data);

		console.log("233");	
		console.log(obj.id);
		console.log(obj.lat);						
		console.log(obj.lng);	

		if (obj.id == 0){  // idが未登録の場合
			console.log("obj.id == 0")
			// データをマーカーとして登録
			var marker = new PruneCluster.Marker(obj.lat, obj.lng);

			// 参考資料  http://embed.plnkr.co/WmtpkEqSDJFuFeuiYP54/
			//var marker = new PruneCluster.Marker(obj.lat, obj.lng, {
			//	//popup: "Bell 206 " + i,
			//	icon: helicopterIcon
			//});


			console.log(marker.hashCode);		
			markers.push(marker);
	
			leafletView.RegisterMarker(marker);
	
			console.log(markers);
			console.log(markers.length)

			obj.id = marker.hashCode;
			//ws.send(marker.hashCode); // テキスト送信
			var json_obj = JSON.stringify(obj);
			ws.send(json_obj);			
		} else if ((Math.abs(obj.lat) > 90.0) || (Math.abs(obj.lng) > 180.0)){ // 異常な座標が入った場合は、マーカーを消去する
			console.log("Math.abs(obj.lat) > 180.0)")
			for (let i = 0; i < markers.length; ++i) {
				if (obj.id == markers[i].hashCode){
					console.log(i)
					console.log(obj.id)										
					console.log("obj.id == markers[i].hashCode")

					//leafletView.RemoveMarkers(markers[obj.id]);  // これでは消えてくれません
					// 1つのマーカーを消すのに、面倒でも以下の2行が必要
					var deleteList = markers.splice(i, 1);					
					leafletView.RemoveMarkers(deleteList);

					// 以下失敗例リスト
					//leafletView.RemoveMarkers(markers[i].hashCode);  //これはダメ
					//leafletView.RemoveMarkers(markers[obj.id],'item');
					//leafletView.ProcessView(); // 試しに入れてみる
					//leafletView.RemoveMarkers(markers[i-1]);
					//leafletView.RemoveMarkers(markers);					
					break;
				}
			}
			obj.lat = 91.0;
			obj.lng = 181.0;
			var json_obj = JSON.stringify(obj);
			ws.send(json_obj);				
		} else {
			// 位置情報更新
			console.log("else")
			for (let i = 0; i < markers.length; ++i) {
				if (obj.id == markers[i].hashCode){
					var ll = markers[i].position;
					ll.lat = obj.lat;
					ll.lng = obj.lng;
					break;
				}
			}
			var json_obj = JSON.stringify(obj);
			ws.send(json_obj);	
		}
	}

	// 位置情報の更新
    window.setInterval(function () {
        leafletView.ProcessView();  // 変更が行われたときに呼び出されれなければならない
	}, 1000);

	// サーバを止めると、ここに飛んでくる
	ws.onclose = function(event) {
		//print("CLOSE");
		ws = null;
	}


    map.addLayer(leafletView);
</script>



</body>
</html>
`))
// pm_proxy5_single_socket.go

/*
	このプログラムは、CSVファイルに記載された順番にエージェントの位置情報を読み取り、
	WebSocket を介してサーバーへ送信・更新する。

	サーバへ転送するプログラムは、server22-1.go

	- WebSocket 切断時に自動で再接続(最大5回リトライ)
	- エージェントが移動した場合のみサーバーへ通知
	- `sync.Mutex` を使用してスレッド安全な処理を実現
	- 位置情報が 999.0 以上の場合、エージェントを削除


	csv情報のサンプル (IDと緯度経度のみ使っている

	50044035447,139.629538,35.365357,00:00:00,WALK
	50044035447,139.629430,35.365151,00:00:30,WALK
	50044035447,139.629321,35.364945,00:01:00,WALK
	50044035447,139.629213,35.364738,00:01:30,WALK
	50044035447,139.629104,35.364532,00:02:00,WALK
	50044035447,139.628996,35.364325,00:02:30,WALK
	50044035447,139.628888,35.364119,00:03:00,WALK
	50044035447,139.628787,35.363937,00:03:30,WALK
	50044035447,139.628742,35.364159,00:04:00,WALK
	50044035447,139.628699,35.364375,00:04:30,WALK
	50044035447,139.628654,35.364592,00:05:00,WALK
	50044035447,139.628533,35.364724,00:05:30,WALK
	50044035447,139.628261,35.364691,00:06:00,WALK
	50044035447,139.627989,35.364658,00:06:30,WALK
	50044035447,139.627716,35.364625,00:07:00,WALK
	50044035447,139.627680,35.364620,00:07:30,WALK
	50044035447,999.9,999.9,00:08:00,WALK

	(50044035447, 999.9, 999.9, 00:08:00, WALK) でエージェントの移動が終了し、サーバからアイコンが消滅する

*/

package main

import (
	"encoding/csv"
	"flag"
	"fmt"
	"log"
	"math"
	"net/url"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

const (
	TopLeftLat     = 35.37574882601201
	TopLeftLon     = 139.61403393574466
	BottomRightLat = 35.36163108058289
	BottomRightLon = 139.6297897196359
)

type GetLoc struct {
	ID  int     `json:"id"`
	Lat float64 `json:"lat"`
	Lng float64 `json:"lng"`
}

type unmTbl struct {
	uniName string
	objType string
	simNum  int
	pmNum   int
	lon     float64
	lat     float64
}

var list = make([]unmTbl, 0)
var addr = flag.String("addr", "0.0.0.0:8080", "http service address")

var mutex sync.Mutex

func connectWebSocket() (*websocket.Conn, error) {
	u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}

	for i := 0; i < 5; i++ {
		c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
		if err == nil {
			log.Println("Reconnected to WebSocket successfully.")
			return c, nil
		}
		log.Println("WebSocket reconnection attempt failed:", err)
		time.Sleep(2 * time.Second)
	}
	return nil, fmt.Errorf("failed to reconnect WebSocket after multiple attempts")
}

func main() {
	file, err := os.Open("agent_track_2018_101_end_and_order.csv")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	fmt.Println("Opened testtest.csv")
	reader := csv.NewReader(file)
	var line []string

	flag.Parse()
	log.SetFlags(0)

	c, err := connectWebSocket()
	if err != nil {
		log.Fatal("WebSocket connection failed:", err)
	}
	defer c.Close()

	for {
		time.Sleep(time.Millisecond * 10)

		line, err = reader.Read()
		if err != nil {
			break
		}

		uniName := line[0]
		objType := line[4]
		lon, _ := strconv.ParseFloat(line[1], 64)
		lat, _ := strconv.ParseFloat(line[2], 64)

		flag := 0

		for i := range list {
			if list[i].uniName == uniName {
				old_lat := list[i].lat
				old_lon := list[i].lon

				list[i].lon = lon
				list[i].lat = lat

				if math.Abs(list[i].lat-old_lat) > 0.000000001 || math.Abs(list[i].lon-old_lon) > 0.000000001 {
					gl := GetLoc{ID: list[i].pmNum, Lat: list[i].lat, Lng: list[i].lon}

					mutex.Lock()
					err = c.WriteJSON(gl)
					if err != nil {
						log.Println("write2 error:", err)
						mutex.Unlock() // **ロック解除**
						c.Close()
						c, err = connectWebSocket()
						if err != nil {
							log.Println("WebSocket reconnection failed:", err)
							return
						}
						continue
					}

					gl3 := new(GetLoc)
					err = c.ReadJSON(gl3)
					if err != nil {
						log.Println("read error:", err)
						mutex.Unlock() // **ロック解除**
						c.Close()
						c, err = connectWebSocket()
						if err != nil {
							log.Println("WebSocket reconnection failed:", err)
							return
						}
						continue
					}
					mutex.Unlock()

					println("move Object!")

					if list[i].lat > 999.0 || list[i].lon > 999.0 {
						list = append(list[:i], list[i+1:]...)
						println("-----------------------------------------------------------------> delete Object!")
					}
				}
				flag = 1
				break
			}
		}

		if flag == 0 {
			uniNum := len(list)
			ut := unmTbl{uniName: uniName, objType: objType, simNum: uniNum, lat: lat, lon: lon}

			gl := new(GetLoc)
			gl.ID = 0
			gl.Lat = ut.lat
			gl.Lng = ut.lon

			mutex.Lock()
			err := c.WriteJSON(gl)
			if err != nil {
				log.Println("write1 error:", err)
				mutex.Unlock() // **ロック解除**
				c.Close()
				c, err = connectWebSocket()
				if err != nil {
					log.Println("WebSocket reconnection failed:", err)
					return
				}
				continue
			}

			gl2 := new(GetLoc)
			err = c.ReadJSON(gl2)
			if err != nil {
				log.Println("read error:", err)
				mutex.Unlock() // **ロック解除**
				c.Close()
				c, err = connectWebSocket()
				if err != nil {
					log.Println("WebSocket reconnection failed:", err)
					return
				}
				continue
			}
			mutex.Unlock()

			ut.pmNum = gl2.ID
			list = append(list, ut)
			println("------------>generate Object!")
		}
	}
}

 

2025,江端さんの技術メモ

vscodeでデバッグをする時に、同じディレクトリにmain()関数を持つGo言語プログラムが複数あると、デバッグができません。ファイル名を指定してデバッグすることはできないでしょうか。ただし、launch.json にファイル名を書き込むと、実行ファイルが固定されてしまうので、それは避けたいのですが。

launch.json を使うと、特定のGoファイルが環境として固定されるため、別のファイルをデバッグしたいときに launch.json を都度編集する必要があり、やや不便です。

ですが、以下の方法を使えば 環境を固定せずに、デバッグするファイルを柔軟に切り替えられます。

launch.json を表示・作成する方法

方法①:実行メニューから作成・表示する

  1. 左側の「実行とデバッグ」アイコン(▶️の横に虫)をクリック
    ※ または Ctrl+Shift+D を押します

1. launch.json${file} を使う

環境を固定せずに、現在開いているGoファイルをデバッグする方法です。

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Current File",
            "type": "go",
            "request": "launch",
            "mode": "debug",
            "program": "${file}"
        }
    ]
}

  • ${file} は、現在アクティブな(開いている)ファイルをデバッグ対象にする特殊変数です。
  • この設定にすると、エディタで開いているGoファイルがどれでも、F5 でデバッグが開始できます。

開いているGoのプログラムでデバッガーが動いた。これから作業がラクになるかな。

2025,江端さんの忘備録

さすがに3つの案件の同時並列処理は無理 ―― と、泣き言を言いながら、今、本当に泣きそうです。

I'm whining that “it's impossible to handle three projects simultaneously,” and I'm about to cry.

経験的に、こういう時『もっと大変な人が、沢山いる』という考え方をすることは、悪手です。

Empirically, thinking that “there are many people in worse situations” is a bad idea in a situation like this.

他人なんぞ、どーでもいい。

I don't care about other people.

「私が辛い」という状態だけで、必要かつ十分です。

The fact that I am in pain is sufficient.

-----

そういえば、今年の年賀状には、長女の結婚式の写真の下に、自分のコメントとして

Speaking of which, on this year's New Year's card, under the photo of my eldest daughter's wedding, I remember I wrote the following  comment 

『昨年、"うつ"になりました』

'I became depressed last year.'

と書いたのを思い出しました。

『これでは、長女の結婚で"うつ"になったように読める』と気がついたのは、年賀状をポストに投函した後でした。

It was only after I had posted the New Year's card in the post that I realized that it could be read as if I had become depressed because of my eldest daughter's marriage.

しかし、私を"うつ"にするのは、いつだって、仕事です。

However, it is always work that makes me depressed.

その正体の多くが、動かないプログラムであり、停止するシステムです。

Many are programs that don't work and systems that become stop.

世の中には「頼めない仕事」というものが、あるのです。

There are some jobs that I can't ask someone to do.

つまりですね、技術というのは、属人性が高く、他人への譲渡が恐しく困難なのです

2025,江端さんの技術メモ

bytes-received send-rate-mbps receive-rate-mbps bandwidth-mbps rtt-ms cb
0 0 0 12 100 1706
0 1.128166 0 6.216 43.597 2000
0 3.958326 0 10.008 80.069 1831
0 4.864244 0 69.084 74.593 2000

この表にでてくる、send-rate-mbps receive-rate-mbps bandwidth-mbps の単位は何ですか

表にある send-rate-mbpsreceive-rate-mbpsbandwidth-mbps の単位は「Mbps(メガビット毎秒)」 です。

各項目の意味と単位

項目 意味 単位
send-rate-mbps 送信レート(今使っている帯域) Mbps(メガビット毎秒)
receive-rate-mbps 受信レート(今使っている帯域) Mbps(メガビット毎秒)
bandwidth-mbps 推定利用可能帯域(まだ使える帯域) Mbps(メガビット毎秒)

1. send-rate-mbps(送信レート)

  • 実際に送信しているデータ量の速度 を表す。
  • 今使っている帯域(現在の送信速度)。
  • 例えば send-rate-mbps = 4.864244 なら、約 4.86 Mbps でデータを送信している。

2. receive-rate-mbps(受信レート)

  • 実際に受信しているデータ量の速度 を表す。
  • 今使っている帯域(現在の受信速度)。
  • このデータでは receive-rate-mbps = 0 なので、受信データはない。

3. bandwidth-mbps(推定帯域幅)

  • SRTプロトコルが計算した「利用可能な最大帯域」
  • まだ使える帯域の推定値
  • 例えば bandwidth-mbps = 69.084 なら、SRTは「この回線では約 69.08 Mbps まで使える」と推定している。

単位換算

1 Mbps(メガビット毎秒)は:

  • 1,000 kbps(キロビット毎秒)
  • 1,000,000 bps(ビット毎秒)
  • 125 KB/s(キロバイト毎秒)
  • 0.125 MB/s(メガバイト毎秒)

例えば:

  • send-rate-mbps = 4.86 Mbps
    • = 4,860 kbps
    • = 約 607.5 KB/s
    • = 約 0.607 MB/s

結論

  • send-rate-mbpsreceive-rate-mbpsbandwidth-mbps の単位は「Mbps(メガビット毎秒)」
  • send-rate-mbps は今使っている送信帯域
  • receive-rate-mbps は今使っている受信帯域
  • bandwidth-mbps はSRTが推定した利用可能な最大帯域

このデータから、送信レートは 最大 4.86 Mbps であり、ネットワークが 最大 69 Mbps まで利用可能 と推定されていることが分かります。