מרחבים ותתי־מרחבים
גרפים ורשתות
Graphs and Networks
מטריצת incidence של גרף הופכת ערכי צמתים להפרשים וזרימות למאזנים; הלפלסיאן L=AᵀA קושר חיבוריות, דרגות ושימור.
- 1נקודת פתיחהמה כבר ידוע ומה השאלה?
- 2ניבוימה יקרה במקרה הפשוט?
- 3המחשהמה באמת משתנה בתמונה?
- 4ניסוח והצדקהשם, נוסחה ולמה היא נכונה
- 5בדיקהדוגמה, תרגול ומשוב
- 6העברהמקרה חדש והמשך המסלול
שאלה לפני נוסחה
מה אנחנו מנסים לראות?
לתרגם רשת לצורה לינארית מלאה: לבנות מטריצת incidence, לפרש הפרשי פוטנציאל ומאזני זרימה, ולקרוא מן המרחבים והלפלסיאן את הרכיבים הקשירים והמעגלים.
כל שורה במטריצת incidence היא מד־הפרש על קשת: היא מחסרת את הערך בצומת המוצא מן הערך בצומת היעד. הטרנספוז מרכז בחזרה את זרימות הקשתות בכל צומת ופועל כמנהל החשבונות של הרשת.
לפני ההמחשה: קראו רק את הכותרת “מגרף למטריצה וללפלסיאן”. מה לדעתכם ישתנה, מה יישאר קבוע, ואיזה סימן בתמונה יוכל להפריך את הניבוי?
לראות לפני שמחשבים · המקרה הפשוט ביותר
מגרף למטריצה וללפלסיאן
משמאל מופיע גרף מכוון עם ערכי צמתים. מימין מוצגים שני מסלולי החישוב x→Ax ו־q→Aᵀq, ומתחתיהם הזהות L=AᵀA.
מה כדאי לגלות: קראו את החצים לפי הסדר: A מעביר ערכי צמתים להפרשי קשתות, ו־Aᵀ מחזיר זרימות למאזני צמתים. שינוי כיוון קשת הופך סימן בשורה המתאימה אך משאיר את L ואת האנרגיה ללא שינוי.
אחרי שראינו
בסוף השיעור תוכלו
- לבנות מטריצת incidence מכיוון מוסכם של קשתות ולפרש את Ax ואת Aᵀq.
- למצוא את N(A) ואת N(Aᵀ) ולחבר אותם לרכיבים קשירים ולזרימות מעגליות.
- לבנות את L=AᵀA ולהוכיח שהוא סימטרי וחיובי למחצה.
- להשתמש בנוסחאות rank(A)=n−c ו־dim N(Aᵀ)=e−n+c ולפתור בעיית רשת עד כדי קבוע בכל רכיב.
נותנים שמות למה שראינו
מושגים שנשתמש בהם
- גרף
- אוסף צמתים וקשתות המחברות זוגות של צמתים.
- כיוון ייחוס
- בחירה שרירותית של זנב וראש לכל קשת; היא קובעת סימנים אך לא משנה את מבנה הרשת.
- מטריצת incidence
- מטריצה A בגודל e×n: בכל שורת קשת יש −1 בזנב, +1 בראש ואפסים בשאר הצמתים.
- פוטנציאל
- ערך xᵢ בכל צומת, למשל מתח, גובה או טמפרטורה.
- זרימה
- ערך qₖ בכל קשת, חיובי בכיוון הייחוס ושלילי נגדו.
- מאזן צומת
- הרכיב המתאים של Aᵀq: זרימה נכנסת פחות זרימה יוצאת לפי מוסכמת הסימנים.
- רכיב קשיר
- קבוצת צמתים שאפשר לעבור בין כל שניים מהם לאורך קשתות, בלי להתחשב בכיווני הייחוס.
- מרחב המעגלים
- N(Aᵀ): זרימות שמאזנן אפס בכל צומת ולכן יכולות להסתובב במעגלים.
- לפלסיאן L
- המטריצה L=AᵀA; האלכסון מכיל דרגות צמתים והאיברים מחוץ לאלכסון הם מינוס מספר הקשתות המחברות.
- חיובית למחצה
- מטריצה סימטרית L שעבורה xᵀLx≥0 לכל x; מותר שהערך יהיה אפס בכיוונים לא־אפסיים.
מהמילים אל הסימנים
כותבים את הפעולה שכבר ראינו
פתחו את הנוסחה רק אחרי שאתם יכולים לומר במילים מה כל פעולה אמורה לעשות. בשלב הבא נצדיק אותה צעד אחר צעד.
להצגת נוסחת המפתח
קראו כל סימן כפעולה, לא כמשהו שצריך לשנןAx=הפרשים; Aᵀq=מאזנים; L=AᵀAמרכיבים את הפעולות
מהתמונה אל הנוסחה — הגזירה, בלי לדלג על הצעדים
בוחרים כיוון ייחוס לכל קשת
הכיוון הוא מוסכמת סימנים בלבד. עבור קשת i→j נרשום −1 בעמודת i ו־+1 בעמודת j. עם e קשתות ו־n צמתים מתקבלת A בגודל e×n.
row(i→j)=eⱼᵀ−eᵢᵀA מחשבת הפרשי פוטנציאל
אם x מכיל ערך בכל צומת, השורה של קשת מחסרת את ערך הזנב מערך הראש. הפיכת כיוון מכפילה את השורה ואת התוצאה ב־−1.
(Ax)ₖ=x_head−x_tailAᵀ מאזנת זרימות בצמתים
q חי על הקשתות וחיובי בכיוון הייחוס. הטרנספוז מחבר בכל צומת את הזרימות הנכנסות ומחסיר את היוצאות; נסמן את מאזן הכניסה ב־g.
g=Aᵀq (net inflow)עוצרים לחשוב: הסכום של רכיבי g בכל רכיב קשיר חייב להיות אפס: זרימה פנימית אינה נוצרת ואינה נעלמת.
מרחב האפס סופר רכיבים קשירים
Ax=0 אומר שאין הפרש על שום קשת, ולכן x קבוע בתוך כל רכיב קשיר. לכל רכיב אפשר לבחור קבוע אחר.
dim N(A)=c, rank(A)=n−cמרחב האפס השמאלי סופר מעגלים
Aᵀq=0 אומר שהזרימה מאוזנת בכל צומת. ביער אין מקום לזרימה לא־אפסית כזו; כל קשת עודפת שסוגרת מעגל מוסיפה דרגת חופש.
dim N(Aᵀ)=e−rank(A)=e−n+cבונים את הלפלסיאן
L=AᵀA היא n×n וסימטרית. האלכסון Lᵢᵢ הוא מספר הקשתות הנוגעות בצומת i, ו־Lᵢⱼ הוא מינוס מספר הקשתות בין i ל־j.
L=AᵀA=D−Adjacencyהאנרגיה מוכיחה חיוביות למחצה
הצורה הריבועית של L היא סכום ריבועי כל ההפרשים על הקשתות, ולכן אינה שלילית. היא אפס בדיוק כאשר הפוטנציאל קבוע בכל רכיב.
xᵀLx=‖Ax‖²=Σ_edges(xⱼ−xᵢ)²≥0פותרים עד כדי קבוע
N(L)=N(A), ולכן L סינגולרית בכל גרף. אם f מסמן הזרקה חיצונית, כלומר יציאה נטו מהצומת, אז Lx=f עקבית כאשר f מאונך לקבועים בכל רכיב. לאחר קיבוע צומת ייחוס אחד בכל רכיב מתקבל פתרון יחיד.
Σ_{i in component} fᵢ=0; x and x+constant give same differencesעוצרים לחשוב: אין להפוך את L ישירות; מקבעים gauge, למשל x₀=0, או משתמשים בפתרון מינימום־נורמה.
עכשיו רואים את השיטה בפעולה
דוגמאות פתורות, צעד אחר צעד
מסלול בן שלושה צמתים
בנו A ו־L עבור הקשתות 0→1 ו־1→2.
שורות ה־incidence הן (−1,1,0) ו־(0,−1,1), ולכן A=[[-1,1,0],[0,−1,1]].
עבור x=(2,5,4), מתקבל Ax=(3,−1): על הקשת הראשונה יש עלייה 3 ועל השנייה ירידה 1.
הכפל AᵀA נותן L=[[1,−1,0],[−1,2,−1],[0,−1,1]].
הגרף קשיר, לכן c=1, rank(A)=3−1=2 ו־N(A)=span{(1,1,1)}.
אין מעגלים: e−n+c=2−3+1=0, ולכן N(Aᵀ)={0}.
מסלול קשיר מודד את כל הפרשי הפוטנציאל אך אינו יכול לזהות הוספת קבוע משותף.
A(x+t𝟙)=Axהמעגל יוצר זרימה מאוזנת
הוסיפו למסלול את הקשת 2→0 וחשבו זרימת מעגל.
השורה החדשה היא (1,0,−1). כעת e=n=3 והגרף עדיין קשיר.
לכן dim N(Aᵀ)=e−n+1=1.
הווקטור q=(1,1,1) מזרים יחידה אחת לאורך כל שלוש הקשתות.
בכל צומת יחידה אחת נכנסת ויחידה אחת יוצאת, ולכן Aᵀq=0.
הפיכת כיוון של קשת דורשת להפוך גם את סימן רכיב הזרימה שלה; הזרימה הפיזיקלית נשארת אותה זרימה.
כל קשת שסוגרת מעגל בלתי תלוי מוסיפה כיוון אחד למרחב המעגלים.
N(Aᵀ)=span{(1,1,1)}איפה זה פוגש את העולם
יישום
ברשת חשמלית x הם מתחים ו־d=Ax=x_head−x_tail הוא הפרש המתח החתום. כאשר q חיובי מזנב הקשת אל ראשה, חוק אוהם נותן q=W(x_tail−x_head)=−WAx. לכן Aᵀq הוא מאזן הכניסה, ואילו מאזן היציאה החיצוני הוא f=−Aᵀq=AᵀWAx=L_Wx. במעבדה q_alg=Ax הוא תגובת הפרש האלגברית, לא זרם אוהמי. אותו מבנה מופיע גם בדיפוזיה, consensus והחלקת נתונים.
מלכודת נפוצה
מה כדאי לבדוק
לבלבל בין A ל־Aᵀ: במוסכמה כאן A היא קשתות×צמתים, ולכן Ax חי על הקשתות ו־Aᵀq חי בצמתים. במוסכמת הסימנים הזאת Ax אינו הזרם האוהמי בכיוון החץ, אלא מינוס הזרם לאחר שקלול ב־W. גם אין להפוך את L ישירות; הסינגולריות מבטאת חופש להוסיף קבוע לפוטנציאלים.
בודקים שהרעיון יושב
תרגול ומשוב
בדיקת הבנה
בחרו תשובה ורק אז בדקו את עצמכם
בחרו אחת מהאפשרויות כדי להמשיך.
עכשיו אתם
אתגר קצר
בגרף עם הקשתות 0→1, 1→2, 2→0 ו־2→3, בנו את A ואת L. מצאו בסיס ל־N(A), זרימת מעגל לא־אפס ב־N(Aᵀ), ואמתו שדרגת A היא 3.
רמז ראשון
שלוש הקשתות הראשונות יוצרות משולש והקשת האחרונה מחברת את צומת 3. השתמשו ב־q=(1,1,1,0) ובווקטור הקבוע 𝟙.
הצגת דרך הפתרון
- לפי סדר הקשתות מתקבלת A=[[-1,1,0,0],[0,−1,1,0],[1,0,−1,0],[0,0,−1,1]].
- הגרף קשיר, ולכן c=1, rank(A)=n−c=3 ו־N(A)=span{(1,1,1,1)}.
- הזרימה q=(1,1,1,0) מאוזנת בכל צומת: שלוש היחידות מסתובבות במשולש והקשת לצומת 3 אינה נושאת זרימה. לכן q∈N(Aᵀ).
- הלפלסיאן הוא [[2,−1,−1,0],[−1,2,−1,0],[−1,−1,3,−1],[0,0,−1,1]]. כל שורה מסתכמת לאפס.
- dim N(Aᵀ)=e−n+c=4−4+1=1, ולכן q פורש את כל מרחב המעגלים.
מימוש הנושא
קוד שאפשר לקחת לניסוי
הקוד מיישם את אותו רעיון מתמטי. ב־JavaScript אפשר להריץ אותו כאן; גרסת Python זמינה להשוואה ולהעתקה.
incidence בונה את A מרשימת קשתות, multiply יוצר את L, ו־componentLabels סופרת רכיבים בלי להתחשב בכיוון הייחוס. בדיקות האנרגיה והמאזן מאמתות את הזהויות.
באתגר תתקבל A בגודל 4×4, הלפלסיאן הנתון, רכיב קשיר אחד, rank צפוי 3, מאזן אפס ל־q=(1,1,1,0) ואנרגיות שוות.
הפכו את הקשת 2→0 ל־0→2 ועדכנו רק את שורת A ואת סימן q באותה קשת. בדקו ש־L, הרכיבים, המאזן והאנרגיה אינם משתנים.
מהרעיון אל הקוד
מימוש חישובי
function incidence(nodeCount, edges) {
return edges.map(([tail, head]) => {
const row = Array(nodeCount).fill(0);
row[tail] = -1;
row[head] = 1;
return row;
});
}
function componentLabels(nodeCount, edges) {
const adjacency = Array.from({ length: nodeCount }, () => []);
edges.forEach(([u, v]) => {
adjacency[u].push(v);
adjacency[v].push(u);
});
const labels = Array(nodeCount).fill(-1);
let count = 0;
for (let start = 0; start < nodeCount; start += 1) {
if (labels[start] !== -1) continue;
const stack = [start];
labels[start] = count;
while (stack.length) {
const node = stack.pop();
adjacency[node].forEach((neighbor) => {
if (labels[neighbor] === -1) {
labels[neighbor] = count;
stack.push(neighbor);
}
});
}
count += 1;
}
return { labels, count };
}
const transpose = (A) =>
A[0].map((_, j) => A.map((row) => row[j]));
const matVec = (A, x) =>
A.map((row) =>
row.reduce((sum, value, j) => sum + value*x[j], 0),
);
const multiply = (A, B) => {
const columns = transpose(B);
return A.map((row) =>
columns.map((column) =>
row.reduce((sum, value, j) => sum + value*column[j], 0),
),
);
};
const dot = (x, y) =>
x.reduce((sum, value, i) => sum + value*y[i], 0);
const edges = [[0, 1], [1, 2], [2, 0], [2, 3]];
const A = incidence(4, edges);
const At = transpose(A);
const L = multiply(At, A);
const potential = [1, -1, 0, 2];
const differences = matVec(A, potential);
const balances = matVec(At, differences);
const cycleFlow = [1, 1, 1, 0];
const components = componentLabels(4, edges);
console.table(A);
console.table(L);
console.log({
differences,
balances,
totalBalance: balances.reduce((sum, value) => sum + value, 0),
constantNullVector: matVec(A, [1, 1, 1, 1]),
cycleBalance: matVec(At, cycleFlow),
energy: dot(differences, differences),
sameEnergy: dot(potential, balances),
components,
expectedRank: 4 - components.count,
cycleDimension: edges.length - 4 + components.count,
});def incidence(node_count, edges):
matrix = []
for tail, head in edges:
row = [0]*node_count
row[tail], row[head] = -1, 1
matrix.append(row)
return matrix
def component_labels(node_count, edges):
adjacency = [[] for _ in range(node_count)]
for u, v in edges:
adjacency[u].append(v)
adjacency[v].append(u)
labels = [-1]*node_count
count = 0
for start in range(node_count):
if labels[start] != -1:
continue
stack = [start]
labels[start] = count
while stack:
node = stack.pop()
for neighbor in adjacency[node]:
if labels[neighbor] == -1:
labels[neighbor] = count
stack.append(neighbor)
count += 1
return {"labels": labels, "count": count}
def transpose(A):
return [list(column) for column in zip(*A)]
def mat_vec(A, x):
return [
sum(value*x[j] for j, value in enumerate(row))
for row in A
]
def multiply(A, B):
columns = transpose(B)
return [
[
sum(a*b for a, b in zip(row, column))
for column in columns
]
for row in A
]
def dot(x, y):
return sum(a*b for a, b in zip(x, y))
edges = [(0, 1), (1, 2), (2, 0), (2, 3)]
A = incidence(4, edges)
A_t = transpose(A)
L = multiply(A_t, A)
potential = [1, -1, 0, 2]
differences = mat_vec(A, potential)
balances = mat_vec(A_t, differences)
cycle_flow = [1, 1, 1, 0]
components = component_labels(4, edges)
print("A =", A)
print("L =", L)
print("differences =", differences)
print("balances =", balances, "sum =", sum(balances))
print("A*1 =", mat_vec(A, [1, 1, 1, 1]))
print("A^T*cycle =", mat_vec(A_t, cycle_flow))
print("energies =", dot(differences, differences), dot(potential, balances))
print("components =", components)
print("expected rank =", 4-components["count"])
print("cycle dimension =", len(edges)-4+components["count"])לפני שממשיכים
מה לוקחים מהשיעור
- בחירת כיוון לקשת קובעת סימנים ב־A אך אינה משנה את L או את מבנה הגרף.
- Ax מודד הפרשים על קשתות ו־Aᵀq מודד מאזן זרימה בצמתים.
- N(A) מייצג קבוע אחד לכל רכיב קשיר; N(Aᵀ) מייצג זרימות מעגליות.
- L=AᵀA חיובית למחצה, N(L)=N(A), ופתרון פוטנציאלים נקבע רק לאחר קיבוע קבוע בכל רכיב.
משימת מעבדה לשיעור הזה
תחנה 4 — מן הגרף אל חוקי השימור
בחרו כיוונים, בנו שורת incidence לכל קשת, חזו את מספר הרכיבים והמעגלים, ואז בדקו את N(A), N(Aᵀ) ואת L=AᵀA.
מקורות להעמקה בלבד — השיעור עומד בפני עצמו
מדריך המרצים: עמ׳ 158–161
ספר הלימוד: עמ׳ 313–324