等值線
無異曲線、等產量曲線與等高線,都是二元函數的等值集。
等值集與切線
定義 · 等值集
對 \(F : \mathbb{R}^2 \to \mathbb{R}\) 與水準 \(c \in \mathbb{R}\),等值集為
對連續可微的 \(F\),以 \(F_x\)、\(F_y\) 表示偏導數,\(\nabla F = (F_x, F_y)\) 為梯度。在 \(\nabla F \ne 0\) 的點附近,等值集是一條曲線,這由隱函數定理得出;以下引述所需的形式。
定理 · 隱函數定理
設 \(\Omega \subset \mathbb{R}^2\) 為開集,\(F : \Omega \to \mathbb{R}\) 連續可微,\(p \in \Omega\),\(F(p) = c\) 且 \(F_y (p) \ne 0\)。則存在開區間 \(I\)、\(J\) 滿足 \(p_x \in I\)、\(p_y \in J\)、\(I \times J \subset \Omega\),以及連續可微的 \(\phi : I \to J\)、\(\phi(p_x) = p_y\),使得對 \((x, y) \in I \times J\),
此定理對任意個變數都成立,見 Rudin (1976) 的定理 9.28、Apostol (1974) 第二版的定理 13.7 與 Munkres (1991) 的定理 9.2。此處不予證明。
定理 · 等值集的切線
設 \(F\) 在開集 \(\Omega \subset \mathbb{R}^2\) 上連續可微,\(p \in \Omega\),\(F(p) = c\) 且 \(\nabla F(p) \ne 0\)。則存在 \(p\) 的開鄰域 \(N\) 與開區間 \(I\) 上的連續可微曲線 \(\gamma : I \to \mathbb{R}^2\),滿足 \(\gamma(I) = L_c \cap N\)、某個 \(u_0 \in I\) 使 \(\gamma(u_0) = p\)、對所有 \(u \in I\) 有 \(\gamma'(u) \ne 0\),且 \(\gamma'(u)\) 平行於 \(\gamma(u)\) 處的 \((F_y, -F_x)\)。向量 \((F_y, -F_x)\) 與 \(\nabla F\) 正交。
切線 \((F_y, -F_x)\) 不需除法,因此垂直切線(\(F_y = 0\))與其他情況一樣容易處理;圖形形式的斜率 \(\mathrm{d} y / \mathrm{d} x = -F_x / F_y\) 在該處則為無窮大。
描繪
trace_implicit 不需解出 \(y\) 就能把等值集轉為三次路徑,因此可以處理會折返、封閉或分成數段的曲線。
trace_implicit(
function,
*,
levels,
viewport,
resolution=(101, 101),
tolerance=1e-2,
gradient=None
)
\(F\),以兩個浮點數呼叫;在每個網格點都必須為有限值,否則拋出 ValueError。
單一水準,或由有限水準值組成的可迭代物件。
沿 \(x\) 與 \(y\) 的網格點數,各至少 2。
簡化與彎曲線段的容許偏差,單位同座標。
\(\nabla F = (F_x, F_y)\),以 tuple 或 Vector 回傳。提供時,直線段會彎成沿曲線切線方向的三次曲線。
在矩形 viewport \(= (x_{\min}, x_{\max}, y_{\min}, y_{\max})\) 內,描繪每個水準 \(c\) 的 \(F(x, y) = c\),位於 bezierkit.implicit。
演算法是行進方格法,即 Lorensen (1987) 行進立方體法的二維類比。網格格子的角點為 \(v_{00}, v_{10}, v_{11}, v_{01}\),由左下角起逆時針排列,\(F\) 的值分別為 \(f_{00}, f_{10}, f_{11}, f_{01}\)。角點的值不小於水準 \(c\) 時稱為高。
定義 · 邊上的交點
設網格的一條邊由 \(p\) 到 \(q\),且 \(F(p) \ge c > F(q)\) 或 \(F(q) \ge c > F(p)\)。其交點為 \(p + \lambda (q - p)\),其中
即 \(F - c\) 沿該邊的線性插值之零點。
一條邊有交點,若且唯若它的兩個角點高低不同。
- 取樣。在 viewport 的規則網格上計算 \(F\)。
- 行進。在每個網格格子中找出四條邊的交點(詳見邊上的交點)。有兩個交點的格子貢獻一條線段。有四個交點的格子是鞍點:兩個對角角點為高、另兩個為低,由下述規則決定如何連接(參見鞍點格子:(a) 中心高、(b) 中心低;實心角點的 \(F \ge c\)。)。
- 縫合。把端點相同的線段串成鏈。回到起點的鏈是封閉的;抵達 viewport 邊界或分支點的鏈是開放的。互不相連的部分保持為不同的路徑。
- 簡化。以
tolerance對每條鏈呼叫fit_polyline(詳見擬合),不保留轉角。 - 彎曲(提供
gradient時)。把每條直線段換成端點切線沿等值集方向的三次曲線(詳見等值集的切線),控制柄長為弦長的三分之一。只有當三次曲線與弦的距離不超過tolerance時才採用;任一端 \(\nabla F = 0\) 時維持直線。
鞍點規則使用四個角點值的雙線性插值
命題 · 雙線性插值的中心值
插值 \(u\) 在角點取角點值,在格子中心的值等於四個角點值的平均:
此平均值不小於 \(c\) 時,套件將中心視為高。在鞍點格子中,與中心同側的兩個角點在格內相連,另外兩個角點各自被一條線段切開(參見鞍點格子:(a) 中心高、(b) 中心低;實心角點的 \(F \ge c\)。)。這就是中點決定法(midpoint decider)Athawale (2019)。Nielson (1991) 的漸近決定法(asymptotic decider)則比較 \(u\) 在其鞍點的值
兩種規則只在 \(u^*\) 與平均值落在 \(c\) 兩側時才不同。
from bezierkit.implicit import trace_implicit
contours = trace_implicit(
lambda x, y: x**2 * y,
levels=[1, 2, 4],
viewport=(0.5, 4, 0, 6),
resolution=(121, 121),
tolerance=0.01,
gradient=lambda x, y: (2 * x * y, x**2),
)
# (1.0, 2.0, 4.0)
print(contours.level_values)
# 15
print(len(contours.for_level(4)[0].segments))
結果
ContourSet
trace_implicit 回傳 ContourSet,其 contours 依給定順序為每個水準保存一個 LevelContours。level_values 列出各水準,paths 匯集所有路徑,for_level(c) 回傳某一水準的路徑(未描繪的水準拋出 KeyError)。
LevelContours
水準值 level 與路徑 paths,每個連通部分一條 PiecewiseBezier。
限制
描繪出的曲線只在網格交點上精確;交點之間是行進方格法得到的折線,再於 tolerance 內簡化與彎曲。需要更貼近時,請提高 resolution 並降低 tolerance。比網格格子小的特徵可能遺漏,鞍點規則也只會選擇兩種可能連接方式之一。Leontief 無異曲線的折角等尖點會在網格間距內被圓滑化,因為網格看不到精確的轉角。