自己平行部分多様体

Definition.
$\Omega$上の関数$C(\omega), F_1(\omega),F_2(\omega),\ldots,F_k(\omega)$, 及び$\mathbb{R}^k$の領域( 連結開集合 )$\Theta$上を動く$k$次元パラメータ$\theta=(\theta^1,\ldots,\theta^k)\in \Theta$を用いて
\[
p_{\theta}(\omega)=\exp \left[ C(\omega) + \sum_{i=1}^k \theta^i F_i(\omega) -\phi(\theta) \right] \] と表される確率分布の族$M=\{p_{\theta}\}_{\theta \in \Theta}$を指数分布族という. 但し, $\phi$は$p_{\theta}$が確率分布となるように調整するための関数である:
\[
\phi(\theta)
= \log \left\{
\sum_{\omega \in \Omega}
\exp\left[
C(\omega) + \sum_{i=1}^k \theta^i F_i(\omega)
\right] \right\}
\]
Remark.
指数分布族は, 正規分布, 指数分布, Poisson分布の形をした分布の族も指数分布族である。

さらに, $\{F_1, \ldots, F_k, 1_{\Omega}\}$が一次独立である事を仮定する。
この仮定の下で, $k+1 \leq n$である.

前回のことから、$S$自身が指数分布族であることがわかる($C=0$, $F_i = \delta_i$としたらそうなる。)。

Theorem.
Sの部分多様体$M$に対して, $M$が$\nabla^{(e)}$- 自己平行部分多様体である iff $M$が指数分布族であることである。

[proof] 先ず, $S$は双対平坦だから、勿論平坦である。
ここで、任意の部分多様体に対して, $\nabla^{(e)}$- 自己平行部分多様体であるためのiff 条件は, その部分多様体($\{\eta_j\}_{1\leq j \leq k}$)が$S$の$\nabla^{(e)}$- アファイン座標系$(\theta_i)_{1\lrq i \leq n-1}$のアファイン部分空間に対応することである。
そこで、
$M$が指数分布族と仮定する。
この時, 部分空間であることから,
\[
C(\omega) + \sum_{j=1}^k \eta F_j(\omega) – \varphi(\eta) = \sum_{i=1}^{n-1} \theta^i(\eta) \delta_i(\omega) -\psi(\theta(\eta))
\] が任意の$\omega \in \Omega$成立する. そこで, $\eta_{\beta} \,(1\leq \beta \leq k)$で偏微分すると,
\[
\sum_{j=1}^k \eta F_j(\omega) -\frac{\partial \varphi(\eta)}{\partial \eta_\beta}(\eta) = \sum_{i=1}^{n-1}\frac{\partial theta^i(\eta) }{\partial \eta_\beta} \\delta_i(\omega) –  \sum_{i=1}^{n-1} \frac{ \partial \psi(\theta(\eta))}{\partial \theta^i}(\theta(\eta)) \frac{\partial \theta^i}{\partial \eta_\beta}(\eta)
\] となり,
\[
F_\beta = \sum_{i=1}^{n-1} f_i^\beta \delta_i(\omega)
\]

と表せるから、 一次独立性から

$1\leq i \leq n$に対して,
\[
f_i = \frac{\partial \theta^i}{\partial \eta_\beta}
\] だから, $\theta^i(\eta)$は$\eta$の一次式で表すことができることがわかった。これは逆に辿れる。
[/proof]

Theorem.
$S$の$\nabla^{(e)}$-自己平行部分多様体である指数型分布族
\[
M= \{ p_{\theta}(\omega) \in S; \log p_{\theta}(\omega) = C(\omega) + \sum_{i=1}^k \theta^i F_i(\omega) – \psi(\theta)  \}
\] に対し,
\[
\eta := E_{p_{\theta}}[F_i] = \sum_{\omega \in \Omega} p_{\theta}(\omega) F_i(\omega)
\] とおけば,
$\eta = (\eta_1, \ldots, \eta_k)$は$M$の局所座標系を与える。
そして, $\{(\theta^i), (\eta_i)\}$は双対構造$(\tilde{g}, \tilde{\nabla^{(e)}}, \tilde{\nabla^*})$
に関する双対アファイン座標系をなす。
[proof] 上でおいた写像写像$\theta \mapsto \eta$のJacobi行列を計算する:
$$
\begin{align}
\frac{\partial \eta_i}{\partial \theta^j}
&= \sum_{\omega \in \Omega} \left( \partial_j p_{\theta}(\omega)\, F_i(\omega) \right) \\
&= \sum_{\omega \in \Omega} \left( \partial_j p_{\theta}(\omega)\, (\partial_i \log p_{\theta}(\omega)) \right)
\end{align}
[/proof]

