فهرست مقاله
در بازبینیِ ماژولِ حلِ تکراری، فقط جوابِ آخر مهم نیست؛ باید بدانیم هر گام دقیقاً کدام وضعیت را خوانده است. یک جایگزینیِ ظاهراً ساده در حلقه میتواند Jacobi را به روشِ دیگری تبدیل کند. این درس با شبکهٔ مقاومتیِ ساختگی، حسابِ کسری و مرجعِ مستقل، قراردادِ بردارِ قبلی و معنای توقف را بررسی میکند. عددهای این آزمایش از مدارِ واقعی یا زمانسنجی به دست نیامدهاند.
۱. یک گام Jacobi باید فقط بردارِ قبلی را بخواند

برای دستگاه A*x=b، مؤلفهٔ تازهٔ i از تفریقِ سهمِ همهٔ مؤلفههای دیگر از b_i و تقسیم بر ضریبِ قطری ساخته میشود. در Jacobi، تمامِ آن مؤلفههای دیگر متعلق به بردارِ قبلیاند. بنابراین بردارِ تازه را جدا میسازیم و پس از کاملشدنِ تمام سطرها جانشین میکنیم. بخشِ Jacobi در کتاب Templates همین وابستگی و نیاز به نگهداشتنِ وضعیتِ قبلی را توضیح میدهد.
اگر پس از محاسبهٔ مؤلفهٔ اول، همان خانه را در بردارِ در حالِ خواندن عوض کنیم، سطرِ بعد از مقدارِ تازه استفاده میکند. این یک پیمایشِ Gauss–Seidel است. آن روش میتواند کاربردِ معتبرِ خودش را داشته باشد؛ ایراد، اعلامِ انجامِ Jacobi پس از عوضکردنِ قرارداد است. شمارِ گام و آزمونِ وابسته به همان دنباله نیز دیگر معنای قبلی را ندارند.
در شاهدِ سهگرهیِ ما، شروعِ همهٔ ولتاژها صفر است. نخستین گامِ Jacobi، نیم، صفر و صفر ولت میدهد. همان فرمولها با بهروزرسانیِ درجا، نیم، یکششم و یکدوازدهم ولت میسازند. این اختلاف از گردکردن نیست؛ همهٔ محاسبهها دقیقاند. «همزمان» اینجا به خواندنِ منطقیِ یک وضعیت اشاره دارد؛ کد رشتهٔ اجرایی یا اجرای موازی ندارد.
۲. از شبکهٔ رسانایی تا گواهِ انقباض

هر یک از سه گره با رساناییِ یک زیمنس به زمین وصل است؛ میان گرههای اول و دوم، و دوم و سوم نیز هرکدام یک زیمنس داریم. تزریقِ خارجیِ جریان به گرهِ اول یک آمپر و به دو گرهٔ دیگر صفر است. با قراردادِ تزریقِ مثبت، روشِ گره و جهتِ جریان در KCL ماتریسِ رسانایی با سطرهای دو، منفییک، صفر؛ منفییک، سه، منفییک؛ و صفر، منفییک، دو را میدهد.
A واحدِ زیمنس، b واحدِ آمپر و x واحدِ ولت دارد. مدل، مقاومتهای ایدئالِ ثابت و زمینِ معلوم فرض میکند. شمارِ تکرار، زمانِ گذرای مدار نیست. برای هر سطر، مجموعِ قدرمطلقِ درایههای بیرونِ قطر را بر درایهٔ قطریِ مثبت تقسیم میکنیم و بزرگترین نسبت را q مینامیم. q بیواحد و در این شاهد دوسوم است.
برای نگاشتِ تکرار، فاصلهٔ دو بردارِ خروجی در نرمِ بینهایت حداکثر q برابرِ فاصلهٔ ورودیهاست؛ این نرم، بیشینهٔ قدرمطلقِ مؤلفههاست. نسبتِ کمتر از یک، شرطِ کافیِ انقباض است. نتیجهٔ عمومیِ همگرایی و پیامدِ نرم در کتابِ Saad جایگاهِ چنین شرطِ کافیای را روشن میکند. وجودِ شنتهای مثبت، در همین شبکه نسبتها را کمتر از یک نگه میدارد؛ مدارِ شناور یا هر ماتریسِ دلخواه را شامل نمیشود.
۳. کد کامل با حسابِ کسری و سقفِ تکرار

