【Python】ド・モルガンの法則をベン図で理解

:::note warn
**ご注意**
数学的な厳密性やManimの最適な利用方法に関して、改善の余地が含まれている可能性がございます。
:::

## 完成したアニメーション

この記事の手順を実行すると、以下のようなド・モルガンの法則をベン図で理解するためのアニメーションが生成されます。

<iframe width="560" height="315" src="https://www.youtube.com/embed/3KuVobHoqVk?si=A23ailIRQfPwiREB" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>




## 実行環境：Google Colab と Manim

このアニメーションを作成するために、以下のツールを使用しました。

*   **Google Colaboratory (Colab):**
    *   Googleが提供する無料のクラウドベースのJupyter Notebook環境。
    *   ブラウザ上でPythonコードを実行できます。
*   **Manim:**
    *   Pythonで高品質な数学アニメーションを作成するためのライブラリ。
    *   数式を美しく表示したり、グラフや図形をプログラム制御で滑らかに動かしたりすることに長けています。

https://docs.manim.community/en/stable/reference.html

Google アカウントさえあれば、誰でもブラウザ上で Python コードを実行でき、ライブラリのインストールやファイルの保存（Google Drive連携）も手軽に行えます。特に、Manim のような環境構築がやや複雑なライブラリを試す際に、ローカル環境を汚さずに済むという大きなメリットがあります。

## 実行コード

以下に、Google Colabでこの積分アニメーションを作成するための手順を示します。


### 1. 環境構築

Google Colaboratory (Colab) でManimを利用するための環境を構築します。
以下のセルを実行して、Manim本体と、その動作に必要な各種ライブラリをインストールします。

```python
# パッケージリストを更新
!sudo apt update

# Manimの動作に必要な各種ライブラリをインストール
!sudo apt install libcairo2-dev \
    texlive texlive-latex-extra texlive-fonts-extra \
    texlive-latex-recommended texlive-science \
    tipa libpango1.0-dev

# Manim（アニメーション作成ライブラリ）をインストール
!pip install manim

# IPython（対話型シェル）を特定のバージョン(8.21.0)でインストール
!pip install IPython==8.21.0
```

:::note warn
上記のインストールが完了したら、変更をシステム全体に確実に反映させるために、ランタイムを再起動します。
再起動後、インストールしたライブラリは保持されていますので、続けて Manim のコードを実行できます。
:::

次に、日本語フォントをインストールします。

```python
# 日本語フォントをインストール
!apt-get update
!apt-get install -y fonts-noto-cjk
```



より詳しい方法や、Jupyter環境でのManimの活用（マジックコマンドの詳細な使い方など）については、以下のManim Community公式ドキュメントも参考にしてください。

https://docs.manim.community/en/stable/installation/jupyter.html

### 2. アニメーションコードの作成

次に、アニメーションのロジックを記述したPythonコードを `py` というファイル名でColab環境内に保存します。

`%%writefile` は、セル内のテキストを指定したファイル名で保存するColabのマジックコマンドです。