$S_{n-1}$の双対幾何2.

先ほどまでのところで、双対アファイン座標系をみつけることができた。ここからは、みつけたアファイン座標系に対応する双対ポテンシャルを見つける。
実は、1. ででてきた$\psi$が$\theta$-座標系のポテンシャル関数である。
実際, 合成関数の微分と, $\theta^i$の定義, $\eta_i$の定義から
\[ \partial_i \psi = \frac{\exp{\theta^i}}{1 + \sum_{j=1}^{n-1}\exp{\theta^j}}= p(n)\exp{\theta^i} = p(i) = \eta_i\]

$p(i)=\eta_i$に注意すると,
\begin{align}
\phi(\eta)
&= \theta^i \eta_i – \psi(\theta) \\[4pt] &= \sum_{i=1}^{n-1} \left( \log \frac{p(i)}{p(n)} \right) p(i) + \log p(n) \\[4pt] &= \sum_{i=1}^{n-1} p(i)\log p(i)
+ \left(1 – \sum_{i=1}^{n-1} p(i)\right)\log p(n) \\[4pt] &= \sum_{i=1}^{n-1} \eta_i \log \eta_i
+ \left(1 – \sum_{i=1}^{n-1} \eta_i\right)
\log\left(1 – \sum_{i=1}^{n-1} \eta_i\right) \\
& = \sum_{\omega =1}^n p(\omega)\log p(\omega)
\end{align}

である。
そこで,
\[
\partial_i \phi = \log \eta_i + 1 – \log \left( 1 – \sum_{i=1}^{n-1} \eta_i \right) -1 = \log\left( \frac{p(i)}{p(n)}\right) = \theta^i
\] となる。(ここで, 二個目の$\log$は$p(n)$になる。)

$\nabla^*$-ダイバージェンスを計算する。
双対接続と、ダイバージェンスの対応から、
\[
D^{*}(p||q) = D(q||p)
\] であるから, $\nabla^{(m)}$のダイバージェンスは, $\psi(\theta)=- \log p(n)$, $\varphi(\eta)= \sum_{\omega =1}^n p(\omega)\log p(\omega)$であったこと, $\theta^i=\log \frac{p(i)}{p(n)}$であったことを思い出すと,
\begin{align}
D^{(m)}(p \| q)
&= \psi(\theta(q)) + \varphi(\eta(p)) – \theta^i(q)\eta_i(p) \\
&= – \log q(n)
+ \sum_{\omega = 1}^n p(\omega)\log p(\omega)
– \sum_{i = 1}^{n-1} \left( \log \frac{q(i)}{q(n)} \right) p(i) \\
&= \sum_{\omega = 1}^n p(\omega)\log p(\omega)
– \sum_{i = 1}^{n-1} p(i)\log q(i)
– \left( 1 – \sum_{i = 1}^{n-1} p(i) \right)\log q(n) \\
&= \sum_{\omega = 1}^n p(\omega)\log \frac{p(\omega)}{q(\omega)}.
\end{align}

Osgood曲線(ルベーグ測度正をもつジョルダン曲線:Knopp’s Osgood Curve)


[参考]:
空間充填曲線とフラクタル; ザーガン, 鎌田清一郎

フラクタル曲線についての解析学―擬等角写像外伝, 谷口 雅彦

https://demonstrations.wolfram.com/KnoppsOsgoodCurveConstruction/