رابط، ماتریسِ مربعیِ یک تا چهار سطر را به شکلِ تاپلِ دقیقِ تاپلها میپذیرد. ضرایب، جریانها و مقدارِ شروع باید دقیقاً Fraction باشند؛ قدرمطلق حداکثر ۶۴ و مخرجِ سادهشده حداکثر ۶۴ است. قطر باید مثبت باشد. این سقفها انتخابِ آموزشیاند؛ واحدهای هر آرایه باید پیش از فراخوانی مطابق قرارداد باشند و از نیتِ پنهانِ فراخواننده تشخیص داده نمیشوند.
تلورانس نیز Fraction مثبت و حداکثر یک ولت، با مخرج حداکثر یکمیلیارد است. سقفِ تکرار، int دقیقِ یک تا ۱۲۸ است؛ bool پذیرفته نیست. ورودیِ اولیهٔ حذفشده صفر در نظر گرفته میشود. q بزرگتر یا مساوی یک با پیامِ «تأییدنشده با این شرطِ کافی» رد میشود؛ برنامه آن را برچسبِ واگرایی نمیزند.
کد کامل با Python 3.14.5 و کتابخانهٔ استاندارد اجرا شده است. مسیرِ اصلی حسابِ گویا دارد؛ این، حلگرِ ممیزِ شناورِ سریع یا پیشنهادِ Cramer برای دستگاهِ بزرگ نیست. صورت و مخرج میتوانند هنگامِ تکرار رشد کنند. فایل و شبکه خوانده نمیشوند، بردارِ قبلی تغییر نمیکند و گزارش، کسرها را به صورتِ رشته نگه میدارد.
"""Exact bounded Jacobi sweeps; a teaching certificate, not float/hardware proof."""
from fractions import Fraction as F
from itertools import permutations
import json
import platform
def _fraction(value, limit=64, denominator=64):
if type(value) is not F:
raise TypeError('exact Fraction required; no implicit numeric conversion')
if abs(value) > limit or value.denominator > denominator:
raise ValueError('value outside own rational teaching bounds')
return value
def _system(matrix, rhs, initial):
if type(matrix) is not tuple or not 1 <= len(matrix) <= 4:
raise ValueError('exact tuple with1..4 rows required')
n = len(matrix)
if any(type(row) is not tuple or len(row) != n for row in matrix):
raise ValueError('exact square tuple rows required')
for row in matrix:
for value in row:
_fraction(value)
if any(matrix[i][i] <= 0 for i in range(n)):
raise ValueError('positive diagonal required by this interface')
if type(rhs) is not tuple or len(rhs) != n:
raise ValueError('exact matching rhs tuple required')
for value in rhs:
_fraction(value)
if initial is None:
initial = (F(0),) * n
if type(initial) is not tuple or len(initial) != n:
raise ValueError('exact matching initial tuple required')
for value in initial:
_fraction(value)
return matrix, rhs, initial
def _step(matrix, rhs, old):
# Internal algebra kernel: callers here already supply validated fixtures.
return tuple((rhs[i] - sum(matrix[i][j] * old[j]
for j in range(len(old)) if j != i)) / matrix[i][i]
for i in range(len(old)))
def certified_jacobi(matrix, rhs, tolerance=F(1, 1000000), max_steps=128, initial=None):
matrix, rhs, old = _system(matrix, rhs, initial)
if type(tolerance) is not F:
raise TypeError('tolerance must be exact Fraction')
if not 0 < tolerance <= 1 or tolerance.denominator > 1000000000:
raise ValueError('positive tolerance<=1 with denominator<=1e9 required')
if type(max_steps) is not int:
raise TypeError('max_steps must be exact Python int, not bool')
if not 1 <= max_steps <= 128:
raise ValueError('max_steps must be in1..128')
q = max(sum(abs(matrix[i][j]) for j in range(len(old)) if j != i)
/ matrix[i][i] for i in range(len(old)))
if q >= 1:
raise ValueError('uncertified by this sufficient row-norm test')
trace = []
for count in range(1, max_steps + 1):
new = _step(matrix, rhs, old)
difference = max(abs(a - b) for a, b in zip(new, old))
bound = q * difference / (1 - q) # q=0 also gives exact bound0.
trace.append({'voltage': new, 'step_norm_V': difference, 'error_bound_V': bound})
if bound <= tolerance:
return {'status': 'certified', 'iterations': count, 'q': q,
'solution_V': new, 'error_bound_V': bound, 'trace': tuple(trace)}
old = new
return {'status': 'iteration_limit', 'iterations': max_steps, 'q': q,
'solution_V': new, 'error_bound_V': bound, 'trace': tuple(trace)}
def determinant(matrix):
# Independent finite Leibniz determinant; no iteration, diagonal split or q.
answer = F(0)
for order in permutations(range(len(matrix))):
inversions = sum(order[i] > order[j] for i in range(len(order))
for j in range(i + 1, len(order)))
term = F((-1) ** inversions)
for row, column in enumerate(order):
term *= matrix[row][column]
answer += term
return answer
def cramer_oracle(matrix, rhs):
divisor = determinant(matrix)
if not divisor:
raise ValueError('singular oracle matrix')
return tuple(determinant(tuple(tuple(rhs[i] if j == column else matrix[i][j]
for j in range(len(matrix)))
for i in range(len(matrix)))) / divisor
for column in range(len(matrix)))
def _gs_diagnostic(matrix, rhs, initial):
# Intentional different algorithm on a private copy, not the Jacobi API.
values = list(initial)
for i in range(len(values)):
values[i] = (rhs[i] - sum(matrix[i][j] * values[j]
for j in range(len(values)) if j != i)) / matrix[i][i]
return tuple(values)
def _fails(kind, function, *args, **kwargs):
try:
function(*args, **kwargs)
except kind:
return True
return False
def run_example():
# Own3node DC model: each node has1S ground shunt; links0-1 and1-2 have1S.
matrix = tuple(tuple(F(x) for x in row) for row in ((2, -1, 0), (-1, 3, -1), (0, -1, 2)))
rhs = (F(1), F(0), F(0)) # Positive external injected current in amperes.
start = (F(0),) * 3
exact = cramer_oracle(matrix, rhs)
first = _step(matrix, rhs, start)
gs_first = _gs_diagnostic(matrix, rhs, start)
main = certified_jacobi(matrix, rhs)
actual_error = max(abs(x - y) for x, y in zip(main['solution_V'], exact))
diagonal = certified_jacobi(((F(2), F(0)), (F(0), F(4))), (F(2), F(8)),
initial=(F(-3), F(5)))
fixed = certified_jacobi(matrix, rhs, initial=exact)
zero = certified_jacobi(matrix, (F(0),) * 3)
slow_matrix = ((F(1), F(-63, 64)), (F(-63, 64), F(1)))
limited = certified_jacobi(slow_matrix, (F(1), F(0)), max_steps=1)
triangular = ((F(1), F(2)), (F(0), F(1)))
triangular_rhs = (F(1), F(1))
diagnostic_first = _step(triangular, triangular_rhs, (F(0), F(0)))
diagnostic_second = _step(triangular, triangular_rhs, diagnostic_first)
row_scales = (F(2), F(3), F(4))
scaled = certified_jacobi(tuple(tuple(x * row_scales[i] for x in row)
for i, row in enumerate(matrix)),
tuple(x * row_scales[i] for i, x in enumerate(rhs)))
order = (2, 0, 1)
reordered = certified_jacobi(tuple(tuple(matrix[i][j] for j in order) for i in order),
tuple(rhs[i] for i in order))
family_ok = True
cases = 0
for n in range(1, 5):
for seed in range(4):
off = [[F(0) if i == j else F((i + j + seed) % 3 - 1)
for j in range(n)] for i in range(n)]
a = tuple(tuple(1 + sum(abs(x) for x in off[i]) if i == j else off[i][j]
for j in range(n)) for i in range(n))
b = tuple(F((i + 1) * (seed + 2) % 7 - 3) for i in range(n))
beginning = tuple(F((-1) ** i * seed, 4) for i in range(n))
result = certified_jacobi(a, b, F(1, 100000000), initial=beginning)
reference = cramer_oracle(a, b)
family_ok &= result['status'] == 'certified'
for row in result['trace']:
error = max(abs(x - y) for x, y in zip(row['voltage'], reference))
family_ok &= error <= row['error_bound_V']
family_ok &= max(abs(x - y) for x, y in zip(result['solution_V'], reference)) <= F(1, 100000000)
cases += 1
before = (matrix, rhs, start)
immutable = certified_jacobi(matrix, rhs, initial=start)
checks = {
'first_sweep_reads_only_old_zero_vector': first == (F(1, 2), F(0), F(0)),
'in_place_sweep_is_different_Gauss_Seidel_not_Jacobi': gs_first == (F(1, 2), F(1, 6), F(1, 12))
and gs_first != first,
'three_node_hand_solution_matches_independent_dense_Cramer': exact == (F(5, 8), F(1, 4), F(1, 8))
and all(sum(matrix[i][j] * exact[j] for j in range(3)) == rhs[i] for i in range(3)),
'exact_contraction_certificate_meets_voltage_target': main['q'] == F(2, 3)
and main['status'] == 'certified' and actual_error <= main['error_bound_V'] <= F(1, 1000000),
'q_zero_diagonal_problem_finishes_in_one_exact_sweep': diagonal['q'] == 0
and diagonal['iterations'] == 1 and diagonal['solution_V'] == (F(1), F(2))
and diagonal['error_bound_V'] == 0,
'zero_rhs_and_already_fixed_initial_have_zero_certificate': zero['solution_V'] == start
and zero['error_bound_V'] == fixed['error_bound_V'] == 0 and fixed['iterations'] == 1,
'valid_contraction_iteration_limit_is_not_success': limited['status'] == 'iteration_limit'
and limited['iterations'] == 1 and limited['error_bound_V'] > F(1, 1000000),
'q_ge_one_rejection_does_not_prove_divergence': _fails(ValueError, certified_jacobi,
triangular, triangular_rhs) and diagnostic_second == (F(-1), F(1))
and diagnostic_second == cramer_oracle(triangular, triangular_rhs),
'positive_row_scaling_preserves_exact_trajectory_and_q': scaled == main,
'consistent_node_permutation_preserves_each_old_vector_sweep': all(
right['voltage'] == tuple(left['voltage'][i] for i in order)
and right['error_bound_V'] == left['error_bound_V']
for left, right in zip(main['trace'], reordered['trace']))
and len(main['trace']) == len(reordered['trace']),
'independent_exact_determinant_oracle_16_systems_each_trace_bound': bool(family_ok and cases == 16),
'exact_fraction_types_and_value_bounds_rejected': all(_fails((TypeError, ValueError), certified_jacobi,
((F(1),),), (x,)) for x in (True, 1, 1.0, '1', complex(1), F(65), F(1, 65))),
'shape_diagonal_tolerance_and_step_budget_contract_rejected': all([
_fails(ValueError, certified_jacobi, (), ()),
_fails(ValueError, certified_jacobi, ((F(1), F(0)),), (F(1),)),
_fails(ValueError, certified_jacobi, ((F(0),),), (F(1),)),
_fails(ValueError, certified_jacobi, ((F(-1),),), (F(1),)),
_fails(ValueError, certified_jacobi, ((F(1),),), (F(1),), tolerance=F(0)),
_fails(TypeError, certified_jacobi, matrix, rhs, tolerance=0.1),
_fails(ValueError, certified_jacobi, matrix, rhs, max_steps=0),
_fails(ValueError, certified_jacobi, matrix, rhs, max_steps=129),
_fails(TypeError, certified_jacobi, matrix, rhs, max_steps=True),
_fails(ValueError, certified_jacobi, matrix, rhs, initial=(F(0),)),
]),
'input_values_and_fresh_call_are_not_changed': before == (matrix, rhs, start) and immutable == main,
}
assert len(checks) == 14 and all(type(x) is bool and x for x in checks.values())
return {'python': platform.python_version(), 'dependencies': 'standard library only; exact Fraction',
'unit_contract': {'matrix': 'S', 'rhs': 'A', 'iterate_and_certificate': 'V', 'q': 'dimensionless'},
'matrix': matrix, 'rhs': rhs, 'exact_solution_V': exact,
'first_Jacobi_V': first, 'first_in_place_GS_V': gs_first,
'iterations': main['iterations'], 'q': main['q'], 'solution_V': main['solution_V'],
'actual_error_V': actual_error, 'certified_error_bound_V': main['error_bound_V'],
'limited_example': {k: limited[k] for k in ('status', 'iterations', 'q', 'error_bound_V')},
'uncertified_triangular_q': F(2), 'uncertified_two_sweeps_V': diagnostic_second,
'independent_oracle_systems': cases, 'meaningful_named_groups': 14, 'checks': checks,
'scope': 'Exact bounded rational algebra, not a floating-point error certificate, circuit transient, '
'hardware validation, convergence-rate benchmark or universal Jacobi solver.'}
def _json_fraction(value):
if type(value) is F:
return str(value)
raise TypeError('unexpected report value')
if __name__ == '__main__':
print(json.dumps(run_example(), ensure_ascii=True, indent=2, default=_json_fraction))
{
"python": "3.14.5",
"dependencies": "standard library only; exact Fraction",
"unit_contract": {
"matrix": "S",
"rhs": "A",
"iterate_and_certificate": "V",
"q": "dimensionless"
},
"matrix": [
[
"2",
"-1",
"0"
],
[
"-1",
"3",
"-1"
],
[
"0",
"-1",
"2"
]
],
"rhs": [
"1",
"0",
"0"
],
"exact_solution_V": [
"5/8",
"1/4",
"1/8"
],
"first_Jacobi_V": [
"1/2",
"0",
"0"
],
"first_in_place_GS_V": [
"1/2",
"1/6",
"1/12"
],
"iterations": 25,
"q": "2/3",
"solution_V": [
"664301/1062882",
"132860/531441",
"66430/531441"
],
"actual_error_V": "1/2125764",
"certified_error_bound_V": "1/1062882",
"limited_example": {
"status": "iteration_limit",
"iterations": 1,
"q": "63/64",
"error_bound_V": "63"
},
"uncertified_triangular_q": "2",
"uncertified_two_sweeps_V": [
"-1",
"1"
],
"independent_oracle_systems": 16,
"meaningful_named_groups": 14,
"checks": {
"first_sweep_reads_only_old_zero_vector": true,
"in_place_sweep_is_different_Gauss_Seidel_not_Jacobi": true,
"three_node_hand_solution_matches_independent_dense_Cramer": true,
"exact_contraction_certificate_meets_voltage_target": true,
"q_zero_diagonal_problem_finishes_in_one_exact_sweep": true,
"zero_rhs_and_already_fixed_initial_have_zero_certificate": true,
"valid_contraction_iteration_limit_is_not_success": true,
"q_ge_one_rejection_does_not_prove_divergence": true,
"positive_row_scaling_preserves_exact_trajectory_and_q": true,
"consistent_node_permutation_preserves_each_old_vector_sweep": true,
"independent_exact_determinant_oracle_16_systems_each_trace_bound": true,
"exact_fraction_types_and_value_bounds_rejected": true,
"shape_diagonal_tolerance_and_step_budget_contract_rejected": true,
"input_values_and_fresh_call_are_not_changed": true
},
"scope": "Exact bounded rational algebra, not a floating-point error certificate, circuit transient, hardware validation, convergence-rate benchmark or universal Jacobi solver."
}
۴. کرانِ توقف را با مرجعِ مستقل بسنجیم