```python
%%writefile de_morgans_law_visualization.py
# manimライブラリから全てのクラス、関数、定数をインポート
from manim import *
# 数値計算ライブラリnumpyをnpという別名でインポート
import numpy as np

# ド・モルガンの法則を視覚化するシーンのためのクラスを定義
class DeMorgansLawScene(Scene): 
    
    # (3Blue1Brown風テーマのための設定定数)
    # 青色C (Manimの定義済みカラー)
    B3B_BLUE = BLUE_C              
    # 緑色C (Manimの定義済みカラー)
    B3B_GREEN = GREEN_C            
    # 青と緑を0.5の割合で混合した色 (共通部分用)
    B3B_MIXED_INTERSECTION_COLOR = interpolate_color(B3B_BLUE, B3B_GREEN, 0.5)
    # 金色C (Manimの定義済みカラー、同値関係表現用)
    B3B_GOLD_EQUIVALENCE = GOLD_C  
    # 金色C (Manimの定義済みカラー、アクセント用)
    B3B_GOLD_ACCENT = GOLD_C       
    # 白色 (テキスト用)
    B3B_WHITE_TEXT = WHITE
    # 灰色C (Manimの定義済みカラー、フレーム用)
    B3B_GRAY_FRAME = GRAY_C        
    # 集合Aの色として青色Cを設定
    SET_A_COLOR = B3B_BLUE
    # 集合Bの色として緑色Cを設定
    SET_B_COLOR = B3B_GREEN
    # 共通部分の色として混合色を設定
    INTERSECTION_COLOR = B3B_MIXED_INTERSECTION_COLOR
    # 全体集合Uの矩形の色として灰色Cを設定
    U_RECT_COLOR = B3B_GRAY_FRAME
    # テキストの色として白色を設定
    TEXT_COLOR = B3B_WHITE_TEXT
    # 常に表示されるメインタイトルの色として白色を設定
    PERSISTENT_MAIN_TITLE_COLOR = B3B_WHITE_TEXT
    # ド・モルガンの法則におけるnot A (Aの補集合) の色として集合Aの色を設定 (強調のため)
    DM_NOT_A_COLOR = SET_A_COLOR
    # ド・モルガンの法則におけるnot B (Bの補集合) の色として集合Bの色を設定 (強調のため)
    DM_NOT_B_COLOR = SET_B_COLOR
    # ド・モルガンの法則における同値関係を示す色として金色Cを設定
    DM_EQUIVALENCE_COLOR = B3B_GOLD_EQUIVALENCE
    # 等号の色として白色を設定
    EQUALS_SIGN_COLOR = B3B_WHITE_TEXT
    # 等号を強調表示する際の色として金色Cを設定
    EQUALS_SIGN_INDICATE_COLOR = B3B_GOLD_ACCENT
    # ヘルパー図形用の単色 (白色)
    HELPER_SOLID_COLOR = WHITE 
    # 常に表示されるメインタイトルの初期フォントサイズ
    PERSISTENT_MAIN_TITLE_FONT_SIZE_INITIAL = 48 
    # 常に表示されるメインタイトルの最終的なスケール係数
    PERSISTENT_MAIN_TITLE_SCALE_FINAL = 0.75     
    # 法則のタイトルのフォントサイズ
    LAW_TITLE_FONT_SIZE = 36 
    # 法則のタイトルの日本語フォントサイズのスケール係数
    LAW_TITLE_JP_FONT_SIZE_SCALE = 0.85 
    # 数式のフォントサイズ
    FORMULA_FONT_SIZE = 42
    # ド・モルガンの法則のラベル (A, B) のフォントサイズ
    DM_LABEL_FONT_SIZE = 40
    # 等号のフォントサイズの乗数; ド・モルガンの法則(デュアル表示)の集合の半径; 集合の線の太さ; 全体集合Uの矩形の線の太さ
    EQUALS_SIGN_FONT_SIZE_MULTIPLIER = 1.4; SET_RADIUS_DM_DUAL = 1.2; SET_STROKE_WIDTH_DM_DUAL = 3; U_RECT_STROKE_WIDTH = 2
    # ド・モルガンの法則の塗りつぶしの不透明度
    DM_FILL_OPACITY = 0.75 
    # アニメーション実行時間 (短); (中); (長); (超長)
    ANIM_RUN_TIME_SHORT = 0.4; ANIM_RUN_TIME_MEDIUM = 0.7; ANIM_RUN_TIME_LONG = 1.0; ANIM_RUN_TIME_XLONG = 1.5
    # タイトル書き込みアニメーションの実行時間
    ANIM_RUN_TIME_TITLE_WRITE = 1.2 
    # タイトル移動アニメーションの実行時間
    ANIM_RUN_TIME_TITLE_MOVE = 1.0
    # 待機時間 (短); (中); (長); (超長)
    WAIT_TIME_SHORT = 0.2; WAIT_TIME_MEDIUM = 0.5; WAIT_TIME_LONG = 1.0; WAIT_TIME_XLONG = 1.5
    # タイトル書き込み後の待機時間
    WAIT_TIME_AFTER_TITLE_WRITE = 0.7
    # タイトル移動後の待機時間
    WAIT_TIME_AFTER_TITLE_MOVE = 0.3
    # ド・モルガンの法則の各ステップ間の待機時間; 強調表示アニメーションの時間; 最後のフェードアウト時間; 最後の待機時間
    WAIT_TIME_DM_STEP = 1.2; ANIM_INDICATE_TIME = 0.6; FINAL_FADE_OUT_TIME = 1.0; FINAL_WAIT_TIME = 1.0
    # 常に表示されるメインタイトルのバッファ(間隔); 法則タイトルのバッファ; レイアウトの上端方向
    PERSISTENT_MAIN_TITLE_BUFF = 0.3; LAW_TITLE_BUFF = MED_LARGE_BUFF; LAYOUT_EDGE_UP = UP
    # ド・モルガンの法則(デュアル表示)の全体集合Uの矩形のY軸シフト量; 幅; 高さ
    LAYOUT_U_RECT_SHIFT_Y_DM = DOWN * 0.2; LAYOUT_U_RECT_WIDTH_DM_DUAL = 6.0; LAYOUT_U_RECT_HEIGHT_DM_DUAL = 4.0
    # ド・モルガンの法則(デュアル表示)の水平オフセット量 (Uの矩形の半分の幅 + 0.5)
    LAYOUT_HORIZONTAL_OFFSET_DM_DUAL = LAYOUT_U_RECT_WIDTH_DM_DUAL / 2 + 0.5
    # ド・モルガンの法則(デュアル表示)の集合AのX軸シフト量; 集合BのX軸シフト量
    LAYOUT_SET_A_SHIFT_X_DM_DUAL = -0.8; LAYOUT_SET_B_SHIFT_X_DM_DUAL = 0.8
    # ド・モルガンの法則(デュアル表示)のラベルAのシフト量 (左上方向); ラベルBのシフト量 (右上方向)
    LAYOUT_LABEL_A_SHIFT_DM_DUAL = UL * 0.6; LAYOUT_LABEL_B_SHIFT_DM_DUAL = UR * 0.6
    # ド・モルガンの法則(デュアル表示)の数式とベン図の間のバッファ; 常に表示されるメインタイトルのテキスト
    LAYOUT_FORMULA_BUFF_DUAL = 0.7; PERSISTENT_MAIN_TITLE_TEXT = "ド・モルガンの法則"
    # ベン図の間の等号のY軸オフセット
    EQUALS_SIGN_BETWEEN_VENN_Y_OFFSET = 0
    # デフォルトの日本語フォント
    DEFAULT_JP_FONT = "Noto Serif CJK JP"    
    # 日本語タイトルと数式の間のバッファ
    TITLE_JP_FORMULA_BUFF = 0.1

    # アニメーションの構築メソッド
    def construct(self):
        # カメラの背景色を黒に設定
        self.camera.background_color = BLACK 

        # 常に表示されるメインタイトルオブジェクトを作成
        self.persistent_main_title = Text(
            self.PERSISTENT_MAIN_TITLE_TEXT, # 表示するテキスト
            font_size=self.PERSISTENT_MAIN_TITLE_FONT_SIZE_INITIAL, # 初期フォントサイズ
            color=self.PERSISTENT_MAIN_TITLE_COLOR, # テキストの色
            font=self.DEFAULT_JP_FONT # 使用するフォント
        )
        # メインタイトルを画面中央に配置
        self.persistent_main_title.move_to(ORIGIN)

        # メインタイトルを書き込むアニメーションを再生
        self.play(Write(self.persistent_main_title), run_time=self.ANIM_RUN_TIME_TITLE_WRITE)
        # タイトル書き込み後に少し待機
        self.wait(self.WAIT_TIME_AFTER_TITLE_WRITE)

        # メインタイトルを画面上部に移動し、スケールを小さくするアニメーションを再生
        self.play(
            self.persistent_main_title.animate.to_edge( # animateプロパティでアニメーションを指定
                self.LAYOUT_EDGE_UP, buff=self.PERSISTENT_MAIN_TITLE_BUFF # 上端に移動、バッファを指定
            ).scale(self.PERSISTENT_MAIN_TITLE_SCALE_FINAL), # スケール変更
            run_time=self.ANIM_RUN_TIME_TITLE_MOVE # アニメーション実行時間
        )
        # タイトル移動後に少し待機
        self.wait(self.WAIT_TIME_AFTER_TITLE_MOVE)
        
        # ド・モルガンの法則のデュアル表示用ターゲットオブジェクト群を準備
        venn_setups = self.prepare_de_morgans_dual_targets()
        # 左側のベン図の構成要素を取得 (全体集合U、集合A、集合B、ラベルA、ラベルB)
        U_L, A_L_orig, B_L_orig, lbl_A_L, lbl_B_L = venn_setups[0:5]
        # 右側のベン図の構成要素を取得 (全体集合U、集合A、集合B、ラベルA、ラベルB)
        U_R, A_R_orig, B_R_orig, lbl_A_R, lbl_B_R = venn_setups[5:10]
            
        # アニメーション用にコピーを作成 (オリジナルを後で使う場合やグループ化のために推奨)
        # 左側のベン図セットアップをVGroupとしてグループ化
        dm_setup_left = VGroup(U_L.copy(), A_L_orig.copy(), B_L_orig.copy(), lbl_A_L.copy(), lbl_B_L.copy())
        # 右側のベン図セットアップをVGroupとしてグループ化
        dm_setup_right = VGroup(U_R.copy(), A_R_orig.copy(), B_R_orig.copy(), lbl_A_R.copy(), lbl_B_R.copy())
        
        # 左右のベン図セットアップを作成するアニメーションを再生し、その後待機
        self.play(Create(dm_setup_left), Create(dm_setup_right), run_time=self.ANIM_RUN_TIME_LONG); self.wait(self.WAIT_TIME_LONG)
        
        # ド・モルガンの法則のアニメーションをデュアル表示で再生 (内部でコピーまたは参照されるオリジナルオブジェクトを渡す)
        self.play_de_morgans_law_animations_dual(U_L, A_L_orig, B_L_orig, U_R, A_R_orig, B_R_orig)
        
        # メインタイトルと左右のベン図セットアップをフェードアウトさせるアニメーションを再生
        self.play(FadeOut(self.persistent_main_title), FadeOut(dm_setup_left), FadeOut(dm_setup_right), run_time=self.FINAL_FADE_OUT_TIME)
        # 最後に少し待機
        self.wait(self.FINAL_WAIT_TIME)

    # ベン図のセットアップ用Mobject群を作成する内部メソッド
    def _create_venn_setup_mobjects(self, base_center_offset):
        # 全体集合Uを表す矩形を作成
        U_rect = Rectangle(
            width=self.LAYOUT_U_RECT_WIDTH_DM_DUAL, # 幅
            height=self.LAYOUT_U_RECT_HEIGHT_DM_DUAL, # 高さ
            color=self.U_RECT_COLOR, # 色
            stroke_width=self.U_RECT_STROKE_WIDTH # 線の太さ
        ).move_to(base_center_offset) # 指定されたオフセット位置に移動

        # 矩形の中心座標を取得
        current_center = U_rect.get_center()
        # 集合Aの中心座標を計算 (矩形中心からX軸方向にシフト)
        target_center_A = current_center + np.array([self.LAYOUT_SET_A_SHIFT_X_DM_DUAL, 0, 0])
        # 集合Bの中心座標を計算 (矩形中心からX軸方向にシフト)
        target_center_B = current_center + np.array([self.LAYOUT_SET_B_SHIFT_X_DM_DUAL, 0, 0])

        # 集合Aを表す円を作成
        set_A = Circle(
            radius=self.SET_RADIUS_DM_DUAL, # 半径
            color=self.SET_A_COLOR, # 色
            stroke_width=self.SET_STROKE_WIDTH_DM_DUAL # 線の太さ
        ).move_to(target_center_A) # 計算した中心位置に移動

        # 集合Bを表す円を作成
        set_B = Circle(
            radius=self.SET_RADIUS_DM_DUAL, # 半径
            color=self.SET_B_COLOR, # 色
            stroke_width=self.SET_STROKE_WIDTH_DM_DUAL # 線の太さ
        ).move_to(target_center_B) # 計算した中心位置に移動

        # 集合Aのラベル (MathTexで"A") を作成
        label_A = MathTex("A", font_size=self.DM_LABEL_FONT_SIZE, color=self.TEXT_COLOR)\
            .move_to(set_A.get_center() + self.LAYOUT_LABEL_A_SHIFT_DM_DUAL) # 集合Aの中心から指定オフセット分離れた位置へ
        # 集合Bのラベル (MathTexで"B") を作成
        label_B = MathTex("B", font_size=self.DM_LABEL_FONT_SIZE, color=self.TEXT_COLOR)\
            .move_to(set_B.get_center() + self.LAYOUT_LABEL_B_SHIFT_DM_DUAL) # 集合Bの中心から指定オフセット分離れた位置へ

        # 作成したMobject群をタプルで返す
        return U_rect, set_A, set_B, label_A, label_B

    # ド・モルガンの法則のデュアル表示用ターゲットオブジェクト群を準備するメソッド
    def prepare_de_morgans_dual_targets(self):
        # 左側ベン図の中心オフセットを計算
        offset_L = LEFT * self.LAYOUT_HORIZONTAL_OFFSET_DM_DUAL + self.LAYOUT_U_RECT_SHIFT_Y_DM
        # 右側ベン図の中心オフセットを計算
        offset_R = RIGHT * self.LAYOUT_HORIZONTAL_OFFSET_DM_DUAL + self.LAYOUT_U_RECT_SHIFT_Y_DM

        # 左側のベン図セットアップ用Mobject群を作成
        U_rect_left, set_A_left, set_B_left, label_A_left, label_B_left = \
            self._create_venn_setup_mobjects(offset_L) # 内部メソッドを呼び出し

        # 右側のベン図セットアップ用Mobject群を作成
        U_rect_right, set_A_right, set_B_right, label_A_right, label_B_right = \
            self._create_venn_setup_mobjects(offset_R) # 内部メソッドを呼び出し
            
        # 左右のMobject群をまとめたタプルを返す
        return (U_rect_left, set_A_left, set_B_left, label_A_left, label_B_left,
                U_rect_right, set_A_right, set_B_right, label_A_right, label_B_right)


    # ド・モルガンの法則のアニメーションをデュアル表示で再生するメソッド
    def play_de_morgans_law_animations_dual(self, U_L, A_L_orig, B_L_orig, U_R, A_R_orig, B_R_orig):
        # アニメーション用にオリジナルオブジェクトのコピーを作成 (dm_setup_left/right の一部である場合、オリジナルを変更しないため)
        A_L, B_L = A_L_orig.copy(), B_L_orig.copy()
        A_R, B_R = A_R_orig.copy(), B_R_orig.copy()

        # --- 共通のジオメトリと位置 ---
        # 左側の全体集合Uのジオメトリ (塗りつぶしなし、線なしの透明な矩形)
        universe_L_geom = Rectangle(width=U_L.width, height=U_L.height, stroke_width=0, fill_opacity=0).move_to(U_L.get_center())
        # 右側の全体集合Uのジオメトリ (塗りつぶしなし、線なしの透明な矩形)
        universe_R_geom = Rectangle(width=U_R.width, height=U_R.height, stroke_width=0, fill_opacity=0).move_to(U_R.get_center())
        
        # 左側集合Aのソリッド版 (ヘルパー色で塗りつぶし、線なし)
        A_L_solid = A_L.copy().set_fill(color=self.HELPER_SOLID_COLOR, opacity=1.0).set_stroke(width=0)
        # 左側集合Bのソリッド版 (ヘルパー色で塗りつぶし、線なし)
        B_L_solid = B_L.copy().set_fill(color=self.HELPER_SOLID_COLOR, opacity=1.0).set_stroke(width=0)
        # 右側集合Aのソリッド版 (ヘルパー色で塗りつぶし、線なし)
        A_R_solid = A_R.copy().set_fill(color=self.HELPER_SOLID_COLOR, opacity=1.0).set_stroke(width=0)
        # 右側集合Bのソリッド版 (ヘルパー色で塗りつぶし、線なし)
        B_R_solid = B_R.copy().set_fill(color=self.HELPER_SOLID_COLOR, opacity=1.0).set_stroke(width=0)

        # 左側ベン図の下に表示する数式の位置
        formula_pos_L = U_L.get_bottom() + DOWN * self.LAYOUT_FORMULA_BUFF_DUAL
        # 右側ベン図の下に表示する数式の位置
        formula_pos_R = U_R.get_bottom() + DOWN * self.LAYOUT_FORMULA_BUFF_DUAL
        
        # 左右のベン図の間に表示する等号のX座標
        equals_sign_between_venn_x_pos = (U_L.get_right()[0] + U_R.get_left()[0]) / 2
        # 左右のベン図の間に表示する等号のY座標 (U_Lの中心Y座標 + オフセット)
        equals_sign_between_venn_y_pos = U_L.get_center()[1] + self.EQUALS_SIGN_BETWEEN_VENN_Y_OFFSET
        # 左右のベン図の間に表示する等号の位置ベクトル
        formula_pos_equals_between = np.array([equals_sign_between_venn_x_pos, equals_sign_between_venn_y_pos, 0])
        
        # 等号のフォントサイズ (基本数式フォントサイズに乗数をかける)
        eq_sign_font_size = self.FORMULA_FONT_SIZE * self.EQUALS_SIGN_FONT_SIZE_MULTIPLIER
        
        # 法則タイトルのY座標 (メインタイトルの下端 - バッファ)
        y_pos_law_title = self.persistent_main_title.get_bottom()[1] - self.LAW_TITLE_BUFF 
        # 法則タイトルのX座標 (左右のU矩形グループの中心X座標)
        x_pos_law_title = VGroup(U_L, U_R).get_center()[0]
        # 法則タイトルの日本語部分の実効フォントサイズ
        effective_law_title_jp_font_size = self.LAW_TITLE_FONT_SIZE * self.LAW_TITLE_JP_FONT_SIZE_SCALE

        # --- 法則1のアニメーションを呼び出し ---
        self._animate_first_law(
            U_L, A_L, B_L, U_R, A_R, B_R, # 左右のベン図要素
            universe_L_geom, universe_R_geom, # 左右の全体集合ジオメトリ
            A_L_solid, B_L_solid, A_R_solid, B_R_solid, # 左右のソリッド集合
            formula_pos_L, formula_pos_R, formula_pos_equals_between, # 数式と等号の位置
            eq_sign_font_size, x_pos_law_title, y_pos_law_title, effective_law_title_jp_font_size # フォントサイズとタイトル位置
        )

        # --- 法則2のアニメーションを呼び出し ---
        self._animate_second_law(
            U_L, A_L, B_L, U_R, A_R, B_R, # 左右のベン図要素
            universe_L_geom, universe_R_geom, # 左右の全体集合ジオメトリ
            A_L_solid, B_L_solid, A_R_solid, B_R_solid, # 左右のソリッド集合
            formula_pos_L, formula_pos_R, formula_pos_equals_between, # 数式と等号の位置
            eq_sign_font_size, x_pos_law_title, y_pos_law_title, effective_law_title_jp_font_size # フォントサイズとタイトル位置
        )

    # ド・モルガンの第一法則のアニメーションを行う内部メソッド
    def _animate_first_law(self, U_L, A_L, B_L, U_R, A_R, B_R,
                           universe_L_geom, universe_R_geom,
                           A_L_solid, B_L_solid, A_R_solid, B_R_solid,
                           formula_pos_L, formula_pos_R, formula_pos_equals_between,
                           eq_sign_font_size, x_pos_law_title, y_pos_law_title, effective_law_title_jp_font_size):
        # --- 法則1: \overline{A \cup B} = \overline{A} \cap \overline{B} ---
        # 法則1の日本語タイトルテキストを作成
        law1_jp_text = Text("法則1: ", font=self.DEFAULT_JP_FONT, font_size=effective_law_title_jp_font_size, color=self.TEXT_COLOR)
        # 法則1の数式タイトルテキストを作成
        law1_formula_text = MathTex(r"\overline{A \cup B} = \overline{A} \cap \overline{B}", font_size=self.LAW_TITLE_FONT_SIZE, color=self.TEXT_COLOR)
        # 日本語タイトルと数式タイトルをグループ化し、右に配置 (下端揃え)
        title_law1 = VGroup(law1_jp_text, law1_formula_text).arrange(RIGHT, buff=self.TITLE_JP_FORMULA_BUFF, aligned_edge=DOWN)
        # タイトルを指定位置に移動
        title_law1.move_to(np.array([x_pos_law_title, y_pos_law_title, 0]))
        
        # 法則1のタイトルを書き込むアニメーションを再生し、待機
        self.play(Write(title_law1), run_time=self.ANIM_RUN_TIME_LONG); self.wait(self.WAIT_TIME_MEDIUM)
        
        # LHS (左辺): A U B (AとBの和集合)
        # 和集合表示用のAの塗りつぶしコピーを作成
        A_L_fill_for_union = A_L.copy().set_fill(self.SET_A_COLOR, opacity=self.DM_FILL_OPACITY).set_stroke(width=0)
        # 和集合表示用のBの塗りつぶしコピーを作成
        B_L_fill_for_union = B_L.copy().set_fill(self.SET_B_COLOR, opacity=self.DM_FILL_OPACITY).set_stroke(width=0)
        # 左辺ステップ1の数式説明 ("A U B") を作成し、位置を設定
        op_L_1_desc_step1 = MathTex(r"A \cup B", font_size=self.FORMULA_FONT_SIZE, color=self.TEXT_COLOR).move_to(formula_pos_L)
        # 数式説明の書き込みと、AとBの塗りつぶしフェードインアニメーションを再生し、待機
        self.play(Write(op_L_1_desc_step1), FadeIn(A_L_fill_for_union), FadeIn(B_L_fill_for_union), run_time=self.ANIM_RUN_TIME_MEDIUM); self.wait(self.WAIT_TIME_DM_STEP)
        
        # LHS (左辺): overline{A U B} (AとBの和集合の補集合)
        # 左辺最終形の数式説明を作成し、位置を設定
        op_L_1_desc_final = MathTex(r"\overline{A \cup B}", font_size=self.FORMULA_FONT_SIZE, color=self.TEXT_COLOR).move_to(formula_pos_L)
        # AとBのソリッド版の和集合 (ヘルパーオブジェクト) を作成
        union_A_B_L_helper = Union(A_L_solid, B_L_solid, stroke_width=0, fill_color=self.HELPER_SOLID_COLOR, fill_opacity=1.0)
        # 補集合の被減数 (全体集合U) の塗りつぶしコピーを作成 (同値色)
        minuend_L_1 = universe_L_geom.copy().set_fill(color=self.DM_EQUIVALENCE_COLOR, opacity=self.DM_FILL_OPACITY) 
        # 左辺の塗りつぶし (全体集合 - (A U B)) を作成 (同値色)
        lhs_fill_1 = Difference(minuend_L_1, union_A_B_L_helper, stroke_width=0).set_fill(color=self.DM_EQUIVALENCE_COLOR, opacity=self.DM_FILL_OPACITY).set_stroke(width=0)
        # 数式説明の変形、AとBの和集合のフェードアウト、左辺の補集合のフェードインアニメーションを再生し、待機
        self.play(Transform(op_L_1_desc_step1, op_L_1_desc_final), FadeOut(A_L_fill_for_union), FadeOut(B_L_fill_for_union), FadeIn(lhs_fill_1), run_time=self.ANIM_RUN_TIME_LONG); self.wait(self.WAIT_TIME_DM_STEP)

        # RHS (右辺): overline{A} (Aの補集合)
        # 右辺ステップ1の数式説明 ("overline{A}") を作成し、位置を設定
        op_R_1_desc_step1_rhs = MathTex(r"\overline{A}", font_size=self.FORMULA_FONT_SIZE, color=self.TEXT_COLOR).move_to(formula_pos_R)
        # Aの補集合の被減数 (全体集合U) の塗りつぶしコピーを作成 (not A の色)
        minuend_R_notA = universe_R_geom.copy().set_fill(color=self.DM_NOT_A_COLOR, opacity=self.DM_FILL_OPACITY)
        # Aの補集合の塗りつぶし表示 (全体集合 - A) を作成 (not A の色)
        not_A_R_fill_display = Difference(minuend_R_notA, A_R_solid, stroke_width=0).set_fill(color=self.DM_NOT_A_COLOR, opacity=self.DM_FILL_OPACITY).set_stroke(width=0)
        # 数式説明の書き込みと、Aの補集合のフェードインアニメーションを再生し、待機
        self.play(Write(op_R_1_desc_step1_rhs), FadeIn(not_A_R_fill_display), run_time=self.ANIM_RUN_TIME_MEDIUM); self.wait(self.WAIT_TIME_DM_STEP)
        
        # RHS (右辺): overline{B} (Bの補集合)
        # 右辺ステップ2の数式説明 ("overline{B}") を作成し、位置を設定
        op_R_1_desc_step2_rhs = MathTex(r"\overline{B}", font_size=self.FORMULA_FONT_SIZE, color=self.TEXT_COLOR).move_to(formula_pos_R)
        # Bの補集合の被減数 (全体集合U) の塗りつぶしコピーを作成 (not B の色)
        minuend_R_notB = universe_R_geom.copy().set_fill(color=self.DM_NOT_B_COLOR, opacity=self.DM_FILL_OPACITY)
        # Bの補集合の塗りつぶし表示 (全体集合 - B) を作成 (not B の色)
        not_B_R_fill_display = Difference(minuend_R_notB, B_R_solid, stroke_width=0).set_fill(color=self.DM_NOT_B_COLOR, opacity=self.DM_FILL_OPACITY).set_stroke(width=0)
        # 数式説明の変形、Aの補集合のフェードアウト、Bの補集合のフェードインアニメーションを再生し、待機
        self.play(Transform(op_R_1_desc_step1_rhs, op_R_1_desc_step2_rhs), FadeOut(not_A_R_fill_display, shift=RIGHT*0.1), FadeIn(not_B_R_fill_display, shift=LEFT*0.1), run_time=self.ANIM_RUN_TIME_MEDIUM); self.wait(self.WAIT_TIME_DM_STEP)
        
        # RHS (右辺): overline{A} cap overline{B} (Aの補集合 と Bの補集合 の共通部分)
        # 右辺最終形の数式説明を作成し、位置を設定
        op_R_1_desc_final_rhs = MathTex(r"\overline{A} \cap \overline{B}", font_size=self.FORMULA_FONT_SIZE, color=self.TEXT_COLOR).move_to(formula_pos_R)
        # Aの補集合 (ヘルパーオブジェクト) を作成
        not_A_R_helper = Difference(universe_R_geom.copy().set_fill(self.HELPER_SOLID_COLOR, 1.0), A_R_solid, stroke_width=0, fill_color=self.HELPER_SOLID_COLOR, fill_opacity=1.0)
        # Bの補集合 (ヘルパーオブジェクト) を作成
        not_B_R_helper = Difference(universe_R_geom.copy().set_fill(self.HELPER_SOLID_COLOR, 1.0), B_R_solid, stroke_width=0, fill_color=self.HELPER_SOLID_COLOR, fill_opacity=1.0)
        # 右辺の塗りつぶし (Aの補集合 と Bの補集合 の共通部分) を作成 (同値色)
        rhs_fill_1 = Intersection(not_A_R_helper, not_B_R_helper, stroke_width=0).set_fill(color=self.DM_EQUIVALENCE_COLOR, opacity=self.DM_FILL_OPACITY).set_stroke(width=0)
        # 数式説明の変形、Bの補集合のフェードアウト、右辺の共通部分のフェードインアニメーションを再生
        self.play(Transform(op_R_1_desc_step1_rhs, op_R_1_desc_final_rhs), FadeOut(not_B_R_fill_display, shift=RIGHT*0.1), FadeIn(rhs_fill_1, shift=LEFT*0.1), run_time=self.ANIM_RUN_TIME_LONG)
        
        # 等号 ("=") を作成し、位置を設定
        equals_sign_1 = MathTex("=", font_size=eq_sign_font_size, color=self.EQUALS_SIGN_COLOR).move_to(formula_pos_equals_between)
        # 等号を書き込むアニメーションを再生し、その後強調表示アニメーションを再生し、待機
        self.play(Write(equals_sign_1), run_time=self.ANIM_RUN_TIME_SHORT); self.play(Indicate(equals_sign_1, color=self.EQUALS_SIGN_INDICATE_COLOR, scale_factor=1.2), run_time=self.ANIM_INDICATE_TIME); self.wait(self.WAIT_TIME_XLONG)
        
        # 法則1の視覚要素 (塗りつぶし、数式説明、等号、タイトル) をグループ化
        law1_visuals_group = VGroup(lhs_fill_1, op_L_1_desc_step1, rhs_fill_1, op_R_1_desc_step1_rhs, equals_sign_1, title_law1)
        # 法則1の視覚要素グループをフェードアウトさせるアニメーションを再生し、待機
        self.play(FadeOut(law1_visuals_group), run_time=self.ANIM_RUN_TIME_MEDIUM); self.wait(self.WAIT_TIME_MEDIUM)

    # ド・モルガンの第二法則のアニメーションを行う内部メソッド
    def _animate_second_law(self, U_L, A_L, B_L, U_R, A_R, B_R,
                            universe_L_geom, universe_R_geom,
                            A_L_solid, B_L_solid, A_R_solid, B_R_solid,
                            formula_pos_L, formula_pos_R, formula_pos_equals_between,
                            eq_sign_font_size, x_pos_law_title, y_pos_law_title, effective_law_title_jp_font_size):
        # --- 法則2: \overline{A \cap B} = \overline{A} \cup \overline{B} ---
        # 法則2の日本語タイトルテキストを作成
        law2_jp_text = Text("法則2: ", font=self.DEFAULT_JP_FONT, font_size=effective_law_title_jp_font_size, color=self.TEXT_COLOR)
        # 法則2の数式タイトルテキストを作成
        law2_formula_text = MathTex(r"\overline{A \cap B} = \overline{A} \cup \overline{B}", font_size=self.LAW_TITLE_FONT_SIZE, color=self.TEXT_COLOR)
        # 日本語タイトルと数式タイトルをグループ化し、右に配置 (下端揃え)
        title_law2 = VGroup(law2_jp_text, law2_formula_text).arrange(RIGHT, buff=self.TITLE_JP_FORMULA_BUFF, aligned_edge=DOWN)
        # タイトルを指定位置に移動
        title_law2.move_to(np.array([x_pos_law_title, y_pos_law_title, 0]))
        
        # 法則2のタイトルを書き込むアニメーションを再生し、待機
        self.play(Write(title_law2), run_time=self.ANIM_RUN_TIME_LONG); self.wait(self.WAIT_TIME_MEDIUM)
        
        # LHS (左辺): A cap B (AとBの共通部分)
        # 左辺ステップ1の数式説明 ("A cap B") を作成し、位置を設定
        op_L_2_desc_step1 = MathTex(r"A \cap B", font_size=self.FORMULA_FONT_SIZE, color=self.TEXT_COLOR).move_to(formula_pos_L)
        # 共通部分表示用のAのコピーを作成 (塗りつぶしなし)
        A_L_for_intersect_display = A_L.copy().set_fill(opacity=0) 
        # 共通部分表示用のBのコピーを作成 (塗りつぶしなし)
        B_L_for_intersect_display = B_L.copy().set_fill(opacity=0)
        # AとBの共通部分の塗りつぶし表示を作成 (共通部分の色)
        intersection_fill_display = Intersection(A_L_for_intersect_display, B_L_for_intersect_display, fill_color=self.INTERSECTION_COLOR, fill_opacity=self.DM_FILL_OPACITY, stroke_width=0)
        # 共通部分がない場合 (点が0個の場合)、空のVMobjectで置き換える (エラー回避)
        if intersection_fill_display.get_num_points() == 0: intersection_fill_display = VMobject() 
        # 数式説明の書き込みと、共通部分のフェードインアニメーションを再生し、待機
        self.play(Write(op_L_2_desc_step1), FadeIn(intersection_fill_display), run_time=self.ANIM_RUN_TIME_MEDIUM); self.wait(self.WAIT_TIME_DM_STEP)
        
        # LHS (左辺): overline{A cap B} (AとBの共通部分の補集合)
        # 左辺最終形の数式説明を作成し、位置を設定
        op_L_2_desc_final = MathTex(r"\overline{A \cap B}", font_size=self.FORMULA_FONT_SIZE, color=self.TEXT_COLOR).move_to(formula_pos_L)
        # AとBのソリッド版の共通部分 (ヘルパーオブジェクト) を作成
        intersection_A_B_L_helper = Intersection(A_L_solid, B_L_solid, stroke_width=0, fill_color=self.HELPER_SOLID_COLOR, fill_opacity=1.0)
        # 補集合の被減数 (全体集合U) の塗りつぶしコピーを作成 (同値色)
        minuend_L_2 = universe_L_geom.copy().set_fill(color=self.DM_EQUIVALENCE_COLOR, opacity=self.DM_FILL_OPACITY)
        # 左辺の塗りつぶし (全体集合 - (A cap B)) を作成 (同値色)
        lhs_fill_2 = Difference(minuend_L_2, intersection_A_B_L_helper, stroke_width=0).set_fill(color=self.DM_EQUIVALENCE_COLOR, opacity=self.DM_FILL_OPACITY).set_stroke(width=0)
        # 数式説明の変形、共通部分のフェードアウト、左辺の補集合のフェードインアニメーションを再生し、待機
        self.play(Transform(op_L_2_desc_step1, op_L_2_desc_final), FadeOut(intersection_fill_display), FadeIn(lhs_fill_2), run_time=self.ANIM_RUN_TIME_LONG); self.wait(self.WAIT_TIME_DM_STEP)

        # RHS (右辺): overline{A} (Aの補集合)
        # 右辺ステップ1の数式説明 ("overline{A}") を作成し、位置を設定
        op_R_2_desc_step1_rhs = MathTex(r"\overline{A}", font_size=self.FORMULA_FONT_SIZE, color=self.TEXT_COLOR).move_to(formula_pos_R)
        # Aの補集合の被減数 (全体集合U) の塗りつぶしコピーを作成 (not A の色、法則2用)
        minuend_R_notA_law2 = universe_R_geom.copy().set_fill(color=self.DM_NOT_A_COLOR, opacity=self.DM_FILL_OPACITY)
        # Aの補集合の塗りつぶし表示 (全体集合 - A) を作成 (not A の色、法則2用)
        not_A_R_fill_display_law2 = Difference(minuend_R_notA_law2, A_R_solid, stroke_width=0).set_fill(color=self.DM_NOT_A_COLOR, opacity=self.DM_FILL_OPACITY).set_stroke(width=0)
        # 数式説明の書き込みと、Aの補集合のフェードインアニメーションを再生し、待機
        self.play(Write(op_R_2_desc_step1_rhs), FadeIn(not_A_R_fill_display_law2), run_time=self.ANIM_RUN_TIME_MEDIUM); self.wait(self.WAIT_TIME_DM_STEP)
        
        # RHS (右辺): overline{B} (Bの補集合)
        # 右辺ステップ2の数式説明 ("overline{B}") を作成し、位置を設定
        op_R_2_desc_step2_rhs = MathTex(r"\overline{B}", font_size=self.FORMULA_FONT_SIZE, color=self.TEXT_COLOR).move_to(formula_pos_R)
        # Bの補集合の被減数 (全体集合U) の塗りつぶしコピーを作成 (not B の色、法則2用)
        minuend_R_notB_law2 = universe_R_geom.copy().set_fill(color=self.DM_NOT_B_COLOR, opacity=self.DM_FILL_OPACITY)
        # Bの補集合の塗りつぶし表示 (全体集合 - B) を作成 (not B の色、法則2用)
        not_B_R_fill_display_law2 = Difference(minuend_R_notB_law2, B_R_solid, stroke_width=0).set_fill(color=self.DM_NOT_B_COLOR, opacity=self.DM_FILL_OPACITY).set_stroke(width=0)
        # 数式説明の変形、Aの補集合のフェードアウト、Bの補集合のフェードインアニメーションを再生し、待機
        self.play(Transform(op_R_2_desc_step1_rhs, op_R_2_desc_step2_rhs), FadeOut(not_A_R_fill_display_law2, shift=RIGHT*0.1), FadeIn(not_B_R_fill_display_law2, shift=LEFT*0.1), run_time=self.ANIM_RUN_TIME_MEDIUM); self.wait(self.WAIT_TIME_DM_STEP)
        
        # RHS (右辺): overline{A} U overline{B} (Aの補集合 と Bの補集合 の和集合)
        # 右辺最終形の数式説明を作成し、位置を設定
        op_R_2_desc_final_rhs = MathTex(r"\overline{A} \cup \overline{B}", font_size=self.FORMULA_FONT_SIZE, color=self.TEXT_COLOR).move_to(formula_pos_R)
        # Aの補集合 (和集合用ヘルパーオブジェクト) を作成
        not_A_R_for_union_helper = Difference(universe_R_geom.copy().set_fill(self.HELPER_SOLID_COLOR, 1.0), A_R_solid, stroke_width=0, fill_color=self.HELPER_SOLID_COLOR, fill_opacity=1.0)
        # Bの補集合 (和集合用ヘルパーオブジェクト) を作成
        not_B_R_for_union_helper = Difference(universe_R_geom.copy().set_fill(self.HELPER_SOLID_COLOR, 1.0), B_R_solid, stroke_width=0, fill_color=self.HELPER_SOLID_COLOR, fill_opacity=1.0)
        # 右辺の塗りつぶし (Aの補集合 と Bの補集合 の和集合) を作成 (同値色)
        rhs_fill_2 = Union(not_A_R_for_union_helper, not_B_R_for_union_helper, stroke_width=0).set_fill(color=self.DM_EQUIVALENCE_COLOR, opacity=self.DM_FILL_OPACITY).set_stroke(width=0)
        # 数式説明の変形、Bの補集合のフェードアウト、右辺の和集合のフェードインアニメーションを再生
        self.play(Transform(op_R_2_desc_step1_rhs, op_R_2_desc_final_rhs), FadeOut(not_B_R_fill_display_law2, shift=RIGHT*0.1), FadeIn(rhs_fill_2, shift=LEFT*0.1), run_time=self.ANIM_RUN_TIME_LONG)
        
        # 等号 ("=") を作成し、位置を設定
        equals_sign_2 = MathTex("=", font_size=eq_sign_font_size, color=self.EQUALS_SIGN_COLOR).move_to(formula_pos_equals_between)
        # 等号を書き込むアニメーションを再生し、その後強調表示アニメーションを再生し、待機
        self.play(Write(equals_sign_2), run_time=self.ANIM_RUN_TIME_SHORT); self.play(Indicate(equals_sign_2, color=self.EQUALS_SIGN_INDICATE_COLOR, scale_factor=1.2), run_time=self.ANIM_INDICATE_TIME); self.wait(self.WAIT_TIME_XLONG)
        
        # 法則2の視覚要素 (塗りつぶし、数式説明、等号、タイトル) をグループ化
        law2_visuals_group = VGroup(lhs_fill_2, op_L_2_desc_step1, rhs_fill_2, op_R_2_desc_step1_rhs, equals_sign_2, title_law2)
        # 法則2の視覚要素グループをフェードアウトさせるアニメーションを再生し、待機
        self.play(FadeOut(law2_visuals_group), run_time=self.ANIM_RUN_TIME_MEDIUM); self.wait(self.WAIT_TIME_MEDIUM)

```