三角形$T$(直角三角形である必要はない)から始まって, $r_1 \in (0,1)$である面積$r_1 J_2(T)$の開三角形を取り除き, 合わせた面積$J_2(T)(1-r)$の二個の閉三角形$T_0,T_1$が残る.
これを続けていくと,
\[
\Lambda_2(C) = J_2(T) \Pi_{j=1}^{\infty} (1- r_j)
\] をもった点集合
\[
C= (T_0 \cup T_1) \cap (T_{00} \cup T_{01}\cup T_{10} \cup T_{11}) \cap \cdots
\] を得る. ここで, $\sum_{i=1}^{\infty}r_j$が収束すような$\{r_j\}_{j\geq 1}$であれば, $\Lambda_2(C) >0$である.
そこで,  初期三角形$T$として, 長さ$2$の底辺を持った直角二等辺三角形を選び(したがって、$J_2(T)=1$), $r \in (0,1)$に対して, $r_j = r^2/ j^2$となるならば,
\[
\Lambda_2(C) = \Pi_{j=1}^{\infty} (1- \frac{r^2}{j^2})
\]

を得る. Weierstrass の分解定理から,
\[
\sin (\pi z) = \pi z \Pi_{j=1}^{\infty} (1- \frac{z^2}{j^2})
\] である.
\[
\lambda_2(C)  = \Pi_{j=1}^{\infty} (1- \frac{z^2}{j^2}) = \frac{\sin(\pi r)}{\pi r}
\] となる. 任意の$\lambda \in (0,1)$に対して, $\frac{\sin(\pi r)}{\pi r} = \lambda$が解$r \in (0,1)$をもつ. 任意の$\lambda \in (0,1)$に対して, 二次元Lebesgue測度$\lambda$の集合$C(\lambda)$を得ることができる.

具体的に言うと,

  • 頂点 $A$ (直角の頂点): $(0, \sqrt{2})$

  • 左端 $B$: $(-\sqrt{2}, 0)$

  • 右端 $C$: $(\sqrt{2}, 0)$

反復 $j$ における縮小率を $s_j = 1 – \frac{r}{j}$ とします。(

反復 $j$ における除去面積率を $r_j = \frac{r^2}{j^2}$ とすると、残る三角形の高さの比率(縮小率) $s_j$ は次のように計算されています。

$$s_j = 1 – \sqrt{r_j} = 1 – \frac{r}{j}$$

)

 

\[
f_{j,1}\begin{pmatrix} x \\ y \end{pmatrix} = \begin{pmatrix} s_j & 0 \\ 0 & s_j \end{pmatrix} \begin{pmatrix} x \\ y \end{pmatrix} + (1 – s_j) \begin{pmatrix} -\sqrt{2} \\ 0 \end{pmatrix}
\]

\[
f_{j,2}\begin{pmatrix} x \\ y \end{pmatrix} = \begin{pmatrix} s_j & 0 \\ 0 & s_j \end{pmatrix} \begin{pmatrix} x \\ y \end{pmatrix} + (1 – s_j) \begin{pmatrix} \sqrt{2} \\ 0 \end{pmatrix}
\]

\[
E = \bigcap_{n=1}^{\infty} \bigcup_{\sigma \in \{1,2\}^n} (f_{n,\sigma_n} \circ \dots \circ f_{1,\sigma_1})(T_0)
\]

  • $s$ が一定(例: 0.5)の場合: 面積は 0 に収束する。(典型的なKoch曲線)。

  • $s_j = 1 – r/j$ の場合: 縮小がだんだん「緩やか」になるため、無限回繰り返しても面積が 0 にならず、正の値($\frac{\sin \pi r}{\pi r}$)として残る。

    また, これは, 二次元の太ったCantor集合から構成も存在を保証できる。(実際の構成は、ザーガンのほんの8.2章に載っている。) そうではない方法としては, 一度作った太ったCantor集合(Smith–Volterra–Cantor set)にDenjoy–Riesz theoremを適用することで, 存在を証明できる。

    On Uncountable Unions and Intersections of Measurable Sets

 

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
from matplotlib.widgets import Slider
from typing import List, Callable
from scipy.optimize import fsolve



# ---- utilities ------------------------------------------


def nest(f: Callable, x, n: int):
for _ in range(n):
x = f(x)
return x



def flatten_once(lst: List[List]):
return [item for sub in lst for item in sub]