نرمِ اختلافِ دو بردارِ پیاپی را Δ و نرمِ خطای بردارِ تازه نسبت به جوابِ دقیق را E بنامیم. از رابطهٔ ثابتِ تکرار و نامساویِ نرم، E حداکثر q ضربدر مجموعِ E و Δ است. چون q کمتر از یک است، جابهجاییِ جملهها کرانِ q*Δ/(۱−q) را میدهد. این استنتاجِ کوتاه برای همان نگاشتِ دقیق است؛ واحدِ کران، ولت خواهد بود.
توقف وقتی مجاز است که همین کران از تلورانس بیشتر نباشد. صرفاً کوچک بودنِ تغییرِ دو گام، بدونِ ضریب و پیششرط، معیارِ ما نیست. همچنین باقیمانده و حساسیتِ جوابِ دستگاه خطی مسئلهٔ دیگری را بررسی میکند؛ اینجا شرطِ مستقلی دربارهٔ انقباضِ نگاشت داریم و آن بحث را جای گواهِ حاضر نمیگذاریم.
جوابِ دستیِ شبکه، پنجهشتم، یکچهارم و یکهشتم ولت است. مرجع، دترمینانِ متراکم را با جمعِ جایگشتها و فرمولِ Cramer حساب میکند؛ گامِ Jacobi، q یا حلقهٔ توقف را فراخوانی نمیکند. کد، همین جواب را مستقل تأیید میکند و در یک خانوادهٔ شانزدهدستگاهی، کرانِ تمام گامهای ثبتشده را با خطای دقیق میسنجد. همهٔ این خانواده یک گروهِ بررسی است.
اجرای شاهد در ۲۵ گام به هدفِ یکمیکروولت رسید. کرانِ گزارششده یک بر ۱۰۶۲۸۸۲ ولت و خطای واقعیِ معلومِ همین شاهد یک بر ۲۱۲۵۷۶۴ ولت است. این تلورانس، دقتِ ابزار نیست. در نمونهٔ معتبرِ دیگری با q برابر ۶۳/۶۴ و بودجهٔ یک گام، کران ۶۳ ولت باقی میماند و status برابر iteration_limit است؛ پایانِ بودجه، موفقیت یا اثباتِ واگراییِ ریاضی نیست.
۵. چهار پرسش دربارهٔ قرارداد و همگرایی

