فهرست مقاله
حل یک مدار با تعداد بیشتری گره، الزاماً به معنی نگهداری یک ماتریس بزرگ و پر از صفر نیست. اگر هر گره فقط با همسایههای نزدیک و زمین ارتباط داشته باشد، ساختار معادلهها فرصت دیگری میدهد. در این درس یک نردبان مقاومتیِ تألیفی را با حذف Thomas حل میکنیم و نتیجه را با محاسبهٔ کسریِ متراکم میسنجیم. مثال، مدل ایدئال و دادهٔ ساختگی دارد؛ هیچ مدار، ابزار اندازهگیری یا زمان اجرای سختافزاری آزموده نشده است.
۱. نردبانِ مقاومتی و سه قطرِ دستگاه

منبع ولتاژ نسبت به زمین معلوم است. مقاومت سری نخست از منبع به گره صفر میرود؛ هر مقاومت سری بعدی، گره قبلی را به گره بعد وصل میکند. از هر گره نیز یک مقاومت شنت به همان زمین داریم. پس برای n گره مجهول، n مقاومت سری و n مقاومت شنت لازم است. در انتهای راست، مقاومت سریِ دیگری به ترمینال معلوم یا گره اضافه نداریم. همین جزئیات، سطر آخر را از سطرهای داخلی متفاوت میکند.
روش گره در جزوهٔ MIT، انتخاب زمین و جایگذاری قانون عنصر در KCL را توضیح میدهد. برای مدار خودمان، رسانایی سری را g و رسانایی شنت را h مینامیم؛ هر دو معکوس مقاومت و برحسب زیمنساند. قطر اصلیِ سطر i مجموع g همان گره، h همان گره و g مسیر بعدی است؛ جملهٔ مسیر بعدی در آخرین سطر وجود ندارد. دو قطر مجاور، منفی رساناییِ اتصال مشترکاند.
طرف راست فقط در سطر نخست ناصفر است: حاصلضرب ولتاژ منبع در رسانایی سری نخست. بقیهٔ سطرها تزریق مستقل ندارند. ضرب ماتریس رسانایی در بردار ولتاژ، جریان با واحد آمپر میدهد. یک اشتباه در شاخص مقاومت بعدی میتواند خروجی ظاهراً معقول بسازد؛ بنابراین مرجع آزمون، ماتریس را از اتصالهای فیزیکی مهر میزند، نه از همین سه آرایهٔ مسیر اصلی.
۲. حذفِ رو به جلو و دلیلِ محورِ مثبت

بخش ۲٫۱ مقالهٔ Laszló، Giles و Appleyard، Thomas را حذف گاوسیِ مخصوص دستگاه سهقطری معرفی میکند: حرکت رو به جلو، سپس جایگذاری از انتها. پیادهسازی آموزشی ما قطر اصلی و طرف راست را در کپیهای جدا اصلاح میکند. برای سطر i، ضریب حذف برابر عضو زیرقطر تقسیم بر محور قبلی است؛ اثر آن از محور جاری و طرف راست کم میشود. اینجا جابهجایی سطر نداریم.
نبود pivoting را نباید برای هر ماتریس سهقطری مجاز دانست. در نردبان حاضر، تمام مقاومتهای شنت و سری مثبتاند. برای بیش از یک گره، محور نخست از رسانایی اتصال به گره بعد بزرگتر است. اگر محور قبلی از رسانایی اتصال جاری بزرگتر باشد، کسرِ مربع آن رسانایی بر محور قبلی، از خود رسانایی کوچکتر میشود؛ پس محور جدید از مجموع شنت و رسانایی مسیر بعدی بزرگتر خواهد بود. استقرا تا آخر، مثبت بودن محورها را در حساب دقیق نشان میدهد.
برای یک گره هم محور فقط مجموع دو رسانایی مثبت است. این استدلال به توپولوژی و شنتهای همین قرارداد وابسته است؛ اتصال شناور، مقاومت منفی یا ماتریس دلخواه را شامل نمیشود. در حساب شناور، کد همچنان مثبت و متناهی بودن محورهای محاسبهشده را بررسی میکند. این بررسی یک مسیر خطای روشن میسازد؛ اثباتِ گردکردن درستِ تمام جوابها یا کران عمومیِ حساسیت نیست.
برای امتداد همین حلقهها به n بزرگتر، تعداد مراحل و آرایههای مسیر اصلی با n رشد خطی دارد. سه قطر فقط سهبرابر n منهای دو جایگاه ضریب میخواهند؛ بردارها و کپیهای موقت جدا هستند و آنها هم رشد خطی دارند. شمار جایگاه، اندازهٔ واقعی حافظهٔ اشیای پایتون نیست. همچنین تخمینهای FMA و پهنایباند مقالهٔ مرجع را به برنامهٔ پایتون یا رایانهٔ خواننده منتقل نمیکنیم.
۳. کد کاملِ حل و قراردادِ ورودی