# ---- Core fractal logic with varying removal rates --------------------


class TriangleSplitterVarying:
"""
Implements the construction where at iteration j, we remove a triangle
with area ratio r_j relative to the parent triangle.


For the special case r_j = r²/j², the final measure is sin(πr)/(πr).
"""


def __init__(self, r: float, max_iterations: int = 10):
"""
Args:
r: The parameter in r_j = r²/j²
max_iterations: Maximum depth of iteration
"""
self.r = r
self.max_iterations = max_iterations
# Precompute removal ratios
self.removal_ratios = [r**2 / j**2 for j in range(1, max_iterations + 1)]


def compute_split_params(self, removal_ratio: float):
"""
Given a removal ratio r_j, compute the split parameters.


For a triangle with base on bottom, if we remove a triangle from the top
with area ratio r_j, we need to find the height ratio.


Since area scales with height, if we keep a fraction h of the height,
each resulting triangle has area (1-r_j)/2.


The removed triangle has area r_j.
The two remaining triangles together have area (1-r_j).


For symmetric removal from the top, we use:
- center = 0.5 (middle of base)
- The split point determines how much we remove
"""
# For a symmetric split, if we remove area r_j from top,
# the remaining two triangles share area (1-r_j)


# Height ratio of removed triangle: sqrt(r_j)
h_removed = np.sqrt(removal_ratio)


# The split happens at height (1 - h_removed) from bottom
h_split = 1 - h_removed


# For the base of the removed triangle, we need to compute
# the width at height h_split
# For a triangle, width scales linearly with height from apex
# At height h from bottom (where total height is 1),
# the distance from apex is (1-h), so width is proportional to (1-h)


# At the split line, the width ratio is (1 - h_split) = h_removed
# So the split points on the base are at:
# center ± h_removed/2


center = 0.5
half_width = h_removed / 2
left = center - half_width
right = center + half_width


return left, center, right


def split_triangle_at_iteration(self, triangle: np.ndarray, iteration: int):
"""
Split a triangle at a given iteration with removal ratio r_iteration.
"""
if iteration >= len(self.removal_ratios):
return [triangle] # No more splitting


r_j = self.removal_ratios[iteration]
left, center, right = self.compute_split_params(r_j)


a, b, c = triangle


# Points on the base
p_left = b + left * (c - b)
p_right = b + right * (c - b)


# The split happens at these points, creating two triangles
return [
np.array([p_left, b, a]),
np.array([p_right, c, a]),
]


def fractal(self, initial_triangle: np.ndarray, n_iterations: int):
"""
Generate the fractal after n_iterations.
"""
triangles = [initial_triangle]


for iteration in range(min(n_iterations, self.max_iterations)):
new_triangles = []
for tri in triangles:
new_triangles.extend(self.split_triangle_at_iteration(tri, iteration))
triangles = new_triangles


return triangles



# ---- Measure computation ------------------------------------------------


def compute_measure_product(r: float, n_terms: int = 100):
"""
Compute the product: ∏(1 - r²/j²) for j=1 to n_terms
"""
product = 1.0
for j in range(1, n_terms + 1):
product *= (1 - r**2 / j**2)
return product



def compute_measure_sinc(r: float):
"""
Compute sin(πr)/(πr) using the Weierstrass product formula.
"""
if abs(r) < 1e-10:
return 1.0
return np.sin(np.pi * r) / (np.pi * r)



def find_r_for_lambda(target_lambda: float):
"""
Find r such that sin(πr)/(πr) = target_lambda.
"""
def equation(r):
return compute_measure_sinc(r) - target_lambda


# Initial guess
r0 = 0.5
solution = fsolve(equation, r0)
return solution[0]



# ---- Visualization ------------------------------------------------------


def visualize_fractal_with_measure(r: float, max_iterations: int = 8):
"""
Visualize the fractal construction with measure computation.
"""
# Create initial right isosceles triangle with base 2, area 1
# Base from (-1, 0) to (1, 0), apex at (0, 1)
b = np.array([-1.0, 0.0])
c = np.array([1.0, 0.0])
a = np.array([0.0, 1.0])
initial_triangle = np.array([a, b, c])


