幾何学の魔法
── ユリディカ株式会社の90日間戦争 ──
経営環境多様体で未来を読む、ある日本企業の物語
幾何学的データサイエンス(Geometric Data Science) を用いた 経営戦略ケーススタディ・ノベル
-
(データ加工&解析手法) 「ジオメトリック・インテリジェンス」
本記事のバックグラウンド
この記事は、Zennで公開されている数理技術書 『ジオメトリックインテリジェンス』にて提唱されているデータ解析フレームワークを、具体的なビジネスケースに適用したPython実装の実践レポート です。
理論編で解説された「幾何学的データサイエンス」の10のステップを、架空の化学メーカーの危機を舞台としたケーススタディ形式で、ソースコードと共に詳説します。
はじめに:経営を「多様体」として解く
「経営環境は、曲がっている。ならば、測地線を引くしかない。」
現代の経営が直面する、米中貿易摩擦、地政学リスク、そして急速な環境規制の強化。これら複雑に絡み合う非線形な変数の動態を、従来の回帰分析やシナリオプランニングだけで捉えることは困難です。
本記事では、1891年創業の老舗メーカー「ユリディカ株式会社」を舞台に、DeepMind出身の数理チームが微分幾何学を武器に1,000億円規模のリスク回避に挑む物語を通じて、以下の技術の実装方法を解説します。
-
VAE(変分オートエンコーダ) を用いた高次元経営データの低次元多様体への圧縮
-
リーマン計量の引き戻しによる「空間の物差し」の定義
-
スカラー曲率を用いた経営環境の「歪み」と危機の可視化
-
Lie微分(リー微分) による政策インパクトの測定と施策間の干渉分析
-
最適制御理論(ポントリャーギンの最大原理) によるリスク最小パスの算出
ビジネスの「真の地形」を数学で描き出す、90日間のシミュレーション記録をぜひご覧ください。
幾何学の魔法 ── ユリディカ株式会社の90日間戦争
経営環境多様体で未来を読む、ある日本企業の物語
登場人物
-
黒田 誠一郎(くろだ せいいちろう) ── ユリディカ株式会社株式会社 代表取締役CEO。62歳。元・海外事業本部長。温厚だが決断は速い。
-
白石 香織(しらいし かおり) ── 取締役CFO。48歳。米国MBA出身。数字に厳しく、ROE至上主義。
-
赤城 隆(あかぎ たかし) ── 取締役CRO(Chief Risk Officer)。55歳。元・経済産業省官僚。地政学リスクの専門家。
-
神谷 遼太郎(かみや りょうたろう) ── 幾何学的データサイエンスチーム リーダー。34歳。東京大学大学院数理科学研究科博士課程修了(微分幾何学専攻)。その後DeepMindに3年在籍。
-
林 美咲(はやし みさき) ── 同チーム サブリーダー。31歳。京都大学情報学研究科修了。VAEとNeural ODEの実装に長ける。
-
張 偉明(チャン・ウェイミン) ── 同チーム データエンジニア。29歳。清華大学出身。中国市場データの収集・前処理の専門家。
-
プリヤ・シャルマ ── 同チーム データサイエンティスト。27歳。IIT Bombay出身。インド市場と自然言語処理の専門家。
企業概要:ユリディカ株式会社株式会社
-
(本社) 東京都港区芝公園
-
(設立) 1891年(明治24年)
-
(連結売上高) 1兆8,200億円(2025年度)
-
(連結従業員数) 38,000人
-
(主力製品) 歯磨き粉「クリアホワイト」シリーズ(世界シェア8.2%)、シャンプー「シルクヴェール」シリーズ(世界シェア6.1%)、ボディソープ、洗濯洗剤、住居用洗剤
-
(世界シェア) 生活化学品カテゴリで第3位
-
(海外売上比率) 62%(米国18%、中国15%、インド8%、EU12%、その他9%)
プロローグ:2026年4月7日(月)午前8時30分 ── 東京都港区、ユリディカ株式会社本社 38階 CEO室
朝の陽光が、東京タワーの赤い骨格を透かして、黒田誠一郎の執務机に斜めに差し込んでいた。
黒田は、目の前のタブレットに表示された3本のニュースヘッドラインを、眉間に深い皺を刻みながら読んでいた。
【速報】 米国商務省、中国製界面活性剤に45%の追加関税を発表 ── 日本企業の対中サプライチェーンにも波及か
(ロイター通信 2026年4月7日 07:15 JST)
【北京発】 中国国務院、「国産日用品優先調達指令」を閣議決定 ── 外資系消費財の政府調達を事実上排除
(新華社通信 2026年4月7日 06:00 JST)
【ブリュッセル発】 EU、化粧品・日用品のマイクロプラスチック含有規制を前倒し ── 2027年1月施行へ
(フィナンシャル・タイムズ 2026年4月6日 23:30 JST)
黒田は椅子の背もたれに深く身を沈め、天井を見上げた。
3つのニュースが同時に飛び込んできた。米中の経済覇権争いの狭間で、ユリディカ株式会社の中国事業と米国事業が同時に圧迫される。加えてEUの環境規制の前倒し。3つのベクトルが同時に、しかも異なる方向から突き刺さってくる。
黒田は内線電話を取った。
「白石さん、赤城さん、それから……神谷くんのチームも。今日の午後2時に、38階の戦略会議室に集まってくれないか。緊急の取締役会を開く」
第1章:暗雲 ── 2026年4月7日(月)午後2時 戦略会議室
ユリディカ株式会社本社38階の戦略会議室は、東京湾を一望する大きな窓ガラスに囲まれた、40人収容の部屋だった。長い楕円形のテーブルの中央に、通常はプレゼンテーション用のスクリーンが降りている。だが今日は、テーブルの中央に何もない。
黒田が入室すると、すでに白石香織CFO、赤城隆CRO、そして神谷遼太郎率いる幾何学的データサイエンスチームの4名が着席していた。
黒田: 「みなさん、急に集まってもらって申し訳ない。今朝のニュースは全員見ていると思う。率直に言う。我々は、過去30年で最も複雑な経営環境に直面している」
黒田は、手元のタブレットを操作して、壁面のスクリーンにデータを映した。
黒田: 「まず現状の数字を確認しよう。白石さん」
白石: 「はい。直近四半期の速報値です」
白石は立ち上がり、スクリーンに向かった。
ユリディカ株式会社 2026年Q1速報(1月~3月)
| 事業 | 売上高 | 営業利益率 | 前年同期比 |
|---|---|---|---|
| 米国事業 | 820億円 | 12.3% | ▲2.1% |
| 中国事業 | 685億円 | 8.7% | ▲8.4%(急落) |
| インド事業 | 365億円 | 14.2% | +12.3% |
| EU事業 | 548億円 | 10.1% | ▲1.8% |
| 日本事業 | 692億円 | 9.5% | +0.3% |
| 連結合計 | 4,550億円 | 10.8% | ▲1.2% |
白石: 「中国事業が前年比マイナス8.4%。これは2020年のCOVID以来の落ち込みです。原因は2つ。国産品優先の消費者ナショナリズムの高まりと、原材料の界面活性剤の調達コスト上昇。一方、インドは好調で前年比プラス12.3%。しかし――」
白石は言葉を切った。
白石: 「問題は、これらが独立した事象ではないということです。米国の対中関税が上がれば、中国でのコストが上がる。中国で国産品優先が進めば、我々の中国売上が落ちる。中国売上が落ちれば、中国工場の稼働率が下がる。稼働率が下がれば、インド向け製品の一部を中国工場で生産しているラインのコストが上がる。すべてが連動しています」
赤城: 「さらに言えば、地政学リスクの問題がある」
赤城は、分厚いファイルを開いた。
赤城: 「今朝の米国商務省の発表は、単なる関税ではありません。事実上、中国製の特定化学原料を使った製品を米国で販売することが、コスト的に不可能になります。しかし、我々の米国向け製品の一部は、中国の蘇州工場で製造した中間原料を使っている。サプライチェーンの組み替えが必要ですが、インドやベトナムの代替工場の立ち上げには最低18ヶ月かかる」
赤城: 「一方、中国側も対抗措置を取ってくるでしょう。『国産日用品優先調達指令』は、政府調達だけでなく、国有企業の福利厚生品にも適用される可能性が高い。中国の国有企業で働く従業員は約7,000万人。その福利厚生品市場だけでも、年間200億元(約4,000億円)規模です」
赤城: 「加えて、サイバーリスクです。当社の上海R&Dセンターが保有する処方データ(フォーミュレーション)は、中国の『データ安全法』と『個人情報保護法』の規制対象になります。このデータを日本に持ち出すことは、中国当局の許可なしには法的に不可能です。しかし同時に、米国の『外国敵対勢力データ保護法』は、中国にデータを置くこと自体をリスクと見なす方向に動いている。つまり――」
黒田: 「データを中国に置いても問題、持ち出しても問題、ということか」
赤城: 「その通りです。まさに二律背反(ダブルバインド)です」
会議室に重い沈黙が流れた。
黒田は、テーブルの端に静かに座っている神谷遼太郎に目を向けた。
黒田: 「神谷くん。1ヶ月前に、きみのチームが提案してくれた『幾何学的データサイエンスによる経営環境分析』のプロジェクト。あの時は、取締役会で『まだ時期尚早』という意見が多かった。正直に言うと、私自身もよく理解できていなかった」
黒田は一呼吸おいた。
黒田: 「しかし、今日の状況を見て、考えが変わった。従来の線形的な分析――回帰分析やシナリオプランニング――では、この複雑さは捉えきれない。3つの変数が同時に動き、しかも互いに干渉する。きみのチームが言っていた『経営環境は曲がった空間だ』という話……今なら、その意味が身体的にわかる」
神谷: 「ありがとうございます、黒田社長。ただ、率直に申し上げると、私たちのアプローチには、かなりの準備期間が必要です。データの収集、前処理、VAEの訓練、多様体の構築、検証。最低でも60日はいただきたい」
黒田: 「60日か」
白石: 「待ってください。60日後は6月上旬です。Q2の決算発表は7月末。米中の追加関税の発動は5月1日。EU規制のパブリックコメント期限は5月15日。60日は長すぎます」
神谷: 「白石CFO、お気持ちはわかります。しかし、数学的に正当でない分析に基づく経営判断は、砂上の楼閣です。不正確な地図で飛行計画を立てるようなものです。東京からニューヨークへの最短飛行ルートを平面地図で引けば、太平洋を横断する直線になります。しかし実際の最短ルートは北極圏を通る大圏航路です。間違った地図を信じて飛べば、燃料切れで墜落します」
神谷の目は真剣だった。
神谷: 「私たちが作るのは、ユリディカ株式会社の経営環境の『正確な立体地形図』です。直線的な分析が見落とす、曲がった空間の構造――安定領域と不安定領域、施策同士の干渉、最小リスクの移行経路――これらを、数学的に厳密な形で計算します。45日に短縮する努力はしますが、品質は妥協できません」
黒田は、白石と赤城を交互に見た。そして言った。
黒田: 「わかった。45日でやってくれ。必要なリソースはすべて出す。データへのアクセス権限も、各事業部に直接指示を出す。白石さん、予算の確保を。赤城さん、各国の規制データと地政学データの提供を。神谷くん、6月初旬の取締役会で、経営環境多様体を使った戦略提言を出してくれ」
神谷: 「承知しました」
神谷は、隣に座る林美咲と目を合わせた。林は小さくうなずいた。
45日間の戦いが、始まった。
第2章:データの海 ── 2026年4月8日~14日(第1週)
Step 1:データ収集と前処理
神谷チームは、本社32階に専用のプロジェクトルームを与えられた。4台のGPUサーバー(NVIDIA A100×4基)が壁際に並び、6台のモニターが弧を描いて配置されている。
神谷: 「まず、データの全体像を把握しよう。我々が集めるべきデータは、大きく7つのカテゴリに分かれる」
神谷はホワイトボードに書き出した。
経営環境多様体の構築に必要なデータセット
| カテゴリ | 内容 |
|---|---|
| 1 | 自社ビジネスデータ(月次×10年×5地域) |
| 2 | 各国の産業規制・貿易政策データ |
| 3 | 金融・マクロ経済データ |
| 4 | SNS消費者センチメントデータ |
| 5 | 競合企業データ |
| 6 | 気象・環境データ |
| 7 | サイバーリスク・知的財産権データ |
2.1 カテゴリ1:自社ビジネスデータ
林美咲が、各事業部から収集したデータを統合するPythonスクリプトを実行した。
import pandas as pd
import numpy as np
from sklearn.preprocessing
import StandardScaler
from sklearn.impute
import KNNImputer
# ===== カテゴリ1:自社ビジネスデータの読み込み =====
#地域 × 月次 × 10年(2016年4月~2026年3月)= 600データポイント/地域
business_columns = [
# 売上関連(8指標) 'revenue_total',
# 売上高(億円) 'revenue_oral_care', # オーラルケア売上 'revenue_hair_care', # ヘアケア売上 'revenue_body_care', # ボディケア売上 'revenue_home_care', # ホームケア売上 'gross_margin',
# 粗利率(%) 'operating_margin',
# 営業利益率(%) 'market_share_local',
# 現地市場シェア(%) # コスト関連(6指標) 'cogs_raw_materials',
# 原材料費(億円) 'cogs_surfactant',
# 界面活性剤コスト(億円) 'logistics_cost',
# 物流費(億円) 'marketing_spend', # マーケティング費(億円) 'rd_spend',
# R&D費(億円) 'labor_cost',
# 人件費(億円)
# 生産関連(4指標) 'factory_utilization',
# 工場稼働率(%) 'inventory_turnover',
# 在庫回転率 'defect_rate',
# 不良品率(ppm) 'supply_chain_lead_time', # サプライチェーンリードタイム(日) ]
regions = ['US', 'CN', 'IN', 'EU', 'JP'] # データの読み込みと統合
all_data = {}
ffor region in regions:
df = pd.read_csv(f'data/business_{region}_monthly.csv') all_data[region] = df
# 統合データフレーム
df_business = pd.concat([all_data[r].assign(region=r)
ffor r in regions])
print(f"ビジネスデータ: {df_business.shape[0]} rows × {df_business.shape[1]} columns")
print(f"期間: {df_business['date'].min()} ~ {df_business['date'].max()}")
print(f"欠損値: {df_business.isnull().sum().sum()} cells")
実行結果:
ビジネスデータ: 3,000 rows × 20 columns 期間: 2016-04 ~ 2026-03 欠損値: 847 cells
林: 「欠損値が847セルあります。主に中国事業の2020年Q1(COVID初期)のデータと、インド事業の2016~2017年のデータです。インドは当時まだ本格進出前で、データ収集体制が整っていませんでした」
神谷: 「KNN補完で埋めよう。ただし、2020年Q1の中国データは『通常とは異なる状態』だから、補完値の妥当性は後で確認する」
2.2 カテゴリ2:規制・政策データ
赤城CROの指示で、法務部と経済安全保障室から集められたデータは、数値化が最も困難なカテゴリだった。
張偉明が、各国の規制データを数値指標に変換するコードを書いた。
# ===== カテゴリ2:各国規制・政策データ =====
regulation_columns = [
# 貿易政策(5指標) 'tariff_rate_import',
# 輸入関税率(%) 'tariff_rate_export',
# 輸出関税率(%) 'trade_barrier_index',
# 非関税障壁指数(0-100) 'fta_coverage_ratio',
# FTAカバー率(%) 'trade_dispute_count',
# 貿易紛争件数(四半期)
# 産業規制(5指標) 'chemical_regulation_strictness',
# 化学物質規制厳格度(0-100) 'environmental_regulation_index',
# 環境規制指数(0-100) 'labeling_requirement_score',
# 表示義務スコア(0-100) 'product_safety_standard_level',
# 製品安全基準レベル(1-5) 'microplastic_regulation_phase', # マイクロプラスチック規制フェーズ(0-4) # データ・知財政策(5指標) 'data_localization_index', # データローカライゼーション指数(0-100) 'ip_protection_score',
# 知的財産権保護スコア(0-100) 'privacy_regulation_strictness',# 個人情報保護規制厳格度(0-100) 'antitrust_enforcement_index',
# 独禁法執行指数(0-100) 'foreign_investment_openness',
# 外資開放度(0-100) ]
# 各国・四半期データを月次に線形補間
regulation_data = pd.read_csv('data/regulation_quarterly.csv')
regulation_monthly = regulation_data.set_index('date').resample('M').interpolate('linear')
print(f"規制データ: {regulation_monthly.shape}")
実行結果:
規制データ: (600, 75) # 5地域 × 15指標 × 120ヶ月
2.3 カテゴリ4:SNS消費者センチメントデータ
プリヤ・シャルマが担当した、最も生々しいデータカテゴリだった。
プリヤ: 「Twitter/X、Weibo、Instagram、Reddit、Amazon/楽天のレビューから、当社製品と競合製品に関する消費者の投稿を収集しました。過去3年分で約480万件です」
以下は、収集されたSNSデータの一部(100件のサンプル)である。この生データが、後にセンチメントスコアに変換され、経営環境多様体の構成要素となる。
SNS消費者投稿データ(サンプル100件)
[ID:001] @TokyoMom_Yuki (Twitter/JP) 2026-03-28 「ユリディカ株式会社のクリアホワイト、子供用の味が変わった?前の方が好きだったのに…成分変わったのかな」
[ID:002] @ShanghaiFashion88 (Weibo/CN) 2026-03-27 「为什么还在用日本的洗发水?国产的效果也很好啊,而且价格便宜一半。支持国货!#国潮 #支持国产」 (訳:なぜまだ日本のシャンプーを使うの?国産品も効果いいし、値段は半分。国産を応援しよう!)
[ID:003] @CleanLiving_Sarah (Instagram/US) 2026-03-26 「Just found out my favorite Lion Chemical body wash contains microplastics?? 😱 Switching to Dr. Bronner's ASAP. #plasticfree #cleanbeauty」
[ID:004] @MumbaiDad_Raj (Twitter/IN) 2026-03-25 「Lion Chemical Clear White toothpaste is amazing but too expensive for daily use. ₹280 for 100g? Colgate is ₹85. Come on.」
[ID:005] @BerlinGreen_Lena (Instagram/EU) 2026-03-25 「The new EU microplastics regulation is a game changer. Brands like Lion Chemical need to reformulate NOW or lose the European market. #sustainability」
[ID:006] @大阪のおばちゃん (Twitter/JP) 2026-03-24 「シルクヴェール、リニューアルしてから泡立ちが悪くなった気がする。値段は上がったのに。ちょっとがっかり」
[ID:007] @BeijingChemist (Weibo/CN) 2026-03-24 「从化学角度分析,日本化学品牌的表面活性剂技术确实领先国内5年以上。但这个差距正在快速缩小。#化学 #科技」 (訳:化学的観点から分析すると、日本のケミカルブランドの界面活性剤技術は確かに国内より5年以上進んでいる。しかしこの差は急速に縮まっている。)
[ID:008] @NYCDentist_Dr.Kim (Twitter/US) 2026-03-23 「As a dentist, I recommend Lion Chemical Clear White for its fluoride formulation. But the price increase this quarter is concerning for my patients.」
[ID:009] @DelhiStartup_Ankit (Twitter/IN) 2026-03-23 「Lion Chemical needs to understand India is not Japan. We need sachets, not premium bottles. Sachet economy drives 60% of FMCG here.」
[ID:010] @ParisBlogger_Marie (Instagram/EU) 2026-03-22 「Teste le nouveau shampoing Lion Chemical SilkVeil — texture incroyable mais l'emballage plastique, c'est non pour moi. 🌍 #zerowaste」 (訳:ユリディカ株式会社の新シャンプーSilkVeilを試した。テクスチャーは素晴らしいけどプラスチックの包装はダメ。)
[ID:011] @家庭主婦_小紅 (Xiaohongshu/CN) 2026-03-22 「好用是好用,但是现在都在说要支持国货。下次试试完美日记的牙膏吧。#国货之光」 (訳:いいのはいいけど、今はみんな国産品を応援すべきって言ってる。次は完美日記の歯磨き粉を試そう。)
[ID:012] @TexasMom_Jennifer (Amazon Review/US) 2026-03-21 ★★★★☆ 「Clear White Premium is our family's go-to. But why did the price jump from $5.99 to $7.49? That's a 25% increase! If it goes up again, we'll switch.」
[ID:013] @TechReview_Tokyo (Twitter/JP) 2026-03-21 「ユリディカ株式会社の歯磨き粉、AIで個人の口腔環境に最適化するサブスクサービス始めたらしい。月額980円。未来感ある」
[ID:014] @GreenActivist_Hamburg (Twitter/EU) 2026-03-20 「Lion Chemical still using SLS (Sodium Lauryl Sulfate) in their European products. When will they switch to plant-based alternatives? The regulation deadline is 2027.」
[ID:015] @Hyderabad_Beauty (Instagram/IN) 2026-03-20 「SilkVeil shampoo works wonders for my thick Indian hair 💇♀️ but availability is so patchy. Can never find it at my local D-Mart. Only on Amazon at premium prices.」
[ID:016] @成都吃货王 (Douyin/CN) 2026-03-19 「日本牙膏真的有那么好吗?我用了三个月,感觉和云南白药没什么区别。但价格贵了两倍!#测评 #牙膏推荐」 (訳:日本の歯磨き粉って本当にそんなにいいの?3ヶ月使ったけど、雲南白薬と大差ない気がする。でも値段は2倍!)
[ID:017] @SecurityAnalyst_DC (Twitter/US) 2026-03-19 「The new Commerce Dept tariffs on Chinese surfactants will hit Japanese FMCG companies hard. Lion Chemical sources 40% of their raw materials from China.」
[ID:018] @名古屋の歯科医 (Twitter/JP) 2026-03-18 「クリアホワイト・プロフェッショナルの新処方、フッ素とハイドロキシアパタイトの配合バランスが秀逸。患者さんにも薦めています」
[ID:019] @ShenzhenEngineer (Weibo/CN) 2026-03-18 「听说美国要对中国的表面活性剂加征45%关税。这对在中国设厂的日本企业影响很大。Lion Chemical的苏州工厂怎么办?」 (訳:米国が中国の界面活性剤に45%の追加関税をかけるらしい。中国に工場がある日本企業への影響は大きい。ユリディカ株式会社の蘇州工場はどうなる?)
[ID:020] @EcoConsumer_London (Reddit/EU) 2026-03-17 「r/sustainability: Has anyone found a Lion Chemical product that's truly microplastic-free? Their EU lineup still contains polyethylene in the body scrub.」
[ID:021] @BangaloreStudent_Priya (Twitter/IN) 2026-03-17 「Lion Chemical should partner with Ayurvedic brands. Neem + modern formulation = winning combo for India. Just making Japanese products cheaper won't work here.」
[ID:022] @横浜ワーママ (Twitter/JP) 2026-03-16 「ユリディカ株式会社の詰め替えパック、もう少し大きいサイズ出してほしい。毎月買うのめんどくさい。1Lサイズ希望」
[ID:023] @WallStreetAnalyst (Twitter/US) 2026-03-16 「$LCHEM Q1 guidance cut. China headwinds + tariff risk = downgrade to HOLD. Target price revised from $42 to $35. The geopolitical premium is too high.」
[ID:024] @广州妈妈团 (WeChat/CN) 2026-03-15 「群里有人还在用日本的儿童牙膏吗?最近新闻说日本食品检测标准可能有问题,虽然是不同产品,但还是有点担心…」 (訳:グループで日本の子供用歯磨き粉を使っている人いる?最近、日本の食品検査基準に問題があるかもってニュース見て、別の商品だけどちょっと心配…)
[ID:025] @MilanPharmacist (Instagram/EU) 2026-03-15 「EU DG Grow just released the draft implementing rules for the microplastics regulation. Lion Chemical's entire body care line will need reformulation. Timeline: 8 months.」
[ID:026] @ChennaiRetailer_Ganesh (Twitter/IN) 2026-03-14 「Stocked Lion Chemical products last month. Sold only 30% in 4 weeks. Colgate and Dabur move 3x faster. Shelf space is expensive — may not reorder.」
[ID:027] @北海道の薬剤師 (Twitter/JP) 2026-03-14 「ユリディカ株式会社の新しい敏感肌用ボディソープ、アミノ酸系で本当に優しい。患者さんにも好評。ただ、価格が競合より3割高い」
[ID:028] @CyberSecurityPro (Twitter/US) 2026-03-13 「Reminder: Any company with R&D data in China is at risk under the new Data Security Law. Lion Chemical's Shanghai lab holds proprietary formulations worth billions.」
[ID:029] @上海白领Lisa (Xiaohongshu/CN) 2026-03-13 「其实我还是喜欢Lion Chemical的洗发水,但是最近同事都在用国产的,不太好意思继续用日本品牌了…」 (訳:実はまだユリディカ株式会社のシャンプーが好きだけど、最近同僚がみんな国産品を使っていて、日本ブランドを使い続けるのが気まずくなってきた…)
[ID:030] @FDAWatcher (Twitter/US) 2026-03-12 「FDA is reviewing fluoride concentration limits in OTC toothpaste. Could affect Lion Chemical's premium Clear White Pro line. Public comment period opens next month.」
[ID:031] @DublinStudent_Aoife (Reddit/EU) 2026-03-12 「r/SkincareEurope: Lion Chemical's moisturizer tested positive for methylisothiazolinone at 0.008%. That's under the EU limit but above some NGO thresholds.」
[ID:032] @MumbaiInfluencer_Zara (Instagram/IN) 2026-03-11 「Collaboration with Lion Chemical for their new SilkVeil launch in India 💕 But honestly, the packaging needs a MAJOR redesign for Indian consumers. Too clinical, not aspirational.」
[ID:033] @福岡のドラッグストア店員 (Twitter/JP) 2026-03-11 「クリアホワイトの売場、今月から1段縮小されました。代わりに韓国ブランドのスペースが拡大。お客さんの流れが変わってきてる」
[ID:034] @TradeWarWatch (Twitter/US) 2026-03-10 「Thread: The new 45% tariff on Chinese surfactants affects at least 12 Japanese consumer goods companies. Lion Chemical, Kao Corp, and Sunstar are most exposed.」
[ID:035] @北京大学教授_王 (Weibo/CN) 2026-03-10 「从经济学角度看,日本日化企业在中国市场的黄金时代已经过去。未来5年,本土品牌将占据70%以上的市场份额。」 (訳:経済学的観点から見て、日本の日用化学品企業の中国市場での黄金時代は終わった。今後5年で国内ブランドが70%以上のシェアを占めるだろう。)
[ID:036] @AmsterdamMom_Eva (Twitter/EU) 2026-03-09 「My daughter has sensitive skin. Lion Chemical's baby line is the only one that doesn't cause rashes. Please don't change the formula because of regulations! 🙏」
[ID:037] @PuneEngineer_Vikram (Twitter/IN) 2026-03-09 「Lion Chemical's India strategy is wrong. They're trying to sell Japanese-quality at Indian prices but with Japanese-style distribution. Won't work. Need kiranas, not supermarkets.」
[ID:038] @札幌ブロガー_ゆき (Twitter/JP) 2026-03-08 「ユリディカ株式会社の工場見学行ってきた!品質管理すごい。でも、こだわりすぎてコストが高くなってるのでは?消費者はそこまで求めてないかも」
[ID:039] @SupplyChainPro (LinkedIn/US) 2026-03-08 「Just published: Impact analysis of US-China tariff escalation on APAC supply chains. Lion Chemical's dual-sourcing strategy may need complete overhaul. See report.」
[ID:040] @深圳科技评论 (Weibo/CN) 2026-03-07 「Lion Chemical的苏州工厂使用的自动化生产线是日本进口的。如果中美科技战升级,这些设备的维修零件怎么办?#供应链风险」 (訳:ユリディカ株式会社の蘇州工場の自動化ラインは日本から輸入。米中テック戦争がエスカレートしたら、このメンテナンス部品はどうなる?)
[ID:041] @DCPolicyNerd (Twitter/US) 2026-03-07 「CISA has flagged consumer goods companies with China-based IT infrastructure as potential supply chain security risks. Lion Chemical is on the list.」
[ID:042] @Roma_Farmacia (Instagram/EU) 2026-03-06 「Il nuovo regolamento UE sulle microplastiche colpirà molti brand. Lion Chemical ha 8 mesi per riformulare. Ce la faranno?」 (訳:EUのマイクロプラスチック新規制は多くのブランドに影響する。ユリディカ株式会社は8ヶ月で処方変更しなければ。間に合うか?)
[ID:043] @KolkataHousewife_Meera (Twitter/IN) 2026-03-06 「Tried Clear White for the first time. Good product but the tube design doesn't work for our bathroom. We need stand-up tubes, not flat ones.」
[ID:044] @東京の投資家 (Twitter/JP) 2026-03-05 「ユリディカ株式会社株、地政学リスクで売り込まれてるけど、インド事業の成長を考えると過小評価では?PER 12倍は割安」
[ID:045] @ShanghaiMBA_Kevin (LinkedIn/CN) 2026-03-05 「Lion Chemical's China strategy needs a fundamental reset. The 'premium Japanese quality' positioning worked in 2015, not in 2026. Chinese consumers have moved on.」
[ID:046] @BrusselsLobbyist (Twitter/EU) 2026-03-04 「Inside scoop: EU Commission is considering fast-tracking the microplastics ban. Instead of Jan 2027, could be as early as Sep 2026. Industry groups are panicking.」
[ID:047] @ChicagoDad_Mike (Amazon Review/US) 2026-03-04 ★★★☆☆ 「Used to love Clear White but the new version tastes different. Also, $7.49 for toothpaste? With Costco's Kirkland brand at $3.99, hard to justify.」
[ID:048] @京都の化学者 (Twitter/JP) 2026-03-03 「ユリディカ株式会社の特許出願動向を分析。2025年はバイオサーファクタント関連が急増。マイクロプラスチック代替の布石か?」
[ID:049] @GuangzhouTrader (Weibo/CN) 2026-03-03 「进出口数据显示,日本日化品牌在中国的进口量已经连续三个季度下降。Lion Chemical下降了12%。#外贸数据」 (訳:輸出入データによると、日本の日用化学品ブランドの中国への輸入量は3四半期連続で減少。ユリディカ株式会社は12%減。)
[ID:050] @Vienna_Dermatologist (Twitter/EU) 2026-03-02 「Concerning trend: Lion Chemical products sold in EU still contain higher preservative levels than needed. They could reduce by 30% and maintain efficacy.」
[ID:051] @JaipurEntrepreneur_Ravi (Twitter/IN) 2026-03-02 「Lion Chemical should acquire a local Indian brand. Trying to build brand awareness from scratch in India takes decades. Buy Vicco or Himalaya's oral care division.」
[ID:052] @仙台のママブロガー (Twitter/JP) 2026-03-01 「ドラッグストアの歯磨き粉コーナー、韓国と中国ブランドが増えてきた。クリアホワイトは定位置キープしてるけど、新興ブランドとの競争激しそう」
[ID:053] @NYTimes_Business (Twitter/US) 2026-03-01 「Breaking: US-China trade tensions escalate as Beijing retaliates against latest tariff round. Consumer goods sector expected to bear significant cost increases.」
[ID:054] @ChinaDailyBiz (Weibo/CN) 2026-02-28 「商务部发言人表示,将对美国加征关税采取'等量等值'反制措施。日化行业可能受到波及。#中美贸易」 (訳:商務部報道官は、米国の追加関税に対して「同量同等」の対抗措置を取ると表明。日用化学品業界にも波及する可能性。)
[ID:055] @LondonAnalyst_James (LinkedIn/EU) 2026-02-28 「Lion Chemical's European margins are under pressure. The microplastics reformulation alone will cost an estimated €120M. Add CSRD reporting requirements and you're looking at €150M+.」
[ID:056] @BengaluruTech_Arun (Twitter/IN) 2026-02-27 「Heard Lion Chemical is building an AI-powered personalization platform for Indian market. Smart move — India's digital-first consumers want customized products.」
[ID:057] @大阪の主婦_さくら (Twitter/JP) 2026-02-27 「シルクヴェールの詰め替え用、今月値上げしてた。前は398円だったのに438円に。10%アップはキツイ」
[ID:058] @GeopoliticsToday (Twitter/US) 2026-02-26 「The 'China+1' strategy is becoming 'China-1'. Companies like Lion Chemical must now plan for scenarios where China operations are completely isolated from US supply chains.」
[ID:059] @杭州网红_小美 (Xiaohongshu/CN) 2026-02-26 「测了一下Lion Chemical和国产品牌的成分,说实话差距没有想象中那么大。但质感确实好一些。性价比的话…国产赢#成分分析」 (訳:ユリディカ株式会社と国産ブランドの成分を比較した。正直、差は想像ほど大きくない。でも質感は確かにいい。コスパだと…国産が勝ち。)
[ID:060] @EU_ECHA_Official (Twitter/EU) 2026-02-25 「ECHA has published the updated restriction proposal for intentionally added microplastics. Transitional period may be shortened. Stakeholder consultation opens April 1.」
[ID:061] @ChennaiPharmacy_Kumar (Twitter/IN) 2026-02-25 「Lion Chemical products are available in only 3 of 12 pharmacy chains in Chennai. Distribution gap is massive. Meanwhile, Colgate is everywhere.」
[ID:062] @名古屋の美容師 (Twitter/JP) 2026-02-24 「シルクヴェール・プレミアムライン、仕上がりは最高。でもお客さんに1本3,200円の説明するのが大変。パンテーンの3倍ですからね」
[ID:063] @SFVentureCapital (Twitter/US) 2026-02-24 「Interesting: Lion Chemical's US competitor グローバルケア社 just acquired a biotech startup for plant-based surfactants. $180M deal. The green chemistry race is heating up.」
[ID:064] @武汉大学生_小李 (Weibo/CN) 2026-02-23 「宿舍里用日本洗发水被室友说了…现在这个氛围,用进口日化品都要偷偷的。唉。#大学生活」 (訳:寮で日本のシャンプー使ってたらルームメイトに言われた…今の雰囲気だと、輸入日用品を使うのも隠さないといけない。はぁ。)
[ID:065] @StockholmSustainability (LinkedIn/EU) 2026-02-23 「Lion Chemical ranks 47th in our Corporate Sustainability Index, down from 38th last year. Main issue: microplastics content and packaging recyclability.」
[ID:066] @Mumbai_Stockbroker (Twitter/IN) 2026-02-22 「Lion Chemical India IPO rumors gaining steam. If they list the India subsidiary, could raise ₹8,000 crore at current valuations. Would accelerate India growth.」
[ID:067] @福岡の化粧品バイヤー (Twitter/JP) 2026-02-22 「百貨店のバイヤーミーティングで、ユリディカ株式会社のプレミアムラインの取り扱い縮小が決定。韓国ブランドのスペース拡大の方針」
[ID:068] @DCTradePolicy (Twitter/US) 2026-02-21 「Senator Rubio's new bill would require all consumer goods sold in US to certify China-free supply chains by 2028. If passed, Lion Chemical faces existential restructuring.」
[ID:069] @天津工厂工人_老张 (Weibo/CN) 2026-02-21 「Lion Chemical的天津工厂最近在招人,但工资比去年降了15%。听说是因为订单减少。#就业」 (訳:ユリディカ株式会社の天津工場が最近募集してるけど、給料が去年より15%下がった。注文が減ったからだとか。)
[ID:070] @HelsinkiChemist (Twitter/EU) 2026-02-20 「Tested Lion Chemical's EU products for PFAS. All clear — but the precautionary approach would be to switch to fluorine-free alternatives now. The regulation is coming.」
[ID:071] @Pune_Mother_Sunita (Twitter/IN) 2026-02-20 「My 5-year-old loves the strawberry flavor of Clear White Kids. But I wish the fluoride content was listed more clearly on the pack. Indian parents worry about fluorosis.」
[ID:072] @東京の経営コンサル (Twitter/JP) 2026-02-19 「ユリディカ株式会社の経営課題は明確。中国リスクの軽減、インド市場の加速、EU規制対応、米国コスト構造改革。問題は4つを同時に進められるかどうか」
[ID:073] @CanadaRetail_Expert (LinkedIn/US) 2026-02-19 「Lion Chemical's North American distribution deal with Walmart expires Q3 2026. Renewal negotiations will be tough given the tariff uncertainty.」
[ID:074] @南京消费者协会 (Weibo/CN) 2026-02-18 「消费者协会收到多起关于进口日化产品标签不符合新国标的投诉。Lion Chemical等品牌需要尽快更新标签。#消费者权益」 (訳:消費者協会は輸入日用化学品のラベルが新国家基準に適合していないとの苦情を多数受けている。ユリディカ株式会社などのブランドは早急にラベルを更新する必要がある。)
[ID:075] @MadridPharmacy_Ana (Instagram/EU) 2026-02-18 「Clientes preguntan cada vez más por productos sin microplásticos. Lion Chemical aún no tiene una línea completamente libre. Esto será un problema pronto.」 (訳:マイクロプラスチックフリーの製品を聞いてくるお客さんが増えている。ユリディカ株式会社にはまだ完全にフリーのラインがない。これは問題になるだろう。)
[ID:076] @Ahmedabad_Distributor (Twitter/IN) 2026-02-17 「Lion Chemical offered me exclusive distribution for Gujarat. But their minimum order is 500 cases. For a new brand in India, that's too much risk for a distributor.」
[ID:077] @新宿のOL_あやか (Twitter/JP) 2026-02-17 「ユリディカ株式会社のハンドソープ、香りがリニューアルで良くなった!ラベンダーがナチュラルで好き。これはリピート確定」
[ID:078] @WashingtonPost_Econ (Twitter/US) 2026-02-16 「Analysis: The new tariff regime will increase US consumer prices for personal care products by an estimated 8-12% over the next 12 months.」
[ID:079] @重庆网友_火锅侠 (Douyin/CN) 2026-02-16 「用了三个月国产牙膏,感觉牙齿没有以前白了…要不要换回Lion Chemical?但是真的太贵了啊!#牙膏 #纠结」 (訳:国産歯磨き粉を3ヶ月使ったけど、歯が前ほど白くなくなった気がする…ユリディカ株式会社に戻そうかな?でも本当に高いんだよね!)
[ID:080] @Berlin_ChemIndustry (LinkedIn/EU) 2026-02-15 「The European Chemical Industry Council estimates that the microplastics ban will require €2.3B in reformulation costs across the industry. Lion Chemical's share: ~€100-150M.」
[ID:081] @Lucknow_Student_Rani (Twitter/IN) 2026-02-15 「College hostel survey: Only 4 out of 50 girls use Lion Chemical products. Main reason? Price. Second? Availability. They need smaller packs under ₹50.」
[ID:082] @京都大学の研究者 (Twitter/JP) 2026-02-14 「ユリディカ株式会社との共同研究で開発したバイオサーファクタント、来年の実用化に向けて最終試験段階。マイクロプラスチック問題の根本解決になりうる」
[ID:083] @PhiladelphiaBlogger (Twitter/US) 2026-02-14 「Valentine's Day gift guide: Lion Chemical SilkVeil Gift Set is $39.99. Beautiful packaging but the ingredients list reads like a chemistry textbook 😬 #VDay」
[ID:084] @成都科技博主 (Bilibili/CN) 2026-02-13 「拆解对比:Lion Chemical vs 国产品牌的牙膏成分。结论:活性成分几乎相同,但赋形剂和口感确实有差异。详见视频。」 (訳:分解比較:ユリディカ株式会社vs国産ブランドの歯磨き粉成分。結論:有効成分はほぼ同じだが、賦形剤と口当たりには確かに違いがある。)
[ID:085] @PragueRegulator (Twitter/EU) 2026-02-13 「Czech Republic fully implementing the EU cosmetics regulation update by Q3 2026. All imported products must comply. No exceptions for Japanese brands.」
[ID:086] @Nagpur_Dentist_Patel (Twitter/IN) 2026-02-12 「Lion Chemical's fluoride toothpaste is superior to Indian alternatives. But fluorosis is a real concern in high-fluoride regions like Rajasthan. Need region-specific formulations.」
[ID:087] @札幌の主婦_まりこ (Twitter/JP) 2026-02-12 「スーパーの洗剤コーナー、ユリディカ株式会社の棚がまた縮小されてた。花王に押されてる感じ。頑張ってほしい」
[ID:088] @ChinaTradeWar_Bot (Twitter/US) 2026-02-11 「ALERT: USTR announces Section 301 investigation into Chinese government subsidies for domestic consumer goods manufacturers. This could lead to additional tariffs.」
[ID:089] @上海律师_陈律师 (Weibo/CN) 2026-02-11 「提醒各外资企业:中国《数据安全法》第36条关于数据出境的规定已经开始严格执行。包括日化企业的配方数据和消费者数据。#法律提醒」 (訳:外資企業に注意:中国『データ安全法』第36条のデータ越境移転規定が厳格に執行され始めている。日用化学品企業の処方データや消費者データも含まれる。)
[ID:090] @LisbonConsumer (Reddit/EU) 2026-02-10 「r/europeanproducts: Lion Chemical body wash caused allergic reaction for my partner. Reported to RAPEX. Their EU customer service response was slow (2 weeks).」
[ID:091] @KochiTrader_Suresh (Twitter/IN) 2026-02-10 「Lion Chemical's India CEO told us they'll launch ₹10 sachet toothpaste by Q3. Finally! This is how you win in Bharat, not in India.」
[ID:092] @大阪の歯科衛生士 (Twitter/JP) 2026-02-09 「今日の勉強会でユリディカ株式会社の研究員が講演。新しいプロバイオティクス配合の歯磨き粉の研究、かなり期待できる」
[ID:093] @US_Chamber_Commerce (LinkedIn/US) 2026-02-09 「US Chamber report: 67% of consumer goods CEOs cite US-China trade tensions as their #1 business risk for 2026. Lion Chemical's CEO was quoted in the report.」
[ID:094] @武汉环保志愿者 (Weibo/CN) 2026-02-08 「调查发现,包括Lion Chemical在内的多家外资日化品牌的工厂废水处理不达新标准。已向环保局反映。#环保 #企业责任」 (訳:ユリディカ株式会社を含む複数の外資系日用化学品ブランドの工場の廃水処理が新基準に達していないことが判明。環境保護局に報告済み。)
[ID:095] @MilanFashionBlog (Instagram/EU) 2026-02-08 「Love the aesthetic of Lion Chemical's new EU packaging redesign 🎨 But still waiting for the 'Clean Beauty' certification. When, @LionChemicalEU?」
[ID:096] @Delhi_HealthMinistry (Twitter/IN) 2026-02-07 「BIS has updated testing standards for oral care products. All imported brands including Lion Chemical must re-certify by September 2026. No extensions.」
[ID:097] @名古屋のドラッグストア店長 (Twitter/JP) 2026-02-07 「うちの店、ユリディカ株式会社の棚の横に中国ブランド『半畝花田』のコーナーを新設。若い女性に売れてる。品質も悪くない」
[ID:098] @Reuters_Markets (Twitter/US) 2026-02-06 「Lion Chemical (TSE:4912) shares fall 6.3% as investors price in tariff impact. Company to hold emergency investor call Friday. 」
[ID:099] @北京外交评论 (Weibo/CN) 2026-02-06 「日本企业在中美之间左右为难。Lion Chemical这样的企业,必须在两个大国之间做出选择,还是能找到第三条路?#地缘政治」 (訳:日本企業は米中の間で板挟みだ。ユリディカ株式会社のような企業は、二大国の間で選択を迫られるのか、それとも第三の道を見つけられるのか?)
[ID:100] @ViennaTechStartup (Twitter/EU) 2026-02-05 「Pitch: We developed a blockchain-based supply chain verification system for microplastics-free certification. Reaching out to Lion Chemical EU for a pilot. 🚀」
プリヤ: 「これら480万件の投稿を、以下のPythonコードでセンチメント分析し、数値化します」
# ===== カテゴリ4:SNSセンチメント分析 =====
import torch
from transformers
import AutoTokenizer, AutoModelForSequenceClassification
import pandas as pd
import numpy as np
# 多言語センチメント分析モデル
model_name = "cardiffnlp/twitter-xlm-roberta-base-sentiment-multilingual"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)
def analyze_sentiment_batch(texts, batch_size=64): """多言語テキストのセンチメントスコアを計算"""
scores = []
ffor i in range(0, len(texts), batch_size):
batch = texts[i:i+batch_size]
inputs = tokenizer(batch, return_tensors="pt", padding=True, truncation=True, max_length=512)
with torch.no_grad():
outputs = model(**inputs)
probs = torch.softmax(outputs.logits, dim=-1) # negative=0, neutral=1, positive=2 → スコア: -1 ~ +1
sentiment = probs[:, 2] - probs[:, 0] scores.extend(sentiment.numpy().tolist())
return scores
# 月次×地域×トピック別にセンチメントスコアを集約
sentiment_columns = [ 'sentiment_brand_overall', # ブランド全体評価 'sentiment_product_quality',
# 製品品質評価 'sentiment_price_value',
# 価格価値評価 'sentiment_sustainability', # サステナビリティ評価 'sentiment_competitor_threat',
# 競合脅威度 'sentiment_regulation_concern',# 規制懸念度 'sentiment_nationalism', # ナショナリズム傾向 'sentiment_safety_concern',
# 安全性懸念 ]
# 結果
print(f"センチメントデータ: 5地域 × 8指標 × 120ヶ月 = 4,800 datapoints")
神谷: 「よし。全カテゴリのデータを統合しよう」
2.4 データ統合
# ===== 全カテゴリの統合 =====
# 最終的な指標数
n_business = 18 # カテゴリ1
n_regulation = 15 # カテゴリ2
n_macro = 10 # カテゴリ3(GDP成長率、インフレ率、金利、為替等)
n_sentiment = 8 # カテゴリ4
n_competitor = 6 # カテゴリ5(主要競合3社の売上、シェア、R&D投資等)
n_weather = 3 # カテゴリ6(平均気温、降水量、異常気象指数)
n_cyber = 5 # カテゴリ7(サイバー攻撃件数、知財訴訟件数等)
total_features = n_business + n_regulation + n_macro + n_sentiment + \ n_competitor + n_weather + n_cyber
print(f"総指標数: {total_features}")
#5指標
#地域 × 120ヶ月 = 600データポイント
# ただし各地域のデータ密度は異なる
# 米国: 120ヶ月(完全)
# 中国: 108ヶ月(2016年は一部欠損) # インド: 96ヶ月(2016-2017年は大幅欠損)
# EU: 120ヶ月(完全)
# 日本: 120ヶ月(完全)
# 統合データの次元: N × n
# N = 564データポイント(欠損除去後) #
n = 65指標
# 標準化
from sklearn.preprocessing
import StandardScaler
scaler = StandardScaler() X_all = scaler.fit_transform(integrated_data)
print(f"統合データ: {X_all.shape}")
実行結果:
総指標数: 65 統合データ: (564, 65)
# ===== gen_all_scenes.py: Scene 0 — データ分布図(Box Plot) =====
# Python 3.12 + Plotly 6.6.0 + Kaleido(静的画像エクスポート)
import numpy as np
import plotly.graph_objects as go
np.random.seed(42)
indicators = {
"Revenue (¥B)": np.random.normal(100, 35, 120),
"Op.Margin (%)": np.random.normal(10.5, 3.2, 120),
"MktShare (%)": np.random.normal(7.5, 2.1, 120),
"Surfactant Cost": np.random.normal(45, 12, 120),
"Factory Util.(%)": np.random.normal(78, 8, 120),
"Tariff Rate (%)": np.concatenate([
np.random.exponential(3, 115), [45, 42, 38, 35, 30]
]),
"Brand Sentiment": np.random.normal(0.15, 0.38, 120),
"Price Sentiment": np.random.normal(-0.10, 0.35, 120),
"Regulation Idx": np.random.normal(52, 18, 120),
"Cyber Risk": np.random.exponential(2.5, 120),
}
colors = ["#00E5FF", "#00FF88", "#FFD700", "#FF69B4", "#A855F7",
"#FF8C00", "#00CED1", "#FF00FF", "#7FFF00", "#FF6B6B"]
fig = go.Figure()
for i, (name, data) in enumerate(indicators.items()):
fig.add_trace(go.Box(
y=data, name=name, marker_color=colors[i],
line=dict(color=colors[i]),
fillcolor=f"rgba({int(colors[i][1:3],16)},"
f"{int(colors[i][3:5],16)},"
f"{int(colors[i][5:7],16)},0.3)",
boxmean="sd",
))
fig.update_layout(
paper_bgcolor="#060612", plot_bgcolor="#060612",
font=dict(family="Arial", color="#CCDDEE", size=12),
title=dict(
text="<b>YURIDICA DATA DISTRIBUTION — KEY INDICATORS"
" (10 Years Monthly)</b><br>"
"<sub>Box=IQR, Whiskers=1.5×IQR, Diamond=Mean±SD</sub>",
font=dict(size=16, color="#00E5FF"), x=0.5
),
showlegend=False,
yaxis=dict(gridcolor="#1a1a2e", zerolinecolor="#1a1a2e"),
xaxis=dict(tickangle=-30),
width=1200, height=800,
)
fig.write_image("s0_boxplot.png", scale=2)
PythonのPlotlyでは、原作『ジオメトリック・インテリジェンス』が期待している立体的でホログラフィックな経営環境多様体を可視化できないため、本記事の終わりで、Blenderを使って立体画像をレンダリングする方法などを提案しています。本記事はシミュレーション小説の為、後ほど、簡便的に画像生成AIに描いてもらった画像を掲載する予定です。
【Gemini画像生成プロンプト — データ分布図(Box Plot)】
Generate a sleek data visualization on dark background (#060612). Ten box-and-whisker plots arranged horizontally, each in a different neon color (cyan, green, yellow, pink, purple, orange, teal, magenta, lime, coral). Labels: "Revenue (¥B)," "Op.Margin (%)," "MktShare (%)," "Surfactant Cost," "Factory Util.(%)," "Tariff Rate (%)," "Brand Sentiment," "Price Sentiment," "Regulation Index," "Cyber Risk." Each box shows median line, IQR box, whiskers, and outlier dots. Diamond markers show mean±SD. Title in cyan: "YURIDICA DATA DISTRIBUTION — KEY INDICATORS (10 Years Monthly)." Subtitle: "Box=IQR, Whiskers=1.5×IQR, Diamond=Mean±SD." Style: Dark sci-fi dashboard aesthetic, clean typography, 8K.

図2-1:主要経営指標の分布(10年分月次データ)。箱=四分位範囲(IQR)、ひげ=1.5×IQR、ひし形=平均±標準偏差。
神谷はテーブルの上に箱ひげ図を映し出した。
神谷: 「取締役の皆様、データの全体像をまずお見せします。これは当社の主要経営指標10年分の分布です。箱の真ん中の線が中央値、箱の上下が四分位点——つまりデータの半分がこの箱の中に入ります。ひげの外にある点は外れ値で、リーマンショックやCOVIDの時期に対応しています」
白石: 「売上の分布がかなり広いわね。最小40億円から最大170億円まで。これは地域差?」
神谷: 「はい。米国事業は売上820億円前後で安定していますが、インド事業は2016年の30億円から2026年の365億円まで10倍以上に成長しています。この成長の非線形性が、直線的な回帰分析では捉えにくい構造です」
神谷: 「特に注目すべきは関税率の分布です。大半はゼロから10%の範囲ですが、2026年4月の45%という値は完全な外れ値です。この外れ値が、経営環境多様体の構造を大きく変形させています」
赤城: 「センチメントデータはどうか」
神谷: 「ブランドセンチメントの中央値は+0.15で、わずかに正——消費者は全体として当社ブランドを好意的に見ています。しかし価格センチメントは中央値-0.10で、やや否定的です。『品質はいいが高い』という消費者の声がデータに現れています。標準偏差は0.35~0.40で、地域や時期によるばらつきが大きいことを示しています」
林: 「564データポイント × 65指標。データは揃いました。次はStep 2、VAEの構築です」
神谷: 「その前に、固有次元の推定をやろう。PCAスクリープロットで、何次元の多様体が適切かを確認する」
第3章:壁 ── 2026年4月15日~21日(第2週) Step 2:最初の失敗
3.1 固有次元の推定
# ===== PCAスクリープロットによる固有次元推定 =====
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
pca = PCA(n_components=min(X_all.shape)) pca.fit(X_all)
cumulative_variance = np.cumsum(pca.explained_variance_ratio_)
# 分散80%以上をカバーする主成分数
d_80 = np.argmax(cumulative_variance >= 0.80) + 1
d_90 = np.argmax(cumulative_variance >= 0.90) + 1
print(f"分散80%カバー: {d_80}主成分")
print(f"分散90%カバー: {d_90}主成分")
print(f"固有次元推定: d = {d_80} ~ {d_90}")
実行結果:
分散80%カバー: 8主成分 分散90%カバー: 12主成分 固有次元推定: d = 8 ~ 12
神谷: 「8主成分で分散の80%をカバーする。潜在次元は $d = 8$ でスタートしよう。これは『ジオメトリック・インテリジェンス』のチェック1(低次元構造、仮定A1)に対応する」
林: 「データ密度のチェックもしましょう。$d = 8$ の場合、経験則 $N > 100 \times 2^d$ だと $100 \times 256 = 25,600$ ポイントが必要です。しかし、今は564ポイントしかありません」
神谷の顔が曇った。
神谷: 「……足りない。圧倒的に足りない」
プリヤが不安そうな顔で手を挙げた。
プリヤ: 「神谷さん、すみません。『データ密度が足りない』というのは、具体的にどういう意味ですか? データは564ポイントあるわけですよね。ゼロじゃない。なぜダメなんですか?」
神谷は振り返り、プリヤに向かって穏やかに語り始めた。
神谷: 「いい質問だ。高校数学で考えてみよう。2次元の平面に点を打って、その点たちから曲線を推測する場面を想像してくれ。点が3つしかなければ、通る曲線は無数にある。30個あれば、かなり形が絞れる。300個あれば、ほぼ1本の曲線に決まる。点の数が増えるほど、推測の精度が上がるのは直感的にわかるよね」
プリヤ: 「はい、それはわかります」
神谷: 「問題は次元だ。2次元なら数十個の点で足りる。でも6次元になると、空間の『体積』が指数関数的に増える。2次元の平面は縦横だけだが、6次元は縦・横・奥行き・さらに3方向。空間が広大すぎて、564個の点では『スカスカ』なんだ。例えるなら、東京ドームの中に564個のビー玉を撒いたような状態。ビー玉とビー玉の間が空きすぎていて、間を埋める曲面を正確に推定できない」
プリヤ: 「だから経験則で、次元が上がると必要なデータが指数的に増える、という話なんですね」
神谷: 「その通り。$d = 8$ 次元なら $100 \times 2^8 = 25,600$ ポイントが経験則の目安。我々の564ポイントはその2%だ。これでは、VAEがデータの本当の構造を学習できず、勝手に『空想の構造』を作ってしまう。空想の地図で飛行計画を立てるのと同じだ」
3.2 最初の危機:データ量不足
林: 「564ポイントで $d = 8$ は、経験則の2%しかありません。これでVAEを訓練しても、多様体仮説(仮定A3)を満たせません」
プロジェクトルームに沈黙が流れた。
張: 「月次データを週次に分解すれば、データ量は4倍になります。しかし、多くの指標は月次でしか集計されていません。週次に分割しても、実質的な情報量は増えません」
プリヤ: 「SNSデータなら日次まで分解できます。ただ、ビジネスデータの方は月次が限界です」
神谷: 「待て。データの構造を変えよう」
神谷はホワイトボードの前に立った。
神谷: 「今のデータ構造は『5地域 × 120ヶ月 = 600ポイント、各ポイントが65次元ベクトル』だ。これを、地域を分離しないで、『月次のスナップショット』として扱う。つまり、各月のデータポイントを、すべての地域の情報を含む1つの高次元ベクトルにする」
変更前:N=564, n=65(各データポイントが1地域の1ヶ月) 変更後:N=120, n=325(各データポイントが5地域すべての1ヶ月)
林: 「でも $N = 120$ ではさらに少なくなりますよ」
神谷: 「いや、違う。考え方を変えるんだ。地域を分離せず、『ある月の世界全体の経営環境状態』を1つの点として扱う。すると、$n = 65 \times 5 = 325$ 次元のデータが120点ある。さらに、週次に補間すれば、120ヶ月 × 約4.3週 = 520点。さらに、SNSセンチメントデータは日次まで分解できるから、ビジネスデータは週次補間、センチメントデータは日次の原データを使い、データ拡張する」
張: 「加えて、各指標の時間遅延版(1ヶ月ラグ、2ヶ月ラグ、3ヶ月ラグ)をデータに追加すると、時間的な因果構造も取り込めます。これはタイムエンベディングと呼ばれる手法です」
神谷: 「それだ。さらに、データ拡張(data augmentation)を使う。元データにガウシアンノイズ($\sigma = 0.05$)を加えた変形版を3セット生成すれば、有効データ数は4倍になる」
# ===== データ構造の再設計 =====
#. 地域統合:5地域を1つの高次元ベクトルに結合
# 各月のデータ: [US_65指標, CN_65指標, IN_65指標, EU_65指標, JP_65指標] # → 325次元
#. 週次補間
from scipy.interpolate import interp1d
def monthly_to_weekly(data_monthly, method='cubic'): """月次データを週次に補間"""
t_monthly = np.arange(len(data_monthly))
t_weekly = np.linspace(0, len(data_monthly)-1, len(data_monthly)*4)
f = interp1d(t_monthly, data_monthly, axis=0, kind=method)
return f(t_weekly) X_weekly = monthly_to_weekly(X_integrated_monthly)
print(f"週次補間後: {X_weekly.shape}") # (480, 325)
#. 時間ラグ特徴量の追加
def add_time_lags(X, lags=[1, 2, 4]): """時間ラグ版を追加""" X_aug = [X]
for lag in lags: X_lagged = np.roll(X, lag, axis=0) X_lagged[:lag] = X[:lag]
# 先頭はコピーで埋める X_aug.append(X_lagged)
return np.hstack(X_aug) X_with_lags = add_time_lags(X_weekly, lags=[1, 2, 4])
print(f"ラグ追加後: {X_with_lags.shape}") # (480, 1300)
#. データ拡張(ガウシアンノイズ)
def augment_data(X, n_augment=3, noise_std=0.05): """ノイズ付きデータ拡張""" X_aug = [X]
for _ in range(n_augment):
noise = np.random.normal(0, noise_std, X.shape) X_aug.append(X + noise)
return np.vstack(X_aug) X_augmented = augment_data(X_with_lags, n_augment=3)
print(f"データ拡張後: {X_augmented.shape}") # (1920, 1300)
#. PCAで次元削減(計算効率のため)
from sklearn.decomposition import PCA
pca_pre = PCA(n_components=80, random_state=42) X_reduced = pca_pre.fit_transform(X_augmented)
print(f"PCA前処理後: {X_reduced.shape}") # (1920, 80)
print(f"PCA累積分散: {pca_pre.explained_variance_ratio_.sum():.3f}")
実行結果:
週次補間後: (480, 325) ラグ追加後: (480, 1300) データ拡張後: (1920, 1300) PCA前処理後: (1920, 80) PCA累積分散: 0.952
林: 「1,920ポイント × 80次元になりました。$d = 8$ の場合の経験則 $25,600$ にはまだ届きませんが、$d = 6$ なら $100 \times 2^6 = 6,400$ で、3割はカバーできます」
神谷: 「潜在次元を $d = 6$ に下げよう。PCAを見直すと、6主成分で分散の75%をカバーする。80%には届かないが、まずは $d = 6$ で進めて、品質チェックで判断しよう」
3.3 VAEの構築と訓練 ── そして2度目の壁
# ===== Step 2: VAEの構築 =====
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
class LionChemicalVAE(nn.Module):
"""
ユリディカ株式会社経営環境多様体のためのVAE
【重要】デコーダの全層でnn.Tanh()を使用(C∞活性化関数)
nn.ReLU()は使用不可 ── 曲率テンソルの計算に必要な
2階微分が、ReLUの折れ曲がり点で定義できなくなるため
"""
def __init__(self, input_dim=80, latent_dim=6, hidden_dims=[256, 128, 64]):
super().__init__()
# ===== エンコーダ =====
encoder_layers = []
prev_dim = input_dim
for h_dim in hidden_dims:
encoder_layers.extend([
nn.Linear(prev_dim, h_dim),
nn.Tanh(), # ★ C∞活性化関数
])
prev_dim = h_dim
self.encoder = nn.Sequential(*encoder_layers)
self.mu_layer = nn.Linear(hidden_dims[-1], latent_dim)
self.logvar_layer = nn.Linear(hidden_dims[-1], latent_dim)
# ===== デコーダ =====
# ★★★ 全層でnn.Tanh()を使用 ★★★
decoder_layers = []
prev_dim = latent_dim
for h_dim in reversed(hidden_dims):
decoder_layers.extend([
nn.Linear(prev_dim, h_dim),
nn.Tanh(), # ★ C∞活性化関数(ReLUは絶対に使わない)
])
prev_dim = h_dim
decoder_layers.append(nn.Linear(hidden_dims[0], input_dim))
self.decoder = nn.Sequential(*decoder_layers)
def encode(self, x):
h = self.encoder(x)
return self.mu_layer(h), self.logvar_layer(h)
def reparameterize(self, mu, logvar):
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
return mu + std * eps
def decode(self, z):
return self.decoder(z)
def forward(self, x):
mu, logvar = self.encode(x)
z = self.reparameterize(mu, logvar)
return self.decode(z), mu, logvar
def vae_loss(x_recon, x, mu, logvar, beta=1.0):
recon_loss = nn.functional.mse_loss(x_recon, x, reduction='sum')
kl_loss = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
return recon_loss + beta * kl_loss
# VAEの訓練
vae = LionChemicalVAE(input_dim=80, latent_dim=6)
optimizer = optim.Adam(vae.parameters(), lr=1e-3)
X_tensor = torch.FloatTensor(X_reduced)
dataset = TensorDataset(X_tensor)
dataloader = DataLoader(dataset, batch_size=64, shuffle=True)
# 訓練ループ
for epoch in range(500):
total_loss = 0
for batch in dataloader:
x = batch[0]
x_recon, mu, logvar = vae(x)
loss = vae_loss(x_recon, x, mu, logvar, beta=0.5)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
if (epoch + 1) % 100 == 0:
avg_loss = total_loss / len(dataset)
print(f"Epoch {epoch+1}/500, Loss: {avg_loss:.4f}")
実行結果:
Epoch 100/500, Loss: 42.831 Epoch 200/500, Loss: 28.174 Epoch 300/500, Loss: 21.556 Epoch 400/500, Loss: 18.923 Epoch 500/500, Loss: 17.641
林: 「訓練が収束しました。次は品質チェックです。命題1.1の4条件を検証します」
# ===== 命題1.1の条件検証 =====
# 条件(i): 関心領域のコンパクト性
# VAEの潜在表現を計算
vae.eval()
with torch.no_grad():
mu_all, _ = vae.encode(X_tensor)
z_all = mu_all.numpy()
# 99.7%のデータが含まれる領域
z_radius = np.max(np.linalg.norm(z_all, axis=1))
print(f"潜在表現の最大ノルム: {z_radius:.3f}")
print(f"関心領域 U = {{z: ||z|| <= {z_radius + 0.5:.1f}}} → コンパクト ✓")
# 条件(ii): C^k級(k≥3)→ tanh使用で自動保証
print("活性化関数: nn.Tanh() (C∞) → 条件(ii) ✓")
# 条件(iii): ヤコビ行列のフルランク検証
def check_jacobian_full_rank(decoder, z_samples, d=6):
min_sv_list = []
for z in z_samples:
z_t = z.clone().requires_grad_(True)
f = decoder(z_t)
n_out = f.shape[0]
J = torch.zeros(n_out, d)
for a in range(n_out):
grad = torch.autograd.grad(
f[a], z_t, retain_graph=True, create_graph=False
)[0]
J[a] = grad
svs = torch.linalg.svdvals(J)
min_sv_list.append(svs[-1].item())
return min_sv_list
# 100点でヤコビ行列のSVD検証
z_test = torch.FloatTensor(z_all[np.random.choice(len(z_all), 100, replace=False)])
min_svs = check_jacobian_full_rank(vae.decoder, z_test)
print(f"\nヤコビ行列の最小特異値:")
print(f" 最小値: {min(min_svs):.6f}")
print(f" 中央値: {np.median(min_svs):.6f}")
print(f" 最大値: {max(min_svs):.6f}")
threshold = 1e-4
n_below = sum(1 for sv in min_svs if sv < threshold)
print(f" 閾値 {threshold} 未満の点: {n_below}/100")
実行結果:
潜在表現の最大ノルム: 3.421 関心領域 U = {z: ||z|| <= 3.9} → コンパクト ✓ 活性化関数: nn.Tanh() (C∞) → 条件(ii) ✓ ヤコビ行列の最小特異値: 最小値: 0.000012 中央値: 0.003847 最大値: 0.018923 閾値 0.0001 未満の点: 23/100
神谷の顔が曇った。
神谷: 「……ダメだ」
プリヤが神谷の表情を見て声をかけた。
プリヤ: 「神谷さん、『フルランク条件を満たしていない』とは、どういう意味ですか? 私はNLPが専門で、ヤコビ行列のランクと言われてもピンと来ません……」
神谷は深呼吸して、ホワイトボードに簡単な図を描き始めた。
神谷: 「プリヤ、大丈夫。高校の数学で説明する。まず、VAEは65次元のデータを6次元に圧縮した。これは、65本の糸を6本の太い紐に束ねたようなものだ。フルランク条件というのは、『6本の紐が全部、違う方向を向いているか?』を確認することだ」
プリヤ: 「違う方向を向いていないと、何がまずいんですか?」
神谷: 「もし6本のうち2本が同じ方向を向いていたら、実質的には5本分の情報しかない。つまり、6次元に圧縮したつもりが、実は5次元しか使っていない。1次元分が『潰れている』状態だ。潰れた方向には距離が測れないから、その方向の曲率も計算できない」
神谷: 「身近な例で言うと、カーナビの地図で東西方向が潰れて南北方向しか表示されなかったら、東に行くべきか西に行くべきかの判断ができない。6本の矢印がすべて独立した方向を向いていること——それが『フルランク』だ」
プリヤ: 「なるほど……で、23%の点でそれが潰れていた、と」
神谷: 「そうだ。しかも潰れているのはデータが疎な辺縁部——VAEがあまり学習できていない領域だ。学習データが少ない場所では、VAEのデコーダが『適当な値』を返してしまい、6本の矢印のうち1本以上がほぼゼロの長さになる。長さゼロの矢印は方向を持たない——だからランク落ちする」
林が画面を食い入るように見つめた。
林: 「23点、つまり23%の点で、ヤコビ行列の最小特異値が閾値 $10^{-4}$ を下回っています。フルランク条件(仮定A2(b))を満たしていません」
神谷: 「これは深刻だ。23%の点でフルランク条件が破れているということは、その領域で引き戻し計量 $g_{ij} = J^\top J$ が退化し、逆行列 $g^{ij}$ が計算できない。つまり、クリストッフェル記号が計算できない。曲率テンソルも計算できない。Lie微分も計算できない。Step 5以降がすべて破綻する」
プロジェクトルームに重い沈黙が流れた。4日間の作業が、水泡に帰した。
3.4 問題の診断
神谷: 「落ち着こう。問題の原因を特定する」
# ===== 問題の診断:どの領域でランク落ちが起きているか =====
# フルランク条件を満たさない点の潜在座標を特定
failed_indices = [i for i, sv in enumerate(min_svs) if sv < threshold]
z_failed = z_test[failed_indices].detach().numpy()
z_passed = z_test[[i for i, sv in enumerate(min_svs) if sv >= threshold]].detach().numpy()
print(f"フルランク不合格の点の潜在座標の統計:")
for dim in range(6):
print(f" z{dim+1}: mean={z_failed[:,dim].mean():.3f}, "
f"std={z_failed[:,dim].std():.3f}")
print(f"\nフルランク合格の点の潜在座標の統計:")
for dim in range(6):
print(f" z{dim+1}: mean={z_passed[:,dim].mean():.3f}, "
f"std={z_passed[:,dim].std():.3f}")
実行結果:
フルランク不合格の点の潜在座標の統計: z1: mean=-0.127, std=1.823 z2: mean=0.045, std=0.312 z3: mean=-2.341, std=0.891 ← z3が大きく負 z4: mean=0.089, std=0.567 z5: mean=1.987, std=0.445 ← z5が大きく正 z6: mean=-0.234, std=0.678 フルランク合格の点の潜在座標の統計: z1: mean=0.034, std=0.987 z2: mean=-0.012, std=0.434 z3: mean=0.178, std=0.654 z4: mean=-0.056, std=0.789 z5: mean=0.234, std=0.567 z6: mean=0.067, std=0.543
神谷: 「見えた。不合格の点はz3が強い負の値、z5が強い正の値を持つ領域に集中している。これは潜在空間の辺縁部——データが疎な領域だ。データ分布の端っこでは、デコーダがあまり学習できていないため、ヤコビ行列のランクが落ちる」
林: 「VAEのKL正則化で潜在分布を標準正規に近づけているので、$|z| > 2$ の領域にはデータがほとんどありません。でもデコーダはその領域にも定義されていて、学習が不十分なままです」
プリヤ: 「z3が大きく負で、z5が大きく正の領域に不合格が集中している……これはどう解釈すればいいですか?」
神谷: 「潜在空間の『端っこ』だ。VAEはKL正則化によって、潜在表現を標準正規分布——つまり原点の近くに集まる鐘形の分布——に近づけようとする。だから、$|z| > 2$ の領域にはデータがほとんど配置されない。でもデコーダの関数はその領域にも定義されている」
神谷: 「イメージとしては、東京23区の詳細な地図を作ったのに、八王子市や奥多摩の部分は白紙のまま、ということだ。白紙の部分にも地形は存在するはずだが、測量データがないから正確な等高線が引けない。VAEも同じで、データがない領域のデコーダは『でたらめ』を返す。でたらめの領域では、6本の矢印がランダムな方向を向くか、潰れてしまう」
神谷: 「原因は2つある」
神谷はホワイトボードに書き出した。
原因1:データ量の絶対的不足(1,920ポイント vs 必要な6,400ポイント) 原因2:特定の経営状態のデータが疎(中国市場の2020年Q1前後、 インド市場の初期データ)→ 潜在空間の辺縁部に「穴」ができる
張: 「原因1に対しては、データをさらに増やすしかありません。原因2に対しては、疎な領域のデータを重点的に追加するか、関心領域を狭めてデータが密な領域だけを分析対象にするか、です」
3.5 データの追加と修正 ── 3日間の集中作業
神谷は、黒田CEOに電話をかけた。
神谷: 「黒田社長、データ品質の問題が発生しました。追加データの収集と、データ構造の再設計に3日間ください」
黒田: 「何が必要だ」
神谷: 「3つあります。第一に、各地域の日次販売データへのアクセス。月次ではなく日次です。これでデータ量が約30倍になります。第二に、SNSデータの収集範囲を過去3年から過去5年に拡大。第三に、競合企業のデータを、有料データベース(Euromonitor、Statista)から追加購入。予算は約300万円です」
黒田: 「300万円か。数十億円の投資判断のための300万円なら安い。すぐに手配する」
3日間の集中作業が始まった。
追加データ1:日次販売データ
# ===== 日次販売データの追加 =====
# 5地域 × 約1,825日(5年分)= 約9,125データポイント
daily_business_columns = [
'daily_revenue', # 日次売上高
'daily_units_sold', # 日次販売数量
'daily_online_ratio', # オンライン販売比率
'daily_return_rate', # 返品率
'daily_promotion_flag', # プロモーション実施フラグ
]
# 日次データは5地域 × 1825日 = 9,125ポイント
# ただし、日次販売データは変動が大きいため、7日移動平均で平滑化
from scipy.ndimage import uniform_filter1d
daily_data_smoothed = {}
for region in regions:
df_daily = pd.read_csv(f'data/daily_sales_{region}.csv')
for col in daily_business_columns:
df_daily[f'{col}_ma7'] = uniform_filter1d(df_daily[col].values, size=7)
daily_data_smoothed[region] = df_daily
print(f"日次データ(平滑化後): {sum(len(v) for v in daily_data_smoothed.values())} rows")
実行結果:
日次データ(平滑化後): 9,125 rows
追加データ2:SNSデータの拡張
# ===== SNSデータ:5年分に拡張 =====
# 追加収集:2021年4月~2023年3月の2年分
# 合計:約780万件の投稿テキスト # センチメント分析を日次で実行
#地域 × 8指標 × 1,825日 = 73,000 datapoints
sentiment_daily = analyze_daily_sentiment( all_posts,
#80万件 regions=regions, topics=sentiment_columns, start_date='2021-04-01', end_date='2026-03-31' )
print(f"日次センチメントデータ: {sentiment_daily.shape}")
実行結果:
日次センチメントデータ: (1825, 40) # 8指標 × 5地域 = 40, 1825日
データ構造の再設計
プリヤ: 「神谷さん、データ構造を変えるというのは、具体的に何が変わるんですか? データの中身は同じですよね?」
神谷: 「いい質問だ。例えば、100枚の写真がある。1枚1枚は小さい——情報量が少ない。でも、12枚を横に並べてパノラマ写真にすれば、1枚あたりの情報量は12倍になる。我々がやっていることは、まさにこれだ」
神谷: 「元のデータは『ある地域の、ある月のスナップショット』だった。これを、『ある月の、全5地域のパノラマ写真+過去3時点の履歴付き』に変えた。さらにノイズを加えたバリエーションを複数作る——これは写真に少しぼかしやコントラスト変更を加えて、学習画像を増やすのと同じ原理だ」
プリヤ: 「画像認識のデータ拡張と同じ考え方ですね。NLPでも、文の言い換えで学習データを増やすことがあります」
神谷: 「まさにそうだ。本質的に新しい情報を作っているわけではないが、VAEにとっては『異なるデータポイント』として見えるため、潜在空間の辺縁部まで学習が行き届くようになる。結果として、先ほど白紙だった八王子や奥多摩にも測量データが入り、正確な等高線が引けるようになる」
# ===== 再設計されたデータ構造 =====
# 方針:
# 1. 時間軸を「週次」に統一
# 2. 月次しかないデータは週次に補間
# 3. 日次データは週次に集約
# 4. 5地域を1つの高次元ベクトルに結合
# 最終的なデータ構造
# 時間軸: 260週(5年分 × 52週)
# 各週のデータ: [US_指標群, CN_指標群, IN_指標群, EU_指標群, JP_指標群]
# 各地域の指標数
n_per_region = (
18 + # ビジネスデータ
5 + # 日次販売データ(集約)
15 + # 規制データ
10 + # マクロ経済データ
8 + # センチメントデータ
6 + # 競合データ
3 + # 気象データ
5 # サイバーリスクデータ
) # = 70指標/地域
n_total = n_per_region * 5 # = 350指標
n_weeks = 260
# 時間ラグ特徴量(1週、2週、4週ラグ)
# 350 × 4 = 1,400
# PCA前処理で80次元に圧縮
# データ拡張(×4)
# 最終データ: 260 × 4 = 1,040 → さらにスライディングウィンドウで拡張
# スライディングウィンドウ:12週ウィンドウを1週ずつスライド
def create_sliding_windows(X, window_size=12):
"""時系列データをスライディングウィンドウで拡張"""
windows = []
for i in range(len(X) - window_size + 1):
window = X[i:i+window_size].flatten()
windows.append(window)
return np.array(windows)
# 12週ウィンドウ × 80次元 = 960次元 → PCAで80次元に再圧縮
X_windows = create_sliding_windows(X_weekly_80, window_size=12)
print(f"ウィンドウデータ: {X_windows.shape}") # (249, 960)
# データ拡張
X_final = augment_data(X_windows, n_augment=5, noise_std=0.03)
print(f"拡張後: {X_final.shape}") # (1494, 960)
# PCA圧縮
pca_final = PCA(n_components=80)
X_vae_input = pca_final.fit_transform(X_final)
print(f"VAE入力データ: {X_vae_input.shape}")
print(f"累積分散: {pca_final.explained_variance_ratio_.sum():.3f}")
実行結果:
ウィンドウデータ: (249, 960) 拡張後: (1494, 960) VAE入力データ: (1494, 80) 累積分散: 0.937
林: 「1,494ポイント × 80次元。$d = 6$ の経験則 $6,400$ にはまだ足りませんが、2割以上カバーするようになりました。さらに重要なのは、データが疎だった中国市場とインド市場の初期データが、日次販売データとSNSデータで大幅に補強されたことです」
3.6 再訓練と再検証
# ===== VAEの再訓練(改良版) =====
# 改良点:
# 1. 隠れ層を深くする(3層→4層)
# 2. β-VAEを採用(β=0.3で再構成精度を優先)
# 3. 学習率スケジューラを追加
class LionChemicalVAE_v2(nn.Module):
def __init__(self, input_dim=80, latent_dim=6):
super().__init__()
self.encoder = nn.Sequential(
nn.Linear(input_dim, 256), nn.Tanh(),
nn.Linear(256, 128), nn.Tanh(),
nn.Linear(128, 96), nn.Tanh(),
nn.Linear(96, 64), nn.Tanh(),
)
self.mu_layer = nn.Linear(64, latent_dim)
self.logvar_layer = nn.Linear(64, latent_dim)
self.decoder = nn.Sequential(
nn.Linear(latent_dim, 64), nn.Tanh(),
nn.Linear(64, 96), nn.Tanh(),
nn.Linear(96, 128), nn.Tanh(),
nn.Linear(128, 256), nn.Tanh(),
nn.Linear(256, input_dim),
)
def encode(self, x):
h = self.encoder(x)
return self.mu_layer(h), self.logvar_layer(h)
def reparameterize(self, mu, logvar):
std = torch.exp(0.5 * logvar)
return mu + std * torch.randn_like(std)
def decode(self, z):
return self.decoder(z)
def forward(self, x):
mu, logvar = self.encode(x)
z = self.reparameterize(mu, logvar)
return self.decode(z), mu, logvar
vae_v2 = LionChemicalVAE_v2()
optimizer = optim.Adam(vae_v2.parameters(), lr=1e-3)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=800)
X_tensor = torch.FloatTensor(X_vae_input)
dataset = TensorDataset(X_tensor)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)
for epoch in range(800):
total_loss = 0
for batch in dataloader:
x = batch[0]
x_recon, mu, logvar = vae_v2(x)
loss = vae_loss(x_recon, x, mu, logvar, beta=0.3)
optimizer.zero_grad()
loss.backward()
optimizer.step()
scheduler.step()
if (epoch + 1) % 200 == 0:
# 再構成誤差の計算
vae_v2.eval()
with torch.no_grad():
x_recon, _, _ = vae_v2(X_tensor)
recon_err = torch.mean((X_tensor - x_recon)**2).item()
data_var = torch.var(X_tensor).item()
recon_ratio = recon_err / data_var * 100
vae_v2.train()
print(f"Epoch {epoch+1}/800, Recon Error: {recon_ratio:.1f}% of data variance")
# 最終検証
vae_v2.eval()
with torch.no_grad():
x_recon, mu_all, logvar_all = vae_v2(X_tensor)
recon_err = torch.mean((X_tensor - x_recon)**2).item()
data_var = torch.var(X_tensor).item()
print(f"\n最終再構成誤差: {recon_err/data_var*100:.1f}% of data variance")
実行結果:
Epoch 200/800, Recon Error: 12.4% of data variance Epoch 400/800, Recon Error: 7.8% of data variance Epoch 600/800, Recon Error: 5.3% of data variance Epoch 800/800, Recon Error: 4.1% of data variance 最終再構成誤差: 4.1% of data variance
林: 「再構成誤差4.1%。基準の10%以下をクリアしました!」
# ===== 命題1.1の条件を再検証 =====
z_all = mu_all.numpy()
# 条件(iii): ヤコビ行列のフルランク検証(再)
z_test_v2 = torch.FloatTensor( z_all[np.random.choice(len(z_all), 200, replace=False)] )
min_svs_v2 = check_jacobian_full_rank(vae_v2.decoder, z_test_v2)
print(f"ヤコビ行列の最小特異値(改良版):")
print(f" 最小値: {min(min_svs_v2):.6f}")
print(f" 中央値: {np.median(min_svs_v2):.6f}")
print(f" 閾値 1e-4 未満の点: {sum(1 for sv in min_svs_v2 if sv < 1e-4)}/200")
実行結果:
ヤコビ行列の最小特異値(改良版): 最小値: 0.000387 中央値: 0.008234 閾値 1e-4 未満の点: 0/200
林が歓声を上げた。
林: 「ゼロです!200点すべてでフルランク条件をクリアしました!」
プリヤが、今度は安堵の表情で聞いた。
プリヤ: 「前回は23%が不合格で、今回はゼロ。何が決定的に違ったんですか?」
神谷: 「3つの改善が効いた。第一に、データ量。各ポイントの情報密度が格段に上がった——12週分のパノラマ写真だからね。第二に、データ拡張で潜在空間の辺縁部にもデータが行き届くようになった。第三に、VAEのアーキテクチャ改良——隠れ層を深くし、β=0.3で再構成精度を優先した」
神谷: 「プリヤ、前回の失敗の教訓を一言でまとめると、こうだ。『AIモデルの性能は、データの品質と密度の上限を超えられない。』データが疎な領域では、どんなに賢いモデルでも、正確な構造を学習できない。我々がやったのは、モデルを改良することよりも、データの密度を上げることだった」
プリヤ: 「NLPでも全く同じです。大規模言語モデルでも、学習データにない言語や方言は苦手です。データがすべての始まりですね」
神谷: 「その通りだ」
神谷: 「最小特異値が0.000387。閾値 $10^{-4}$ の3.87倍。十分な余裕がある」
張: 「条件(iv)の単射性も確認しましょう」
# 条件(iv): 単射性の近似検証
def check_injectivity_approx(decoder, z_samples, threshold=1e-3):
outputs = [decoder(z).detach()
for z in z_samples]
collisions = 0
n = len(outputs)
for i in range(n):
for j in range(i+1, min(i+50, n)):
# 近傍50点と比較
z_dist = torch.norm(z_samples[i] - z_samples[j]).item()
f_dist = torch.norm(outputs[i] - outputs[j]).item()
if z_dist > threshold and f_dist < threshold: collisions += 1
return collisions
collisions = check_injectivity_approx(vae_v2.decoder, z_test_v2)
print(f"衝突(非単射の疑い): {collisions}")
実行結果:
衝突(非単射の疑い): 0
神谷: 「命題1.1の4条件、すべてクリアだ」
命題1.1 検証結果
| 条件 | 結果 | 判定 |
|---|---|---|
| (i) コンパクト性 | ‖z‖ ≤ 4.0 の有界閉領域 | ✓ 合格 |
| (ii) C^k級 (k≥3) | 全層tanh (C∞) | ✓ 合格 |
| (iii) フルランク | σ_min ≥ 3.87×10⁻⁴ > 10⁻⁴ | ✓ 合格 |
| (iv) 単射性 | 衝突 0件 | ✓ 合格 |
**
プリヤ: 「4つの条件をすべてクリアしたということは、具体的にどういう状態ですか?高校生に説明するとしたら?」
神谷: 「いいチャレンジだ。やってみよう」
神谷: 「条件1のコンパクト性。これは、我々が分析する領域が『有限の大きさ』であること。宇宙全体の地図は作れないが、日本列島の地図なら作れる。分析対象を半径4.0の球の中に限定することで、計算が確実に収束する」
神谷: 「条件2のC∞級。これは、地形図に『角』や『折れ目』がないこと。等高線が滑らかにつながっていること。我々はVAEの全層でtanhという滑らかな活性化関数を使っている。もしReLUという関数を使うと、関数の折れ曲がり点で等高線が途切れ、そこで曲率が計算不能になる」
神谷: 「条件3のフルランク。さっき説明した、6本の矢印が全部違う方向を向いていること。これで、6次元のどの方向にも距離が測れる」
神谷: 「条件4の単射性。これは、6次元空間の2つの異なる点が、65次元空間で同じ場所に『重なって』しまわないこと。2つの異なる経営状態が、同じデータに見えてしまったら区別がつかない。幸い、衝突ゼロだった」
神谷: 「この4つが揃うと、逆関数定理という数学の定理が使えて、我々のVAEのデコーダの像が、正式に6次元の滑らかな多様体——つまり、正確な立体地形図——であることが数学的に保証される。これは数学者の前に出しても恥ずかしくない、厳密な結果だ」
プリヤ: 「ありがとうございます。ようやく、なぜこの4つのチェックに何日もかけたのかが腑に落ちました」
結論:f_θ(U) は R^80 内の 6次元 C∞級埋め込み部分多様体**
神谷は、チームメンバー全員の顔を見回した。
神谷: 「ユリディカ株式会社の経営環境多様体が、数学的に正当な可微分多様体として構成された。この多様体の上で、計量、曲率、Lie微分、制御シミュレーション――すべての計算が可能だ」
林が小さくガッツポーズをした。張とプリヤは互いにハイタッチした。
しかし、これはまだ始まりにすぎなかった。
第4章:地形図の完成 ── 2026年4月22日~5月5日(第3~4週)
Step 3-4:幾何構造の抽出とリーマン計量の計算
【ナレーション】
多様体が構成された。しかし、多様体だけでは「形」があるだけで、「距離」がない。UMAPの散布図で2つの点が3センチ離れていても、それが実際にどれだけの経営環境の差異を表しているかはわからない。
リーマン計量を導入することで、多様体に「距離」が定義される。引き戻し計量 $g_{ij}(z) = (J^\top J)_{ij}$ は、VAEデコーダのヤコビ行列から計算され、「潜在空間上の微小な移動が、データ空間でどれだけの変化を引き起こすか」を各点・各方向で定量化する。
# ===== Step 4: 引き戻し計量の計算 =====
class PullbackMetric:
def __init__(self, decoder):
self.decoder = decoder
def compute(self, z): """引き戻し計量 g_{ij}(z) = (J^T J)_{ij} を計算"""
z_t = z.clone().requires_grad_(True)
f =1ecoder(z_t) n_out,
d = f.shape[0], z_t.shape[0] J = torch.zeros(n_out, d)
ffor a in range(n_out):
grad = torch.autograd.grad( f[a], z_t, retain_graph=True, create_graph=True )[0] J[a] = grad
g = J.T @ J # d × d の正定値対称行列
return g, J
metric = PullbackMetric(vae_v2.decoder)
# 潜在空間の格子点上で計量を計算
grid_size = 20
z_grid_2d = np.mgrid[-3:3:grid_size*1j, -3:3:grid_size*1j]
#次元のうち最初の2次元を格子、残りを0に固定
z_grid_points = []
for i in range(grid_size):
for j in range(grid_size):
z = np.zeros(6) z[0] = z_grid_2d[0, i, j] z[1] = z_grid_2d[1, i, j] z_grid_points.append(z)
metrics = []
det_g_map = np.zeros((grid_size, grid_size))
cond_g_map = np.zeros((grid_size, grid_size))
for idx, z in enumerate(z_grid_points):
z_t = torch.FloatTensor(z) g, J = metric.compute(z_t)
g_np = g.detach().numpy() metrics.append(g_np) i,
j = idx // grid_size, idx % grid_size det_g_map[i, j] = np.linalg.det(g_np) cond_g_map[i, j] = np.linalg.cond(g_np)
print(f"計量テンソル条件数の統計:")
print(f" 最小: {cond_g_map.min():.1f}")
print(f" 中央値: {np.median(cond_g_map):.1f}")
print(f" 最大: {cond_g_map.max():.1f}")
print(f" 10^4超の点: {(cond_g_map > 1e4).sum()}/{grid_size**2}")
実行結果:
計量テンソル条件数の統計: 最小: 4.2 中央値: 87.3 最大: 2847.6 10^4超の点: 0/400
林: 「条件数は最大でも2,848。すべて $10^4$ 以下です。数値的に安定な計量が得られました」
Step 5-6:クリストッフェル記号と曲率テンソル
【ナレーション】
計量が定義されたことで、次は「空間の曲がり具合」を計算できる。クリストッフェル記号 $\Gamma^k_{ij}$ は「座標系の曲がりに対する補正値」であり、計量テンソルの1階微分から計算される。そしてリーマン曲率テンソル $R^l{}_{ijk}$ は、クリストッフェル記号のさらなる微分から計算され、空間が「どれだけ、どの方向に曲がっているか」を完全に記述する。
スカラー曲率 $\mathrm{Scal}(z) = g^{ij} R^l{}_{ilj}$ は、曲率テンソルを1つの数値に縮約したもので、各点の「総合的な曲がり具合」を表す。正のスカラー曲率は安定構造(測地線が収束)、負のスカラー曲率は不安定構造(測地線が発散)に対応する。
# ===== Step 5-6: クリストッフェル記号と曲率テンソルの計算 =====
def christoffel_and_curvature(metric_fn, z, d=6): """自動微分によるフルチェーン: 計量→Γ→曲率→スカラー曲率"""
z_t = z.clone().requires_grad_(True) g,
_ = metric_fn.compute(z_t)
g_inv = torch.inverse(g)
# Step 5: ∂g_{jl}/∂z^i を自動微分で計算
dg = torch.zeros(d, d, d)
ffor j in range(d):
ffor l in range(j, d):
# 対称性を利用
grad = torch.autograd.grad( g[j, l], z_t, retain_graph=True, create_graph=True )[0] dg[:, j, l] = grad
if j != l: dg[:, l, j] = grad # クリストッフェル記号 Γ^k_{ij} Gamma = torch.zeros(d, d, d)
for k in range(d):
for i in range(d):
for j in range(i, d):
s = 0.0
for l in range(d): s += g_inv[k, l] * (dg[i, j, l] + dg[j, i, l] - dg[l, i, j]) Gamma[k, i, j] = 0.5 * s
if i != j: Gamma[k, j, i] = Gamma[k, i, j]
# 対称性
# Step 6: リーマン曲率テンソル R^l_{ijk}
# R^l_{ijk} = ∂_j Γ^l_{ik} - ∂_i Γ^l_{jk} + Γ^l_{jm} Γ^m_{ik} - Γ^l_{im} Γ^m_{jk} # ∂Γ/∂z を自動微分で計算 dGamma = torch.zeros(d, d, d, d) # dGamma[l,i,k,j] = ∂Γ^l_{ik}/∂z^j
for l in range(d):
for i in range(d):
for k in range(d):
if Gamma[l, i, k].requires_grad:
grad = torch.autograd.grad( Gamma[l, i, k], z_t, retain_graph=True, create_graph=False )[0] dGamma[l, i, k, :] = grad # リッチテンソル Ric_{ij} = R^l_{ilj} Ric = torch.zeros(d, d)
for i in range(d):
for j in range(d):
for l in range(d):
# R^l_{ilj} = ∂_l Γ^l_{ij} - ∂_i Γ^l_{lj} + Γ^l_{lm}Γ^m_{ij} - Γ^l_{im}Γ^m_{lj} Ric[i, j] += dGamma[l, i, j, l] - dGamma[l, l, j, i]
ffor m in range(d): Ric[i, j] += Gamma[l, l, m] * Gamma[m, i, j] Ric[i, j] -= Gamma[l, i, m] * Gamma[m, l, j] # スカラー曲率 Scal = g^{ij} Ric_{ij} Scal = torch.sum(g_inv * Ric).item()
return Scal, Ric.detach(), Gamma.detach()
# 格子点上でスカラー曲率を計算
scalar_curvature_map = np.zeros((grid_size, grid_size))
ffor idx, z in enumerate(z_grid_points):
z_t = torch.FloatTensor(z) try: scal, _,
_ = christoffel_and_curvature(metric, z_t, d=6) i,
j = idx // grid_size, idx % grid_size scalar_curvature_map[i, j] = scal except: scalar_curvature_map[i // grid_size, idx % grid_size] = np.nan
print(f"スカラー曲率の統計:")
print(f" 最小値: {np.nanmin(scalar_curvature_map):.3f}")
print(f" 最大値: {np.nanmax(scalar_curvature_map):.3f}")
print(f" 正の曲率の領域: {(scalar_curvature_map > 0).sum()}/{grid_size**2}")
print(f" 負の曲率の領域: {(scalar_curvature_map < 0).sum()}/{grid_size**2}")
実行結果:
スカラー曲率の統計:
- 最小値: -4.872
- 最大値: 3.241
- 正の曲率の領域: 247/400(安定領域)
- 負の曲率の領域: 153/400(不安定領域)
神谷: 「スカラー曲率について、わかりやすく説明します。地球の表面を考えてください。球面は正の曲率を持ちます。球面の上にボールを置くと、ボールは転がり落ちますが、球面の内側——ボウルの底——にボールを置くと、ボールは安定して止まります。これが正の曲率の直感です」
神谷: 「一方、馬のサドル(鞍)を考えてください。前後方向には凹んでいますが、左右方向には膨らんでいます。サドルの上にボールを置くと、前後どちらかに転がり落ちます。これが負の曲率です。前後と左右で曲がり方が逆だから、不安定なのです」
神谷: 「経営環境の多様体でも同じことが起きます。正の曲率の領域は、経営状態が少し揺らいでも元に戻る安定構造です。負の曲率の領域は、少しの揺らぎが増幅されて、経営状態が大きく変動しうる不安定構造です」
神谷: 「スカラー曲率マップが完成した。153点が負の曲率——不安定領域だ。これを可視化しよう」
第5章:取締役会の啓示 ── 2026年5月12日(月)午前10時 戦略会議室
38階の戦略会議室は、一変していた。
長い楕円形のテーブルの上に、半透明のホログラフィック・ディスプレイが浮かんでいる。林が3日がかりでセットアップした、最新の空間映像投影システムだった。
黒田CEO、白石CFO、赤城CRO、そして社外取締役4名が着席している。神谷チームの4名は、ディスプレイの操作卓に立っている。
黒田: 「神谷くん、準備はいいか」
神谷: 「はい。本日は、5週間かけて構築した『ユリディカ株式会社経営環境多様体』をお見せします。まず、全体像をご覧ください」
神谷がタブレットを操作すると、テーブルの上に、山脈のような立体的な地形が浮かび上がった。
赤い尖塔が危険を示すようにそそり立ち、その間を青白い谷が走り、緑色に輝く安定した台地が広がっている。地形の表面には、等高線のような細い光の線が縦横に走り、地形の全体が微かに脈動するように光を変えていた。
# ===== gen_all_scenes.py: Scene 1 — 経営環境多様体の全体像 =====
# Plotlyのgo.Surfaceで3D曲面を描画し、surfacecolorでスカラー曲率を色マッピング
import numpy as np
import plotly.graph_objects as go
from scipy.interpolate import RegularGridInterpolator
np.random.seed(42)
x = np.linspace(-3, 3, 80)
y = np.linspace(-3, 3, 80)
X, Y = np.meshgrid(x, y)
# 曲率ランドスケープ: 2つの不安定ピーク(負の曲率)と安定な谷
Z = (1.5 * np.exp(-((X+1.8)**2 + (Y-0.5)**2)/1.2) +
1.8 * np.exp(-((X-1.5)**2 + (Y+0.8)**2)/1.5) -
0.8 * np.exp(-((X-0.2)**2 + (Y-0.3)**2)/2.5) +
0.3 * np.sin(X*0.8) * np.cos(Y*0.6))
# スカラー曲率を色マッピング用に計算
Scal = -(2.5 * np.exp(-((X+1.8)**2 + (Y-0.5)**2)/1.0) +
3.0 * np.exp(-((X-1.5)**2 + (Y+0.8)**2)/1.2)) + \
1.5 * np.exp(-((X-0.2)**2 + (Y-0.3)**2)/3.0) + 0.5
fig = go.Figure()
# go.Surfaceで3D曲面を描画、surfacecolorでスカラー曲率を色マッピング
fig.add_trace(go.Surface(
x=X, y=Y, z=Z, surfacecolor=Scal,
colorscale=[[0, "#0000FF"], [0.3, "#00BFFF"], [0.5, "#222222"],
[0.7, "#FF4500"], [1.0, "#FF0000"]],
lighting=dict(ambient=0.3, diffuse=0.6, specular=0.4,
fresnel=0.3, roughness=0.5),
opacity=0.85, showscale=True,
))
# go.Scatter3dで測地線経路(緑)を描画
interp = RegularGridInterpolator((x, y), Z.T,
bounds_error=False, fill_value=0)
t_geo = np.linspace(0, 1, 100)
x_geo = -2.5 + 5.0*t_geo + 0.8*np.sin(t_geo*3.5*np.pi)
y_geo = -1.5 + 3.0*t_geo + 0.5*np.cos(t_geo*2.5*np.pi)
x_geo = np.clip(x_geo, -3, 3); y_geo = np.clip(y_geo, -3, 3)
z_geo = interp(np.column_stack([x_geo, y_geo])) + 0.05
fig.add_trace(go.Scatter3d(
x=x_geo, y=y_geo, z=z_geo,
mode='lines', line=dict(color="#00FF66", width=6),
name="Geodesic (Min-Risk)"
))
# 直線経路(赤破線)
x_dir = np.linspace(-2.0, 2.0, 50)
y_dir = np.linspace(-1.0, 1.5, 50)
z_dir = interp(np.column_stack([x_dir, y_dir])) + 0.05
fig.add_trace(go.Scatter3d(
x=x_dir, y=y_dir, z=z_dir,
mode='lines', line=dict(color="#FF0000", width=4, dash='dash'),
name="Direct Path (High Risk)"
))
# 現在位置(金色の球体)
fig.add_trace(go.Scatter3d(
x=[-0.8], y=[-0.3],
z=[interp(np.array([[-0.8, -0.3]]))[0] + 0.1],
mode='markers+text',
marker=dict(size=10, color="#FFD700", symbol='diamond'),
text=["YURIDICA 現在位置"], textposition="top center",
textfont=dict(color="#FFD700", size=10), name="Current Position"
))
fig.update_layout(
paper_bgcolor="#060612", plot_bgcolor="#060612",
font=dict(family="Arial", color="#CCDDEE"),
title=dict(text="<b>REAL-TIME STRATEGIC MANIFOLD</b>",
font=dict(size=16, color="#00E5FF"), x=0.5),
scene=dict(
xaxis=dict(title="z¹ (Market/Geopolitics)",
backgroundcolor="#060612", gridcolor="#1a1a2e"),
yaxis=dict(title="z² (Cost/Regulation)",
backgroundcolor="#060612", gridcolor="#1a1a2e"),
zaxis=dict(title="z³ (Competition)",
backgroundcolor="#060612", gridcolor="#1a1a2e"),
camera=dict(eye=dict(x=1.6, y=-1.4, z=0.9)),
),
width=1200, height=800,
)
fig.write_image("s1_manifold.png", scale=2)
【Gemini画像生成プロンプト — Scene 1: 経営環境多様体の全体像】
Generate a photorealistic image of a high-tech Japanese corporate boardroom on the 38th floor. Eight Japanese executives in dark business suits sit around a long elliptical black conference table. Floor-to-ceiling windows show a daytime Tokyo skyline with Tokyo Tower visible. The entire table surface is a giant embedded display showing a holographic 3D terrain map that glows upward. A young Japanese man (age 34) stands at one end, gesturing at the display. The 3D terrain is a business environment manifold: dramatic wireframe mesh with sharp red/crimson peaks on the left (labeled "US-CHINA TARIFF RISK") and right (labeled "EU REGULATION RISK"), with a cyan/teal valley between them (labeled "CURRENT STABLE ZONE"). The most prominent element is a single bright luminous GREEN line — the geodesic minimum-risk route — curving across the terrain from a glowing golden sphere ("YURIDICA CURRENT POSITION") around the red peaks to a yellow diamond ("TARGET STATE"). A RED dashed line goes straight through the danger zone ("DIRECT PATH — HIGH RISK"). A MAGENTA dash-dot line follows an intermediate route ("OPTIMAL CONTROL"). Yellow cone arrows scatter across the surface as policy vector field. Crisis markers glow in the unstable zones: cyan "2008 LEHMAN," green "2020 COVID," orange "2026 TARIFF." Bottom: "REAL-TIME STRATEGIC MANIFOLD." Style: Photorealistic, cinematic, dark moody, neon glow, 8K.
図5-1:戦略会議室のテーブルに映し出された経営環境多様体。緑に光る線=測地線(当社がたどるべき最小リスク経路)。赤破線=直線経路(高リスク)。マゼンタ=最適制御経路。
神谷は、テーブル上の地形を横切って輝く緑の線を指さした。
神谷: 「皆様、この緑色に光る線をご覧ください。これが、計算の結果導き出された、当社がたどるべき経営ルートです」
会議室が静まり返った。
神谷: 「この線は『測地線』と呼ばれます。『ジオメトリック・インテリジェンス』のStep 9で、測地線方程式——曲がった空間の中でエネルギーが最小の経路を求める微分方程式——を数値的に解くことで計算されます。Step 5で求めたクリストッフェル記号——空間の曲がりの補正値——を使って、不安定領域を自然に避ける最適ルートを算出します」
神谷: 「赤い破線は従来の直線的な計画です。不安定領域——リーマンショックやCOVIDが起きた青い谷——を真っ直ぐ突き抜けます。マゼンタの線は、Step 8-9のポントリャーギンの最大原理——最適制御理論の中心定理——で計算した、利用可能な施策の制約下でのコスト最小化経路です。測地線が自由落下の経路なら、制御経路はロケットエンジンで軌道修正した最適経路です」
白石: 「これは……」
白石: 「待って。この立体図の軸は何を表しているの? x軸、y軸、z軸は、それぞれ何の数値?」
神谷は、これが最も重要な質問だと思った。経営者がこの地形図を理解するには、軸の意味を正確に把握する必要がある。
神谷: 「非常に重要なご質問です。順を追って説明します」
神谷はテーブル脇のホワイトボードに向かった。
神谷: 「まず前提を確認させてください。当社のデータは、もともと65個の経営指標です。売上、利益率、為替レート、関税率、センチメント、工場稼働率——65個の数値が、毎月1つの点として65次元の空間に存在しています。人間には65次元の空間は見えませんし、想像もできません」
神谷: 「そこでVAE——変分オートエンコーダ——というAIモデルを使って、65次元を6次元に圧縮しました。この圧縮は、PCAの分析結果に基づいて、6次元で元データの情報の75%以上を保存できると確認した上で行っています」
黒田: 「65次元を6次元に。情報は失われないのか」
神谷: 「完全には保存されません。再構成誤差は4.1%——つまり95.9%の情報が保存されています。残りの4.1%は、多様体の構造にとって本質的でないノイズや微小な変動です」
神谷: 「さて、ここからが核心です。6次元の潜在空間のうち、画面に表示しているのは最も重要な3次元です。VAEのデコーダのヤコビ行列を分析して、各潜在次元が元の65指標のどの指標群と最も強く連動しているかを特定しました」
神谷はホワイトボードに3つの軸を描いた。
神谷: 「z¹軸——画面の左右方向——は、主に市場需要と地政学的リスクを表しています。具体的には、各地域の売上高、市場シェア、消費者センチメント、そして米中関税率・貿易障壁指数と最も強く相関しています。ヤコビ行列の第1列を見ると、これらの指標の偏微分値が最も大きい。z¹が負の方向(画面の左)に動くと、地政学リスクが高まり市場需要が縮小する経営状態を意味します。z¹が正の方向(画面の右)に動くと、市場が安定し需要が伸びる状態です」
神谷: 「z²軸——画面の奥行き方向——は、コスト構造と規制環境です。原材料費、界面活性剤コスト、物流費、そしてEUの化学物質規制指数、マイクロプラスチック規制フェーズと強く連動しています。z²が負の方向に動くとコストが上がり規制が厳しくなる。正の方向はコスト低下と規制緩和です」
神谷: 「z³軸——画面の上下方向——は、競争ポジションです。営業利益率、工場稼働率、在庫回転率、特許出願数、そして競合他社との市場シェア差と連動しています。z³が上に行くほど競争優位が高い。下に行くほど競争劣位です」
白石: 「つまり、この地形図の右上の高い場所にいれば、市場は安定していて、コストは低く、競争に勝っている状態ということ?」
神谷: 「まさにその通りです。そして当社の現在位置——この金色の球体——は、z¹がやや負(地政学リスクの高まり)、z²が中程度(コスト構造はまだ持ちこたえている)、z³がやや正(競争優位はまだある)の位置にいます」
赤城: 「左右の赤い山脈は、z¹方向とz²方向のリスクが同時に悪化する領域に対応するのか」
神谷: 「正確です。赤城CRO、鋭いご指摘です。不安定領域は、1つの軸だけが悪化するのではなく、複数の軸が連動して悪化する領域です。これが非線形構造の本質で、直線的な分析では見えない部分です。為替が悪化すると原材料コストも上がり、それが工場稼働率に影響し、稼働率の低下が単位コストを押し上げ——この連鎖が、多様体の曲率として数値化されているのです」
神谷: 「補足ですが、残りの3次元——z⁴、z⁵、z⁶——も重要です。z⁴はブランドセンチメントの地域間格差、z⁵はサプライチェーンの構造的強度、z⁶はサイバーリスクと知的財産の保全状態に対応しています。今日の3D表示では、z⁴〜z⁶は当社の現在値に固定して断面を切っています。必要であれば、異なるz⁴〜z⁶の値で断面を切り替えて、別の角度から地形を見ることも可能です」
黒田: 「なるほど。65本の柱を6本に束ねて、そのうち最も太い3本を目に見える形にした、ということだな」
神谷: 「素晴らしい要約です。そしてこの『束ね方』は、VAEのデコーダが学習した非線形関数で決まっています。線形的な束ね方——PCAのような手法——では、65指標の直線的な組み合わせしか表現できません。VAEは非線形な組み合わせを学習するため、為替と原材料コストの非線形な連動構造のような、直線的分析では見えない構造を捉えることができます」
神谷: 「これが、ユリディカ株式会社の経営環境多様体のスカラー曲率マップです。赤い山脈は負のスカラー曲率——構造的に不安定な領域です。経営環境がちょっとした外乱で大きく変動しうる、危険な地帯です。緑色の台地は正のスカラー曲率——安定した構造を持つ領域です。そして、この金色の球——」
テーブル上の地形の中に、1つの光る金色の球体が浮かんでいた。
神谷: 「これが、ユリディカ株式会社の現在の経営状態です。2026年4月現在の、すべてのデータが示す当社の位置です」
赤城: 「……我々は今、2つの赤い山脈に挟まれた谷間にいるのか」
神谷: 「正確にはそうです。左側の赤い山脈は米中貿易摩擦リスクの不安定構造、右側の赤い山脈はEU規制リスクの不安定構造です。当社の現在位置は、この2つの不安定領域に挟まれた、比較的安定な谷間(正の曲率の領域)にありますが——」
神谷は画面を操作した。金色の球体が微かに振動し始めた。
神谷: 「米国商務省の45%追加関税と、中国の国産品優先調達指令により、当社の経営状態ベクトルが、左側の不安定領域に向かって移動し始めています。現在の移動速度から推定すると、あと約4ヶ月で不安定領域の境界に到達します」
# ===== gen_all_scenes.py: Scene 2 — スカラー曲率ランドスケープ =====
# Height = curvature value: 山頂=安定(正の曲率)、谷=不安定(負の曲率)
import numpy as np
import plotly.graph_objects as go
from scipy.interpolate import RegularGridInterpolator
np.random.seed(42)
x = np.linspace(-3, 3, 80); y = np.linspace(-3, 3, 80)
X, Y = np.meshgrid(x, y)
# スカラー曲率マップ(高さ=曲率値)
Scal = (1.8*np.exp(-((X-1)**2+(Y-1)**2)/1.5) +
1.2*np.exp(-((X+2)**2+(Y+1.5)**2)/1.8) -
2.5*np.exp(-((X+0.5)**2+(Y-0.2)**2)/0.8) -
1.8*np.exp(-((X-2)**2+(Y-1.5)**2)/1.0))
fig = go.Figure()
fig.add_trace(go.Surface(
x=X, y=Y, z=Scal, surfacecolor=Scal,
colorscale=[[0,"#0000CC"],[0.25,"#00BFFF"],[0.45,"#111133"],
[0.55,"#331111"],[0.75,"#FF4500"],[1.0,"#FF0000"]],
lighting=dict(ambient=0.35, diffuse=0.55, specular=0.5,
fresnel=0.4, roughness=0.4),
opacity=0.9,
))
# go.Scatter3dで測地線(安定な尾根を通るルート)を描画
interp = RegularGridInterpolator((x,y), Scal.T,
bounds_error=False, fill_value=0)
t = np.linspace(0, 1, 80)
xp = -2.5+4.5*t+0.6*np.sin(t*4*np.pi)
yp = -2.0+4.0*t+0.4*np.cos(t*3*np.pi)
xp = np.clip(xp,-3,3); yp = np.clip(yp,-3,3)
zp = interp(np.column_stack([xp,yp])) + 0.08
fig.add_trace(go.Scatter3d(x=xp,y=yp,z=zp,
mode='lines', line=dict(color="#00FF66",width=6),
name="Geodesic"))
fig.update_layout(
paper_bgcolor="#060612",
title=dict(
text="<b>SCALAR CURVATURE LANDSCAPE — YURIDICA MANIFOLD</b>"
"<br><sub>Height=Curvature | Valleys=Unstable | "
"Peaks=Stable</sub>",
font=dict(size=16, color="#00E5FF"), x=0.5),
scene=dict(
xaxis=dict(title="z¹",backgroundcolor="#060612"),
yaxis=dict(title="z²",backgroundcolor="#060612"),
zaxis=dict(title="Scal(z)",backgroundcolor="#060612"),
camera=dict(eye=dict(x=1.5,y=-1.3,z=1.0)),
),
width=1200, height=800,
)
fig.write_image("s2_curvature.png", scale=2)
【Gemini画像生成プロンプト — Scene 2: スカラー曲率ランドスケープ】
Generate a dramatic 3D data visualization on dark background (#070710). No people. A "SCALAR CURVATURE LANDSCAPE" where HEIGHT represents curvature value. Tall sharp peaks in hot red/orange/white glow labeled "STABLE ZONE +3.2." Deep craters in electric blue/cyan labeled "CRISIS ZONE: Scal = -4.8." A bright GREEN geodesic line curves across the terrain, staying on elevated ridges to avoid the blue valleys. A RED dashed line plunges through the deepest valley. A MAGENTA dash-dot line takes an intermediate route. Golden sphere at valley edge labeled "YURIDICA APRIL 2026." Yellow diamond on distant peak labeled "TARGET." Crisis markers in the blue valleys: cyan "2008 LEHMAN," green "2020 COVID," orange "2026 TARIFF." Colorbar: blue (-1.5 Unstable) to red (+1.0 Stable). Title: "SCALAR CURVATURE LANDSCAPE — YURIDICA MANIFOLD." Subtitle: "Height = Curvature | Valleys = Unstable | Peaks = Stable." Style: Dark sci-fi, neon wireframe, volumetric fog in valleys, 8K.
図5-2:スカラー曲率ランドスケープ。谷=不安定(負の曲率)、山頂=安定(正の曲率)。緑の光る線が不安定な谷を避けて安定な尾根を通る。
神谷: 「次の画面では、曲率の値そのものを高さとして表示しています。高い山は安定構造、深い谷は不安定構造です。そして緑の光る線——当社がたどるべきルート——は、谷底を避けて尾根を通っているのがお分かりいただけると思います」
【ナレーション】スカラー曲率は、『ジオメトリック・インテリジェンス』のStep 6で計算される。VAEデコーダのヤコビ行列から引き戻し計量を求め(Step 4)、その計量の偏微分からクリストッフェル記号を算出し(Step 5)、さらにクリストッフェル記号の偏微分からリーマン曲率テンソルを計算する(Step 6)。スカラー曲率は曲率テンソルの縮約であり、各点の「総合的な曲がり具合」を1つの数値で表す。正の値は安定構造(測地線が収束)、負の値は不安定構造(測地線が発散)を示す。
白石: 「4ヶ月……2026年8月か。Q3のど真ん中じゃない」
神谷: 「はい。しかし、重要なのはここからです。次に、Lie微分分析の結果をお見せします」
第6章:Lie微分の啓示 ── Step 7の実行
神谷: 「Lie微分についても、わかりやすく説明します。普通の政策評価は、『この施策を打った結果、売上がどう変わったか』を測定します。いわば、車の速度計です。Lie微分は違います。『この施策を打つと、道路そのものの形がどう変わるか』を測定します」
白石: 「道路の形が変わる?」
神谷: 「はい。たとえば、既存顧客サービス強化は、今の道路の上をちょっとだけ前に進む施策です。道路の形は変わりません。一方、事業ポートフォリオの抜本再編は、道路そのものを付け替える——坂道を平らにしたり、新しい橋を架けたりする——施策です。前者はLie微分がほぼゼロ(構造保存型)、後者はLie微分が大きい(構造変形型)です」
赤城: 「構造変形型は危険なのか?」
神谷: 「必ずしも。道路を付け替えれば、もっと良い道路にできる可能性があります。しかし、工事中は通行止めになり、予想外の問題が起きるリスクがある。Lie微分の大きさは、その『工事の規模』を定量的に教えてくれます」
# ===== gen_all_scenes.py: Scene 5 — Lie微分の数学的概念図 =====
# 青い曲面上の2つのベクトル場(緑・赤)のフローとLie微分(マゼンタ)
import numpy as np
import plotly.graph_objects as go
from scipy.interpolate import RegularGridInterpolator
np.random.seed(42)
x = np.linspace(-3,3,60); y = np.linspace(-3,3,60)
X,Y = np.meshgrid(x,y)
Z = 0.4*np.sin(X*0.6)*np.cos(Y*0.4) + 0.2*np.exp(-(X**2+Y**2)/8)
fig = go.Figure()
fig.add_trace(go.Surface(
x=X, y=Y, z=Z,
colorscale=[[0,"#001144"],[0.5,"#003388"],[1,"#0066CC"]],
lighting=dict(ambient=0.35,diffuse=0.5,specular=0.3),
showscale=False, opacity=0.7,
))
interp = RegularGridInterpolator((x,y),Z.T,
bounds_error=False, fill_value=0)
# ベクトル場X(緑): 左→右のフロー
for yi in np.linspace(-2,2,5):
xs = np.linspace(-2.5,2.5,15)
ys = np.full_like(xs,yi)+0.1*np.sin(xs*0.5)
zs = interp(np.column_stack([xs,ys]))+0.02
fig.add_trace(go.Scatter3d(x=xs,y=ys,z=zs,
mode='lines',line=dict(color="#00FF66",width=3),
showlegend=False))
# ベクトル場Y(赤): 異なる方向
for xi in np.linspace(-2,2,5):
ys2 = np.linspace(-2.5,2.5,15)
xs2 = np.full_like(ys2,xi)+0.15*np.cos(ys2*0.4)
zs2 = interp(np.column_stack([xs2,ys2]))+0.02
fig.add_trace(go.Scatter3d(x=xs2,y=ys2,z=zs2,
mode='lines',line=dict(color="#FF4444",width=3),
showlegend=False))
# Lie微分ベクトル(マゼンタ矢印)— go.Coneで3D矢印を描画
fig.add_trace(go.Cone(
x=[1.0],y=[0.5],z=[interp(np.array([[1.0,0.5]]))[0]+0.05],
u=[0.3],v=[0.4],w=[0.15],
colorscale=[[0,"#FF00FF"],[1,"#FF00FF"]],
showscale=False, sizemode="absolute", sizeref=0.4,
name="Lie Derivative"))
fig.update_layout(
paper_bgcolor="#0a0a18",
title=dict(text="<b>LIE DERIVATIVE — VECTOR FIELD INTERACTION"
"</b>", font=dict(size=16,color="#00E5FF"),x=0.5),
scene=dict(camera=dict(eye=dict(x=1.4,y=-1.2,z=0.8))),
width=1200, height=800,
)
fig.write_image("s5_lie_concept.png", scale=2)
【Gemini画像生成プロンプト — Scene 5: Lie微分の数学的概念図】
Generate a mathematical diagram on dark background (#0a0a18). A gently curved semi-transparent BLUE wireframe surface floating in 3D space. On the surface: GREEN arrows labeled "VECTOR FIELD X / FLOW OF X" streaming left to right following the curvature. RED arrows labeled "VECTOR FIELD Y" pointing in a different direction. Two points "P" and "Q" connected by a red dashed flow line. At Q, a MAGENTA arrow shows the Lie derivative — the difference between naive transport and actual vector. Equation at bottom: "[X, Y] = L_X Y" in magenta. Small white coordinate axes labeled "LOCAL COORDINATES." Title: "LIE DERIVATIVE FOR MANIFOLD DIFFERENTIAL EQUATIONS." Style: Clean mathematical diagram, dark background, neon vectors on blue wireframe, educational but cinematic, 8K.
図6-2:Lie微分の数学的概念図。青い曲面上の2つのベクトル場(緑・赤)のフローに沿ったLie微分(マゼンタの矢印)。
神谷: 「Lie微分を直感的に説明します。この青い曲面が経営環境多様体です。緑の矢印が施策Aのフロー、赤の矢印が施策Bの効果方向です。施策Aを実行した後に施策Bの効果がどう変わるか——そのずれがマゼンタの矢印、つまりLie微分です。この矢印がゼロなら干渉なし。大きければ、施策Aが施策Bの効果を歪めてしまう」
6.1 政策ベクトル場の学習
【ナレーション】
Lie微分は、「政策が空間の構造そのものをどう変えるか」を測定する数学的ツールである。従来の政策評価が「結果がどう変わったか」を事後的に測定するのに対し、Lie微分は「政策を打つ前に、その政策が経営環境の構造をどう変形させるか」をシミュレーションできる。
計量のLie微分 $(\mathcal{L}V g){ij} = \nabla_i V_j + \nabla_j V_i$ がゼロ(Killing方程式)なら、その施策は「環境の構造を壊さずに、状態だけを移動させる」穏やかな施策。ゼロでなければ、「環境の構造そのもの——どこが安定でどこが不安定か——を書き換えてしまう」構造変革型の施策。
# ===== Step 7: Lie微分分析 =====
# まず、4つの政策オプションをベクトル場として学習
# 各政策の「過去の類似施策が経営状態をどう動かしたか」のデータから推定
class PolicyVectorField(nn.Module): """政策ベクトル場をニューラルネットで表現"""
def __init__(self, d=6):
super().__init__()
self.net = nn.Sequential( nn.Linear(d, 32), nn.Tanh(), nn.Linear(32, 32), nn.Tanh(), nn.Linear(32, d) )
def forward(self, z):
return1et(z)
#つの政策オプション
policies = { 'A_china_withdrawal': PolicyVectorField(),
# 中国市場段階的撤退 'B_india_acceleration': PolicyVectorField(), # インド市場加速 'C_supply_chain_reorg': PolicyVectorField(), # サプライチェーン再編 'D_eu_reformulation': PolicyVectorField(),
# EU向け処方変更 }
# Lie微分の計算
def lie_derivative_metric(V_net, metric_fn, z, d=6): """計量のLie微分 (L_V g)_{ij} を計算"""
z_t = z.clone().requires_grad_(True) V = V_net(z_t) g,
_ = metric_fn.compute(z_t)
# V_j = g_{jk} V^k (添字の下げ) V_lower = g @ V # ∂V_j/∂z^i dV = torch.zeros(d, d)
ffor j in range(d):
grad = torch.autograd.grad( V_lower[j], z_t, retain_graph=True, create_graph=True )[0] dV[:, j] = grad # Γ^k_{ij} の計算 _, _, Gamma = christoffel_and_curvature(metric_fn, z_t, d) # ∇_i V_j = ∂V_j/∂z^i - Γ^k_{ij} V_k nabla_V = torch.zeros(d, d)
for i in range(d):
for j in range(d): nabla_V[i, j] = dV[i, j]
for k in range(d): nabla_V[i, j] -= Gamma[k, i, j] * V_lower[k]
# L_V
g = ∇_i V_j + ∇_j V_i L_V_g = nabla_V + nabla_V.T
return L_V_g
#つの政策のLie微分ノルムを計算
z_current = torch.FloatTensor(z_all.mean(axis=0))
# 現在の平均状態
results = {}
ffor name, V_net in policies.items(): L_V_g = lie_derivative_metric(V_net, metric, z_current)
norm = torch.norm(L_V_g).item() results[name] = norm
print("政策のLie微分ノルム(構造的インパクト):")
ffor name, norm in sorted(results.items(), key=lambda x: x[1]):
impact = "構造保存型"
if norm < 0.5 else ("構造変形型(中)"
if norm < 2.0 else "構造変形型(強)")
print(f" {name}: ||L_V g|| = {norm:.3f} [{impact}]")
実行結果:
政策のLie微分ノルム(構造的インパクト): D_eu_reformulation: ||L_V g|| = 0.342 [構造保存型] B_india_acceleration: ||L_V g|| = 1.247 [構造変形型(中)] C_supply_chain_reorg: ||L_V g|| = 2.891 [構造変形型(強)] A_china_withdrawal: ||L_V g|| = 4.673 [構造変形型(強)]
6.2 Lie括弧による干渉分析
# ===== Lie括弧 [V_A, V_B] の計算 =====
def lie_bracket(V_net, W_net, z, d=6): """2つのベクトル場のLie括弧"""
z_t = z.clone().requires_grad_(True) V = V_net(z_t) W = W_net(z_t) JV = torch.zeros(d, d) JW = torch.zeros(d, d)
ffor k in range(d): JV[k] = torch.autograd.grad(V[k], z_t, retain_graph=True, create_graph=True)[0] JW[k] = torch.autograd.grad(W[k], z_t, retain_graph=True, create_graph=True)[0]
bracket = JW @ V - JV @ W
return bracket
# 全ペアのLie括弧ノルム
policy_names = list(policies.keys())
print("\n政策間の干渉(Lie括弧ノルム):")
print(" ", " ".join([n[:6]
ffor n in policy_names]))
ffor i, name_i in enumerate(policy_names):
row = f"{name_i[:6]}: "
ffor j, name_j in enumerate(policy_names):
if i >= j: row += " --- " else:
bracket = lie_bracket( policies[name_i], policies[name_j], z_current )
norm = torch.norm(bracket).item() row += f" {norm:5.2f} "
print(row)
実行結果:
政策間の干渉(Lie括弧ノルム): A_chin B_indi C_supp D_eu_r A_chin: --- 3.42 5.17 0.89 B_indi: --- --- 1.23 0.34 C_supp: --- --- --- 2.78 D_eu_r: --- --- --- ---
神谷は結果をテーブル上のホログラムに反映させた。4つの政策が、異なる色の矢印として地形図の上に表示された。
神谷: 「取締役の皆様、重要な発見があります」
地形図の上で、赤い矢印(中国撤退)と青い矢印(サプライチェーン再編)が交差する領域が、激しく点滅した。
# ===== gen_all_scenes.py: Scene 3 — Lie微分トリプティク =====
# 3つの施策の構造的インパクトを3つの3D曲面で並列比較
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
np.random.seed(42)
x = np.linspace(-3,3,60); y = np.linspace(-3,3,60)
X,Y = np.meshgrid(x,y)
Z_base = 0.5*np.sin(X*0.5)*np.cos(Y*0.5)+0.3*np.exp(-(X**2+Y**2)/4)
# 3段階の変形: EU処方変更(穏やか)、インド加速(中)、中国撤退(激烈)
Z_mild = Z_base + 0.1*np.random.randn(*Z_base.shape)*0.3
Z_med = Z_base + 0.6*np.exp(-((X-1)**2)/1.5)*np.sin(Y*1.5)
Z_strong = Z_base + 1.5*np.exp(-((X+0.5)**2+(Y-0.3)**2)/0.8) * \
np.cos(X*2) - 1.0*np.exp(-((X-1.5)**2)/0.5)
fig = make_subplots(rows=1, cols=3,
specs=[[{"type":"surface"},{"type":"surface"},
{"type":"surface"}]],
subplot_titles=[
"EU REFORMULATION<br>||L_V g||=0.34 (Preserving)",
"INDIA ACCELERATION<br>||L_V g||=1.25 (Moderate)",
"CHINA WITHDRAWAL<br>||L_V g||=4.67 (Strong)"])
colorscales = [
[[0,"#003333"],[0.5,"#006666"],[1,"#00CCCC"]],
[[0,"#332200"],[0.5,"#996600"],[1,"#FFCC00"]],
[[0,"#330011"],[0.5,"#990033"],[1,"#FF0066"]],
]
for i,(Z,cs) in enumerate(zip([Z_mild,Z_med,Z_strong],colorscales)):
fig.add_trace(go.Surface(x=X,y=Y,z=Z,colorscale=cs,
lighting=dict(ambient=0.3,diffuse=0.6,specular=0.3),
showscale=False, opacity=0.85), row=1, col=i+1)
fig.update_layout(
paper_bgcolor="#060612",
title=dict(text="<b>LIE DERIVATIVE ANALYSIS — POLICY "
"STRUCTURAL IMPACT</b>",
font=dict(size=15,color="#00E5FF"),x=0.5),
width=1500, height=500)
fig.write_image("s3_lie_triptych.png", scale=2)
【Gemini画像生成プロンプト — Scene 3: Lie微分トリプティク】
Generate a wide panoramic image (2.5:1 aspect ratio) on dark background (#050510) showing THREE side-by-side 3D wireframe terrains. LEFT: "EU REFORMULATION ||L_V g||=0.34 (Preserving)" — calm teal/cyan wireframe, gentle undulations, a thin glowing green geodesic line traces a gentle curve. CENTER: "INDIA ACCELERATION ||L_V g||=1.25 (Moderate)" — yellow/amber wireframe with visible peaks, the green path navigates between peaks. RIGHT: "CHINA WITHDRAWAL ||L_V g||=4.67 (Strong)" — violently deformed red/magenta wireframe with jagged spikes, the green path makes sharp turns to avoid hazards. Visual progression left-to-right: calm lake → choppy seas → tsunami. Title: "LIE DERIVATIVE ANALYSIS — POLICY STRUCTURAL IMPACT." Style: Dark, neon wireframe, holographic, 8K.
図6-1:Lie微分トリプティク。左=穏やか(構造保存)、中央=中程度の変形、右=激しい変形。各パネルの緑の光る線が、変形後の地形上での最適ルートを示す。
神谷: 「3枚の画面をご覧ください。同じ経営環境多様体に、3つの異なる施策を適用した結果です。緑の光る線——最適ルート——が、施策ごとにまったく異なる形をしていることに注目してください」
神谷: 「左のEU処方変更では、地形がほとんど変わらないため、光る線も穏やかな曲線です。右の中国撤退では、地形が激しく変形しているため、光る線は急カーブを余儀なくされています。光る線が急カーブするということは、その施策の実行中に方向転換が必要になる——つまり、追加のコストとリスクが発生するということです」
【ナレーション】Lie微分は『ジオメトリック・インテリジェンス』のStep 7で計算される。政策をベクトル場Vとしてモデル化し、計量テンソルgのLie微分 (L_V g)_ij = ∇_i V_j + ∇_j V_i を計算する。∇はStep 5のクリストッフェル記号を使った共変微分——曲がった空間での「正しい微分」——である。Lie微分がゼロ(Killing方程式)なら、施策は等長変換を生成し空間構造を保存する。ゼロでなければ、空間構造そのものが変形する。
神谷: 「中国段階的撤退(A)とサプライチェーン再編(C)のLie括弧ノルムが5.17——極めて大きい値です。これは、2つの施策を同時に実行すると、実行順序によって結果がまったく異なることを意味します。中国から撤退しながら同時にサプライチェーンを再編しようとすると、2つの施策が互いに打ち消し合い、最悪の場合、どちらの目標も達成できない」
赤城: 「具体的にはどういう意味だ」
神谷: 「中国工場から撤退すると、サプライチェーン再編の選択肢が狭まります。なぜなら、中国工場は現在、インド向けの中間原料も一部生産しているからです。逆に、サプライチェーンを先に再編すると、中国工場の役割が変わり、撤退の優先順位や方法が変わります。つまり、AをやってからCをやるのと、CをやってからAをやるのでは、到達する経営状態がまったく違う」
白石: 「では、どちらを先にすべきなの?」
神谷: 「それを、次のステップ——最適制御シミュレーション——で計算します」
第7章:測地線を描く ── Step 8-9:制御シミュレーション
7.1 最適経路の計算
【ナレーション】
測地線は、多様体上の「最もエネルギーが低い経路」である。平面では直線だが、曲がった空間では「大圏航路」のように曲がった経路になる。東京からニューヨークへの最短飛行ルートが北極圏を通るのと同じ原理で、経営環境多様体上の測地線は、不安定領域(負の曲率の領域)を自然に避ける。
さらにポントリャーギンの最大原理を使えば、「利用可能な施策の制約の下で、コストを最小化する移行経路」が計算できる。
# ===== Step 8-9: 制御シミュレーション =====
from torchdiffeq
import odeint
class ManifoldController(nn.Module): """多様体上の制御付きODEシステム"""
def __init__(self, drift_net, control_nets, d=6):
super().__init__()
self.drift = drift_net # ドリフト F(z): 自然な変化
self.controls = control_nets
# 制御 B_α(z): 各施策の効果
self.d = d
def forward(self, t, state, u=None):
z = state[:self.d]
dz =1rift(z)
if u is not None:
ffor alpha, (name, B) in enumerate(self.controls.items()):
dz = dz + u[alpha] * B(z)
return dz
#つの経路を計算
# 経路1: 何もしない(ドリフトのみ)
# 経路2: 測地線(エネルギー最小経路)
# 経路3: 制御付き最適経路(4施策の組み合わせ)
z_start = torch.FloatTensor(z_all[-12:].mean(axis=0))
# 直近12ヶ月の平均
z_target = torch.FloatTensor([0.5, 1.2, -0.3, 0.8, 0.5, -0.2])
# 目標状態
# 経路1: ドリフトのみ
drift_net = NeuralODEFunc(dim=6)
t_span = torch.linspace(0, 24, 100)
#4ヶ月
trajectory_drift = odeint(drift_net, z_start.unsqueeze(0), t_span)
print(f"経路1(ドリフト): {trajectory_drift.shape}")
# 経路3: 最適制御(勾配降下法で制御入力を最適化)
u_params = torch.zeros(4, 100, requires_grad=True)
#施策 × 100タイムステップ
opt_u = optim.Adam([u_params], lr=0.01)
ffor iteration in range(200):
u_current = torch.sigmoid(u_params) # [0, 1] に制約
trajectory = odeint( lambda t, z: ManifoldController( drift_net, policies, d=6 ).forward(t, z, u=u_current[:, int(t.item()*99/24)]), z_start.unsqueeze(0), t_span )
# 終点の目標との距離 + 制御コスト + 不安定領域回避ペナルティ
loss_target = torch.norm(trajectory[-1] - z_target)
loss_control = 0.1 * torch.sum(u_current**2)
loss = loss_target + loss_control opt_u.zero_grad() loss.backward() opt_u.step()
if (iteration + 1) % 50 == 0:
print(f"Iter {iteration+1}: target_dist={loss_target.item():.3f}, " f"control_cost={loss_control.item():.3f}")
print(f"\n最適制御スケジュール:")
u_optimal = torch.sigmoid(u_params).detach()
phase_names = ['0-6ヶ月', '6-12ヶ月', '12-18ヶ月', '18-24ヶ月']
policy_labels = ['中国撤退', 'インド加速', 'SC再編', 'EU処方変更']
ffor phase_idx, phase in enumerate(phase_names):
t_start = phase_idx * 25
t_end = (phase_idx + 1) * 25
print(f" {phase}:")
ffor p_idx, p_name in enumerate(policy_labels):
intensity = u_optimal[p_idx, t_start:t_end].mean().item()
bar = '█' * int(intensity * 20)
print(f" {p_name}: {intensity:.1%} {bar}")
実行結果:
Iter 50: target_dist=2.341, control_cost=0.456 Iter 100: target_dist=1.123, control_cost=0.389 Iter 150: target_dist=0.567, control_cost=0.312 Iter 200: target_dist=0.234, control_cost=0.287 最適制御スケジュール: 0-6ヶ月: 中国撤退: 12% ██ インド加速: 8% █ SC再編: 85% █████████████████ EU処方変更: 72% ██████████████ 6-12ヶ月: 中国撤退: 45% █████████ インド加速: 67% █████████████ SC再編: 43% ████████ EU処方変更: 91% ██████████████████ 12-18ヶ月: 中国撤退: 78% ███████████████ インド加速: 89% █████████████████ SC再編: 15% ███ EU処方変更: 34% ██████ 18-24ヶ月: 中国撤退: 91% ██████████████████ インド加速: 95% ███████████████████ SC再編: 5% █ EU処方変更: 12% ██
7.2 取締役会での戦略発表
神谷はテーブル上のホログラムを操作した。3つの経路が、立体地形図の上に描かれた。
黒い破線——何もしない場合のドリフト経路。不安定領域(赤い山脈)に向かってまっすぐ突き進んでいる。
緑の曲線——測地線。不安定領域を大きく迂回し、安定な谷間を通って目標状態に至る。しかし、24ヶ月では到達しない。
マゼンタの曲線——制御付き最適経路。不安定領域を巧みに避けながら、24ヶ月で目標状態に到達する。
神谷: 「最適制御シミュレーションの結果、以下の4フェーズ戦略を提案します」
ユリディカ株式会社 24ヶ月戦略ロードマップ——「幾何学的最適経路」に基づく提案
■ Phase 1(0-6ヶ月): 基盤整備 最優先: サプライチェーン再編(85%)+ EU処方変更(72%) Lie括弧分析により、SC再編とEU処方変更の干渉は小さい ( $||[V_C, V_D]|| = 2.78$ → 同時実行可能)
■ Phase 2(6-12ヶ月): 市場シフト開始 EU処方変更完了(91%)、インド市場加速(67%)、中国撤退開始(45%) ★重要: Lie括弧分析により、中国撤退はSC再編の後に開始 ( $||[V_A, V_C]|| = 5.17$ → 同時実行は危険)
■ Phase 3(12-18ヶ月): 本格移行 中国撤退加速(78%)、インド全面展開(89%) SC再編はPhase 1で完了済みのため干渉なし
■ Phase 4(18-24ヶ月): 新体制確立 中国撤退完了(91%)、インド市場が主要収益源に(95%) 新しい経営状態(目標)に到達 推定コスト: 総額 約480億円(24ヶ月間) リスク回避効果: 不安定領域通過の回避により、 経営環境急変時の最大損失を約1,200億円回避
白石: 「480億円……。しかし、何もしなければ1,200億円のリスクがあるということ?」
神谷: 「はい。ドリフト経路(何もしない場合)では、4ヶ月後にスカラー曲率が $-4.8$ の不安定領域に突入します。この領域は、2008年リーマンショック時と2020年COVID時の当社の経営状態が位置していた領域と一致しています。曲率マップの信頼性を、過去の既知危機との整合性で検証済みです」
赤城: 「Lie括弧分析で、中国撤退とサプライチェーン再編を同時に進めてはいけない、という結論は直感とも一致する。蘇州工場を閉鎖しながらベトナム工場を立ち上げるのは、物流網が混乱する。順序が重要だ」
黒田: 「フェーズ1でサプライチェーン再編とEU処方変更を先行させ、フェーズ2で中国撤退を開始する。この順序でいく。質問は?」
社外取締役の1人が手を挙げた。
社外取締役(元・経産省次官): 「神谷さん、1つ確認したい。この分析は、因果関係を示しているのか。それとも相関関係か」
神谷: 「極めて重要なご質問です。本手法が示す幾何構造は、非線形相関構造であり、因果関係ではありません。『米中関税が上がるとユリディカ株式会社の中国売上が下がる』という因果推論は、本手法の範囲外です。本手法が示すのは、『米中関税と中国売上とEU規制とインド市場が、どのような非線形の連動構造を持っているか』であり、その連動構造の上で最適な移行経路を計算しています」
神谷: 「因果推論は、介入データ、構造因果モデル、自然実験などの別の手法で補完する必要があります。本日の提案は、あくまで『探索的洞察』として、経営判断の参考情報としてお使いいただくことを推奨します」
黒田: 「理解した。因果ではなく構造、ということだな。しかし、その構造が過去の危機と整合している以上、無視はできない」
黒田は立ち上がった。
黒田: 「全員に聞く。この4フェーズ戦略を承認するか」
7名の取締役が、順に手を挙げた。全員一致。
黒田: 「決定だ。明日から動く」
第8章:リアルタイム経営 ── 2026年5月~8月
8.1 経営環境多様体のリアルタイム更新
神谷チームは、経営環境多様体を週次で更新する体制を構築した。
毎週月曜日の朝8時、新しいデータ(先週の日次販売データ、SNSセンチメント、規制変更、マクロ経済指標)がパイプラインに投入され、VAEが再訓練(ファインチューニング)され、計量と曲率が再計算される。
金色の球体——ユリディカ株式会社の現在位置——は、毎週更新され、マゼンタの最適経路の上を着実に進んでいく。
5月28日(水) ── 新たな嵐
中国商務部が、日本製日用品に対する「品質安全特別検査」を突然発表した。事実上の非関税障壁であり、ユリディカ株式会社の中国向け全製品の通関が2ヶ月間止まる可能性があった。
神谷チームのモニターに、経営環境多様体がリアルタイムで変形する様子が映し出された。
林: 「曲率マップが急変しています。中国事業関連の潜在次元(z3)方向のスカラー曲率が、$+0.8$ から $-3.2$ に転落。安定領域が一気に不安定領域に変わりました」
神谷: 「最適経路を再計算する。今すぐだ」
# ===== 緊急再計算:新しい規制データを反映 =====
# 新しいデータポイントを追加してVAEをファインチューニング
new_data_point = { 'trade_barrier_index_CN': 89,
# 前週の52から急上昇 'chemical_regulation_strictness_CN': 78,
# 前週の61から上昇 'sentiment_regulation_concern_CN': -0.82,
# 極めて否定的 }
# Lie微分の再計算
# 結果:中国撤退の構造的インパクトが変化 # ||L_{V_A} g|| が 4.673 → 3.121 に低下 # → 中国市場の構造が不安定化したことで、撤退のインパクトが
# 相対的に小さくなった(不安定な領域からの離脱は構造変形が小さい)
print("緊急再計算結果:")
print(" 中国撤退 Lie微分ノルム: 4.673 → 3.121")
print(" → 中国撤退のタイミング前倒しを推奨")
print(" 最適経路: Phase 2(6-12ヶ月目)の中国撤退強度を 45% → 72% に引き上げ")
神谷: 「黒田社長、緊急報告です。中国の品質安全特別検査により、経営環境多様体の構造が変化しました。Lie微分の再計算結果、中国撤退のタイミングを前倒しすべきです」
黒田: 「根拠は?」
神谷: 「中国市場の構造が急速に不安定化しています。不安定な領域からの撤退は、安定な領域からの撤退よりも構造変形が小さい——つまり、今撤退した方が、後で撤退するよりも『環境への衝撃』が小さいのです。わかりやすく言えば、嵐の中で港を離れるより、嵐が来る前に出港した方がいい。でも嵐が来てしまったら、嵐の目を通り抜けるのが最善です」
# ===== gen_all_scenes.py: Scene 4 — Lie括弧干渉マップ =====
# 政策間の干渉強度をgo.Surfaceで色マッピング、go.Coneでベクトル場矢印
import numpy as np
import plotly.graph_objects as go
from scipy.interpolate import RegularGridInterpolator
np.random.seed(42)
x = np.linspace(-3,3,80); y = np.linspace(-3,3,80)
X,Y = np.meshgrid(x,y)
# 干渉強度: 施策が交差する領域でピーク
interference = (3.0*np.exp(-((X-0.5)**2+(Y+0.3)**2)/0.8) +
1.5*np.exp(-((X+1)**2+(Y-1)**2)/1.2))
fig = go.Figure()
fig.add_trace(go.Surface(
x=X, y=Y, z=interference*0.5, surfacecolor=interference,
colorscale=[[0,"#1a0033"],[0.3,"#330066"],[0.5,"#663399"],
[0.7,"#CCAA00"],[0.85,"#FFDD00"],[1,"#FFFF66"]],
lighting=dict(ambient=0.3,diffuse=0.6,specular=0.4),
opacity=0.85))
# go.Coneで政策ベクトル場の3D矢印を描画
interp = RegularGridInterpolator((x,y),interference.T,
bounds_error=False, fill_value=0)
arrow_pts = [(-2,-1),(-1,-1),(0,-1),(1,-1),(-2,0),(1,0)]
for ax,ay in arrow_pts:
az = interp(np.array([[ax,ay]]))[0]*0.5+0.05
fig.add_trace(go.Cone(x=[ax],y=[ay],z=[az],
u=[0.5],v=[0.1],w=[0.05],
colorscale=[[0,"#FF3333"],[1,"#FF3333"]],
showscale=False, sizemode="absolute", sizeref=0.3))
# 推奨ルート(緑)— 干渉領域を迂回
t = np.linspace(0,1,60)
xp = -2.5+5*t+1.0*np.sin(t*2*np.pi)
yp = -2.0+4*t-0.8*np.cos(t*2.5*np.pi)
xp = np.clip(xp,-3,3); yp = np.clip(yp,-3,3)
zp = interp(np.column_stack([xp,yp]))*0.5+0.08
fig.add_trace(go.Scatter3d(x=xp,y=yp,z=zp,
mode='lines',line=dict(color="#00FF66",width=6),
name="Recommended Route"))
fig.update_layout(
paper_bgcolor="#06041a",
title=dict(text="<b>LIE BRACKET — POLICY INTERFERENCE MAP</b>"
"<br><sub>Purple=Independent | Yellow=Strong "
"Interference</sub>",
font=dict(size=16,color="#00E5FF"),x=0.5),
scene=dict(camera=dict(eye=dict(x=1.4,y=-1.5,z=1.0))),
width=1200, height=800)
fig.write_image("s4_lie_bracket.png", scale=2)
【Gemini画像生成プロンプト — Scene 4: Lie括弧干渉マップ】
Generate a 3D visualization on dark purple-black background (#06041a). A terrain where COLOR represents interference strength between two policies. DARK PURPLE areas: independent zones, safe to combine. BRIGHT YELLOW glowing areas: strong interference zones where execution order matters. The brightest spot has a large X marker labeled "MAX INTERFERENCE ||[V_A,V_C]||=5.17." RED arrows (China Withdrawal) flow left-to-right, GREEN arrows (Supply Chain Reorg) flow bottom-to-top. Where they cross in the yellow zone, a turbulent swirl pattern. A bright GREEN geodesic path line navigates AROUND the interference zone, labeled "RECOMMENDED: SC Reorg first, China Withdrawal second." Title: "LIE BRACKET — POLICY INTERFERENCE MAP." Subtitle: "Purple=Independent | Yellow=Strong Interference." Style: Dark purple sci-fi, neon, 8K.
図8-1:Lie括弧干渉マップ。紫=独立(同時実行可能)、黄色=強い干渉(順序が重要)。緑の光る線が干渉領域を避けた推奨ルートを示す。
神谷: 「黄色く光っている領域が、2つの施策が強く干渉する領域です。そして緑の光る線——推奨ルート——は、この干渉領域を迂回しています。つまり、サプライチェーン再編を先に完了してから中国撤退を開始する、という順序です」
【ナレーション】Lie括弧は『ジオメトリック・インテリジェンス』のStep 7で計算される。2つのベクトル場V, Wの Lie括弧 [V,W]^k = V^j ∂_j W^k - W^j ∂_j V^k は、「2つの施策を順番に実行したとき、実行順序によって結果がどれだけずれるか」を定量化する。各ベクトル場のヤコビ行列をPyTorchの自動微分で計算し、行列積の差として Lie括弧を求める。
黒田: 「わかった。フェーズ2を前倒しする。白石さん、蘇州工場のダウンサイジング計画を1ヶ月前倒しで開始してくれ」
8.2 競合企業の動き ── 経営環境多様体上の「もう1つの球体」
7月14日(月) ── 競合の動き
赤城: 「グローバルケア社(GlobalCare Inc.)が、インドのAyurvedicブランド『パタンジャリ』の口腔ケア事業を買収すると発表した。買収額は推定18億ドル」
神谷チームは、グローバルケア社の動きを経営環境多様体上にマッピングした。テーブルのホログラムに、金色の球体(ユリディカ株式会社)の横に、赤い球体(グローバルケア社)が出現した。
神谷: 「グローバルケア社のインド市場ポジションが、経営環境多様体上で大きくシフトしています。パタンジャリ買収により、グローバルケア社はインドのアーユルヴェーダ市場にアクセスを得た。我々のインド事業加速戦略(フェーズ3-4)が、グローバルケア社との直接競合に巻き込まれるリスクが高まっています」
# ===== 競合企業の動きを多様体上にプロット =====
# グローバルケア社のインド市場ポジションの変化
z_pg_before = torch.FloatTensor([...])
# グローバルケア社買収前の位置
z_pg_after = torch.FloatTensor([...])
# グローバルケア社買収後の位置 # リーマン距離の計算 # ユリディカ株式会社 ↔ グローバルケア社(買収前): 距離 5.2(遠い) # ユリディカ株式会社 ↔ グローバルケア社(買収後): 距離 2.1(近い) # → グローバルケア社がユリディカ株式会社のインド市場ポジションに接近
print("リーマン距離の変化:")
print(" ユリディカ株式会社 ↔ グローバルケア社(買収前): 5.2")
print(" ユリディカ株式会社 ↔ グローバルケア社(買収後): 2.1")
print(" → 距離が60%縮小。インド市場での直接競合リスク増大")
白石: 「つまり、我々のインド戦略を修正する必要があるということ?」
神谷: 「必ずしも。Lie括弧分析をもう一度実行します。グローバルケア社のアーユルヴェーダ路線と、我々のプレミアム・テクノロジー路線は、多様体上で異なる方向を向いています。干渉は限定的です。ただし、価格帯が重なるミッドレンジ市場では、スカラー曲率が負に転じるリスクがあります」
赤城: 「ミッドレンジは避けて、ハイエンドとサシェット(低価格小包装)の二極戦略に集中すべきだ」
神谷: 「多様体上の分析もそれを支持しています。ミッドレンジ領域の曲率が $-2.8$ であるのに対し、ハイエンド領域は $+1.4$、サシェット領域は $+0.6$ です。安定した領域で戦うべきです」
第9章:嵐を越えて ── 2026年8月~9月
9.1 サイバー攻撃
8月3日(日)午前3時17分 JST
林美咲のスマートフォンが鳴った。
「林さん、大変です。蘇州工場のIT管理部から連絡がありました。工場の生産管理システムにランサムウェアが侵入しています」
神谷チームは緊急召集された。
赤城: 「攻撃の種類は?」
張: 「ロシアのハッキング集団Contiの亜種と見られます。生産管理システムのデータが暗号化されました。身代金は500ビットコイン(約30億円)」
赤城: 「身代金は払わない。バックアップから復旧する」
神谷: 「問題はそこではありません。サイバー攻撃が経営環境多様体に与える影響を確認します」
# ===== サイバー攻撃の影響をリアルタイムで多様体に反映 ===== # サイバーリスク指標の急変
cyber_data_update = { 'cyber_attack_severity': 9.5,
#-10スケールで最大級 'data_breach_scale': 7.2,
# 処方データの一部が暗号化 'supply_chain_disruption': 8.8,
# 蘇州工場の生産停止 'sentiment_safety_concern_JP': -0.91,
# 日本での安全性懸念 'sentiment_safety_concern_CN': -0.78, }
# VAEファインチューニングと多様体の即時更新
# 結果:現在位置の金色球体が、不安定領域の境界に接近
print("サイバー攻撃後の現在位置:")
print(" 最寄りの不安定領域(Scal < 0)までの距離: 0.34")
print(" 攻撃前: 1.87 → 攻撃後: 0.34")
print(" ★★★ 警告:不安定領域への突入まで推定2週間 ★★★")
テーブルのホログラムに、金色の球体が激しく点滅し始めた。不安定領域の赤い山脈が、球体に迫ってくるように見えた。
黒田: 「2週間?」
神谷: 「はい。しかし、最適制御を再計算すれば、回避経路があります」
# 緊急最適制御:サイバー攻撃からの回復経路
# 追加の制御オプション:
# E_cyber_response: サイバーインシデント対応(バックアップ復旧、広報対応)
# F_insurance_activation: サイバー保険の発動
# 結果: print("緊急回避経路:")
print(" 1. 即座にサイバーインシデント対応チームを蘇州に派遣(強度100%)")
print(" 2. サイバー保険を発動(強度100%)") print(" 3. 蘇州工場の生産を天津工場に一時移管(強度85%)")
print(" 4. 中国撤退スケジュールを2ヶ月前倒し(これはチャンスでもある)")
print(" → 不安定領域への突入を回避し、かつ中国撤退を加速")
赤城: 「逆転の発想だ。サイバー攻撃を、中国撤退の加速材料にする」
神谷: 「多様体上のLie微分分析が、まさにそれを示しています。サイバー攻撃によって中国事業の構造がすでに不安定化しているため、撤退の構造的インパクト(Lie微分ノルム)がさらに低下しています。今が撤退の最適タイミングです」
黒田: 「……やる。全力で動く」
エピローグ:2026年10月15日(木)午前10時 ── 取締役会
# ===== gen_all_scenes.py: Scene 6 — エピローグ・安定領域到達 =====
# 緑に輝く安定領域の頂上に到達した金色の球体(ユリディカ株式会社)
import numpy as np
import plotly.graph_objects as go
from scipy.interpolate import RegularGridInterpolator
np.random.seed(42)
x = np.linspace(-3,3,80); y = np.linspace(-3,3,80)
X,Y = np.meshgrid(x,y)
# 安定領域が広がる曲率ランドスケープ(緑主体)
Scal = (2.0*np.exp(-((X-0.5)**2+(Y-0.3)**2)/3.0) +
1.0*np.exp(-((X+1)**2+(Y+1)**2)/2.0) + 0.3)
fig = go.Figure()
fig.add_trace(go.Surface(
x=X, y=Y, z=Scal, surfacecolor=Scal,
colorscale=[[0,"#003333"],[0.2,"#006655"],[0.4,"#00AA88"],
[0.6,"#00DDAA"],[0.8,"#00FFCC"],[1,"#66FFE0"]],
lighting=dict(ambient=0.35,diffuse=0.55,specular=0.45),
opacity=0.88))
# 完了した軌跡(18ヶ月)
interp = RegularGridInterpolator((x,y),Scal.T,
bounds_error=False, fill_value=0)
t = np.linspace(0,1,80)
xt = -2.0+3.0*t+0.3*np.sin(t*3*np.pi)
yt = -1.5+2.0*t+0.2*np.cos(t*2*np.pi)
xt = np.clip(xt,-3,3); yt = np.clip(yt,-3,3)
zt = interp(np.column_stack([xt,yt]))+0.05
fig.add_trace(go.Scatter3d(x=xt,y=yt,z=zt,
mode='lines',line=dict(color="#00FF88",width=5),
name="Completed Trajectory (18mo)"))
# 金色の球体(安定領域の頂上)
fig.add_trace(go.Scatter3d(
x=[0.5],y=[0.3],
z=[interp(np.array([[0.5,0.3]]))[0]+0.15],
mode='markers+text',
marker=dict(size=12,color="#FFD700",symbol='diamond'),
text=["YURIDICA Scal:+1.82 STABLE"],
textposition="top center",
textfont=dict(color="#FFD700",size=10),
name="Current Position"))
fig.update_layout(
paper_bgcolor="#060612",
title=dict(text="<b>STRATEGIC MANIFOLD — VIBRANT GROWTH & "
"STABILITY ACHIEVED</b>",
font=dict(size=16,color="#00E5FF"),x=0.5),
scene=dict(camera=dict(eye=dict(x=1.4,y=-1.3,z=0.9))),
width=1200, height=800)
fig.write_image("s6_epilogue.png", scale=2)
【Gemini画像生成プロンプト — Scene 6: エピローグ・安定領域到達】
Generate a photorealistic image of the same Japanese boardroom as Scene 1, but NOW triumphant and calm. The table display shows a manifold that is PREDOMINANTLY GREEN/TEAL — the red unstable peaks have shrunk. The golden sphere now sits ATOP the highest stable green plateau, glowing warmly, far from any danger. The GREEN geodesic path is fully traced from lower-left (18 months ago) to the golden sphere's current position, labeled "COMPLETED TRAJECTORY — 18 MONTHS." Side dashboards show green metrics: "Scal: +1.82 STABLE," "Distance to Unstable: 3.47 SAFE," "Risk Avoidance: ¥98B saved." The 62-year-old CEO stands with a slight smile looking down at the display. Other executives look relieved. Windows show GOLDEN SUNSET over Tokyo. Bottom: "STRATEGIC MANIFOLD — VIBRANT GROWTH & STABILITY ACHIEVED." Style: Photorealistic, warm triumphant mood, sunset lighting meets teal display glow, 8K.
図E-1:エピローグ。緑に輝く安定領域の頂上に到達した金色の球体(ユリディカ株式会社)。18ヶ月の軌跡が光る線として完全にトレースされている。
テーブル上のホログラムは、鮮やかな緑色に輝いていた。
3ヶ月前、赤い山脈に挟まれて点滅していた金色の球体は、今、広大な緑の台地――正のスカラー曲率の安定領域――の中央に静かに浮かんでいた。
神谷: 「取締役の皆様に、最終報告を申し上げます」
ユリディカ株式会社「幾何学的最適経路」戦略の中間報告(2026年10月15日)
■ 実行済みフェーズ
Phase 1(完了): サプライチェーン再編、EU処方変更
Phase 2(進行中): 中国撤退加速、インド市場拡大
■ 定量的成果
中国事業: 計画的ダウンサイジング完了(売上比率 15%→8%)
インド事業: 前年同期比 +34%(サシェット展開が奏功)
EU事業: マイクロプラスチックフリー製品ラインの先行投入 (規制対応コスト ▲40%(競合比))
米国事業: サプライチェーン再編により関税影響を65%低減
■ 経営環境多様体の現在位置
スカラー曲率: +1.82(安定領域)
最寄りの不安定領域までの距離: 3.47(十分な余裕)
■ Lie微分分析の結果 現在のベクトル場のKillingノルム: 0.28(構造保存型) → 現在の経営状態は安定しており、大きな構造変革なしに 持続的成長が可能
■ リスク回避効果 ドリフト経路(何もしなかった場合)との比較: 推定回避損失: 約980億円 実際の戦略投資コスト: 約340億円 純粋なリスク回避効果: 約640億円
白石: 「340億円の投資で、推定980億円の損失を回避した。ROIで言えば188%。これは……」
黒田: 「数字だけの話じゃない」
黒田は立ち上がり、テーブルの上のホログラムを見つめた。
黒田: 「我々は、初めて経営環境を『立体的に見る』ことができた。平面地図で飛行計画を立てていた時代から、地球儀と3D地形データで飛行計画を立てる時代に移行した。神谷くん、きみのチームのおかげだ」
神谷: 「ありがとうございます。ただ、1つだけ申し上げたいことがあります」
神谷は、プロジェクトルームでの最初の壁――ヤコビ行列のフルランク条件が23%の点で破れていた、あの夜のことを思い出した。
神谷: 「この分析が成功した理由は、数学の力でもAIの力でもありません。データの品質に妥協しなかったことです。最初の試みでは、ヤコビ行列のフルランク条件を満たせず、多様体の構築に失敗しました。データ量が足りなかったのです。3日間かけてデータを追加し、構造を再設計し、ようやく数学的に正当な多様体を構築できました」
神谷: 「数学は嘘をつきません。条件を満たさないデータからは、どんなに高度なAIを使っても、信頼できる結果は得られません。逆に、条件を満たすデータがあれば、数学が最適解を教えてくれます。これが、幾何学的データサイエンスの本質です」
黒田は微笑んだ。
黒田: 「来期から、幾何学的データサイエンスチームを正式な部署として設立する。神谷くん、部長を頼む」
神谷: 「光栄です」
テーブル上のホログラムが、静かに緑色の光を放っていた。ユリディカ株式会社の金色の球体は、安定した台地の上で、穏やかに輝いていた。
しかし、神谷は知っていた。経営環境多様体は、つねに変化する。今日の安定が、明日も安定である保証はない。
だからこそ、毎週月曜日の朝8時に、多様体は更新される。嵐が来る前に、嵐を見つけるために。
── 了 ──
補遺:本作品で使用された幾何学的データサイエンスの主要概念
本作品は、『ジオメトリック・インテリジェンス』で示された手法に基づくフィクションです。以下に、作中で登場した主要な数学的概念とAIモデルの対応関係を整理します。
| 作中の場面 | 使用した数学・AI | 参照章 |
|---|---|---|
| VAEで65次元→6次元に圧縮 | 変分オートエンコーダ(VAE) | 第5章 |
| ヤコビ行列のフルランク検証 | 命題1.1(埋め込み部分多様体) | 第1章 |
| 引き戻し計量の計算 | リーマン計量 g_{ij} = J^{\top} J | 第2章 |
| 曲率マップの生成 | スカラー曲率 $\mathrm{Scal}$ | 第3章 |
| 施策の構造的インパクト測定 | Lie微分 $\mathcal{L}_V g$ | 第4章 |
| 施策間の干渉判定 | Lie括弧 $[V_A, V_B]$ | 第4章 |
| 最適移行経路の計算 | ポントリャーギンの最大原理 | 第13章 |
| リアルタイム多様体更新 | Neural ODE + 自動微分 | 第7章 |
「経営環境は曲がった空間である。直線的な分析は、メルカトル図法のように現実を歪める。多様体の上で計算することで、初めて経営の真の地形が見える」 ── 神谷遼太郎
本作品は完全なフィクションです。登場する企業名、人物名、データはすべて架空のものです。
幾何学的データサイエンスの手法と数学的概念は、『ジオメトリック・インテリジェンス』(微分幾何学×AI×社会データ)に基づいています。
企業経営リスク管理の観点は、株式会社オリガミツキ(www.origamizki.co.jp)の『企業経営リスク管理論シリーズ』を参考にしています。
おわりに:経営は「測地線」の探求である
本記事では、ユリディカ株式会社という架空の舞台を通じて、幾何学的データサイエンスがどのように実務上の意思決定を支えるかをシミュレーションしました。
「ヤコビ行列のランク落ち」という技術的な壁や、施策間の「Lie括弧による干渉」といった課題は、実際のデータ解析現場でも必ず直面するものです。
これらを単なるエラーとして処理せず、微分幾何学という正当な数学の枠組みで捉え直すことで、1,000億円規模のリスクを回避する「測地線」が見えてきます。
もし、この記事で紹介した手法の数理的な詳細や、汎用的な実装パイプラインについてより深く学びたい方は、ぜひ本手法の体系をまとめた技術書
『ジオメトリック・インテリジェンス』
をご参照ください。
(参考) 理論体系(Zenn Book)と実践シミュレーション(本記事)の対応表
| 比較項目 | Zenn Book:理論・体系編 | Qiita記事:実践編(本記事) |
|---|---|---|
| タイトル | 『ジオメトリック・インテリジェンス』 | 『幾何学の魔法 ── 90日間戦争』 |
| 位置付け | 数理技術の標準プロトコルの定義 | 特定ケースへの手法適用と実装戦記 |
| 記述形式 | 数学的定義・一般化されたコード | 物語(ノベル形式)・実務の実装記録 |
| トラブル対応 | 汎用的な制約の解説 | ヤコビ行列のランク落ちの克服プロセス |
| アウトプット | 多様体解析のパイプライン構築 | 24ヶ月の戦略策定とリスク回避の成果 |
| 参照のメリット | 手法の「なぜ(Why)」がわかる | 手法の「いかに(How)」がわかる |
ステップ別の実装対応
| Step | 項目 | Zenn Bookでの内容 | 本記事(実践編)での展開 |
|---|---|---|---|
| 1-2 | 多様体構成 | 多様体としての数学的正当性証明 | データ拡張によるランク落ちの克服 |
| 4-6 | 計量・曲率 | 計量と曲率テンソルの算出 | 関税リスクを「空間の歪み」として可視化 |
| 7 | 構造変形 | Lie微分による等長変換の判定 | 施策の順序による干渉(Lie括弧)分析 |
| 8-9 | 最適経路 | 測地線方程式と最大原理の定式化 | リスク最小パス(測地線)の数値算出 |
| 10 | 意思決定 | 幾何学的解釈に基づくアクション論 | 取締役会への提示と戦略の即時更新 |
幾何学的データサイエンスの世界へようこそ。
データの中に眠る「真の地形」を、共に描き出していきましょう。
付録A:本作品の可視化画像について
小説内の設定と実際の画像生成方法の違い
本作品中で、神谷遼太郎は「VAEの潜在空間から計算した曲率データをBlenderのPython API(bpy)に渡し、Cyclesレンダラーで描画した」と取締役に説明している。これは小説の設定として現実的かつ実行可能な手法であるが、本作品に掲載された画像の実際の生成方法はこれとは異なる。
本作品の画像5点は、以下の方法で生成された。
使用ツール: Python 3.12 + Plotly 6.6.0 + Kaleido(静的画像エクスポート)
生成コード: gen_all_scenes.py(本作品に同梱)
手法: Plotlyのgo.Surfaceで3D曲面を描画し、surfacecolorパラメータでスカラー曲率を色マッピング。lightingパラメータ(ambient, diffuse, specular, fresnel, roughness)とカスタムカラースケールで、暗い背景に対するネオン調の高コントラスト描画を実現。go.Coneで政策ベクトル場の3D矢印を、go.Scatter3dで経路とマーカーを描画。fig.write_image()でPNG静的画像としてエクスポート。
参考画像との品質差について: 本作品の著者が参考とした画像群(Gemini等のAI画像生成モデルで生成されたコンセプトアート)と比較すると、Plotlyによる描画は以下の点で品質が劣る。(1) メッシュの頂点数とポリゴン解像度がPlotlyのWebGL制約により制限される。(2) グローバルイルミネーション(GI)、スクリーンスペースリフレクション(SSR)、アンビエントオクルージョン(AO)などの高度な照明効果が利用できない。(3) ポストプロセッシング(ブルーム、被写界深度、カラーグレーディング)が適用されていない。
読者がより高品質な画像を生成したい場合: 以下の手順を推奨する。
- PythonでVAE潜在空間の曲率データを計算し、頂点座標・法線・曲率値をOBJ + CSVファイルとしてエクスポート
- Blender(無料、blender.org)を開き、OBJファイルをインポート
- CSVの曲率値をカスタムカラーランプで頂点カラーに変換(BlenderのPython APIで自動化可能)
- Cycles/Eeveeレンダラーで、HDRIライティング、ガラス/発光マテリアル、ブルームエフェクトを適用して描画
- あるいは、Three.js(JavaScript WebGLライブラリ)でHTMLファイルを作成し、ブラウザ上でリアルタイム3D表示することも可能
付録B:『ジオメトリック・インテリジェンス』の手順への準拠状況
本作品は、『ジオメトリック・インテリジェンス』で定義された10ステップのデータ加工・分析パイプラインに基づいている。以下に、各ステップの準拠状況を記す。
| Step | 内容 | 準拠状況 | 備考 |
|---|---|---|---|
| 1 | データ収集・前処理 | ✓ 準拠 | KNN補完、標準化を実施。7カテゴリ65指標を収集 |
| 2 | VAE構成・訓練 | ✓ 準拠 | tanh活性化関数、命題1.1の4条件検証を実施 |
| 3 | VDM幾何構造抽出 | △ 部分的 | 小説内ではVDMの実行を明示的に記述していない。引き戻し計量のみで進行 |
| 4 | 引き戻し計量計算 | ✓ 準拠 | g_ij = J^T J をautogradで計算。条件数チェック実施 |
| 5 | クリストッフェル記号 | ✓ 準拠 | 自動微分でΓ^k_ijを計算。create_graph=True使用 |
| 6 | 曲率テンソル | ✓ 準拠 | R^l_ijk、Ric_ij、Scal(z)の完全な計算コードを提示 |
| 7 | Lie微分・Lie括弧 | ✓ 準拠 | (L_V g)_ij = ∇_i V_j + ∇_j V_i を計算。4施策の比較と干渉分析を実施 |
| 8 | 制御系定式化 | △ 部分的 | ManifoldControllerクラスを定義したが、ポントリャーギンの最大原理の厳密な実装ではなく、勾配ベースの射撃法で近似 |
| 9 | 最適経路計算 | △ 部分的 | 測地線方程式は定式化したが、数値解の実行結果は小説内の仮想値。ポントリャーギンの随伴方程式は未実装 |
| 10 | 可視化・意思決定 | ✓ 準拠 | Plotlyによる3D可視化、信頼度の議論、因果関係との区別を明記 |
忠実に実行しきれていない部分の説明
Step 3(VDM): 『ジオメトリック・インテリジェンス』ではVDM(Vector Diffusion Maps)による離散データからの幾何構造抽出をStep 3として位置づけているが、本作品ではVDMの実行を省略し、VAEの引き戻し計量のみで幾何構造を構成した。理由は、VDMは引き戻し計量と相補的な検証手法であり、物語の進行上、両方を詳述すると読者の負担が過大になるためである。実務上はVDMとの整合性確認を推奨する。
Step 8-9(最適制御): 『ジオメトリック・インテリジェンス』ではポントリャーギンの最大原理に基づく厳密な最適制御計算を規定しているが、本作品の実装は勾配降下法による近似解法(射撃法の変形)である。ポントリャーギンの最大原理の厳密な実装には、随伴方程式 dp_k/dt = −∂H/∂z^k の時間逆向き数値積分が必要であり、Neural ODEの随伴法と数学的に対応するが、本作品ではこの部分のコードは提示していない。
データの仮想性: 本作品はフィクションであり、ユリディカ株式会社のデータセットは実在しない。小説内で提示される数値結果(スカラー曲率 −4.872、Lie微分ノルム 4.673 など)は、物語の展開のために設定された仮想値であり、実際のVAE訓練から得られた計算結果ではない。ただし、Pythonコードの構造は実際に動作するものであり、実データを入力すれば同様の計算が実行可能である。
曲率の解釈: 「正のスカラー曲率=安定、負のスカラー曲率=不安定」という解釈は、物理学(一般相対性理論)からの類推である。社会データの文脈でこの対応が成り立つかどうかは、個別のデータセットと問題設定に依存し、理論的な保証はない。本作品では、過去の危機イベントとの整合性をもって間接的に検証したが、これは因果的検証ではなく相関的検証である。
本作品は完全なフィクションです。登場する企業名、人物名、データはすべて架空のものです。
幾何学的データサイエンスの手法と数学的概念は、『ジオメトリック・インテリジェンス』(微分幾何学×AI×社会データ)に基づいています。
gen_all_scenes.py
#!/usr/bin/env python3
"""
gen_all_scenes.py — ユリディカ株式会社 経営環境多様体の可視化
Python 3.12 + Plotly 6.6.0 + Kaleido(静的画像エクスポート)
付録Aに記述の通り、Plotlyのgo.Surfaceで3D曲面を描画し、
surfacecolorパラメータでスカラー曲率を色マッピング。
"""
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# ===== 共通設定 =====
DARK_BG = "#060612"
FONT_COLOR = "#CCDDEE"
WIDTH = 1200
HEIGHT = 800
def make_dark_layout(title="", subtitle=""):
return dict(
paper_bgcolor=DARK_BG,
plot_bgcolor=DARK_BG,
font=dict(family="Arial", color=FONT_COLOR, size=12),
title=dict(
text=f"<b>{title}</b><br><sub>{subtitle}</sub>",
font=dict(size=16, color="#00E5FF"),
x=0.5
),
margin=dict(l=40, r=40, t=80, b=40),
)
# ===== Scene 0: Box Plot — データ分布図 =====
def gen_scene0_boxplot():
np.random.seed(42)
indicators = {
"Revenue (¥B)": np.random.normal(100, 35, 120),
"Op.Margin (%)": np.random.normal(10.5, 3.2, 120),
"MktShare (%)": np.random.normal(7.5, 2.1, 120),
"Surfactant Cost": np.random.normal(45, 12, 120),
"Factory Util.(%)": np.random.normal(78, 8, 120),
"Tariff Rate (%)": np.concatenate([np.random.exponential(3, 115), [45, 42, 38, 35, 30]]),
"Brand Sentiment": np.random.normal(0.15, 0.38, 120),
"Price Sentiment": np.random.normal(-0.10, 0.35, 120),
"Regulation Idx": np.random.normal(52, 18, 120),
"Cyber Risk": np.random.exponential(2.5, 120),
}
colors = ["#00E5FF", "#00FF88", "#FFD700", "#FF69B4", "#A855F7",
"#FF8C00", "#00CED1", "#FF00FF", "#7FFF00", "#FF6B6B"]
fig = go.Figure()
for i, (name, data) in enumerate(indicators.items()):
fig.add_trace(go.Box(
y=data, name=name,
marker_color=colors[i],
line=dict(color=colors[i]),
fillcolor=f"rgba({int(colors[i][1:3],16)},{int(colors[i][3:5],16)},{int(colors[i][5:7],16)},0.3)",
boxmean="sd",
))
fig.update_layout(
**make_dark_layout(
"YURIDICA DATA DISTRIBUTION — KEY INDICATORS (10 Years Monthly)",
"Box=IQR, Whiskers=1.5×IQR, Diamond=Mean±SD"
),
showlegend=False,
yaxis=dict(gridcolor="#1a1a2e", zerolinecolor="#1a1a2e"),
xaxis=dict(tickangle=-30),
width=WIDTH, height=HEIGHT,
)
fig.write_image("/home/claude/images/s0_boxplot.png", scale=2)
print("Generated: s0_boxplot.png")
# ===== Scene 1: 経営環境多様体の全体像 =====
def gen_scene1_manifold():
np.random.seed(42)
x = np.linspace(-3, 3, 80)
y = np.linspace(-3, 3, 80)
X, Y = np.meshgrid(x, y)
# Curvature landscape: two unstable peaks (negative curvature) with stable valley
Z = (1.5 * np.exp(-((X+1.8)**2 + (Y-0.5)**2)/1.2) +
1.8 * np.exp(-((X-1.5)**2 + (Y+0.8)**2)/1.5) -
0.8 * np.exp(-((X-0.2)**2 + (Y-0.3)**2)/2.5) +
0.3 * np.sin(X*0.8) * np.cos(Y*0.6))
# Scalar curvature for color
Scal = -(2.5 * np.exp(-((X+1.8)**2 + (Y-0.5)**2)/1.0) +
3.0 * np.exp(-((X-1.5)**2 + (Y+0.8)**2)/1.2)) + \
1.5 * np.exp(-((X-0.2)**2 + (Y-0.3)**2)/3.0) + 0.5
fig = go.Figure()
# Surface
fig.add_trace(go.Surface(
x=X, y=Y, z=Z,
surfacecolor=Scal,
colorscale=[[0, "#0000FF"], [0.3, "#00BFFF"], [0.5, "#222222"],
[0.7, "#FF4500"], [1.0, "#FF0000"]],
lighting=dict(ambient=0.3, diffuse=0.6, specular=0.4, fresnel=0.3, roughness=0.5),
colorbar=dict(title="Scal", tickfont=dict(color=FONT_COLOR), ),
opacity=0.85,
showscale=True,
))
# Geodesic path (green) — curves around peaks
t_geo = np.linspace(0, 1, 100)
x_geo = -2.5 + 5.0 * t_geo + 0.8 * np.sin(t_geo * 3.5 * np.pi)
y_geo = -1.5 + 3.0 * t_geo + 0.5 * np.cos(t_geo * 2.5 * np.pi)
x_geo = np.clip(x_geo, -3, 3)
y_geo = np.clip(y_geo, -3, 3)
from scipy.interpolate import RegularGridInterpolator
interp = RegularGridInterpolator((x, y), Z.T, bounds_error=False, fill_value=0)
z_geo = interp(np.column_stack([x_geo, y_geo])) + 0.05
fig.add_trace(go.Scatter3d(
x=x_geo, y=y_geo, z=z_geo,
mode='lines', line=dict(color="#00FF66", width=6),
name="Geodesic (Min-Risk)"
))
# Direct path (red dashed)
x_dir = np.linspace(-2.0, 2.0, 50)
y_dir = np.linspace(-1.0, 1.5, 50)
z_dir = interp(np.column_stack([x_dir, y_dir])) + 0.05
fig.add_trace(go.Scatter3d(
x=x_dir, y=y_dir, z=z_dir,
mode='lines', line=dict(color="#FF0000", width=4, dash='dash'),
name="Direct Path (High Risk)"
))
# Current position (golden sphere)
fig.add_trace(go.Scatter3d(
x=[-0.8], y=[-0.3],
z=[interp(np.array([[-0.8, -0.3]]))[0] + 0.1],
mode='markers+text',
marker=dict(size=10, color="#FFD700", symbol='diamond'),
text=["YURIDICA<br>現在位置"], textposition="top center",
textfont=dict(color="#FFD700", size=10),
name="Current Position"
))
# Crisis markers
crisis = [(-1.5, 0.3, "2008 LEHMAN", "#00FFFF"),
(-0.2, 1.2, "2020 COVID", "#00FF88"),
(-1.8, -0.5, "2026 TARIFF", "#FFA500")]
for cx, cy, label, color in crisis:
cz = interp(np.array([[cx, cy]]))[0] + 0.08
fig.add_trace(go.Scatter3d(
x=[cx], y=[cy], z=[cz],
mode='markers+text', marker=dict(size=5, color=color),
text=[label], textposition="top center",
textfont=dict(color=color, size=8),
showlegend=False
))
fig.update_layout(
**make_dark_layout(
"REAL-TIME STRATEGIC MANIFOLD",
"Green=Geodesic | Red=Direct Path | Gold=Current Position"
),
scene=dict(
xaxis=dict(title="z¹ (Market/Geopolitics)", backgroundcolor=DARK_BG, gridcolor="#1a1a2e", showbackground=True),
yaxis=dict(title="z² (Cost/Regulation)", backgroundcolor=DARK_BG, gridcolor="#1a1a2e", showbackground=True),
zaxis=dict(title="z³ (Competition)", backgroundcolor=DARK_BG, gridcolor="#1a1a2e", showbackground=True),
camera=dict(eye=dict(x=1.6, y=-1.4, z=0.9)),
),
width=WIDTH, height=HEIGHT,
showlegend=True, legend=dict(x=0.02, y=0.98, bgcolor="rgba(6,6,18,0.8)"),
)
fig.write_image("/home/claude/images/s1_manifold.png", scale=2)
print("Generated: s1_manifold.png")
# ===== Scene 2: スカラー曲率ランドスケープ =====
def gen_scene2_curvature():
np.random.seed(42)
x = np.linspace(-3, 3, 80)
y = np.linspace(-3, 3, 80)
X, Y = np.meshgrid(x, y)
# Height = curvature value (peaks = stable, valleys = unstable)
Scal = (1.8 * np.exp(-((X-1.0)**2 + (Y-1.0)**2)/1.5) +
1.2 * np.exp(-((X+2.0)**2 + (Y+1.5)**2)/1.8) +
0.8 * np.exp(-((X-0.5)**2 + (Y+2.0)**2)/1.0) -
2.5 * np.exp(-((X+0.5)**2 + (Y-0.2)**2)/0.8) -
1.8 * np.exp(-((X-2.0)**2 + (Y-1.5)**2)/1.0) -
1.5 * np.exp(-((X+1.5)**2 + (Y+0.5)**2)/0.6))
fig = go.Figure()
fig.add_trace(go.Surface(
x=X, y=Y, z=Scal,
surfacecolor=Scal,
colorscale=[[0, "#0000CC"], [0.25, "#00BFFF"], [0.45, "#111133"],
[0.55, "#331111"], [0.75, "#FF4500"], [1.0, "#FF0000"]],
lighting=dict(ambient=0.35, diffuse=0.55, specular=0.5, fresnel=0.4, roughness=0.4),
colorbar=dict(title="Scal(z)", tickfont=dict(color=FONT_COLOR), ),
opacity=0.9,
))
# Geodesic on curvature surface
from scipy.interpolate import RegularGridInterpolator
interp = RegularGridInterpolator((x, y), Scal.T, bounds_error=False, fill_value=0)
t = np.linspace(0, 1, 80)
xp = -2.5 + 4.5 * t + 0.6 * np.sin(t * 4 * np.pi)
yp = -2.0 + 4.0 * t + 0.4 * np.cos(t * 3 * np.pi)
xp = np.clip(xp, -3, 3); yp = np.clip(yp, -3, 3)
zp = interp(np.column_stack([xp, yp])) + 0.08
fig.add_trace(go.Scatter3d(
x=xp, y=yp, z=zp,
mode='lines', line=dict(color="#00FF66", width=6),
name="Geodesic"
))
# Direct path
xd = np.linspace(-2.0, 2.0, 40)
yd = np.linspace(-1.5, 1.5, 40)
zd = interp(np.column_stack([xd, yd])) + 0.08
fig.add_trace(go.Scatter3d(
x=xd, y=yd, z=zd,
mode='lines', line=dict(color="#FF0000", width=4, dash='dash'),
name="Direct Path"
))
# Current position
fig.add_trace(go.Scatter3d(
x=[-0.5], y=[0.0], z=[interp(np.array([[-0.5, 0.0]]))[0]+0.15],
mode='markers+text', marker=dict(size=10, color="#FFD700", symbol='diamond'),
text=["YURIDICA<br>Apr 2026"], textposition="top center",
textfont=dict(color="#FFD700", size=10),
name="Current"
))
# Crisis markers in valleys
for cx, cy, lb, cl in [(-0.3, 0.1, "Scal=-4.8", "#00FFFF"),
(-1.6, -0.3, "2008 LEHMAN", "#00FFFF"),
(0.5, -0.5, "2020 COVID", "#00FF88")]:
cz = interp(np.array([[cx, cy]]))[0]+0.1
fig.add_trace(go.Scatter3d(
x=[cx], y=[cy], z=[cz], mode='markers+text',
marker=dict(size=4, color=cl), text=[lb],
textposition="top center", textfont=dict(color=cl, size=8),
showlegend=False
))
fig.update_layout(
**make_dark_layout(
"SCALAR CURVATURE LANDSCAPE — YURIDICA MANIFOLD",
"Height = Curvature | Valleys = Unstable | Peaks = Stable"
),
scene=dict(
xaxis=dict(title="z¹", backgroundcolor=DARK_BG, gridcolor="#1a1a2e", showbackground=True),
yaxis=dict(title="z²", backgroundcolor=DARK_BG, gridcolor="#1a1a2e", showbackground=True),
zaxis=dict(title="Scal(z)", backgroundcolor=DARK_BG, gridcolor="#1a1a2e", showbackground=True),
camera=dict(eye=dict(x=1.5, y=-1.3, z=1.0)),
),
width=WIDTH, height=HEIGHT,
showlegend=True, legend=dict(x=0.02, y=0.98, bgcolor="rgba(6,6,18,0.8)"),
)
fig.write_image("/home/claude/images/s2_curvature.png", scale=2)
print("Generated: s2_curvature.png")
# ===== Scene 3: Lie微分トリプティク =====
def gen_scene3_lie_triptych():
np.random.seed(42)
x = np.linspace(-3, 3, 60)
y = np.linspace(-3, 3, 60)
X, Y = np.meshgrid(x, y)
# Base landscape
Z_base = 0.5 * np.sin(X*0.5) * np.cos(Y*0.5) + 0.3 * np.exp(-(X**2+Y**2)/4)
# Three deformation levels
Z_mild = Z_base + 0.1 * np.random.randn(*Z_base.shape) * 0.3 # EU reformulation
Z_med = Z_base + 0.6 * np.exp(-((X-1)**2)/1.5) * np.sin(Y*1.5) # India acceleration
Z_strong = Z_base + 1.5 * np.exp(-((X+0.5)**2 + (Y-0.3)**2)/0.8) * np.cos(X*2) - \
1.0 * np.exp(-((X-1.5)**2)/0.5) # China withdrawal
fig = make_subplots(
rows=1, cols=3,
specs=[[{"type": "surface"}, {"type": "surface"}, {"type": "surface"}]],
subplot_titles=[
"EU REFORMULATION<br>||L_V g||=0.34 (Preserving)",
"INDIA ACCELERATION<br>||L_V g||=1.25 (Moderate)",
"CHINA WITHDRAWAL<br>||L_V g||=4.67 (Strong)"
],
horizontal_spacing=0.02,
)
colorscales = [
[[0, "#003333"], [0.5, "#006666"], [1, "#00CCCC"]], # calm teal
[[0, "#332200"], [0.5, "#996600"], [1, "#FFCC00"]], # amber
[[0, "#330011"], [0.5, "#990033"], [1, "#FF0066"]], # violent red
]
for i, (Z, cs) in enumerate(zip([Z_mild, Z_med, Z_strong], colorscales)):
fig.add_trace(go.Surface(
x=X, y=Y, z=Z, colorscale=cs,
lighting=dict(ambient=0.3, diffuse=0.6, specular=0.3, roughness=0.5),
showscale=False, opacity=0.85,
), row=1, col=i+1)
scenes = {}
for i in range(3):
key = f"scene{''+str(i+1) if i > 0 else ''}"
scenes[key] = dict(
xaxis=dict(backgroundcolor=DARK_BG, gridcolor="#1a1a2e", showbackground=True, showticklabels=False),
yaxis=dict(backgroundcolor=DARK_BG, gridcolor="#1a1a2e", showbackground=True, showticklabels=False),
zaxis=dict(backgroundcolor=DARK_BG, gridcolor="#1a1a2e", showbackground=True, showticklabels=False),
camera=dict(eye=dict(x=1.5, y=-1.3, z=0.9)),
)
fig.update_layout(
paper_bgcolor=DARK_BG, plot_bgcolor=DARK_BG,
font=dict(family="Arial", color=FONT_COLOR, size=11),
title=dict(text="<b>LIE DERIVATIVE ANALYSIS — POLICY STRUCTURAL IMPACT</b>",
font=dict(size=15, color="#00E5FF"), x=0.5),
margin=dict(l=20, r=20, t=80, b=20),
width=1500, height=500,
**scenes,
)
fig.write_image("/home/claude/images/s3_lie_triptych.png", scale=2)
print("Generated: s3_lie_triptych.png")
# ===== Scene 4: Lie括弧干渉マップ =====
def gen_scene4_lie_bracket():
np.random.seed(42)
x = np.linspace(-3, 3, 80)
y = np.linspace(-3, 3, 80)
X, Y = np.meshgrid(x, y)
# Interference strength: peaks where policies interact
interference = (3.0 * np.exp(-((X-0.5)**2 + (Y+0.3)**2)/0.8) +
1.5 * np.exp(-((X+1.0)**2 + (Y-1.0)**2)/1.2) +
0.5 * np.exp(-((X-2.0)**2 + (Y+1.5)**2)/1.0))
fig = go.Figure()
fig.add_trace(go.Surface(
x=X, y=Y, z=interference * 0.5,
surfacecolor=interference,
colorscale=[[0, "#1a0033"], [0.3, "#330066"], [0.5, "#663399"],
[0.7, "#CCAA00"], [0.85, "#FFDD00"], [1.0, "#FFFF66"]],
lighting=dict(ambient=0.3, diffuse=0.6, specular=0.4, fresnel=0.2, roughness=0.5),
colorbar=dict(title="||[V,W]||", tickfont=dict(color=FONT_COLOR), ),
opacity=0.85,
))
# Max interference marker
fig.add_trace(go.Scatter3d(
x=[0.5], y=[-0.3], z=[1.7],
mode='markers+text', marker=dict(size=8, color="#FFFF00", symbol='x'),
text=["MAX ||[V_A,V_C]||=5.17"], textposition="top center",
textfont=dict(color="#FFFF00", size=10),
name="Max Interference"
))
# Recommended path (green)
from scipy.interpolate import RegularGridInterpolator
interp = RegularGridInterpolator((x, y), interference.T, bounds_error=False, fill_value=0)
t = np.linspace(0, 1, 60)
xp = -2.5 + 5.0*t + 1.0*np.sin(t*2*np.pi)
yp = -2.0 + 4.0*t - 0.8*np.cos(t*2.5*np.pi)
xp = np.clip(xp, -3, 3); yp = np.clip(yp, -3, 3)
zp = interp(np.column_stack([xp, yp]))*0.5 + 0.08
fig.add_trace(go.Scatter3d(
x=xp, y=yp, z=zp,
mode='lines', line=dict(color="#00FF66", width=6),
name="Recommended Route"
))
# Vector field arrows (red = China withdrawal, green = SC reorg)
arrow_pts = [(-2, -1), (-1, -1), (0, -1), (1, -1), (-2, 0), (-1, 0), (1, 0), (2, 0)]
for ax, ay in arrow_pts:
az = interp(np.array([[ax, ay]]))[0]*0.5 + 0.05
fig.add_trace(go.Cone(
x=[ax], y=[ay], z=[az], u=[0.5], v=[0.1], w=[0.05],
colorscale=[[0, "#FF3333"], [1, "#FF3333"]],
showscale=False, sizemode="absolute", sizeref=0.3,
))
for ax, ay in arrow_pts:
az = interp(np.array([[ax, ay]]))[0]*0.5 + 0.05
fig.add_trace(go.Cone(
x=[ax], y=[ay], z=[az], u=[0.1], v=[0.5], w=[0.05],
colorscale=[[0, "#33FF33"], [1, "#33FF33"]],
showscale=False, sizemode="absolute", sizeref=0.3,
))
fig.update_layout(
**make_dark_layout(
"LIE BRACKET — POLICY INTERFERENCE MAP",
"Purple=Independent | Yellow=Strong Interference"
),
scene=dict(
xaxis=dict(title="z¹", backgroundcolor="#06041a", gridcolor="#1a1a2e", showbackground=True),
yaxis=dict(title="z²", backgroundcolor="#06041a", gridcolor="#1a1a2e", showbackground=True),
zaxis=dict(title="||[V,W]||", backgroundcolor="#06041a", gridcolor="#1a1a2e", showbackground=True),
camera=dict(eye=dict(x=1.4, y=-1.5, z=1.0)),
),
width=WIDTH, height=HEIGHT,
showlegend=True, legend=dict(x=0.02, y=0.98, bgcolor="rgba(6,4,26,0.8)"),
)
fig.write_image("/home/claude/images/s4_lie_bracket.png", scale=2)
print("Generated: s4_lie_bracket.png")
# ===== Scene 5: Lie微分の数学的概念図 =====
def gen_scene5_lie_concept():
np.random.seed(42)
x = np.linspace(-3, 3, 60)
y = np.linspace(-3, 3, 60)
X, Y = np.meshgrid(x, y)
Z = 0.4 * np.sin(X*0.6) * np.cos(Y*0.4) + 0.2 * np.exp(-(X**2+Y**2)/8)
fig = go.Figure()
fig.add_trace(go.Surface(
x=X, y=Y, z=Z,
colorscale=[[0, "#001144"], [0.5, "#003388"], [1, "#0066CC"]],
lighting=dict(ambient=0.35, diffuse=0.5, specular=0.3, roughness=0.6),
showscale=False, opacity=0.7,
))
from scipy.interpolate import RegularGridInterpolator
interp = RegularGridInterpolator((x, y), Z.T, bounds_error=False, fill_value=0)
# Vector field X (green) flowing left to right
for yi in np.linspace(-2, 2, 5):
xs = np.linspace(-2.5, 2.5, 15)
ys = np.full_like(xs, yi) + 0.1*np.sin(xs*0.5)
zs = interp(np.column_stack([xs, ys])) + 0.02
fig.add_trace(go.Scatter3d(
x=xs, y=ys, z=zs,
mode='lines', line=dict(color="#00FF66", width=3),
showlegend=False
))
# Vector field Y (red) at different angles
for xi in np.linspace(-2, 2, 5):
ys2 = np.linspace(-2.5, 2.5, 15)
xs2 = np.full_like(ys2, xi) + 0.15*np.cos(ys2*0.4)
zs2 = interp(np.column_stack([xs2, ys2])) + 0.02
fig.add_trace(go.Scatter3d(
x=xs2, y=ys2, z=zs2,
mode='lines', line=dict(color="#FF4444", width=3),
showlegend=False
))
# Points P and Q
px, py = -1.0, 0.5
qx, qy = 1.0, 0.5
pz = interp(np.array([[px, py]]))[0]+0.05
qz = interp(np.array([[qx, qy]]))[0]+0.05
fig.add_trace(go.Scatter3d(
x=[px, qx], y=[py, qy], z=[pz, qz],
mode='markers+text',
marker=dict(size=6, color=["#FFFFFF", "#FFFFFF"]),
text=["P", "Q"], textposition="top center",
textfont=dict(color="#FFFFFF", size=12),
name="Points P, Q"
))
# Lie derivative arrow (magenta)
fig.add_trace(go.Cone(
x=[qx], y=[qy], z=[qz],
u=[0.3], v=[0.4], w=[0.15],
colorscale=[[0, "#FF00FF"], [1, "#FF00FF"]],
showscale=False, sizemode="absolute", sizeref=0.4,
name="Lie Derivative"
))
fig.update_layout(
**make_dark_layout(
"LIE DERIVATIVE — MANIFOLD VECTOR FIELD INTERACTION",
"[X, Y] = L_X Y | Green=Flow X | Red=Field Y | Magenta=Lie Derivative"
),
scene=dict(
xaxis=dict(backgroundcolor="#0a0a18", gridcolor="#1a1a2e", showbackground=True, title=""),
yaxis=dict(backgroundcolor="#0a0a18", gridcolor="#1a1a2e", showbackground=True, title=""),
zaxis=dict(backgroundcolor="#0a0a18", gridcolor="#1a1a2e", showbackground=True, title=""),
camera=dict(eye=dict(x=1.4, y=-1.2, z=0.8)),
),
width=WIDTH, height=HEIGHT,
showlegend=True, legend=dict(x=0.02, y=0.98, bgcolor="rgba(10,10,24,0.8)"),
)
fig.write_image("/home/claude/images/s5_lie_concept.png", scale=2)
print("Generated: s5_lie_concept.png")
# ===== Scene 6: エピローグ — 安定領域到達 =====
def gen_scene6_epilogue():
np.random.seed(42)
x = np.linspace(-3, 3, 80)
y = np.linspace(-3, 3, 80)
X, Y = np.meshgrid(x, y)
# Predominantly positive curvature (green) with small unstable areas
Scal = (2.0 * np.exp(-((X-0.5)**2 + (Y-0.3)**2)/3.0) +
1.0 * np.exp(-((X+1.0)**2 + (Y+1.0)**2)/2.0) +
0.8 * np.exp(-((X-2.0)**2 + (Y-1.5)**2)/1.5) -
0.5 * np.exp(-((X+2.5)**2 + (Y-2.0)**2)/0.5) + 0.3)
fig = go.Figure()
fig.add_trace(go.Surface(
x=X, y=Y, z=Scal,
surfacecolor=Scal,
colorscale=[[0, "#003333"], [0.2, "#006655"], [0.4, "#00AA88"],
[0.6, "#00DDAA"], [0.8, "#00FFCC"], [1.0, "#66FFE0"]],
lighting=dict(ambient=0.35, diffuse=0.55, specular=0.45, fresnel=0.3, roughness=0.4),
colorbar=dict(title="Scal", tickfont=dict(color=FONT_COLOR), ),
opacity=0.88,
))
# Completed trajectory (18 months)
from scipy.interpolate import RegularGridInterpolator
interp = RegularGridInterpolator((x, y), Scal.T, bounds_error=False, fill_value=0)
t = np.linspace(0, 1, 80)
xt = -2.0 + 3.0*t + 0.3*np.sin(t*3*np.pi)
yt = -1.5 + 2.0*t + 0.2*np.cos(t*2*np.pi)
xt = np.clip(xt, -3, 3); yt = np.clip(yt, -3, 3)
zt = interp(np.column_stack([xt, yt])) + 0.05
fig.add_trace(go.Scatter3d(
x=xt, y=yt, z=zt,
mode='lines', line=dict(color="#00FF88", width=5),
name="Completed Trajectory (18mo)"
))
# Golden sphere at top of stable plateau
fig.add_trace(go.Scatter3d(
x=[0.5], y=[0.3], z=[interp(np.array([[0.5, 0.3]]))[0]+0.15],
mode='markers+text', marker=dict(size=12, color="#FFD700", symbol='diamond'),
text=["YURIDICA<br>Scal: +1.82 STABLE"], textposition="top center",
textfont=dict(color="#FFD700", size=10),
name="Current Position"
))
fig.update_layout(
**make_dark_layout(
"STRATEGIC MANIFOLD — VIBRANT GROWTH & STABILITY ACHIEVED",
"Scal: +1.82 STABLE | Distance to Unstable: 3.47 SAFE | Risk Avoidance: ¥98B saved"
),
scene=dict(
xaxis=dict(backgroundcolor=DARK_BG, gridcolor="#0a2a2a", showbackground=True, title="z¹"),
yaxis=dict(backgroundcolor=DARK_BG, gridcolor="#0a2a2a", showbackground=True, title="z²"),
zaxis=dict(backgroundcolor=DARK_BG, gridcolor="#0a2a2a", showbackground=True, title="Scal(z)"),
camera=dict(eye=dict(x=1.4, y=-1.3, z=0.9)),
),
width=WIDTH, height=HEIGHT,
showlegend=True, legend=dict(x=0.02, y=0.98, bgcolor="rgba(6,6,18,0.8)"),
)
fig.write_image("/home/claude/images/s6_epilogue.png", scale=2)
print("Generated: s6_epilogue.png")
# ===== Main =====
if __name__ == "__main__":
import os
os.makedirs("/home/claude/images", exist_ok=True)
gen_scene0_boxplot()
gen_scene1_manifold()
gen_scene2_curvature()
gen_scene3_lie_triptych()
gen_scene4_lie_bracket()
gen_scene5_lie_concept()
gen_scene6_epilogue()
print("\n=== All 7 images generated successfully ===")