دو مجموعهٔ مقاومت باید list یا tuple دقیق، همطول و دارای یک تا سیودو عضو باشند. مقاومت فقط int دقیق بین یک و یکمیلیون اهم است؛ bool، عدد اعشاری و رشته پذیرفته نمیشوند. ولتاژ منبع int یا float دقیق، متناهی و بین منفی صد تا مثبت صد ولت است. همهٔ انتهاها بستهاند. این دامنه انتخاب آموزشیِ محاسبه است و دربارهٔ توان مجاز قطعه یا ایمنی مدار چیزی نمیگوید.
فایل زیر با Python 3.14.5 و کتابخانهٔ استاندارد اجرا شده است. برای اجرای مستقل، تمام فایل را ذخیره و با پایتون اجرا کنید؛ دادهٔ بیرونی لازم نیست. ورودیها تغییر نمیکنند و خروجی، ولتاژها، محورهای اصلاحشده و شمار عملیات انتخابشده را برمیگرداند. تابع اصلی برای همین نردبان است؛ یک حلگر عمومیِ سهقطری با ورودی سه آرایهٔ دلخواه ارائه نشده است.
"""Bounded passive DC ladder; stdlib only, no hardware or timing benchmark."""
from fractions import Fraction
import json
import math
import platform
def validate(series_ohm, shunt_ohm, source_V):
if type(series_ohm) not in (list, tuple) or type(shunt_ohm) not in (list, tuple):
raise TypeError("resistance collections must be list or tuple")
if not 1 <= len(series_ohm) <= 32 or len(shunt_ohm) != len(series_ohm):
raise ValueError("equal resistance lengths in 1..32 required")
for resistance in tuple(series_ohm) + tuple(shunt_ohm):
if type(resistance) is not int:
raise TypeError("resistances must be exact int, not bool")
if not 1 <= resistance <= 1_000_000:
raise ValueError("resistance outside 1..1000000 ohm")
if type(source_V) not in (int, float):
raise TypeError("source must be an exact int or float")
if not -100 <= source_V <= 100 or not math.isfinite(source_V):
raise ValueError("source must be finite in -100..100 V")
return tuple(series_ohm), tuple(shunt_ohm), float(source_V)
def solve_ladder(series_ohm, shunt_ohm, source_V):
series, shunt, source = validate(series_ohm, shunt_ohm, source_V)
n = len(series)
g = [1.0 / r for r in series]
h = [1.0 / r for r in shunt]
diagonal = [g[i] + h[i] + (g[i + 1] if i + 1 < n else 0.0)
for i in range(n)]
upper = [-g[i] for i in range(1, n)]
lower = upper.copy()
rhs = [source * g[0]] + [0.0] * (n - 1)
pivots = diagonal.copy()
counts = {"divisions": 1, "multiplications": 0, "subtractions": 0}
for i in range(1, n):
if not math.isfinite(pivots[i - 1]) or pivots[i - 1] <= 0:
raise ArithmeticError("nonpositive or nonfinite computed pivot")
multiplier = lower[i - 1] / pivots[i - 1]
pivots[i] -= multiplier * upper[i - 1]
rhs[i] -= multiplier * rhs[i - 1]
counts["divisions"] += 1
counts["multiplications"] += 2
counts["subtractions"] += 2
if not all(math.isfinite(p) and p > 0 for p in pivots):
raise ArithmeticError("nonpositive or nonfinite computed pivot")
voltage = [0.0] * n
voltage[-1] = rhs[-1] / pivots[-1]
for i in range(n - 2, -1, -1):
voltage[i] = (rhs[i] - upper[i] * voltage[i + 1]) / pivots[i]
for operation in counts:
counts[operation] += 1
if not all(math.isfinite(v) for v in voltage):
raise ArithmeticError("nonfinite solution")
# Counts concern these two elimination/substitution loops only. They omit
# building conductances, validation, diagnostics and the reference tests.
return {"voltages_V": tuple(voltage), "pivots_S": tuple(pivots),
"solver_counts": counts,
"coefficient_slots_three_diagonals": 3 * n - 2}
def dense_fraction_oracle(series, shunt, source):
"""Stamp physical edges into a dense matrix; no Thomas coefficients reused."""
n = len(series)
a = [[Fraction(0) for _ in range(n)] for _ in range(n)]
b = [Fraction(0) for _ in range(n)]
for right, resistance in enumerate(series):
conductance = Fraction(1, resistance)
a[right][right] += conductance
if right == 0:
b[right] += conductance * Fraction(source)
else:
left = right - 1
a[left][left] += conductance
a[left][right] -= conductance
a[right][left] -= conductance
for i, resistance in enumerate(shunt):
a[i][i] += Fraction(1, resistance)
# Dense Gauss-Jordan with row pivot selection, different storage and sweep.
for column in range(n):
pivot = max(range(column, n), key=lambda row: abs(a[row][column]))
if not a[pivot][column]:
raise ArithmeticError("singular oracle matrix")
a[column], a[pivot] = a[pivot], a[column]
b[column], b[pivot] = b[pivot], b[column]
divisor = a[column][column]
a[column] = [value / divisor for value in a[column]]
b[column] /= divisor
for row in range(n):
if row != column:
factor = a[row][column]
a[row] = [x - factor * y for x, y in zip(a[row], a[column])]
b[row] -= factor * b[column]
return tuple(b)
def circuit_accounting(series, shunt, source, voltage):
# Positive series current flows from the source toward the last node;
# positive shunt current flows from a node to ground.
previous = (source,) + tuple(voltage[:-1])
current = [(left - right) / r for left, right, r in zip(previous, voltage, series)]
ground = [v / r for v, r in zip(voltage, shunt)]
residual = [current[i] - ground[i] - (current[i + 1] if i + 1 < len(current) else 0)
for i in range(len(current))]
dissipation = sum(i * i * r for i, r in zip(current, series))
dissipation += sum(i * i * r for i, r in zip(ground, shunt))
return {"series_currents_A": current, "shunt_currents_A": ground,
"KCL_residuals_A": residual, "resistor_power_W": dissipation,
"source_supplied_power_W": source * current[0]}
def close(a, b):
return math.isclose(a, float(b), rel_tol=1e-10, abs_tol=1e-11)
def rejects(exception, function, *args):
try:
function(*args)
except exception:
return True
return False
def run():
checks = {}
series = [1000] * 3
shunt = [1000] * 3
own = solve_ladder(series, shunt, 13)
accounting = circuit_accounting(series, shunt, 13.0, own["voltages_V"])
checks["three_node_hand_solution"] = all(close(a, b) for a, b in zip(own["voltages_V"], [5, 2, 1]))
checks["one_node_divider_and_empty_offdiagonal"] = close(solve_ladder([1000], [2000], 12)["voltages_V"][0], 8)
errors = []
fixtures = []
for n in range(1, 13):
for seed in range(6):
s = [1 + ((i + 3) * (seed + 7) * 137) % 1_000_000 for i in range(n)]
h = [1 + ((i + 5) * (seed + 11) * 239) % 1_000_000 for i in range(n)]
source = (-1 if seed % 2 else 1) * (seed + 1) / 8
fixtures.append((s, h, source))
for s, h, source in fixtures:
actual = solve_ladder(s, h, source)["voltages_V"]
expected = dense_fraction_oracle(s, h, source)
assert all(close(a, b) for a, b in zip(actual, expected))
errors.extend(abs(a - float(b)) for a, b in zip(actual, expected))
checks["dense_fraction_reference_72_ladders"] = True
exact = dense_fraction_oracle(series, shunt, 13)
exact_accounts = circuit_accounting(series, shunt, Fraction(13), exact)
checks["exact_KCL_and_power_independent_fixture"] = (all(x == 0 for x in exact_accounts["KCL_residuals_A"])
and exact_accounts["resistor_power_W"] == exact_accounts["source_supplied_power_W"] == Fraction(13, 125))
accounts = [circuit_accounting(s, h, source, solve_ladder(s, h, source)["voltages_V"])
for s, h, source in fixtures]
checks["floating_KCL_and_power_grid"] = all(
max(abs(x) for x in a["KCL_residuals_A"]) < 1e-10
and close(a["resistor_power_W"], a["source_supplied_power_W"])
for a in accounts)
checks["source_sign_linearity_and_zero"] = (
all(close(a, -b) for a, b in zip(solve_ladder(series, shunt, -13)["voltages_V"], own["voltages_V"]))
and solve_ladder(series, shunt, 0)["voltages_V"] == (0.0, 0.0, 0.0))
checks["uniform_resistance_scaling"] = all(close(a, b) for a, b in zip(
solve_ladder([10 * r for r in series], [10 * r for r in shunt], 13)["voltages_V"], own["voltages_V"]))
boundary = solve_ladder([1 if i % 2 else 1_000_000 for i in range(32)], [1_000_000 if i % 2 else 1 for i in range(32)], 100)
boundary_ref = dense_fraction_oracle([1 if i % 2 else 1_000_000 for i in range(32)], [1_000_000 if i % 2 else 1 for i in range(32)], 100)
checks["32_node_mixed_boundaries_and_positive_pivots"] = all(close(a, b) for a, b in zip(boundary["voltages_V"], boundary_ref)) and all(p > 0 for p in boundary["pivots_S"])
checks["maximum_principle_on_fixtures"] = all(all(min(0, source) - 1e-11 <= v <= max(0, source) + 1e-11 for v in solve_ladder(s, h, source)["voltages_V"]) for s, h, source in fixtures)
checks["linear_loop_arithmetic_counts"] = all(solve_ladder([1000] * n, [1000] * n, 1)["solver_counts"] == {"divisions": 2 * n - 1, "multiplications": 3 * n - 3, "subtractions": 3 * n - 3} for n in [1, 2, 8, 32])
checks["collection_shape_rejection"] = all(rejects(ValueError, solve_ladder, s, h, 1) for s, h in [([], []), ([1], [1, 2]), ([1] * 33, [1] * 33)]) and rejects(TypeError, solve_ladder, iter([1]), [1], 1)
checks["resistance_type_and_range_rejection"] = all(rejects(TypeError, solve_ladder, [x], [1], 1) for x in [True, 1.0, "1", Fraction(1)]) and all(rejects(ValueError, solve_ladder, [x], [1], 1) for x in [0, -1, 1_000_001])
checks["source_type_finite_and_range_rejection"] = all(rejects(TypeError, solve_ladder, [1], [1], x) for x in [True, "1", Fraction(1), 1j]) and all(rejects(ValueError, solve_ladder, [1], [1], x) for x in [float("nan"), float("inf"), -101, 10 ** 1000])
before = (series.copy(), shunt.copy())
solve_ladder(series, shunt, 13)
checks["inputs_unchanged_and_tuple_parity"] = (series, shunt) == before and solve_ladder(tuple(series), tuple(shunt), 13) == own
assert len(checks) == 14 and all(checks.values())
return {"python": platform.python_version(), "checks": checks,
"own_example": own, "own_accounting": accounting,
"dense_reference_ladders_one_group": len(fixtures),
"maximum_grid_voltage_difference_V": max(errors),
"exact_fixture_power_W": str(exact_accounts["resistor_power_W"]),
"scope": "Ideal passive DC ladder only; counts are loop arithmetic, not runtime, hardware accuracy or a general tridiagonal stability bound."}
if __name__ == "__main__":
print(json.dumps(run(), ensure_ascii=True, indent=2))
شمارندهها فقط تقسیم، ضرب و تفریقِ حذف و جایگذاری را دنبال میکنند. برای n، بهترتیب دوبرابر n منهای یک تقسیم و سهبرابر n منهای سه ضرب و تفریق داریم. ساخت رسانایی، اعتبارسنجی، گزارش مدار، مدیریت اشیا و آزمون مرجع در این شمار نیستند. بنابراین عدد هفده برای مجموع عملیاتِ انتخابشدهٔ نمونهٔ سهگرهی، زمان اجرا، تعداد دستور پردازنده یا مقایسهٔ سرعت با یک کتابخانه محسوب نمیشود.
۴. مرجعِ متراکم، ترازِ جریان و توان

