シミュレーションモデルをWebシステムとして運用するための共通アーキテクチャについて
1. はじめに
今さらですが、数値シミュレーションのプログラムは、最初は「入力ファイルを読み込んで計算し、結果をCSVや画像に出す」という形から始まることが多いと思います。
この形は研究開発やモデル検証には向いていますが、実際に複数の利用者が使うようになると、次のような課題が出てきます。特にfortranプログラマーにとっては計算することに主眼を置いてしまう傾向が高いです。
- 入力ファイルの置き場所や書式が利用者ごとにばらつく
- 計算中に画面が固まり、処理が終わったのか分からない
- 計算が失敗したとき、どこで止まったのか確認しにくい
- 同じ計算を複数人が同時に実行しづらい
- 新しいシミュレーションモデルを追加するたびに、画面やAPIを作り直す必要がある
- 計算コードと画面処理が一体化し、数値モデルだけを改善するのが難しくなる
そこで、シミュレーションの「計算部分」と「利用者が操作する部分」を分離し、Webシステムとして運用できる共通基盤を用意します。
今回の1D河道ネットワーク不定流モデルを最初の実装例としつつ、将来的には2D洪水、土石流、溶岩流、火砕流、降雨流出、ダム操作など、別のシミュレーションモデルも同じ仕組みの上で動かせる構成を目指します。
また、gtihubやMLflowなどと組み合わせも考えていきます。
2. 基本的な考え方
共通化の中心は、すべての数値モデルを無理に同じ形にすることではありません。
河川1Dモデルでは node や reach が必要ですが、2D洪水モデルではDEMや計算格子が必要になります。土石流、泥流、溶岩流、火砕流や降灰予測では、さらに別の物性値や初期条件が必要になります。
そのため、共通化する範囲と、モデルごとに持つ範囲を分けて考えます。
共通化する部分
- Web画面
- ケース管理
- API
- 計算ジョブの受付
- 非同期処理
- 計算状況の確認
- ログ管理
- 結果ファイル管理
- エラー処理
- Dockerによる実行環境
- テストの進め方
モデルごとに分ける部分
- 支配方程式
- 数値計算法
- 初期条件
- 境界条件
- 入力データ形式
- 計算格子
- モデル固有の物理量
- モデル固有の結果表示
この分け方を守ることで、共通基盤を大きく変更せずに新しい数値モデルを追加できるようになります。
3. 全体構成
基本構成は次のようにします。
役割は明確に分けます。
| 構成要素 | 主な役割 |
|---|---|
| NiceGUI | プリ処理、計算実行、進捗表示、ポスト処理 |
| FastAPI | ケース管理、入力確認、計算受付、結果取得 |
| Redis | Celeryのメッセージブローカー、ジョブ状態管理 |
| Celery | 長時間計算の非同期実行 |
| Solver | 実際の数値シミュレーション |
| Storage | 入力、出力、ログ、計算条件の保存 |
| Docker | 実行環境の統一 |
4. Pre、Solver、Postを分ける
シミュレーションシステムは、大きく3つに分けて考えます。
Pre Process
↓
Simulation
↓
Post Process
4.1 Pre Process
プリ処理では、計算に必要な入力データを準備します。
最初の段階では、Web画面でCSVファイルを選択する方式で十分です。
1D河道モデルであれば、例えば次のようなファイルを扱います。
nodes.csv
reaches.csv
boundary.csv
simulation.csv
NiceGUI側では、ファイルを選択し、内容を表示します。
ただし、CSVの正式な検証はFastAPI側で行います。
Web画面の実装と数値モデルの入力仕様を分離しておくことで、将来GUI上で河道ネットワークを編集できるようになっても、Solver側の入力仕様を大きく変更する必要がなくなります。
4.2 Simulation
計算部分はWeb画面から直接呼び出しません。
FastAPIからCeleryへ計算依頼を送り、Celery WorkerがSolverを実行します。
NiceGUI
↓
FastAPI
↓
Redis
↓
Celery Worker
↓
Solver
この構成にする理由は、数値計算が重くなってもWeb画面やAPIを止めないためです。
今は6時間の1D不定流計算が短時間で終わっていても、将来は、
- 河道数の増加
- 格子間隔の細分化
- 計算期間の長期化
- 複数ケースの同時計算
- 2Dモデルへの展開
などによって、1回の計算に数時間以上かかることが考えられます。
そのため、最初から「計算はジョブとして実行する」形にしておきます。
4.3 Post Process
ポスト処理では、計算結果を読み込み、Web画面上で確認できるようにします。
1D河道モデルであれば、まず次の表示を用意します。
- 流量ハイドログラフ
- 水位ハイドログラフ
- 河道縦断水位
- 最大水深
- 最大流量
- 合流点の質量収支
- Junction Solverの収束状況
将来的に2Dモデルを追加する場合は、
- 最大浸水深
- 流速分布
- 到達時間
- GeoTIFF
- 時系列アニメーション
などを追加します。
共通のPost画面にすべてを詰め込むのではなく、モデルごとに表示部品を追加できる構成にします。
5. FastAPIの役割
FastAPIは数値計算そのものを担当しません。
主な役割は次のとおりです。
入力受付
↓
入力データ確認
↓
case_id発行
↓
計算ジョブ登録
↓
job_id発行
↓
状態確認
↓
結果提供
例えば、計算開始は次のようなAPIにします。
POST /api/v1/cases/{case_id}/run
レスポンス例:
{
"case_id": "case_20260910_001",
"job_id": "8a1d...",
"status": "queued"
}
利用者は計算終了までHTTP接続を維持する必要はありません。
状態は別APIで確認します。
GET /api/v1/jobs/{job_id}
レスポンス例:
{
"status": "running",
"progress": 62.5,
"simulation_time_s": 13500
}
この方式にしておけば、NiceGUI以外のクライアントからも同じSolverを利用できます。
6. CeleryとRedis
Celeryは、時間のかかる計算をWeb APIとは別のプロセスで実行するために使用します。
Redisは、FastAPIからCelery Workerへ計算依頼を渡すためのBrokerとして使用します。
Celeryへ渡すデータはできるだけ小さくします。
例えば、計算用のNumPy配列全体をRedis経由で送るのではなく、
run_simulation.delay(case_id)
のように case_id だけを渡します。
Workerは共有Storageから入力データを読み込みます。
7. ケース単位でデータを管理する
シミュレーションは「ケース」という単位で管理します。
例えば次のような構成です。
data/
└── cases/
└── case_20260910_001/
├── input/
│ ├── nodes.csv
│ ├── reaches.csv
│ ├── boundary.csv
│ └── simulation.csv
│
├── output/
│ ├── timeseries.csv
│ ├── junction_diagnostics.csv
│ ├── summary.json
│ └── figures/
│
└── log/
└── simulation.log
ケース単位に分けておくと、
- 入力条件の再現
- 計算結果の比較
- エラー調査
- 再計算
- 複数ケース管理
が行いやすくなります。
計算結果だけでなく、「どの入力を使って計算したのか」を残すことが大切です。
8. Solverは共通インターフェースにする
シミュレーションモデルを追加しやすくするため、Solverには共通の入口を用意します。
例えば次のような考え方です。
class SimulationSolver:
"""シミュレーションモデル共通の基本インターフェース。"""
def validate_input(self, case_dir):
"""入力データの内容と整合性を確認する。"""
raise NotImplementedError
def preprocess(self, case_dir):
"""入力ファイルを計算用データへ変換する。"""
raise NotImplementedError
def run(self, case_dir, progress_callback=None):
"""数値シミュレーションを実行する。"""
raise NotImplementedError
def postprocess(self, case_dir):
"""計算結果を整理し、出力ファイルを生成する。"""
raise NotImplementedError
def get_summary(self, case_dir):
"""代表的な計算結果を返す。"""
raise NotImplementedError
個別モデルはこれを実装します。
SimulationSolver
├── River1DSolver
├── Flood2DSolver
├── DebrisFlowSolver
├── LavaFlowSolver
└── PyroclasticFlowSolver
Celery Worker側では、モデルの種類を見てSolverを選びます。
solver = solver_registry[model_type]
solver.run(case_dir)
この形にすると、FastAPIやCeleryのコードをモデルごとに作り直す必要がありません。
9. NumbaはSolver内部だけで使う
Numbaは数値計算を高速化するために使用します。
ただし、システム全体をNumba化する必要はありません。
役割を次のように分けます。
NiceGUI
↓ Python
FastAPI
↓ Python
Pre Process
↓ pandas / NumPy
Solver
↓ NumPy ndarray
↓ Numba
Post Process
↓ pandas / Plotly
Numbaに渡す部分では、できるだけPythonのオブジェクトや辞書を避け、NumPy配列を使います。
例えば、
A
Q
bed
width
manning
dx
reach_start
reach_count
node_reach
node_side
などを配列化します。
この考え方は1D河川だけでなく、2D格子モデルにもそのまま使えます。
10. 並列化の考え方
並列化には2つのレベルがあります。
ケース単位の並列化
Celeryが担当します。
Case A → Worker 1
Case B → Worker 2
Case C → Worker 3
これは比較的扱いやすい並列化です。
1ケース内部の並列化
Numbaが担当します。
1ケース
↓
複数セル・複数格子の計算
↓
Numba / prange
ただし、Celery Worker数とNumbaスレッド数を同時に増やしすぎると、CPUを奪い合って遅くなることがあります。
そのため、最初は
Celery : 複数Worker
Numba : serial
から始め、ベンチマークを見ながら調整します。
11. Dockerで実行環境を固定する
数値モデルは、Pythonやライブラリのバージョンによって挙動が変わることがあります。
そのため、実行環境はDockerで固定します。
基本構成は次のとおりです。
docker-compose.yml
services:
web
api
worker
redis
SolverはCelery Workerの中からPythonパッケージとして呼び出します。
この構成であれば、Ubuntu Server上でも同じ手順で起動できます。
12. エラーを利用者に見える形にする
数値計算では、単に「計算失敗」と表示されても原因が分かりません。
例えば1D不定流モデルでは、
- CFL条件の異常
- 負水深
- Junction Solverの非収束
- NaNの発生
- 入力ネットワークの接続ミス
などが考えられます。
そのため、Solver内部ではエラーの種類を分けます。
InputValidationError
SolverError
JunctionConvergenceError
TimeStepError
ResultWriteError
Celeryはエラー内容を記録し、FastAPI経由でNiceGUIへ返します。
画面では例えば次のように表示します。
計算に失敗しました
計算時刻 : 03:12:45
対象 : J_MID
内容 : Junction Solver did not converge
反復回数 : 30
質量残差 : 1.3e-3 m3/s
計算担当者が原因を追えるだけの情報を残すことが重要です。
13. テストを開発手順に組み込む
シミュレーションモデルでは、「プログラムが最後まで動いた」だけでは十分ではありません。
数値計算の変更によって結果が変わっていないかも確認する必要があります。
そのため、テストを次のように分けます。
Unit Test
↓
Input Validation Test
↓
Solver Test
↓
Regression Test
↓
Celery Task Test
↓
API Test
↓
Docker Integration Test
Unit Test
数式や小さな関数を確認します。
例:
- Flux
- Manning抵抗
- CFL
- 境界条件
- Newton法
Regression Test
各モデルに、結果が分かっている基準ケースを用意します。
例:
River 1D
→ 6時間洪水ケース
Flood 2D
→ 単純矩形領域のダムブレーク
Lava Flow
→ 一様斜面流下ケース
Solverを高速化したりアルゴリズムを変更したりした場合も、この基準ケースと比較します。
Docker Integration Test
最終的にはDocker上で、
build
↓
起動
↓
APIへ計算投入
↓
Celery Workerで計算
↓
結果生成
↓
結果検証
まで通して確認します。
今後のソース更新では、このテストがPASSしてから更新版を採用する運用にします。
14. 共通化しすぎない
共通基盤を作ると、何でも一つの仕組みにまとめたくなります。
しかし、数値モデルでは共通化しすぎると逆に扱いにくくなります。
例えば、
River 1D
nodes.csv
reaches.csv
Flood 2D
dem.tif
roughness.tif
boundary.geojson
Lava Flow
dem.tif
material.csv
source.csv
のように、必要なデータはモデルによって違います。
そのため、
Case
Job
Status
Log
Result
API
Web UI framework
Docker
Test
は共通化し、
Input Schema
Solver
Boundary Condition
Grid
Physical Parameter
Post Process
はモデル側に持たせます。
この境界を明確にしておくことが、長く使える基盤にするうえで重要です。
15. 想定するディレクトリ構成
将来的には次のような構成を想定します。
simulation-platform/
│
├── docker-compose.yml
├── .env
│
├── web/
│ └── app/
│ ├── pages/
│ ├── components/
│ └── main.py
│
├── api/
│ └── app/
│ ├── routers/
│ ├── schemas/
│ ├── services/
│ └── main.py
│
├── worker/
│ ├── celery_app.py
│ └── tasks.py
│
├── common/
│ ├── case/
│ ├── storage/
│ ├── logging/
│ └── exceptions/
│
├── solvers/
│ ├── base/
│ │ └── solver.py
│ │
│ ├── river1d/
│ │ ├── solver.py
│ │ ├── preprocess.py
│ │ ├── kernel.py
│ │ └── postprocess.py
│ │
│ ├── flood2d/
│ ├── debrisflow2d/
│ ├── lava2d/
│ └── pyroclastic2d/
│
├── tests/
│ ├── common/
│ ├── api/
│ ├── river1d/
│ └── integration/
│
└── data/
└── cases/
16. 1D河道モデルの位置付け
現在作成している1D河道ネットワーク不定流モデルは、この共通基盤の最初のリファレンス実装として扱います。
このモデルで、
- CSV入力
- node / reach
- Saint-Venant Solver
- Junction Solver
- FastAPI
- Celery
- Redis
- Docker
- Regression Test
まで一通り動かします。
この1本をきちんと仕上げておけば、次に2Dモデルを追加するときは、Webやジョブ管理をゼロから作る必要がありません。
つまり、今回の目的は1D河道モデルだけを完成させることではなく、
数値シミュレーションを追加していける土台を作る
ことにあります。
17. 今後の開発順序
一度にすべてを作らず、次の順番で進めます。
Phase 1 — 1D河道モデルの基盤化
- FastAPI
- Celery
- Redis
- Docker
- 6時間洪水Regression Test
- ケース管理
Phase 2 — 計算高速化
- Solver内部のNumPy配列化
- Numba対応
- Python版とNumba版の比較
- ベンチマーク
Phase 3 — Pre / Post WebUI
- NiceGUI
- CSVアップロード
- 入力確認
- 計算開始
- 進捗表示
- グラフ表示
Phase 4 — Solver Plugin化
- 共通Solver Interface
- Solver Registry
- モデル別Input Schema
- モデル別Post Process
Phase 5 — 別モデル追加
候補としては、
- 2D洪水氾濫
- 土石流
- 泥流
- 溶岩流
- 火砕流
- 降雨流出
- ダム操作
- 降灰予測
などがあります。
18. まとめ
このアーキテクチャでは、Web画面、API、非同期処理、数値計算を明確に分けます。
利用者
↓
NiceGUI
↓
FastAPI
↓
Redis
↓
Celery
↓
Solver
↓
Result
NiceGUIは利用者との接点、FastAPIはシステムの窓口、Celeryは計算ジョブの実行管理、Solverは数値解析に専念します。
この分離ができていれば、数値モデルを改良してもWebシステムへの影響を小さくでき、逆に画面を改善しても計算ロジックを触らずに済みます。
また、各モデルにRegression Testを持たせることで、Numba化や並列化、高次精度化を進めても、以前の計算結果と比較しながら安全に更新できます。
今回の1D河道ネットワーク不定流モデルは、この仕組みの最初の実例として位置付けます。
まず1Dモデルで一通りの運用を完成させ、その経験を使って2D洪水や土石流、溶岩流、火砕流などへ広げていく、という進め方が現実的です。
19. 参考リンク集
この章では、実装や運用で確認することが多い公式ドキュメントをまとめます。
ライブラリやフレームワークは更新が早いため、実装時にはブログ記事だけで判断せず、できるだけ公式ドキュメントで現在の仕様を確認するようにします。
19.1 NiceGUI
公式ドキュメント
- NiceGUI Documentation
https://nicegui.io/documentation/
Web画面全般の入口です。ページ、ボタン、テーブル、ダイアログなどを実装するときは、まずここを確認します。
ファイルアップロード
-
ui.upload
https://nicegui.io/documentation/upload
プリ処理で nodes.csv、reaches.csv、境界条件CSVなどを選択・アップロードするときに使用します。
複数ファイルのアップロード、ファイルサイズ制限、アップロード完了イベントなども確認できます。
Plotlyによる結果表示
-
ui.plotly
https://nicegui.io/documentation/plotly
流量ハイドログラフ、水位ハイドログラフ、縦断水位などの表示で利用します。
計算点数が多くなった場合は、グラフ表示そのものの負荷も確認する必要があります。
19.2 FastAPI
公式ドキュメント
- FastAPI
https://fastapi.tiangolo.com/
APIのルーティング、Pydanticによるデータ検証、依存性注入、エラー処理などはここから確認します。
ファイルアップロード
- Request Files /
UploadFile
https://fastapi.tiangolo.com/tutorial/request-files/
CSVや将来のGeoTIFF、GeoJSONなどをAPIで受け取るときの基本になります。
大きな入力ファイルを扱う可能性があるため、単純な bytes 受信ではなく UploadFile を基本とします。
Background Tasks
- Background Tasks
https://fastapi.tiangolo.com/tutorial/background-tasks/
FastAPI自身にもレスポンス返却後に処理を行う仕組みがあります。
ただし、本システムで想定している数値シミュレーションのような重い処理は、APIプロセスと分離できるCelery Workerで実行する方針とします。
19.3 Celery
公式ドキュメント
- Celery Documentation
https://docs.celeryq.dev/en/stable/
Celery全体のドキュメントです。
タスク、Worker、Retry、Timeout、Routing、Monitoringなどを調べるときの入口になります。
Getting Started
- Getting Started
https://docs.celeryq.dev/en/stable/getting-started/
タスクキューの基本、Workerの起動方法、タスクの呼び出し方、Result Backendなどを確認できます。
RedisをBroker / Result Backendとして使用する
今回の構成で特に重要なページです。
例えば次のような接続設定を行います。
Broker
redis://redis:6379/0
Result Backend
redis://redis:6379/1
CeleryやRedisのバージョンを更新するときは、このページで対応状況を確認します。
19.4 Redis
公式ドキュメント
- Redis Docs
https://redis.io/docs/latest/
Redis全般のドキュメントです。
今回のシステムでは主にCeleryのBroker、Result Backend、ジョブ状態管理のために使用します。
Quick Start
- Redis Quick Starts
https://redis.io/docs/latest/develop/get-started/
Redis単体の動作確認や、開発環境で接続を確認するときに利用します。
本システムでは計算結果の大きな配列やCSV全体をRedisへ保存せず、ジョブID、状態、進捗、エラー概要などの小さな情報を中心に扱います。
19.5 Docker / Docker Compose
Docker Docs
- Docker Documentation
https://docs.docker.com/
Docker全般の入口です。
Compose Specification / Services
- Compose services
https://docs.docker.com/reference/compose-file/services/
services、depends_on、healthcheck、volume、networkなどを確認するときに使用します。
コンテナ起動順序
- Control startup and shutdown order in Compose
https://docs.docker.com/compose/how-tos/startup-order/
Redisが利用可能になる前にFastAPIやCelery Workerが接続して失敗する、といった問題を避けるために重要です。
本システムでは、単にコンテナが起動したかだけでなく、healthcheck を使ってサービスが利用可能な状態か確認する構成を基本とします。
19.6 Numba
公式ドキュメント
- Numba Documentation
https://numba.readthedocs.io/en/stable/
数値計算高速化に関するドキュメントの入口です。
@jit / @njit
- Compiling Python code with
@jit
https://numba.readthedocs.io/en/stable/user/jit.html
1D Saint-Venant SolverのFlux計算、CFL計算、source term、セル更新、Junction Solverなどを高速化するときに参照します。
本システムでは、まず @njit によるシングルスレッド高速化を行い、Python版とのRegression Testを通した後に並列化を検討します。
並列化
- Automatic parallelization with
@jit
https://numba.readthedocs.io/en/stable/user/parallel.html
parallel=True や prange を使用するときに参照します。
Celery Worker数とNumbaのスレッド数を同時に増やすとCPUの取り合いになることがあるため、実測ベンチマークを行って設定を決めます。
19.7 pytest
公式ドキュメント
- pytest Documentation
https://docs.pytest.org/en/stable/
Getting Started
Unit Test、Regression Test、API Test、Solver Testなどの基本に使用します。
本プロジェクトでは、ソースを更新する前にテストを実行し、PASSしたことを確認してから更新版として扱います。
特に数値計算部分では、単に例外なく終了するだけではなく、基準ケースとの数値比較をRegression Testとして残します。
19.8 Mermaid
公式ドキュメント
- Mermaid Documentation
https://mermaid.js.org/intro/
User Guide
- Mermaid User Guide
https://mermaid.js.org/intro/getting-started.html
システム構成図、処理フロー、シーケンス図などをMarkdown内で管理するときに利用します。
設計資料の図を画像ファイルだけで管理するのではなく、Mermaidのソースも残しておけば、構成変更時に修正しやすくなります。
20. 実装時によく確認するページ
普段の開発で特に参照する可能性が高いものを絞ると、次のページになります。
| 確認したい内容 | 参照先 |
|---|---|
| NiceGUI全般 | https://nicegui.io/documentation/ |
| CSVアップロード画面 | https://nicegui.io/documentation/upload |
| Plotly表示 | https://nicegui.io/documentation/plotly |
| FastAPI全般 | https://fastapi.tiangolo.com/ |
| FastAPIファイル受信 | https://fastapi.tiangolo.com/tutorial/request-files/ |
| Celery全般 | https://docs.celeryq.dev/en/stable/ |
| Celery + Redis | https://docs.celeryq.dev/en/v5.6.2/getting-started/backends-and-brokers/redis.html |
| Redis | https://redis.io/docs/latest/ |
| Docker Compose | https://docs.docker.com/reference/compose-file/services/ |
| Numba | https://numba.readthedocs.io/en/stable/ |
| Numba並列化 | https://numba.readthedocs.io/en/stable/user/parallel.html |
| pytest | https://docs.pytest.org/en/stable/ |
| Mermaid | https://mermaid.js.org/intro/ |
21. 参考資料の扱いについて
この資料では、システムの基本方針をできるだけライブラリの細かなバージョンに依存しない形で整理しています。
一方で、FastAPI、Celery、NiceGUI、Numbaなどは継続的に更新されています。
そのため実装時には、
- この設計資料で全体方針を確認する
- 公式ドキュメントで現在のAPIや推奨設定を確認する
- 小さなテストケースで動作を確認する
- Regression Testを実行する
- Docker上の統合テストを実行する
という順番で確認します。
特に数値Solverについては、ライブラリの更新や高速化によって「プログラムは動くが計算結果が少し変わった」という状態が起こり得ます。
Webシステム側の動作確認と、数値モデルとしての妥当性確認は分けて行うことが重要です。