پرسش اول: اگر Gauss–Seidel جوابِ قابلقبول بدهد، اختلافِ گام مهم است؟ پاسخ: بله؛ آزمونِ ما هویتِ الگوریتم و وابستگیِ هر گام را نیز حفاظت میکند. نخستین بردارِ متفاوت، قراردادِ Jacobi را نقض کرده است. انتخابِ روشِ دیگر باید صریح باشد و معیارِ توقفِ مناسبِ همان روش نیز جدا بررسی شود.
پرسش دوم: q بزرگتر از یک، واگرایی را ثابت میکند؟ پاسخ: خیر. شاهدِ جبریِ جدا، سطرهای یک، دو و صفر، یک دارد و q آن دو است؛ با طرفِ راستِ یک، یک، دو گام از صفر به منفییک، یک میرسند. این ماتریس، مدلِ شبکهٔ پسیوِ ما معرفی نمیشود. رابط آن را به علتِ نبودِ گواهِ انتخابی رد میکند.
پرسش سوم: در q صفر چه میشود؟ پاسخ: خانههای بیرونِ قطر صفرند و جواب در یک پیمایش ساخته میشود. مخرجِ یکمنهایq برابر یک است و کران صفر خواهد بود. آزمونِ قطری و شروعِ ازپیشدرست، این مرزها را جدا میسنجند؛ تابع همیشه حداقل یک گام انجام میدهد.
پرسش چهارم: میتوان همین گواه را به float یا سختافزار منتقل کرد؟ پاسخ: این کد چنین نتیجهای نمیدهد. گردکردنِ هر گام و محاسبهٔ q، یا خطای مدلِ قطعه، استدلالِ دقیق را تغییر میدهد. چهارده گروه، از جمله جابهجاییِ سازگارِ گرهها و مقیاسِ مثبتِ سطرها، موفقاند؛ اندازهگیریِ سرعت یا صحتِ مدارِ واقعی نداریم. منابع در ۲۷ مهر ۱۴۰۵ خوانده شدند و تصویرهای همراه مفهومیاند.
منابع و مطالعهٔ بیشتر
- Templates، ویرایش دوم؛ Jacobi و Gauss–Seidel، بخشهای۲٫۲٫۱ و۲٫۲٫۲
- Yousef Saad، ویرایش دوم؛ همگراییِ عمومی و شرطِ کافیِ نرم
بررسی منابع: ۲۷ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

