משוואות לינאריות ומטריצות הפוכות
פתרון באמצעות אלימינציה
Solving Linear Equations by Elimination
הופכים מערכת למשולשת באמצעות פעולות שורה, pivots והחלפות; אחר כך אורזים את החישוב בפירוק PA=LU.
- 1נקודת פתיחהמה כבר ידוע ומה השאלה?
- 2ניבוימה יקרה במקרה הפשוט?
- 3המחשהמה באמת משתנה בתמונה?
- 4ניסוח והצדקהשם, נוסחה ולמה היא נכונה
- 5בדיקהדוגמה, תרגול ומשוב
- 6העברהמקרה חדש והמשך המסלול
שאלה לפני נוסחה
מה אנחנו מנסים לראות?
לפתור מערכת באמצעות אלימינציה והצבה לאחור, לבחור pivot בטוח, ורק לאחר שהאלגוריתם מובן לתעד את פעולותיו במבנה PA=LU.
אלימינציה מסדרת את המידע כך שכל שורה חדשה עוסקת בפחות נעלמים. ה־pivots הם נקודות האחיזה, L זוכרת באילו כפולות השתמשנו כדי לבטל, ו־P זוכרת אילו שורות הוחלפו.
לפני ההמחשה: קראו רק את הכותרת “פעולת שורה אחת חושפת מערכת משולשת”. מה לדעתכם ישתנה, מה יישאר קבוע, ואיזה סימן בתמונה יוכל להפריך את הניבוי?
לראות לפני שמחשבים · המקרה הפשוט ביותר
פעולת שורה אחת חושפת מערכת משולשת
ההמחשה מתחילה ב־[[2,3|1],[10,9|11]], מסמנת pivot=2 ומבצעת R₂←R₂−5R₁. אחר כך היא מציגה את השורה [0,−6|6], הצבה לאחור ואת A=LU.
מה כדאי לגלות: עקבו גם אחר אגף ימין: מן השורה החדשה מתקבל y=−1, ומהשורה הראשונה x=2. פעולת השורה שינתה את הייצוג אך לא את זוג הפתרונות.
אחרי שראינו
בסוף השיעור תוכלו
- לבצע פעולות שורה חוקיות על מטריצה מורחבת ולהסביר מדוע הן שומרות את קבוצת הפתרונות.
- להפוך מערכת למבנה משולש ולפתור אותו בהצבה לאחור.
- לזהות pivot אפס או קטן, לבצע החלפת שורות ולתעד אותה במטריצת P.
- לבנות את L ואת U מתוך מכפילי האלימינציה ולהשתמש בהן לפתרון אגפי ימין נוספים.
נותנים שמות למה שראינו
מושגים שנשתמש בהם
- מטריצה מורחבת
- הטבלה [A|b] שמצמידה את אגף ימין למקדמים כדי שכל פעולת שורה תפעל על המערכת כולה.
- פעולת שורה חוקית
- החלפת שורות, כפל שורה במספר לא־אפס או הוספת כפולה של שורה לשורה אחרת; כל אחת הפיכה ושומרת פתרונות.
- pivot
- האיבר המוביל שבו משתמשים כדי לאפס את שאר האיברים מתחתיו באותה עמודה.
- מכפיל אלימינציה
- היחס ℓᵢₖ=aᵢₖ/uₖₖ שמספר איזו כפולה של שורת ה־pivot יש לחסר משורה i.
- מטריצה משולשת עליונה U
- מטריצה שכל האיברים מתחת לאלכסון שלה אפס, ולכן אפשר לפתור ממנה מן השורה האחרונה כלפי מעלה.
- הצבה לאחור
- פתרון מערכת משולשת עליונה החל מן הנעלם האחרון והצבתו בשורות שמעליו.
- scaled partial pivoting
- בחירת שורה לפי היחס בין מועמד ה־pivot לבין האיבר הגדול באותה שורה. כך כפל משוואה שלמה במספר קטן אינו הופך אותה בטעות לסינגולרית.
- פירוק PA=LU
- תיעוד האלימינציה: P שומרת החלפות שורה, L שומרת מכפילים ו־U היא התוצאה המשולשת.
מהמילים אל הסימנים
כותבים את הפעולה שכבר ראינו
פתחו את הנוסחה רק אחרי שאתם יכולים לומר במילים מה כל פעולה אמורה לעשות. בשלב הבא נצדיק אותה צעד אחר צעד.
להצגת נוסחת המפתח
קראו כל סימן כפעולה, לא כמשהו שצריך לשנןPA = LU (עם pivoting)מרכיבים את הפעולות
מהתמונה אל הנוסחה — הגזירה, בלי לדלג על הצעדים
מצמידים את אגף ימין למקדמים
כותבים [A|b] כדי שכל שינוי במשוואה יופיע באותה שורה משני צדי הקו. כך לא מאבדים את הקשר בין המקדמים לבין התוצאה.
Ax=b ⇒ [A|b]משתמשים רק בפעולות הפיכות
מותר להחליף שתי שורות, לכפול שורה במספר לא־אפס או להוסיף לשורה כפולה של שורה אחרת. לכל פעולה יש פעולת ביטול, ולכן קבוצת הפתרונות אינה משתנה.
Rᵢ←Rᵢ−ℓRₖ ⇔ Rᵢ←Rᵢ+ℓRₖבוחרים pivot ומאפסים מתחתיו
כדי לאפס את aᵢₖ מחשבים את המכפיל ℓᵢₖ=aᵢₖ/uₖₖ ומחסרים ℓᵢₖ פעמים את שורת ה־pivot. מבצעים בדיוק אותה פעולה גם על b.
ℓᵢₖ=aᵢₖ/uₖₖ, Rᵢ←Rᵢ−ℓᵢₖRₖחוזרים עד שמתקבלת מטריצה משולשת
עוברים עמודה אחר עמודה ומשאירים אפסים מתחת לכל pivot. בסוף מתקבלת U, ובה השורה האחרונה כוללת בדרך כלל נעלם אחד בלבד.
[A|b] → [U|c]פותרים מלמטה למעלה
מוצאים את הנעלם האחרון מן השורה האחרונה, מציבים אותו בשורה שמעליה וממשיכים עד לנעלם הראשון. זהו שלב ההצבה לאחור.
uₙₙxₙ=cₙ, xᵢ=(cᵢ−Σⱼ>ᵢuᵢⱼxⱼ)/uᵢᵢpivot אפס דורש החלפת שורות
אם עמדת ה־pivot היא אפס אך יש מתחתיה איבר לא־אפס, מחליפים שורות. בקוד משתמשים ב־scaled partial pivoting: משווים כל מועמד לגודל השורה שלו, כדי שההכרעה לא תשתנה אם כופלים משוואה שלמה במספר. P מתעדת את ההחלפות. אם אין מועמד יחסי בטוח, אין פתרון יחיד ויש לבדוק סתירה או משתנה חופשי.
Rₖ↔Rᵣ, ובסוף PA=LUרק עכשיו אורזים את הצעדים ב־L וב־U
L שומרת מתחת לאלכסון את מכפילי האלימינציה ו־U היא המטריצה המשולשת. לפתרון אגפי ימין נוספים פותרים Ly=Pb ואז Ux=y. האימות המלא של PA=LU משתמש בכפל מטריצות, שילמד בשיעור 4.3.
PA=LU, Ly=Pb, Ux=y
עכשיו רואים את השיטה בפעולה
דוגמאות פתורות, צעד אחר צעד
אלימינציה והצבה לאחור ב־2×2
פתרו את 2x+y=7, x−y=1 באמצעות אלימינציה.
כותבים את המטריצה המורחבת [[2,1|7],[1,−1|1]].
ה־pivot הראשון הוא 2 והמכפיל הוא ℓ₂₁=1/2. מבצעים R₂←R₂−(1/2)R₁.
השורה השנייה נהפכת ל־[0,−3/2|−5/2], ולכן y=(−5/2)/(−3/2)=5/3.
מציבים בשורה הראשונה: 2x+5/3=7, ולכן x=8/3.
בדיקה במשוואה השנייה נותנת 8/3−5/3=1.
האלימינציה לא שינתה את הפתרון; היא רק החליפה את המשוואה השנייה במשוואה שקולה ונוחה יותר.
(x,y)=(8/3,5/3)כאשר ה־pivot הראשון הוא אפס
פתרו את 2y=4, x+y=3 והסבירו מדוע חייבים להחליף שורות.
המטריצה היא [[0,2|4],[1,1|3]]. אי אפשר להשתמש ב־0 כדי לאפס את האיבר שמתחתיו.
מחליפים R₁↔R₂ ומקבלים [[1,1|3],[0,2|4]].
מן השורה השנייה מתקבל y=2, ומן הראשונה x+2=3 ולכן x=1.
מטריצת ההחלפה היא P=[[0,1],[1,0]]. כאן כבר PA משולשת, ולכן L=I ו־U=PA.
החלפת השורות משנה את הסדר בלבד ושומרת את שתי המשוואות ואת פתרונן.
(x,y)=(1,2)איפה זה פוגש את העולם
יישום
בניתוח מעגל או רשת אפשר לפרק את A פעם אחת ואז לפתור במהירות עבור אגפי ימין רבים. השלב היקר הוא הפירוק; ההצבות קדימה ואחורה זולות יותר וממחזרות את המבנה.
מלכודת נפוצה
מה כדאי לבדוק
לשנות את A בלי לשנות את b, או לחלק ב־pivot אפס או זעיר בלי להחליף שורות. בנוסף, L שומרת את הכפולות ℓ עצמן, אף שבפעולת השורה מחסרים אותן.
בודקים שהרעיון יושב
תרגול ומשוב
בדיקת הבנה
בחרו תשובה ורק אז בדקו את עצמכם
בחרו אחת מהאפשרויות כדי להמשיך.
עכשיו אתם
אתגר קצר
עבור A=[[2,1,1],[4,−6,0],[−2,7,2]] ו־b=(5,−2,9), בצעו אלימינציה ללא החלפת שורות, בנו את L ואת U ופתרו את Ax=b.
רמז ראשון
השתמשו לפי הסדר במכפילים ℓ₂₁=2, ℓ₃₁=−1 ו־ℓ₃₂=−1. זכרו: L שומרת את המכפילים עצמם, אף שבפעולות השורה מחסרים אותם.
הצגת דרך הפתרון
- אחרי R₂←R₂−2R₁ ו־R₃←R₃+R₁ מתקבלות השורות [0,−8,−2|−12] ו־[0,8,3|14].
- המכפיל הבא הוא ℓ₃₂=8/(−8)=−1, ולכן R₃←R₃+R₂ ומתקבלת [0,0,1|2].
- לכן U=[[2,1,1],[0,−8,−2],[0,0,1]] ו־L=[[1,0,0],[2,1,0],[−1,−1,1]]. אין החלפות, ולכן P=I.
- אפשר לקרוא את אגף ימין שעבר אלימינציה כ־y=(5,−12,2), כלומר כפתרון של Ly=b. לאחר מכן Ux=y נותנת בהצבה לאחור x₃=2, x₂=1 ו־x₁=1.
- בשיעור 4.3 תוכלו לכפול ולוודא במפורש ש־LU=A; כרגע אפשר לאמת על ידי שחזור פעולות האלימינציה.
מימוש הנושא
קוד שאפשר לקחת לניסוי
הקוד מיישם את אותו רעיון מתמטי. ב־JavaScript אפשר להריץ אותו כאן; גרסת Python זמינה להשוואה ולהעתקה.
pluSolve מחשבת scale נפרד לכל שורה, בוחרת את יחס ה־pivot הבטוח ביותר, ומחליפה יחד את U, b, P, L ו־rowScales. הכפל בסוף הקוד הוא תצוגה מקדימה לשיעור 4.3.
עבור הדוגמה יתקבל x קרוב ל־[1,1,2], P=I ו־factorizationError קרוב לאפס. גם המערכת שבה השורה השנייה מוכפלת ב־10⁻¹³ תחזיר [1,2].
נסו תחילה A=[[0,2],[1,1]], b=[4,3] כדי לראות החלפה. אחר כך כִּפלו שורה אחת של A ושל b באותו מספר קטן וודאו שהפתרון אינו משתנה.
מהרעיון אל הקוד
מימוש חישובי
function pluSolve(matrix, rhs, relativeTolerance=1e-12) {
const n = matrix.length;
if (!n || rhs.length !== n || matrix.some((row) => row.length !== n)) {
throw new Error("A must be square and match b");
}
const U = matrix.map((row) => row.map(Number));
const rowScales = U.map((row) => Math.max(...row.map(Math.abs)));
if (rowScales.some((scale) => scale === 0)) {
throw new Error("no unique solution");
}
const b = rhs.map(Number);
const L = Array.from({ length: n }, () => Array(n).fill(0));
const P = Array.from(
{ length: n },
(_, i) => Array.from({ length: n }, (_, j) => Number(i === j)),
);
for (let k = 0; k < n; k += 1) {
let pivot = k;
for (let i = k + 1; i < n; i += 1) {
const candidateRatio = Math.abs(U[i][k]) / rowScales[i];
const pivotRatio = Math.abs(U[pivot][k]) / rowScales[pivot];
if (candidateRatio > pivotRatio) pivot = i;
}
if (U[pivot][k] === 0) throw new Error("no unique solution");
if (Math.abs(U[pivot][k]) <= relativeTolerance*rowScales[pivot]) {
throw new Error("nonzero pivot is numerically unsafe at this tolerance");
}
if (pivot !== k) {
[U[k], U[pivot]] = [U[pivot], U[k]];
[b[k], b[pivot]] = [b[pivot], b[k]];
[P[k], P[pivot]] = [P[pivot], P[k]];
[rowScales[k], rowScales[pivot]] = [rowScales[pivot], rowScales[k]];
for (let j = 0; j < k; j += 1) {
[L[k][j], L[pivot][j]] = [L[pivot][j], L[k][j]];
}
}
L[k][k] = 1;
for (let i = k + 1; i < n; i += 1) {
const multiplier = U[i][k] / U[k][k];
L[i][k] = multiplier;
for (let j = k; j < n; j += 1) {
U[i][j] -= multiplier * U[k][j];
}
b[i] -= multiplier * b[k];
}
}
const x = Array(n).fill(0);
for (let i = n - 1; i >= 0; i -= 1) {
const known = U[i].slice(i + 1)
.reduce((sum, value, j) => sum + value*x[i + 1 + j], 0);
x[i] = (b[i] - known) / U[i][i];
}
return { x, P, L, U };
}
function multiplyMatrices(A, B) {
return A.map((row) =>
B[0].map((_, j) =>
row.reduce((sum, value, k) => sum + value*B[k][j], 0),
),
);
}
const A = [[2, 1, 1], [4, -6, 0], [-2, 7, 2]];
const result = pluSolve(A, [5, -2, 9]);
const PA = multiplyMatrices(result.P, A);
const LU = multiplyMatrices(result.L, result.U);
const factorizationError = Math.max(
...PA.flatMap((row, i) => row.map((value, j) => Math.abs(value - LU[i][j]))),
);
console.log({ ...result, factorizationError });
console.log(pluSolve([[1, 0], [0, 1e-13]], [1, 2e-13]).x);def plu_solve(matrix, rhs, relative_tolerance=1e-12):
n = len(matrix)
if not n or len(rhs) != n or any(len(row) != n for row in matrix):
raise ValueError("A must be square and match b")
U = [[float(value) for value in row] for row in matrix]
row_scales = [max(abs(value) for value in row) for row in U]
if any(scale == 0 for scale in row_scales):
raise ValueError("no unique solution")
b = [float(value) for value in rhs]
L = [[0.0]*n for _ in range(n)]
P = [[float(i == j) for j in range(n)] for i in range(n)]
for k in range(n):
pivot = max(
range(k, n),
key=lambda i: abs(U[i][k])/row_scales[i],
)
if U[pivot][k] == 0:
raise ValueError("no unique solution")
if abs(U[pivot][k]) <= relative_tolerance*row_scales[pivot]:
raise ArithmeticError(
"nonzero pivot is numerically unsafe at this tolerance"
)
if pivot != k:
U[k], U[pivot] = U[pivot], U[k]
b[k], b[pivot] = b[pivot], b[k]
P[k], P[pivot] = P[pivot], P[k]
row_scales[k], row_scales[pivot] = row_scales[pivot], row_scales[k]
for j in range(k):
L[k][j], L[pivot][j] = L[pivot][j], L[k][j]
L[k][k] = 1.0
for i in range(k+1, n):
multiplier = U[i][k] / U[k][k]
L[i][k] = multiplier
for j in range(k, n):
U[i][j] -= multiplier*U[k][j]
b[i] -= multiplier*b[k]
x = [0.0]*n
for i in range(n-1, -1, -1):
known = sum(U[i][j]*x[j] for j in range(i+1, n))
x[i] = (b[i] - known) / U[i][i]
return {"x": x, "P": P, "L": L, "U": U}
def multiply_matrices(A, B):
return [
[
sum(a*b for a, b in zip(row, column))
for column in zip(*B)
]
for row in A
]
A = [[2, 1, 1], [4, -6, 0], [-2, 7, 2]]
result = plu_solve(A, [5, -2, 9])
PA = multiply_matrices(result["P"], A)
LU = multiply_matrices(result["L"], result["U"])
result["factorization_error"] = max(
abs(value - LU[i][j])
for i, row in enumerate(PA)
for j, value in enumerate(row)
)
print(result)
print(plu_solve([[1, 0], [0, 1e-13]], [1, 2e-13])["x"])לפני שממשיכים
מה לוקחים מהשיעור
- פעולות שורה הפיכות משנות את צורת המערכת אך לא את קבוצת הפתרונות שלה.
- כל פעולה על שורת A חייבת להתבצע גם על הרכיב המתאים של b.
- אלימינציה יוצרת U והצבה לאחור פותרת ממנה את הנעלמים בסדר הפוך.
- P מתעדת החלפות שורה ו־L מתעדת מכפילים; כך אפשר למחזר פירוק אחד עבור אגפי ימין רבים.
משימת מעבדה לשיעור הזה
תחנה 2 — אלימינציה צעד אחר צעד
בחרו pivot, חזו את המכפיל ואת השורה החדשה, ורק אז הפעילו את הצעד. עקבו אחר אגף ימין וודאו שהפתרון נשמר.
מקורות להעמקה בלבד — השיעור עומד בפני עצמו
מדריך המרצים: עמ׳ 104–109
ספר הלימוד: עמ׳ 210–218