### 3. アニメーションの生成と動画の表示

`.py` ファイルを作成したら、以下のManimコマンドを実行して動画ファイルを生成します。このコマンドを実行し、完了すると、指定した品質に応じたディレクトリ内に `.mp4` ファイルが作成され, Colab上に動画を表示して確認できます。

```python
import os
from IPython.display import Video, display

# --- ユーザー設定項目 ---
PYTHON_FILE = "de_morgans_law_visualization.py"  # ManimのPythonスクリプトファイル名
SCENE_NAME = "DeMorgansLawScene"                 # レンダリングするシーン名 (ファイル全体なら "" または None)

# Manimのレンダリングオプション
# 品質フラグ例: "-pql" (480p15), "-pqm" (720p30), "-pqh" (1080p60), "-pqk" (2160p60)
MANIM_FLAGS = "-pql"

# レンダリング前に出力ディレクトリをクリーンアップするか
CLEAN_OUTPUT_BEFORE_RENDER = False

# Manimの出力ビデオのベースディレクトリ
# Google Colabの場合
BASE_VIDEO_DIR = "/content/media/videos"

# 動画ファイルの拡張子 (通常は変更不要)
VIDEO_EXTENSION = ".mp4"

# (オプション) 動画の解像度とFPSの手動オーバーライド
# 通常、これらはMANIM_FLAGSから自動判別されます。
# 特定の値を強制したい場合のみ、None以外の値を設定してください。
OVERRIDE_VIDEO_HEIGHT = None  # 例: 720
OVERRIDE_VIDEO_FPS = None     # 例: 30



# --- 内部設定 & 導出変数 ---
SCRIPT_FILE_BASENAME = os.path.splitext(PYTHON_FILE)[0]

# Manim品質フラグと対応する解像度・FPSの辞書
# Manim v0.18.0以降の -q l/m/h/k や、それ以前の -ql/-qm/-qh/-qk, -pql/-pqm/-pqh/-pqk に対応
QUALITY_TO_RES_FPS = {
    "-pql": (480, 15), "l": (480, 15), "-ql": (480, 15),
    "-pqm": (720, 30), "m": (720, 30), "-qm": (720, 30),
    "-pqh": (1080, 60), "h": (1080, 60), "-qh": (1080, 60),
    "-pqk": (2160, 60), "k": (2160, 60), "-qk": (2160, 60),
}

# MANIM_FLAGSから主要な品質フラグを抽出
parsed_quality_flag = None
for flag_part in MANIM_FLAGS.split():
    if flag_part in QUALITY_TO_RES_FPS:
        parsed_quality_flag = flag_part
        break

# VIDEO_HEIGHT と VIDEO_FPS を決定
# まずオーバーライド値を確認
video_height = OVERRIDE_VIDEO_HEIGHT
video_fps = OVERRIDE_VIDEO_FPS

# オーバーライド値がなければ、MANIM_FLAGSから導出した値を使用
if video_height is None or video_fps is None: # 片方だけオーバーライドされた場合も考慮
    derived_h, derived_f = None, None
    if parsed_quality_flag:
        derived_h, derived_f = QUALITY_TO_RES_FPS[parsed_quality_flag]
    
    if video_height is None: # OVERRIDE_VIDEO_HEIGHT が None の場合のみ、導出値で埋める
        video_height = derived_h
    if video_fps is None:    # OVERRIDE_VIDEO_FPS が None の場合のみ、導出値で埋める
        video_fps = derived_f

# --- 処理実行 ---

# 1. (オプション) 既存の出力ディレクトリを削除
if CLEAN_OUTPUT_BEFORE_RENDER:
    # Google Colabの典型的なパス "/content/media/videos" の場合、削除対象は "/content/media"
    # それ以外の場合 (ローカル環境など)、カレントディレクトリ直下の "media" フォルダを対象とする
    if BASE_VIDEO_DIR == "/content/media/videos":
        path_to_remove = "/content/media"
    else:
        path_to_remove = "media" # カレントディレクトリの 'media'
    
    print(f"既存の出力ディレクトリ '{path_to_remove}' を削除します...")
    get_ipython().system(f"rm -rf {path_to_remove}")

# 2. Manimコマンドの組み立てと実行
command_parts = ["manim", MANIM_FLAGS, PYTHON_FILE]
# SCENE_NAME が指定されている場合のみコマンドに追加 (空文字列やNoneは除く)
if SCENE_NAME and SCENE_NAME.strip():
    command_parts.append(SCENE_NAME)

# filter(None,...)で空要素を除去し、余分なスペースも除去してコマンド文字列を生成
manim_command = " ".join(filter(None, command_parts)).strip()

print(f"\n実行するManimコマンド: {manim_command}\n")

# Manimコマンドを実行
get_ipython().system(manim_command)

print("\nレンダリング処理が完了しました。")


# 3. 動画パスの組み立てと表示
if video_height is None or video_fps is None:
    print("\n動画の解像度またはFPSが特定できませんでした。")

elif not SCENE_NAME or not SCENE_NAME.strip():
    print("\n警告: SCENE_NAME が指定されていません。")

else:
    quality_directory_name = f"{video_height}p{video_fps}"
    
    video_file_path = os.path.join(
        BASE_VIDEO_DIR,
        SCRIPT_FILE_BASENAME,
        quality_directory_name,
        f"{SCENE_NAME}{VIDEO_EXTENSION}"
    )

    print(f"\n表示しようとしている動画パス: {video_file_path}")
    if os.path.exists(video_file_path):
        try:
            display(Video(video_file_path, embed=True, width=1000)) # widthはお好みで調整
        except Exception as e:
            print(f"動画表示中にエラーが発生しました: {e}")
            print(f"動画ファイルパス: {video_file_path}")
    else:
        print(f"エラー: 動画ファイルが見つかりません: {video_file_path}")

```




### 4. その他
・その他の注意事項です。

:::note warn
ランタイムの接続: Colab のランタイムは一定時間操作がないと切断されたり、最大利用時間に制限があります。ランタイムがリセットされると、インストールしたライブラリも消えてしまうため、再度実行する必要があります。ノートブックの「コードセル」は保存されているので、再度実行するだけで環境を復元できます。
:::


・20250520_「集合論の基本演算」に関する動画を追加しました。



<iframe width="560" height="315" src="https://www.youtube.com/embed/c5BbKp8HTM8?si=VZZ9iDpkjEpRiJr9" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

・20250526_「微分」に関する動画を追加しました。

<iframe width="325" height="578" src="https://www.youtube.com/embed/c2nmQqpB75s" title="【Python】微分の概念とその例 #shorts  #maths #式の値 #mathematics" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>