همهٔ مقاومتها را هزار اهم و منبع را سیزده ولت انتخاب میکنیم. معادلههای مقیاسشده برابر سهبرابر ولتاژ اول منهای دوم، برابر سیزده؛ منفی اول بهاضافهٔ سهبرابر دوم منهای سوم، برابر صفر؛ و منفی دوم بهاضافهٔ دوبرابر سوم، برابر صفرند. جواب کسری دقیق، پنج، دو و یک ولت است. خروجی شناورِ گره آخر حدود ۰٫۹۹۹۹۹۹۹۹۹۹۹۹۹۹۹۹ شد؛ آن را عین عدد صحیح چاپ نمیکنیم.
جریان سری در جهت منبع به انتهای نردبان، هشت، سه و یک میلیآمپر است. جریان شنتها پنج، دو و یک میلیآمپر میشود؛ اختلاف جریانهای ورودی و خروجی هر گره صفرِ دقیقِ مرجع است. با رابطهٔ توان مقاومت در OpenStax، مجموع اتلافها برابر ۱۰۴ میلیوات است؛ منبع نیز سیزده ولت ضربدر هشت میلیآمپر توان میدهد. این تراز، شاهد مکملِ مدل است، نه اندازهگیری گرمایش.
مرجع متراکم، هر اتصال را با Fraction در ماتریس کامل درج میکند و حذف گاوس–ژوردان با انتخاب سطر انجام میدهد. سه قطر یا محورهای Thomas را بازاستفاده نمیکند. طبق مستندات حساب گویا، کسرِ ساختهشده از float همان مقدار دودویی را نگه میدارد؛ در مجموعهٔ مرجع، منبعها مضرب یکهشتماند. این مرجع سریعترین راه حل نیست و هزینهٔ حساب کسری به اندازهٔ اعداد نیز وابسته است.
{
"python": "3.14.5",
"checks": {
"three_node_hand_solution": true,
"one_node_divider_and_empty_offdiagonal": true,
"dense_fraction_reference_72_ladders": true,
"exact_KCL_and_power_independent_fixture": true,
"floating_KCL_and_power_grid": true,
"source_sign_linearity_and_zero": true,
"uniform_resistance_scaling": true,
"32_node_mixed_boundaries_and_positive_pivots": true,
"maximum_principle_on_fixtures": true,
"linear_loop_arithmetic_counts": true,
"collection_shape_rejection": true,
"resistance_type_and_range_rejection": true,
"source_type_finite_and_range_rejection": true,
"inputs_unchanged_and_tuple_parity": true
},
"own_example": {
"voltages_V": [
5.0,
2.0,
0.9999999999999999
],
"pivots_S": [
0.003,
0.0026666666666666666,
0.0016250000000000001
],
"solver_counts": {
"divisions": 5,
"multiplications": 6,
"subtractions": 6
},
"coefficient_slots_three_diagonals": 7
},
"own_accounting": {
"series_currents_A": [
0.008,
0.003,
0.001
],
"shunt_currents_A": [
0.005,
0.002,
0.0009999999999999998
],
"KCL_residuals_A": [
0.0,
0.0,
2.168404344971009e-19
],
"resistor_power_W": 0.104,
"source_supplied_power_W": 0.10400000000000001
},
"dense_reference_ladders_one_group": 72,
"maximum_grid_voltage_difference_V": 1.1102230246251565e-16,
"exact_fixture_power_W": "13/125",
"scope": "Ideal passive DC ladder only; counts are loop arithmetic, not runtime, hardware accuracy or a general tridiagonal stability bound."
}
چهارده گروه نامدار اجرا شدهاند. مقایسهٔ هفتادودو نردبان، فقط یک گروه است؛ بیشترین اختلاف ولتاژ آنها حدود ۱٫۱۱ ضربدر ده به توان منفی شانزده ولت بود. مقایسهٔ عمومی، تلورانس مطلق ده به توان منفی یازده ولت و نسبی ده به توان منفی ده دارد. مرز سیودو گره، علامت منبع، مقیاس مشترک مقاومت، حفظ ورودی و رد دادهٔ نامعتبر جدا بررسی میشوند. مدار دوگرهی توپولوژی متفاوتی دارد و درس حساسیت دستگاه خطی توضیح میدهد چرا باقیمانده بهتنهایی کافی نیست.
۵. چهار پرسش دربارهٔ مقیاس و مرزِ مدل