fig, axes = plt.subplots(2, 4, figsize=(16, 8))
axes = axes.flatten()


splitter = TriangleSplitterVarying(r, max_iterations)


# Compute theoretical measure
measure_theory = compute_measure_sinc(r)
measure_product = compute_measure_product(r, max_iterations)


fig.suptitle(
f'Fractal Construction with r = {r:.4f}\n'
f'Theoretical Measure λ = sin(πr)/(πr) = {measure_theory:.6f}\n'
f'Product Formula (n={max_iterations}): {measure_product:.6f}',
fontsize=12, fontweight='bold'
)


for idx, n in enumerate(range(9)):
if idx >= len(axes):
break


ax = axes[idx]
triangles = splitter.fractal(initial_triangle, n)


# Compute actual measure (sum of triangle areas)
total_area = sum(triangle_area(tri) for tri in triangles)


for tri in triangles:
ax.add_patch(
Polygon(
tri,
closed=True,
edgecolor='black',
facecolor='lightblue',
linewidth=0.5,
)
)


ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-0.1, 1.2)
ax.set_aspect('equal')
ax.set_title(f'Iteration {n}\nArea = {total_area:.6f}', fontsize=10)
ax.grid(True, alpha=0.3)


plt.tight_layout()
return fig



def triangle_area(triangle: np.ndarray):
"""
Compute the area of a triangle using the cross product formula.
"""
a, b, c = triangle
return 0.5 * abs(np.cross(b - a, c - a))



# ---- Interactive visualization with slider ------------------------------


def interactive_measure_exploration():
"""
Interactive exploration of the relationship between r and measure λ.
"""
fig = plt.figure(figsize=(14, 10))


# Create subplots
ax_fractal = plt.subplot(2, 2, (1, 3))
ax_graph = plt.subplot(2, 2, 2)
ax_product = plt.subplot(2, 2, 4)


plt.subplots_adjust(bottom=0.15, hspace=0.3)


# Initial parameters
r0 = 0.5
max_iter = 8


# Create initial triangle
b = np.array([-1.0, 0.0])
c = np.array([1.0, 0.0])
a = np.array([0.0, 1.0])
initial_triangle = np.array([a, b, c])


def redraw(r, n_iter):
# Clear axes
ax_fractal.clear()
ax_graph.clear()
ax_product.clear()


# Generate fractal
splitter = TriangleSplitterVarying(r, max_iter)
triangles = splitter.fractal(initial_triangle, n_iter)


# Draw fractal
for tri in triangles:
ax_fractal.add_patch(
Polygon(
tri,
closed=True,
edgecolor='black',
facecolor='lightblue',
linewidth=0.5,
)
)


total_area = sum(triangle_area(tri) for tri in triangles)
measure_theory = compute_measure_sinc(r)


ax_fractal.set_xlim(-1.2, 1.2)
ax_fractal.set_ylim(-0.1, 1.2)
ax_fractal.set_aspect('equal')
ax_fractal.set_title(
f'Iteration {n_iter}, r = {r:.4f}\n'
f'Actual Area: {total_area:.6f}\n'
f'Theory λ = sin(πr)/(πr): {measure_theory:.6f}',
fontsize=11
)
ax_fractal.grid(True, alpha=0.3)


# Plot measure vs r
r_values = np.linspace(0.01, 0.99, 200)
lambda_values = [compute_measure_sinc(r_val) for r_val in r_values]


ax_graph.plot(r_values, lambda_values, 'b-', linewidth=2, label='λ = sin(πr)/(πr)')
ax_graph.plot(r, measure_theory, 'ro', markersize=10, label=f'Current: r={r:.3f}, λ={measure_theory:.3f}')
ax_graph.set_xlabel('r', fontsize=11)
ax_graph.set_ylabel('λ (Measure)', fontsize=11)
ax_graph.set_title('Measure vs Parameter r', fontsize=11)
ax_graph.grid(True, alpha=0.3)
ax_graph.legend()
ax_graph.set_xlim(0, 1)
ax_graph.set_ylim(0, 1)


# Plot convergence of product
iterations = range(1, 21)
product_values = [compute_measure_product(r, n) for n in iterations]


