乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

Python

今回は名探偵コナンの
緋色の弾丸シミュレーションです。


解説動画はこちら




緋色の弾丸

『名探偵コナン 緋色の弾丸』

2021年4月16日公開のアニメ映画で
劇場版『名探偵コナン』シリーズの24作目です。

映画の中ではリニアの超高速移動に合わせて
数キロ以上の距離から、動く犯人の狙撃に成功しているシーンがあります

この弾丸の到達距離のシミュレーションを行なっていきます。


最長狙撃記録

現代の主要な最長狙撃記録です。
1位:約4,000m(2024年、ウクライナ軍、Snipex Alligator)
2位:3,800m(2023年、ウクライナ軍、MCR Horizon's Lord)
3位:3,540m(2017年、カナダ軍、TAC-50)

現実世界では3-4キロの到達が限界だと思われます。




映画上の設定

映画上には狙撃を成功させるための
重要なポイントとなる設定がいくつかありました。
世界初の「真空トンネル」内を走行する超電導リニアで時速は約1000km
ライフル弾には特注の「銀の弾丸」が使用された

リニアも弾丸も時速1000kmで飛んだ設定
リニアが減速した際に、ライフル弾は減速しないので、そのまま犯人に命中した

つまりは真空状態のリニア線路上を銀の弾丸が飛んでいったというのが
重要なポイントです。


銀の弾丸使用のポイント


狙撃用の弾の素材が銀が最良かどうかは分かりませんが
銀である必要はあったようです。


高い導電性:
銀は金属の中で電気を通しやすい

レンズの法則(渦電流):
導体である銀が、リニアの強力な変動磁場の中を高速で移動すると
弾丸表面に強力な「渦電流」が発生

磁気反発:
この渦電流が、レールの磁場と反発する磁力(ローレンツ力)を
生み出し、弾丸を浮かせる 「揚力」 となる


ということで
リニア線路内は重力の影響を受けない弾丸になる
と仮定することにします。


射撃シミュレーター

重力 0 や真空の状況を加味したものです

通常射撃(空気抵抗、重力影響有り)
真空射撃(空気抵抗 0、重力影響有り)
リニア射撃(空気抵抗 0、重力影響 0)
の状態を選べます。

コードはこちら
import numpy as np
import matplotlib.pyplot as plt
from math import pi, cos, sin, sqrt

class BallisticSimulator:
    def __init__(self, dt=0.01):
        self.dt = dt
        self.g = 9.80665

    def get_accelerations(self, vx, vy, k, am):
        """
        現在の速度と設定から、x方向とy方向の加速度を返す
        k: 空気抵抗係数, am: 磁気浮上加速度
        """
        v = sqrt(vx**2 + vy**2)
        # x方向: 空気抵抗のみ
        ax = -k * vx * v
        # y方向: 重力(下) + 磁気浮上(上) + 空気抵抗(上下)
        ay = -self.g + am - (k * vy * v)
        return ax, ay

    def simulate(self, mode="normal", v0_kmh=1000.0, deg=0.0, ht=1.5, mas_g=9.0, area_cm2=0.5):
        # モードごとの環境設定
        config = {
            "normal":      {"rho": 1.225, "am": 0.0},      # 1. 通常(空気あり、浮力なし)
            "vacuum":      {"rho": 0.0,   "am": 0.0},      # 2. 真空(空気なし、浮力なし)
            "maglev":      {"rho": 0.0,   "am": 9.80665}   # 3. 真空 + 磁気浮上
        }
        env = config.get(mode, config["normal"])
        v0 = v0_kmh / 3.6
        rad = deg * pi / 180
        # 物理定数の計算
        mas = mas_g / 1000.0
        s_area = area_cm2 / 10000.0
        cd = 0.3
        k = 0.5 * s_area * cd * env["rho"] / mas
        # 初期値
        x, y = 0.0, ht
        vx, vy = v0 * cos(rad), v0 * sin(rad)
        res_x, res_y = [x], [y]
        for _ in range(1000000):
            k1vx, k1vy = self.get_accelerations(vx, vy, k, env["am"])
            k2vx, k2vy = self.get_accelerations(vx + k1vx*self.dt/2, vy + k1vy*self.dt/2, k, env["am"])
            k3vx, k3vy = self.get_accelerations(vx + k2vx*self.dt/2, vy + k2vy*self.dt/2, k, env["am"])
            k4vx, k4vy = self.get_accelerations(vx + k3vx*self.dt, vy + k3vy*self.dt, k, env["am"])
            vx += (k1vx + 2*k2vx + 2*k3vx + k4vx) / 6 * self.dt
            vy += (k1vy + 2*k2vy + 2*k3vy + k4vy) / 6 * self.dt
            x += vx * self.dt
            y += vy * self.dt
            if y < 0 or x >= 300000: break
            res_x.append(x)
            res_y.append(y)
        return res_x, res_y

# --- 実行と可視化 ---
sim = BallisticSimulator()
modes = ["normal", "vacuum", "maglev"]
mode = modes[1]

rx, ry = sim.simulate(mode=mode, v0_kmh=1000.0, deg=0)
print(f"飛行時間  : {(len(rx) - 1) * sim.dt:.2f} 秒")
print(f"移動距離  : {rx[-1]:.2f} メートル, {rx[-1]/1000:.5f} キロメートル")
plt.figure(figsize=(16, 3))
plt.plot(rx, ry, label=f"Mode: {mode}")
plt.axhline(0, color='black', linewidth=1)
plt.title("Comparison of Bullet Trajectories (v=1000km/h)")
plt.xlabel("Distance (m)")
plt.ylabel("Height (m)")
plt.legend()
plt.grid(True)
plt.show()
飛行時間  : 0.54 秒
移動距離  : 150.00 メートル, 0.15000 キロメートル

download-3

 真空状態であったとしても
水平に撃ったのでは150メートルほどしか飛びません。


上の方に向けて撃った場合はもう少し長い距離飛びます。

download-2

射角5度くらいで1400メーターくらいです。
全然届かないですね。
しかも高さ30メートルの高低差が出てしまうので
リニアトンネルの天井にぶつかって終わってしまいます。


しかし、重力の影響が無いと仮定すると

download
銀の弾丸はまっすぐ飛んでいけます。



まとめ


弾丸が重力の影響を受けない限定的な条件下であれば
シミュレーション上ではトンネルが真っ直ぐ続く限り
銀の弾丸はどこまでも飛んでいくと推測されます。

狙撃距離は100kmあたり(2分後くらい)になると推測される

銀の弾丸に浮力が付き
リニアの線路がマジで真っ直ぐだったら
あり得ないでもない(赤井さんなら)
というのが結論です。


製作陣がどれだけの考慮をしたかは分かりませんが
特殊条件下であれば不可能でも無いと思います。

そういえば
来週からコナンの新作映画が公開されますね!!

速攻見にいきましょう


PS:
個人的には
逆襲のシャアの決着を
コナンで付けてもらいたかったのですが
もう叶わないのですかね






今回は数式から美しいアニメーションを作成できる
Manimのご紹介です

解説動画はこちら






Manimとは

Manim(Mathematical Animation Engine)
数学や物理を視覚的に表現するための
Pythonのアニメーションエンジンです。

3Blue1Brownの制作者(Grant Sanderson)によって開発
数学の教育動画で幅広く使用されています。

Python コードで数式やグラフをアニメーションとして
動画化してくれるライブラリです。


Manim を利用するうえで必要なこと

関連ライブラリのインストール
   - FFmpeg と必須ライブラリ
   - LaTeX (MacTeX) 
   - Manim本体
Python の知識
TeX 記法の知識

この辺りが必要です。


関連ライブラリのインストール

Manimを動かすには以下の3つが必要です。

1.FFmpeg と必須ライブラリ
2.LaTeX (MacTeX) 
3.Manim本体

早速インストールしていきましょう。

こちらはMac用のインストールコマンドです。
(Windowsの人はMacを買うか、Macを買いましょう)

1.FFmpeg と必須ライブラリのインストール

Manimが動画を描画・出力するために必要なツールです。
brew install ffmpeg py3cairo pkg-config


2.LaTeX (MacTeX) のインストール

美しい数式を描画するためのLaTeX環境です。
brew install --cask mactex-no-gui


3.Manim のインストール
pip install manim


インストール後の確認
manim --version



基本的な使い方

ファイルを作成して実行する

Pythonファイルとして作成した後
そのファイルをターミナルで実行することで
動画を作成できます。

manim -pqh ファイル名 シーン名


 - -p: レンダリング後に動画を自動再生
 - -q: 品質 (l=低, h=高, k=4K)
 - ファイル名 : hello.py
 - シーン名 : Hello


動画ファイルの出力先は
media/videos/...
になる(デフォルト)

hello.py というファイルでサンプルを用意しました。
# hello.py
from manim import *

class Hello(Scene):
    def construct(self):
        text = Text("どうもこんばんわ、乙pyです")
        self.play(Write(text))
        self.wait()

実行方法は次のようになります。

サンプルの実行
manim -pqh hello.py Hello




基本文法

インポートとコンストラクタの定義

ライブラリをインポートして
コンストラクタを最初に定義します。
from manim import *

class MyAnimation(Scene):
    def construct(self):


コンストラクタを作ったら
描きたいものを定義していきます。

変数名 = オブジェクト名(引数など)

self.play(): 
アニメーションを実行

.animate: 
オブジェクトのプロパティ(位置や色)を滑らかに変化

ReplacementTransform: 
前の物体を消して、次の物体へモーフィング

オブジェクトを生成して動かすサンプルも用意しました。

サンプルコード

# basicflow.py
# manim -pqh basicflow.py BasicFlow
from manim import *

class BasicFlow(Scene):
    def construct(self):
        # 1. オブジェクトの生成
        title = Text("Manimの基本操作", font_size=40)
        square = Square(color=BLUE, fill_opacity=0.5)
        circle = Circle(color=RED, fill_opacity=0.5)

        # 2. 配置の設定
        title.to_edge(UP) # 画面上端へ

        # 3. アニメーションの実行
        self.play(Write(title))        # 文字を書く
        self.play(DrawBorderThenFill(square)) # 枠線から塗りつぶし
        self.wait(1)

        # 4. 移動と変形 (Transform)
        self.play(
            square.animate.shift(LEFT * 2), # 左に動かしながら
            run_time=1
        )
        # 四角を円に変形させる
        self.play(ReplacementTransform(square, circle))
        self.play(circle.animate.shift(RIGHT * 4))
        
        self.wait(2)

オブジェクトを定義して配置し
アニメーション内容を書いていくことで
様々なアニメーションを作ることができます。




いろんなサンプルを見てみよう

5つの動画サンプルを用意しました。

1.正規分布のパラメータ変化
2.三角関数
3.フーリエ変換
4.ロジスティック写像
5.勾配降下法


どんなアニメーションを作れるかは
動画の方で確認してみてください。



まとめ


数学や物理系の綺麗なアニメーションを作るのは
かなりしんどいと思います。

このライブラリであれば、かなり綺麗な
アニメーションを作ることが出来るので
その道の動画を作りたい方は
試してみるのも面白いと思います。

それでは。









南海トラフ地震はいつ起きるのか
過去データから予測してみる事としました。

解説動画はこちら





南海トラフ地震とは

駿河湾から日向灘沖にかけてのプレート境界を震源域として
概ね100~150年間隔で繰り返し発生してきた大規模地震

静岡県から宮崎県にかけての一部では震度7となる可能性があります。

過去の発生年

発生年 名称
1946年 昭和南海地震
1944年 昭和東南海地震
1854年 安政南海地震
1707年 宝永地震
1605年 慶長地震
1498年 明応地震
1361年 正平南海地震


地震の発生確率の計算方法


最近の調査ではBPT分布というものを
用いた予測が基本になっています。

BPT分布
(Brownian Passage Time:ブラウン通過時間)
地震の発生間隔をモデル化する確率分布

確率的に「平均的な間隔」の周辺で起こるが
バラつきがあるというモデル


ポアソン分布は発生確率が常に一定(ランダム)とする一方
BPT分布は時間経過による「成熟度」を考慮したものです。



BPT分布の基本式

BPT分布の確率密度関数  𝑓(𝑡)  は、以下の式で表されます。
スクリーンショット 2026-03-14 17.25.49



条件付き確率の考え方

「前回の地震から  𝑡  年経過した時点で
今後  Δ𝑡  年以内に地震が発生する確率」

𝑃(𝑡,Δ𝑡)  は、以下の式で計算します。

スクリーンショット 2026-03-14 17.26.44


コード上では下記が確率計算部分になります。
prob = (f_future - f_t) / (1 - f_t)

分子:(f_future - f_t)
「現在からXX年の期間内に地震が発生する」という確率

分母:(1 - f_t)
「現在で、まだ地震が起きていない」という確率

BPTモデルでは時間が経つほど  𝐹(𝑡)  が大きくなるため
分母はどんどん小さくなっていき、確率が高まります。


Pythonで地震確率をシミュレーションする

あらかじめことわりますが
政府予測とは異なります!!!!


予測条件

平均再来間隔 (南海トラフの直近データ 1361 ~ 1946)
97.5

ばらつき係数 (地震調査委員会採用値)
0.24

前回の発生年 (昭和南海地震)
1946

今後30年間の地震発生確率を求めるコードは以下です。
import numpy as np
from scipy.stats import norm

def bpt_cdf_30(t, mu, alpha, delta_t=30):
    """
    BPT分布に基づき、今後 delta_t 年以内の地震発生確率を計算する
    
    Parameters:
    t: 前回からの経過年数
    mu: 平均再来間隔
    alpha: ばらつき係数
    delta_t: 評価期間(デフォルトは30年)
    """
    if t <= 0:
        return 0.0
    
    # 現在までの間に、既に地震が起きているはずだった累積確率
    inv_alpha_sq = 2 / (alpha**2)
    term1_t = norm.cdf((t / mu - 1) / (alpha * np.sqrt(t / mu)))
    term2_t = np.exp(inv_alpha_sq) * norm.cdf(-(t / mu + 1) / (alpha * np.sqrt(t / mu)))
    f_t = term1_t + term2_t
    
    # delta_t 年後までの間に、地震が起きる累積確率
    t_future = t + delta_t
    term1_f = norm.cdf((t_future / mu - 1) / (alpha * np.sqrt(t_future / mu)))
    term2_f = np.exp(inv_alpha_sq) * norm.cdf(-(t_future / mu + 1) / (alpha * np.sqrt(t_future / mu)))
    f_future = term1_f + term2_f
    
    if f_t >= 1.0:
        return 1.0
        
    prob = (f_future - f_t) / (1 - f_t)
    return max(0.0, min(prob, 1.0))

mu = 97.5       # 平均再来間隔 (南海トラフの直近データ 1361 ~ 1946)
alpha = 0.24    # ばらつき係数 (地震調査委員会採用値)
last_event = 1946  # 前回の発生年 (昭和南海地震)
target_year = 2026
t = target_year - last_event

# 30年分を予測
for delta_t in range(1, 31):
  prob = bpt_cdf_30(t, mu, alpha, delta_t)
  print(f"今後 {delta_t} 年以内の発生確率: {prob:.2%}")

今後30年間の発生確率は
ぜひ動画を見てみてください。




まとめ

地震の発生確率は確率分布をもとに
予測が行われています。

政府予測では
今後、30年の間に 60%~90%程度以上の確率で
南海トラフ地震来るという予測です

絶対は無いけど、明日来るかもしれない
いつ来ても良いように備えておきましょう!!!

それでは
 

プログラミング未経験の方のための
プログラミング学習講座を作成しました

その名も
「1時間で学べるPythonプログラミング」


講義動画はこちら




この講座は初学者の方が
短時間でPython言語を学ぶことのできる
プログラミング学習用の講座です

プログラミングが分からないない方は
Python言語を通じて
プログラミングの基礎を学習できます

講座は動画に加えてGoogle Colabを用いて
手元でコードを動かすことのできます
コードがどう動くのかを確認をしながら
進めていってください

資料はここ:
Google Colabの資料


00:00 1.はじめに
02:13 2.導入方法
02:55 3.GoogleColaboratoryの操作方法
06:19 4.Pythonの計算の基礎
27:27 5.Pythonの制御文
42:14 6.Pythonのクラス
49:11 7.Pythonのその他構文
64:30 8.まとめ

なおPythonチートシートを作成しています。

コーディングに迷った際に役に立ち

WEB検索する時間を無くして

作業時間を効率化できます。

note
Pythonチートシート


 

今回は最適化問題を解くことが出来る
OR-Toolsについてです。

解説動画はこちら




OR-Toolsとは


Googleが開発している、組合せ最適化のためのライブラリ

様々な最適化問題を解くためのソルバーが充実していて
C++実装だが、Pythonなどいろいろな言語で使えます。


解ける最適化問題

次のような最適化問題を解くことができます。


1.配送ルート最適化
巡回セールスマン問題 : 1台の車両で最短経路を回る
配送計画問題 : 複数台の車両で分担して配送する

2.スケジューリングと制約プログラミング
決められた制約を全て満たしつつ、最適なスケジュールを組む
従業員のシフト作成
工場のライン計画

3.線形計画・整数計画(LP, MIP)
目的関数を最大化、または最小化する問題を数式で解く手法
生産計画 : 在庫と予算の範囲内で利益の最大化
リソース配分 : 予算内での広告効果最大化の割り当て

4.詰込み・割当問題
ナップサック問題 : 容量制限のあるバッグに価値が最大になるよう詰める
ビンピッキング問題 : サイズの異なる荷物を出来るだけ少ないトラックに詰める

5.ネットワークフロー
最大流問題 : パイプラインで一度に流せる最大量を求める
最小費用問題 : 指定量の輸送で、コストが最も安くなるルートと量の特定


インストール

Google Colab でも無いみたいなので、入れる必要あります。
pip install ortools


OR-Toolsの基本的な使い方

1.ライブラリの読み込み

解く問題に応じて、対応するライブラリを読み込みします
# CP-SATソルバー(制約プログラミング/MIP)
from ortools.sat.python import cp_model

# 線形計画法ソルバー(LP)
from ortools.linear_solver import pywraplp

# 配送計画(Routing)
from ortools.constraint_solver import pywrapcp, routing_enums_pb2

2. 最適化モデル構築

次に最適化モデルを作ります。
基本の「4ステップ」があります。

STEP 1: モデルのインスタンス作成
まず、問題を定義するための「箱」を作ります。

from ortools.sat.python import cp_model

model = cp_model.CpModel()


STEP 2: 変数の作成
「何を求めたいか」を変数として定義します。

整数変数: model.NewIntVar(下限, 上限, '変数名')
ブール変数: model.NewBoolVar('変数名')


STEP 3: 制約条件の追加
model.Add(...) を使って、守らなければならないルールを記述します。

等式・不等式: model.Add(x + y <= 10)
論理制約: 「Aの時だけBを適用する」といった
条件付き制約(OnlyEnforceIf)も可能


STEP 4: 目的関数の設定と実行
「何を最大化(または最小化)したいか」を決め
ソルバーを起動します。

# 最小化の場合
model.Minimize(目的の式)

# ソルバーの起動
solver = cp_model.CpSolver()
result = solver.Solve(model)

ここまでのコードを作れば
最適化問題が解ける様になっていると思います。


問題を解いてみる

簡単なつるかめ算をやって
あっているかを確認してみましょう。


問題1:
「つる」と「かめ」が合計で10匹、足の合計は28本です
それぞれ何匹ずついるでしょうか?

コードはこんな感じになります。
from ortools.sat.python import cp_model

def solve_tsurukame():
    # モデルの作成
    model = cp_model.CpModel()

    # 変数の定義 (0匹以上10匹以下)
    tsuru = model.NewIntVar(0, 10, 'tsuru')
    kame = model.NewIntVar(0, 10, 'kame')

    # 制約1: 合計が10匹
    model.Add(tsuru + kame == 10)

    # 制約2: 足の合計が28本
    model.Add(2 * tsuru + 4 * kame == 28)

    # ソルバーの準備と実行
    solver = cp_model.CpSolver()
    result = solver.Solve(model)

    if result == cp_model.OPTIMAL:
        print(f'つる: {solver.Value(tsuru)} 羽')
        print(f'かめ: {solver.Value(kame)} 匹')

solve_tsurukame()
つる: 6 羽
かめ: 4 匹


問題2:
50円切手と80円切手が計20枚、合計で1240円になるとき
それぞれ何枚ずつあるでしょうか?

from ortools.sat.python import cp_model

def solve_stamps():
    model = cp_model.CpModel()

    # 変数の定義 (0枚以上20枚以下)
    s50 = model.NewIntVar(0, 20, '50yen')
    s80 = model.NewIntVar(0, 20, '80yen')

    # 制約1: 合計20枚
    model.Add(s50 + s80 == 20)

    # 制約2: 合計金額が1240円
    model.Add(50 * s50 + 80 * s80 == 1240)

    solver = cp_model.CpSolver()
    result = solver.Solve(model)

    if result == cp_model.OPTIMAL:
        print(f'50円切手: {solver.Value(s50)} 枚')
        print(f'80円切手: {solver.Value(s80)} 枚')

solve_stamps()
50円切手: 12 枚
80円切手: 8 枚



問題3:

動画内ではすこし難しめの
入試問題なんかもやっています。

是非みてみてください。



まとめ


OR-Toolsは最適化問題を解くのに、かなり有効なライブラリです。

実社会の巨大な課題にも応用可能で
「Amazonのような配送ルート作成」や
「学校の複雑な時間割作成」など
他にもいろいろな最適化に応用できるものがあります。

だだし、使いこなすにはちょっとした
数学の知識が必要かもしれません。

使いこなせるとかなり問題を解ける幅が広がるので
実社会の問題解決にかなり有利かもしれません。

是非使ってみてください
それでは。



 

このページのトップヘ