پرسش اول: چرا قطر سطر آخر یک رسانایی کمتر دارد؟ پاسخ: پس از آخرین گره، اتصال سری دیگری تعریف نشده است. افزودن آن رسانایی بدون اتصال واقعی، دستگاه و جواب را تغییر میدهد؛ در نمونهٔ سهگرهی، ضریب آخر دو است، نه سه.
پرسش دوم: دهبرابر شدن همهٔ مقاومتها چه میکند؟ پاسخ: برای همان منبع ایدئال، ولتاژ گرهها ثابت میماند؛ جریانها و توان کل یکدهم میشوند. کد ثابتماندن ولتاژ را میسنجد و این نتیجه به مدل خطیِ ثابت وابسته است.
پرسش سوم: مثبت بودن محور، تمام خطاهای عددی را حذف میکند؟ پاسخ: خیر؛ فقط امکان حذفِ این مدل را در حساب دقیق پشتیبانی میکند. محاسبهٔ شناور هنوز گرد میشود و اختلاف جواب با مرجع باید با تلورانس اعلامشده بررسی شود.
پرسش چهارم: آیا شمار عملیات خطی، برتری زمانی را ثابت میکند؟ پاسخ: خیر؛ زمانسنجی انجام نشده و سربار پایتون، اندازهٔ مسئله و پیادهسازی مرجع اثر دارند. منابع در ۲۶ مهر ۱۴۰۵ خوانده شدند؛ مقاله ژوئن ۲۰۱۶، آرشیو MIT بهار ۲۰۰۷ با سرصفحهٔ پاییز ۲۰۰۰، کتاب OpenStax سال ۲۰۱۶ و مستندات پایتون نسخهٔ 3.14.8، از اجرای 3.14.5 جدا ثبت شدهاند.
منابع و مطالعهٔ بیشتر
- مقالهٔ نویسندگان؛ بخش کامل Thomas، حرکت رو به جلو و جایگذاری
- MIT 6.002؛ روش گره، زمین و دستگاه رسانایی
- OpenStax؛ قانون اهم و توانِ مقاومت
- Python3.14؛ مقدار دقیقِ Fraction و تبدیل float
بررسی منابع: ۲۶ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