ax_product.plot(iterations, product_values, 'b-o', linewidth=2, markersize=4, label='Product formula')
ax_product.axhline(y=measure_theory, color='r', linestyle='--', linewidth=2, label='Theoretical limit')
ax_product.set_xlabel('Number of terms', fontsize=11)
ax_product.set_ylabel('Product value', fontsize=11)
ax_product.set_title(f'Convergence of ∏(1 - r²/j²) for r={r:.4f}', fontsize=11)
ax_product.grid(True, alpha=0.3)
ax_product.legend()


fig.canvas.draw_idle()


# Create sliders
ax_r = plt.axes([0.15, 0.05, 0.7, 0.03])
ax_iter = plt.axes([0.15, 0.01, 0.7, 0.03])


s_r = Slider(ax_r, 'r', 0.01, 0.99, valinit=r0)
s_iter = Slider(ax_iter, 'iterations', 0, max_iter, valinit=max_iter, valstep=1)


def update(_):
redraw(s_r.val, int(s_iter.val))


s_r.on_changed(update)
s_iter.on_changed(update)


redraw(r0, max_iter)
plt.show()



# ---- Examples for specific target measures -----------------------------


def show_specific_measures():
"""
Show fractals with specific target measures.
"""
target_lambdas = [0.9, 0.7, 0.5, 0.3]


fig, axes = plt.subplots(2, 2, figsize=(12, 12))
axes = axes.flatten()


b = np.array([-1.0, 0.0])
c = np.array([1.0, 0.0])
a = np.array([0.0, 1.0])
initial_triangle = np.array([a, b, c])


for idx, target_lambda in enumerate(target_lambdas):
ax = axes[idx]


# Find r for this lambda
r = find_r_for_lambda(target_lambda)


# Generate fractal
splitter = TriangleSplitterVarying(r, max_iterations=10)
triangles = splitter.fractal(initial_triangle, 10)


# Draw
for tri in triangles:
ax.add_patch(
Polygon(
tri,
closed=True,
edgecolor='black',
facecolor='lightblue',
linewidth=0.3,
)
)


total_area = sum(triangle_area(tri) for tri in triangles)


ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-0.1, 1.2)
ax.set_aspect('equal')
ax.set_title(
f'Target λ = {target_lambda:.1f}\n'
f'r = {r:.4f}\n'
f'Actual area = {total_area:.4f}',
fontsize=11
)
ax.grid(True, alpha=0.3)


fig.suptitle(
'Fractal Sets with Prescribed Measures\n'
'Using r_j = r²/j² and λ = sin(πr)/(πr)',
fontsize=13, fontweight='bold'
)


plt.tight_layout()
return fig



# ---- Main ----------------------------------------------------------------


if __name__ == "__main__":
print("Fractal Construction with Prescribed Measure")
print("=" * 60)
print("\nTheory: For r_j = r²/j², the limiting measure is:")
print("λ = sin(πr)/(πr)")
print("\nThis allows constructing a set with ANY measure λ ∈ (0,1)")
print("by choosing appropriate r ∈ (0,1)")
print("=" * 60)


# Example calculations
print("\nExamples:")
for target_lambda in [0.9, 0.7, 0.5, 0.3, 0.1]:
r = find_r_for_lambda(target_lambda)
actual_lambda = compute_measure_sinc(r)
print(f" Target λ = {target_lambda:.1f} → r = {r:.6f} → λ = {actual_lambda:.6f}")


print("\n" + "=" * 60)
print("Generating visualizations...")
print("=" * 60)


# Generate static visualizations
fig1 = visualize_fractal_with_measure(r=0.5, max_iterations=8)
fig1.savefig('fractal_iterations.png', dpi=150, bbox_inches='tight')


print("✓ Saved: fractal_iterations.png")


fig2 = show_specific_measures()
fig2.savefig('fractal_specific_measures.png', dpi=150, bbox_inches='tight')
print("✓ Saved: fractal_specific_measures.png")


# Launch interactive visualization
print("\nLaunching interactive visualization...")
print("Use sliders to explore different values of r and iterations")
interactive_measure_exploration()