הגאומטריה של כל הפתרונות
מרחבים ותתי־מרחבים
Vector Spaces and Subspaces
בפרק הקודם פתרנו מערכת אחת. עכשיו נבין את המבנה של כל המערכות שאותה מטריצה יכולה לפתור: מרחב העמודות מתאר אילו יעדים אפשר להגיע אליהם, מרחב האפס מתאר אילו קלטים אינם משנים את הפלט, ובסיס וממד הופכים את שתי התמונות למפה שאפשר לחשב איתה.
מפת הפרק
6 תחנות בדרך
6 יחידות לימוד עצמאיות · כ־6 שעות ו־30 דקות לשיעורים ולתרגול
- 5.1יחידת לימוד60 דקות
מרחב העמודות
שאלה 1: מה ננסה להבין?מגדירים תת־מרחב ו־span, ואז מזהים את C(A)⊆ℝᵐ כאוסף כל היעדים b שעבורם Ax=b פתירה; בסיסו מתקבל מעמודות ה־pivot של A המקורית.
לשיעור ← - 5.2יחידת לימוד60 דקות
מרחב האפס
שאלה 2: מה ננסה להבין?פותרים Ax=0 בעזרת pivot ומשתנים חופשיים, בונים פתרונות מיוחדים ומקבלים בסיס לכל הכיוונים שהמטריצה מוחקת.
לשיעור ← - 5.3יחידת לימוד65 דקות
הפתרון השלם של Ax=b
שאלה 3: מה ננסה להבין?בודקים תחילה אם b∈C(A); אם כן, מחברים פתרון פרטי אחד לכל מרחב האפס ומקבלים קבוצה אפינית של כל הפתרונות.
לשיעור ← - 5.4יחידת לימוד65 דקות
אי־תלות, בסיס וממד
שאלה 4: מה ננסה להבין?מפרידים בין יצירה ללא יתירות לבין תלות לינארית, בוחרים בסיס ומוכיחים שמספר וקטורי הבסיס קבוע ושווה לממד.
לשיעור ← - 5.5יחידת לימוד70 דקות
ארבעת תתי־המרחבים היסודיים
שאלה 5: מה ננסה להבין?ממקמים את מרחב השורות ומרחב האפס ב־ℝⁿ, ואת מרחב העמודות ומרחב האפס השמאלי ב־ℝᵐ, עם ממדים משלימים וזוגות אורתוגונליים.
לשיעור ← - 5.6יחידת לימוד70 דקות
גרפים ורשתות
שאלה 6: מה ננסה להבין?מטריצת incidence של גרף הופכת ערכי צמתים להפרשים וזרימות למאזנים; הלפלסיאן L=AᵀA קושר חיבוריות, דרגות ושימור.
לשיעור ←
ביד, שלב אחר שלב
מטריצה אחת חושפת את כל מבנה הפתרונות
עבור A=[[2,4,6,4],[2,5,7,6],[2,3,5,2]] והיעד b=(4,3,5), מצאו את מרחב העמודות, מרחב האפס וכל פתרונות Ax=b.
- 1
אלימינציה נותנת RREF=[[1,0,1,−2],[0,1,1,2],[0,0,0,0]]. יש שני pivots בעמודות 1 ו־2, ולכן rank(A)=2.
- 2
עמודות ה־pivot של A המקורית הן a₁=(2,2,2) ו־a₂=(4,5,3). לכן C(A)=span{a₁,a₂}, כלומר המישור −2b₁+b₂+b₃=0 בתוך ℝ³.
- 3
ב־Ax=0 המשתנים x₃=s ו־x₄=t חופשיים. מן השורות מקבלים x₁=−s+2t ו־x₂=−s−2t.
- 4
לכן בסיס למרחב האפס הוא n₁=(−1,−1,1,0), n₂=(2,−2,0,1), ומתקיים dim N(A)=4−2=2.
- 5
היעד b=(4,3,5) מקיים −2·4+3+5=0, ולכן הוא ב־C(A). בחירת המשתנים החופשיים כאפס נותנת פתרון פרטי xₚ=(4,−1,0,0).
- 6
כל פתרון מתקבל מהזזת xₚ בכיוון כלשהו מ־N(A): x=(4,−1,0,0)+s n₁+t n₂. זהו מישור אפיני ב־ℝ⁴, ולא תת־מרחב כי הוא אינו עובר בראשית.
- 7
בדיקה סוגרת את המעגל: A n₁=A n₂=0 ולכן A(xₚ+s n₁+t n₂)=Axₚ=b לכל s,t.
C(A)={b∈ℝ³:−2b₁+b₂+b₃=0}, N(A)=span{n₁,n₂}, x=xₚ+s n₁+t n₂ניבוי, ניסוי והסבר
מעבדת חקר: מיעדים אפשריים לפתרון שלם, ארבעה מרחבים ורשתות
התחילו מתחנה אחת, נבאו את התוצאה ורק אז שנו פרמטר. בכל תחנה תקבלו משוב וקישור לשיעור שעליו היא בנויה.
פרק 5 · ארבע תחנות חקר
מיעדים אפשריים למרחבים יסודיים ורשתות
נבאו, בדקו והסבירו: נגישות של b, מרחב האפס והפתרון השלם, בסיס וממד, ארבעת המרחבים ולפלסיאן של גרף.
מעבדת פרק 5 מוכנה. תחנה 1 פעילה.
המעבדה בונה שפה אחת סביב Ax=b: תחילה אילו פלטים נגישים, אחר כך אילו תנועות נעלמות, משם אל בסיס וממד, ולבסוף אל רשת שבה אותם מרחבים מתארים הפרשים, מאזנים ומעגלים. בכל תחנה מנבאים לפני שמשנים לכל היותר שלושה פרמטרים.
תחנה 1 מתוך 4
אילו יעדים המטריצה יכולה לייצר?
- המטרה
- לראות את C(A) כאזור הפלט הנגיש ולתרגם b∈C(A) לעקביות של Ax=b.
- המשימה
- נבאו לפי הדרגה, בחרו מטריצה, והזיזו את b עד שתעברו מיעד נגיש ליעד שאי אפשר לייצר.
b∈C(A) ⇔ rank(A)=rank([A|b])
- dim C(A)
- 1
- rank([A|b])
- 2
- עמודות pivot
- 1
- הכרעה
- לא נגיש — אין פתרון ל־Ax=b
בסיס מתוך העמודות המקוריות: {(1, 2)}
| 1 עמודת pivot | 2 | −1 |
| 2 | 4 | −2 |
| 1 | 2 | −1 | 3 |
| 2 | 4 | −2 | 2 |
עכשיו בקוד
אותו רעיון, כאלגוריתם
הקוד קצר בכוונה: כל שורה מקבילה לצעד בחישוב הידני.
מהרעיון אל הקוד
מימוש חישובי
function rref(input, tolerance = 1e-10, scaleColumnCount) {
if (!Array.isArray(input) || input.length === 0 || !Array.isArray(input[0]) || input[0].length === 0) {
throw new TypeError("matrix must be a nonempty rectangular array");
}
const columnCount = input[0].length;
if (!input.every((row) => Array.isArray(row) && row.length === columnCount && row.every(Number.isFinite))) {
throw new TypeError("matrix entries must be finite and rows must have equal length");
}
if (!Number.isFinite(tolerance) || tolerance <= 0) {
throw new RangeError("tolerance must be positive and finite");
}
const referenceColumns = scaleColumnCount ?? columnCount;
if (!Number.isInteger(referenceColumns) || referenceColumns < 1 || referenceColumns > columnCount) {
throw new RangeError("scaleColumnCount must select existing columns");
}
const rowScales = input.map((row) =>
row.slice(0, referenceColumns)
.reduce((largest, value) => Math.max(largest, Math.abs(value)), 0) ||
row.reduce((largest, value) => Math.max(largest, Math.abs(value)), 0) || 1,
);
const R = input.map((row, index) => row.map((value) => value / rowScales[index]));
const rows = R.length, columns = R[0].length;
const pivotColumns = [];
let pivotRow = 0;
for (let column = 0; column < columns && pivotRow < rows; column += 1) {
let bestRow = pivotRow;
for (let row = pivotRow + 1; row < rows; row += 1) {
if (Math.abs(R[row][column]) > Math.abs(R[bestRow][column])) bestRow = row;
}
if (Math.abs(R[bestRow][column]) <= tolerance) continue;
[R[pivotRow], R[bestRow]] = [R[bestRow], R[pivotRow]];
const pivot = R[pivotRow][column];
R[pivotRow] = R[pivotRow].map((value) => value / pivot);
for (let row = 0; row < rows; row += 1) {
if (row === pivotRow) continue;
const factor = R[row][column];
R[row] = R[row].map((value, index) => value - factor * R[pivotRow][index]);
}
pivotColumns.push(column);
pivotRow += 1;
}
for (const row of R) {
for (let column = 0; column < columns; column += 1) {
if (Math.abs(row[column]) <= tolerance) {
row[column] = 0;
} else {
const nearestInteger = Math.round(row[column]);
if (Math.abs(row[column] - nearestInteger) <= tolerance * Math.max(1, Math.abs(row[column]))) {
row[column] = nearestInteger;
}
}
}
}
return { matrix: R, pivotColumns, numericalRank: pivotColumns.length, tolerance };
}
function analyzeMatrix(A, tolerance = 1e-10) {
const reduced = rref(A, tolerance);
const columnCount = A[0].length;
const freeColumns = Array.from({ length: columnCount }, (_, index) => index)
.filter((index) => !reduced.pivotColumns.includes(index));
const nullspaceBasis = freeColumns.map((freeColumn) => {
const vector = Array(columnCount).fill(0);
vector[freeColumn] = 1;
reduced.pivotColumns.forEach((pivotColumn, row) => {
vector[pivotColumn] = -reduced.matrix[row][freeColumn];
});
return vector;
});
const columnBasis = reduced.pivotColumns.map((column) => A.map((row) => row[column]));
return { ...reduced, freeColumns, columnBasis, nullspaceBasis };
}
function particularSolution(A, b, tolerance = 1e-10) {
if (!Array.isArray(A) || !Array.isArray(b) || b.length !== A.length || !b.every(Number.isFinite)) {
throw new TypeError("b must contain one finite value per row of A");
}
const columnCount = A[0].length;
const reduced = rref(
A.map((row, index) => [...row, b[index]]),
tolerance,
columnCount,
);
if (reduced.pivotColumns.includes(columnCount)) return null;
const solution = Array(columnCount).fill(0);
reduced.pivotColumns.forEach((column, row) => {
if (column < columnCount) solution[column] = reduced.matrix[row][columnCount];
});
return solution;
}
const A = [[2, 4, 6, 4], [2, 5, 7, 6], [2, 3, 5, 2]];
const b = [4, 3, 5];
console.log(analyzeMatrix(A));
console.log({ particular: particularSolution(A, b) });from math import isfinite
def rref(values, tolerance=1e-10, scale_column_count=None):
if (not isinstance(values, list) or not values or
not isinstance(values[0], list) or not values[0]):
raise TypeError("matrix must be a nonempty rectangular list")
column_count = len(values[0])
if (any(not isinstance(row, list) or len(row) != column_count or
any(not isinstance(value, (int, float)) or not isfinite(value) for value in row)
for row in values)):
raise TypeError("matrix entries must be finite and rows must have equal length")
if not isinstance(tolerance, (int, float)) or not isfinite(tolerance) or tolerance <= 0:
raise ValueError("tolerance must be positive and finite")
reference_columns = column_count if scale_column_count is None else scale_column_count
if (not isinstance(reference_columns, int) or
reference_columns < 1 or reference_columns > column_count):
raise ValueError("scale_column_count must select existing columns")
row_scales = [
max((abs(value) for value in row[:reference_columns]), default=0)
or max((abs(value) for value in row), default=0)
or 1
for row in values
]
R = [[value / row_scales[index] for value in row]
for index, row in enumerate(values)]
rows, columns = len(R), len(R[0])
pivot_columns = []
pivot_row = 0
for column in range(columns):
if pivot_row == rows:
break
best_row = max(range(pivot_row, rows), key=lambda row: abs(R[row][column]))
if abs(R[best_row][column]) <= tolerance:
continue
R[pivot_row], R[best_row] = R[best_row], R[pivot_row]
pivot = R[pivot_row][column]
R[pivot_row] = [value / pivot for value in R[pivot_row]]
for row in range(rows):
if row == pivot_row:
continue
factor = R[row][column]
R[row] = [value - factor * pivot_value
for value, pivot_value in zip(R[row], R[pivot_row])]
pivot_columns.append(column)
pivot_row += 1
def clean(value):
if abs(value) <= tolerance:
return 0
nearest_integer = round(value)
if abs(value - nearest_integer) <= tolerance * max(1, abs(value)):
return nearest_integer
return value
R = [[clean(value) for value in row] for row in R]
return {"matrix": R, "pivot_columns": pivot_columns,
"numerical_rank": len(pivot_columns), "tolerance": tolerance}
def analyze_matrix(A, tolerance=1e-10):
reduced = rref(A, tolerance)
column_count = len(A[0])
free_columns = [column for column in range(column_count)
if column not in reduced["pivot_columns"]]
nullspace_basis = []
for free_column in free_columns:
vector = [0] * column_count
vector[free_column] = 1
for row, pivot_column in enumerate(reduced["pivot_columns"]):
vector[pivot_column] = -reduced["matrix"][row][free_column]
nullspace_basis.append(vector)
column_basis = [[row[column] for row in A]
for column in reduced["pivot_columns"]]
return {**reduced, "free_columns": free_columns,
"column_basis": column_basis, "nullspace_basis": nullspace_basis}
def particular_solution(A, b, tolerance=1e-10):
if (not isinstance(A, list) or not isinstance(b, list) or len(b) != len(A) or
any(not isinstance(value, (int, float)) or not isfinite(value) for value in b)):
raise TypeError("b must contain one finite value per row of A")
column_count = len(A[0])
reduced = rref(
[row + [b[index]] for index, row in enumerate(A)],
tolerance,
column_count,
)
if column_count in reduced["pivot_columns"]:
return None
solution = [0] * column_count
for row, column in enumerate(reduced["pivot_columns"]):
if column < column_count:
solution[column] = reduced["matrix"][row][column_count]
return solution
A = [[2, 4, 6, 4], [2, 5, 7, 6], [2, 3, 5, 2]]
b = [4, 3, 5]
print(analyze_matrix(A))
print({"particular": particular_solution(A, b)})בדיקת שליטה מצטברת
האם כל פרק 5 יושב במקום?
שש שאלות קצרות בודקות את המסלול השלם — מיעדים אפשריים ומרחב האפס ועד בסיסים, ארבעת המרחבים ורשתות. לאחר הבדיקה תקבלו הסבר וקישור לשיעור המדויק שכדאי לחזור אליו.
מקורות להעמקה בלבד — הפרק באתר עומד בפני עצמו
מדריך המרצים: עמ׳ 131–161. הפניות מדויקות לספר מופיעות בתחתית כל שיעור.