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

۱. جهت ولتاژ و منظور از بقای بار را روشن کنیم

صفحه‌های متناظر دو خازن و مرجع مشترکِ قرارداد ولتاژ
تصویر مفهومی: صفحه‌های متناظر دو خازن و مرجع مشترکِ قرارداد ولتاژ؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

هر خازن خطیِ ایدئال دو صفحه دارد. در قرارداد ما صفحهٔ اول و دوم را برای هر دو قطعه یکسان برچسب‌گذاری ذهنی می‌کنیم و ولتاژ را پتانسیل صفحهٔ اول منهای دوم می‌گیریم. ظرفیت همیشه مثبت است؛ ولتاژ می‌تواند مثبت، منفی یا صفر باشد. رابطهٔ q=Cv، بار علامت‌دارِ صفحهٔ اول را می‌دهد؛ صفحهٔ دوم بار مخالف دارد. پس خودِ هر خازن در این مدل خنثی است.

بخش ظرفیت و اختلاف پتانسیل در OpenStax، رابطهٔ پایه را با اندازهٔ بار و ولتاژ شرح می‌دهد. علامت‌گذاریِ صفحات در این مثال، قراردادِ صریحِ خودمان است. ولتاژ منفی یعنی جهت اختلاف پتانسیل با مرجع انتخابی مخالف است؛ معنی آن ظرفیت منفی یا دستور اتصال معکوسِ قطعهٔ قطب‌دار نیست.

فرض می‌کنیم منبع خارجی جداست و صفحات متناظر به هم می‌پیوندند: اول به اول و دوم به دوم. کمیت Q=C1V1+C2V2 بارِ مجموعِ گروهِ صفحات اول است؛ همین گروه از بیرون بار نمی‌گیرد. Q را بار خالصِ همهٔ صفحات یا جریان نمی‌نامیم. بار گروه دوم، منفیِ آن است و بار خالصِ کل مجموعه در مدل صفر می‌ماند؛ ممکن است Q نیز صفر شود.

۲. جواب نهایی، میانگین وزن‌دار ظرفیت‌هاست

اتصال مفهومی صفحه‌های متناظر برای حالت نهایی با ولتاژ مشترک
تصویر مفهومی: اتصال مفهومی صفحه‌های متناظر برای حالت نهایی با ولتاژ مشترک؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

در حالت نهاییِ فرض‌شده، هر دو خازن اختلاف پتانسیل مشترک Vf دارند. روابط ترکیب موازی خازن‌ها، ولتاژ مشترک و جمع ظرفیت‌ها را توضیح می‌دهد. با بقای بارِ گروهِ تعریف‌شده، (C1+C2)Vf=Q و بنابراین Vf=(C1V1+C2V2)/(C1+C2) می‌شود. میانگین ساده فقط وقتی ظرفیت‌ها برابرند این پاسخ را می‌دهد.

نمونهٔ مستقل ما خازن یک‌میکروفارادی با دوازده ولت و خازن سه‌میکروفارادی با صفر ولت است. بار گروه اول، دوازده میکروکولن و ظرفیت مجموع، چهار میکروفاراد است؛ پس ولتاژ نهایی سه ولت می‌شود. میانگین سادهٔ دوازده و صفر، شش ولت است و برای این مسئله جواب نادرستی خواهد بود. با ظرفیت مثبت، جواب دقیق بین دو ولتاژ اولیه قرار می‌گیرد.

این حالت نهایی را به‌عنوان فرضِ مدل می‌پذیریم. سیم کاملاً بی‌اتلاف و اتصال آنی را به گذرای قابل‌تحقق تبدیل نکرده‌ایم؛ مسیر بازتوزیع بار، مقاومت، اندوکتانس و تابش تعیین نشده‌اند. بنابراین نه زمان نشست داریم، نه جریان اوج و نه سازوکار مشخصِ تبدیل انرژی. برای حل آن پرسش‌ها باید مدل دینامیکیِ مناسب نوشت؛ از یک جواب نهایی، شکل موج استخراج نمی‌شود.

۳. انرژی ذخیره‌شده را با بارِ علامت‌دار اشتباه نگیریم

بار علامت‌دار در برابر انرژی ذخیره‌شدهٔ نامنفی با دو سینی متفاوت
تصویر مفهومی: بار علامت‌دار در برابر انرژی ذخیره‌شدهٔ نامنفی با دو سینی متفاوت؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

طبق رابطهٔ انرژی خازن، انرژی الکترواستاتیکیِ هر قطعه CV²/2 است. با ظرفیت برحسب فاراد و ولتاژ برحسب ولت، نتیجه ژول می‌شود. علامت ولتاژ در مربع حذف می‌شود؛ در نتیجه بارِ دو خازن ممکن است در گروه صفحات اول یکدیگر را خنثی کند، ولی انرژی‌های اولیهٔ مثبت با هم خنثی نمی‌شوند.

برای نمونهٔ دوازده‌ولتی، انرژی اولیه ۷۲ میکروژول و انرژی نهایی ۱۸ میکروژول است؛ کاهشِ ذخیرهٔ الکترواستاتیکی ۵۴ میکروژول می‌شود. این کاهش، نقض بقای انرژی کل نیست. برنامه فقط انرژی ذخیره‌شده در خازن‌ها را مقایسه می‌کند؛ مقدار انرژی در بقیهٔ مسیر و سازوکار انتقال آن را محاسبه نکرده است. همچنین ادعا نمی‌کنیم همهٔ کاهش الزاماً در یک مقاومتِ مشخص به گرما تبدیل شده است.

با جای‌گذاری ولتاژ نهایی و ساده‌سازی، کاهش برابر است با نصفِ C1C2/(C1+C2) ضرب‌در مربعِ V1−V2. این شکل برای ظرفیت‌های مثبت نامنفی است و برای ولتاژهای برابر صفر می‌شود. اگر ظرفیت‌ها را با هم دوبرابر کنیم، ولتاژ نهایی ثابت و بار و انرژی دوبرابر می‌شوند؛ دوبرابرکردن هر دو ولتاژ، انرژی را چهاربرابر می‌کند. این‌ها خاصیت‌های محدودِ همین مدل‌اند.

۴. کد کامل، افت کوچک و مرجع کسری مستقل

مقایسهٔ افت انرژی کوچک با مرجع کسری و دو مسیر محاسبه
تصویر مفهومی: مقایسهٔ افت انرژی کوچک با مرجع کسری و دو مسیر محاسبه؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

کد فقط int یا float دقیقِ متناهی می‌پذیرد؛ bool و رشته تبدیل نمی‌شوند. هر ظرفیت بین ده به توان منفی دوازده و یک فاراد است. ولتاژ صفر یا علامت‌دار با اندازهٔ بین ده به توان منفی نه و صد ولت مجاز است. واحدهای ورودی باید دقیقاً F و V باشند؛ میکروفاراد را باید پیش از فراخوانی به فاراد تبدیل کرد. این کران‌ها دامنهٔ آموزشی‌اند، نه مشخصات یا توصیهٔ اتصال قطعه.

"""Bounded final-state capacitor-sharing model; no physical transient simulation."""
import json
import math
import platform
from fractions import Fraction


def numeric(value, name):
    if type(value) not in (int, float):
        raise TypeError(name + " must be exact int or float, not bool")
    try:
        result = float(value)
    except OverflowError as error:
        raise ValueError(name + " must be finite") from error
    if not math.isfinite(result):
        raise ValueError(name + " must be finite")
    return result


def sharing(c1_f, c2_f, v1_v, v2_v, *, capacitance_unit="F", voltage_unit="V"):
    if capacitance_unit != "F" or voltage_unit != "V":
        raise ValueError("units must be exactly F and V")
    c1, c2 = numeric(c1_f, "c1"), numeric(c2_f, "c2")
    v1, v2 = numeric(v1_v, "v1"), numeric(v2_v, "v2")
    if any(not 1e-12 <= c <= 1.0 for c in (c1, c2)):
        raise ValueError("capacitance outside [1e-12, 1] F")
    if any(v != 0 and not 1e-9 <= abs(v) <= 100.0 for v in (v1, v2)):
        raise ValueError("voltage must be zero or signed magnitude [1e-9, 100] V")
    total = math.fsum((c1, c2))
    final_v = math.fsum(((c1 / total) * v1, (c2 / total) * v2))
    q_initial = math.fsum((c1 * v1, c2 * v2))
    q_final = total * final_v
    initial_j = math.fsum((0.5 * c1 * v1 * v1, 0.5 * c2 * v2 * v2))
    final_j = 0.5 * total * final_v * final_v
    equivalent = (c1 * c2) / total
    difference = v1 - v2
    loss_j = 0.5 * equivalent * difference * difference
    return {"final_voltage_v": final_v, "initial_charge_c": q_initial,
            "final_charge_c": q_final, "initial_energy_j": initial_j,
            "final_energy_j": final_j, "stable_loss_j": loss_j,
            "rounded_energy_difference_j": initial_j - final_j}


def reference(c1, c2, v1, v2):
    # Exact actual represented floats; compute energy loss by rational subtraction.
    a, b, x, y = map(lambda z: Fraction.from_float(float(z)), (c1, c2, v1, v2))
    charge = a * x + b * y
    final_v = charge / (a + b)
    initial = (a * x * x + b * y * y) / 2
    final = (a + b) * final_v * final_v / 2
    return {"final_voltage_v": final_v, "initial_charge_c": charge,
            "final_charge_c": charge, "initial_energy_j": initial,
            "final_energy_j": final, "stable_loss_j": initial - final}


def close(a, b):
    return math.isclose(a, float(b), rel_tol=3e-14, abs_tol=1e-30)


def rejects(args, error, **kwargs):
    try:
        sharing(*args, **kwargs)
    except error:
        return True
    return False


def run_example():
    checks = {}
    def check(name, condition):
        checks[name] = bool(condition)
        if not checks[name]:
            raise AssertionError(name)

    hand = sharing(1e-6, 3e-6, 12, 0)
    check("hand_unequal_capacitance_voltage_and_charge", close(hand["final_voltage_v"], 3) and close(hand["initial_charge_c"], 12e-6))
    check("hand_energies_72_18_54_microjoule", all(close(hand[k], e) for k, e in [("initial_energy_j", 72e-6), ("final_energy_j", 18e-6), ("stable_loss_j", 54e-6)]))
    equal = sharing(1e-6, 3e-6, 5, 5)
    check("equal_voltages_zero_loss", equal["final_voltage_v"] == 5 and equal["stable_loss_j"] == 0)
    opposite = sharing(2e-6, 2e-6, 5, -5)
    check("opposite_charge_cancels_not_initial_energy", opposite["initial_charge_c"] == 0 and opposite["final_voltage_v"] == 0 and close(opposite["stable_loss_j"], 50e-6) and opposite["final_energy_j"] == 0)
    zero = sharing(1e-6, 3e-6, 0, 0)
    check("all_zero_voltage_all_zero_outputs", all(v == 0 for v in zero.values()))
    swapped = sharing(3e-6, 1e-6, 0, 12)
    check("swap_capacitor_pairs_invariant", all(close(hand[k], swapped[k]) for k in hand))
    negated = sharing(1e-6, 3e-6, -12, 0)
    check("reverse_reference_changes_charge_voltage_not_energy", all(close(negated[k], -hand[k]) for k in ("final_voltage_v", "initial_charge_c", "final_charge_c")) and all(close(negated[k], hand[k]) for k in ("initial_energy_j", "final_energy_j", "stable_loss_j")))
    scaled_c = sharing(2e-6, 6e-6, 12, 0)
    check("common_capacitance_scale_two", close(scaled_c["final_voltage_v"], hand["final_voltage_v"]) and all(close(scaled_c[k], 2 * hand[k]) for k in ("initial_charge_c", "initial_energy_j", "final_energy_j", "stable_loss_j")))
    scaled_v = sharing(1e-6, 3e-6, 24, 0)
    check("voltage_scale_two_energy_scale_four", close(scaled_v["final_voltage_v"], 2 * hand["final_voltage_v"]) and close(scaled_v["initial_charge_c"], 2 * hand["initial_charge_c"]) and all(close(scaled_v[k], 4 * hand[k]) for k in ("initial_energy_j", "final_energy_j", "stable_loss_j")))
    cases = 0
    max_v_error = 0.0
    for c1 in (1e-12, 1e-6, 1.0):
        for c2 in (1e-12, 3e-6, 1.0):
            for v1 in (-12.0, 0.0, 5.0):
                for v2 in (-5.0, 0.0, 12.0):
                    result = sharing(c1, c2, v1, v2)
                    expected = reference(c1, c2, v1, v2)
                    if not all(close(result[k], expected[k]) for k in expected):
                        raise AssertionError("Fraction reference")
                    if not min(v1, v2) - 1e-14 <= result["final_voltage_v"] <= max(v1, v2) + 1e-14:
                        raise AssertionError("weighted interval")
                    if result["stable_loss_j"] < 0 or not close(result["initial_charge_c"], result["final_charge_c"]):
                        raise AssertionError("charge or loss invariant")
                    max_v_error = max(max_v_error, abs(result["final_voltage_v"] - float(expected["final_voltage_v"])))
                    cases += 1
    check("fraction_reference_81_cases_one_group", cases == 81)
    check("signed_weighted_voltage_inside_initial_interval", close(sharing(1e-6, 3e-6, -12, 4)["final_voltage_v"], 0))
    next_v = math.nextafter(100.0, 0.0)
    cancellation = sharing(1e-6, 1e-6, 100.0, next_v)
    exact_loss = reference(1e-6, 1e-6, 100.0, next_v)["stable_loss_j"]
    stable_error = abs(Fraction.from_float(cancellation["stable_loss_j"]) - exact_loss)
    direct_error = abs(Fraction.from_float(cancellation["rounded_energy_difference_j"]) - exact_loss)
    check("near_equal_voltage_stable_loss_keeps_small_positive_value", cancellation["stable_loss_j"] > 0 and stable_error < direct_error and cancellation["rounded_energy_difference_j"] == 0)
    check("finite_boundary_inputs_accepted", all(math.isfinite(x) for x in sharing(1e-12, 1, -100, 100).values()) and all(math.isfinite(x) for x in sharing(1, 1e-12, 1e-9, -1e-9).values()))
    check("invalid_numeric_types_rejected", all(rejects(a, TypeError) for a in [(True, 1e-6, 1, 0), (1e-6, "1e-6", 1, 0), (1e-6, 1e-6, False, 0), (1e-6, 1e-6, None, 0)]))
    check("nonfinite_numeric_inputs_rejected", all(rejects(a, ValueError) for a in [(float("nan"), 1e-6, 1, 0), (1e-6, float("inf"), 1, 0), (1e-6, 1e-6, -float("inf"), 0), (1e-6, 1e-6, float("nan"), 0)]))
    check("capacitance_range_rejected", all(rejects(a, ValueError) for a in [(0, 1e-6, 1, 0), (-1e-6, 1e-6, 1, 0), (1e-13, 1e-6, 1, 0), (1e-6, 2, 1, 0)]))
    check("voltage_range_rejected", all(rejects((1e-6, 1e-6, v, 0), ValueError) for v in (1e-10, -1e-10, 101, -101, 10**400)))
    check("explicit_unit_contract_rejected", rejects((1e-6, 1e-6, 1, 0), ValueError, capacitance_unit="uF") and rejects((1e-6, 1e-6, 1, 0), ValueError, voltage_unit="mV"))
    return {"python_version": platform.python_version(),
            "scope": "assumed isolated final equal-voltage state; corresponding labelled plates joined, signed initial voltages; no current, transition time, wiring or loss mechanism simulation",
            "units": {"capacitance": "F", "voltage": "V", "charge": "C", "energy": "J"},
            "input_domain": {"capacitance_f": [1e-12, 1.0], "voltage_v": "zero or signed absolute magnitude [1e-9,100]"},
            "hand_fixture": hand, "opposite_voltage_fixture": opposite,
            "reference_cases_in_one_group": cases, "max_reference_voltage_error_v": max_v_error,
            "near_equal_fixture": {"c1_f": 1e-6, "c2_f": 1e-6, "v1_v": 100.0, "v2_v": next_v, **cancellation,
                                   "exact_loss_as_float_j": float(exact_loss), "stable_absolute_error_j": float(stable_error), "direct_absolute_error_j": float(direct_error)},
            "fixture_compare_tolerance": {"relative": 3e-14, "absolute_in_compared_quantity_unit": 1e-30, "quantities": ["V", "C", "J"], "scope": "these finite fixtures only, not correct rounding or hardware uncertainty"},
            "checks": checks}


if __name__ == "__main__":
    print(json.dumps(run_example(), ensure_ascii=False, indent=2))

برای جمعِ مؤلفه‌ها از math.fsum استفاده شده و افت انرژی از شکل مثبت محاسبه می‌شود. تفاضل دو انرژیِ گرد‌شده نیز جدا ثبت شده است. در شاهد نزدیک، هر دو ظرفیت یک میکروفاراد و ولتاژها صد و نزدیک‌ترین float کوچک‌تر از صدند. تفاضل مستقیم، صفر شد؛ شکل مثبت حدود ۵٫۰۴۸۷ ضرب‌در ده به توان منفی سی‌وپنج ژول را حفظ کرد.

{
  "python_version": "3.14.5",
  "scope": "assumed isolated final equal-voltage state; corresponding labelled plates joined, signed initial voltages; no current, transition time, wiring or loss mechanism simulation",
  "units": {
    "capacitance": "F",
    "voltage": "V",
    "charge": "C",
    "energy": "J"
  },
  "input_domain": {
    "capacitance_f": [
      1e-12,
      1.0
    ],
    "voltage_v": "zero or signed absolute magnitude [1e-9,100]"
  },
  "hand_fixture": {
    "final_voltage_v": 3.0,
    "initial_charge_c": 1.2e-05,
    "final_charge_c": 1.2e-05,
    "initial_energy_j": 7.2e-05,
    "final_energy_j": 1.8e-05,
    "stable_loss_j": 5.4e-05,
    "rounded_energy_difference_j": 5.4e-05
  },
  "opposite_voltage_fixture": {
    "final_voltage_v": 0.0,
    "initial_charge_c": 0.0,
    "final_charge_c": 0.0,
    "initial_energy_j": 4.9999999999999996e-05,
    "final_energy_j": 0.0,
    "stable_loss_j": 4.9999999999999996e-05,
    "rounded_energy_difference_j": 4.9999999999999996e-05
  },
  "reference_cases_in_one_group": 81,
  "max_reference_voltage_error_v": 1.7763568394002505e-15,
  "near_equal_fixture": {
    "c1_f": 1e-06,
    "c2_f": 1e-06,
    "v1_v": 100.0,
    "v2_v": 99.99999999999999,
    "final_voltage_v": 100.0,
    "initial_charge_c": 0.00019999999999999998,
    "final_charge_c": 0.00019999999999999998,
    "initial_energy_j": 0.009999999999999998,
    "final_energy_j": 0.009999999999999998,
    "stable_loss_j": 5.048709793414475e-35,
    "rounded_energy_difference_j": 0.0,
    "exact_loss_as_float_j": 5.048709793414475e-35,
    "stable_absolute_error_j": 0.0,
    "direct_absolute_error_j": 5.048709793414475e-35
  },
  "fixture_compare_tolerance": {
    "relative": 3e-14,
    "absolute_in_compared_quantity_unit": 1e-30,
    "quantities": [
      "V",
      "C",
      "J"
    ],
    "scope": "these finite fixtures only, not correct rounding or hardware uncertainty"
  },
  "checks": {
    "hand_unequal_capacitance_voltage_and_charge": true,
    "hand_energies_72_18_54_microjoule": true,
    "equal_voltages_zero_loss": true,
    "opposite_charge_cancels_not_initial_energy": true,
    "all_zero_voltage_all_zero_outputs": true,
    "swap_capacitor_pairs_invariant": true,
    "reverse_reference_changes_charge_voltage_not_energy": true,
    "common_capacitance_scale_two": true,
    "voltage_scale_two_energy_scale_four": true,
    "fraction_reference_81_cases_one_group": true,
    "signed_weighted_voltage_inside_initial_interval": true,
    "near_equal_voltage_stable_loss_keeps_small_positive_value": true,
    "finite_boundary_inputs_accepted": true,
    "invalid_numeric_types_rejected": true,
    "nonfinite_numeric_inputs_rejected": true,
    "capacitance_range_rejected": true,
    "voltage_range_rejected": true,
    "explicit_unit_contract_rejected": true
  }
}

مرجع با Fraction.from_float دقیقاً همان ورودیِ نمایش‌یافته را می‌گیرد؛ بار و ولتاژ نهایی و دو انرژی را کسری حساب می‌کند و سپس انرژی‌ها را دقیق تفریق می‌کند. بنابراین مسیرِ مرجع، تکرارِ فرمول مثبتِ شناور نیست. هشتادویک ترکیب یک گروه از هجده گروه‌اند؛ شاهدِ افت کوچک نیز خطایش را جدا با مرجع دقیق می‌سنجد، نه با تلورانس مطلقِ نسبتاً بزرگِ شبکهٔ مقایسه.

بیشترین اختلاف ولتاژ در شبکه حدود ۱٫۷۸ ضرب‌در ده به توان منفی پانزده ولت بود. این عدد، دقت دستگاه یا تضمین همهٔ ورودی‌ها نیست؛ حتی ولتاژ نهاییِ شاهد نزدیک به صد گرد می‌شود. آزمون گذرای RC و خطای بارگذاری تقسیم ولتاژ مدل‌های دیگری دارند. اجرا Python 3.14.5، مستندات 3.14.8 و تاریخ دسترسی ۲۵ مهر ۱۴۰۵ است؛ کتاب OpenStax در اکتبر ۲۰۱۶ منتشر شده است.

۵. چهار پرسش با پاسخ کامل

تمرین خازن‌های نابرابر، ولتاژهای مخالف و محدودیت مدل نهایی
تصویر مفهومی: تمرین خازن‌های نابرابر، ولتاژهای مخالف و محدودیت مدل نهایی؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

پرسش اول: چرا مثال یک و سه میکروفاراد به شش ولت نمی‌رسد؟ پاسخ: وزن‌ها برابر نیستند؛ دوازده میکروکولن بر چهار میکروفاراد، سه ولت می‌دهد. میانگین ساده، شرط بقای بارِ این دو ظرفیت را حفظ نمی‌کند.

پرسش دوم: دو خازنِ دومیکروفارادی با پنج و منفی پنج ولت چه می‌شوند؟ پاسخ: Q و ولتاژ نهایی صفرند؛ انرژی اولیه مجموعاً پنجاه میکروژول و انرژی نهایی صفر است. صفرشدنِ بارِ گروه، انرژی اولیه را صفر نمی‌کند.

پرسش سوم: افت صفرِ تفاضل مستقیم، ولتاژهای اولیه را برابر ثابت می‌کند؟ پاسخ: خیر؛ گردشدن می‌تواند افتِ مثبتِ کوچک را پنهان کند. شاهد nextafter همین را نشان داد؛ مرجع کسری و شکل مثبت دو شاهد مناسب‌ترِ این حالت‌اند.

پرسش چهارم: از ۵۴ میکروژول کاهش می‌توان جریان اوج را یافت؟ پاسخ: خیر؛ مدل فقط دو حالت و ذخیرهٔ انرژی را دارد. بدون مسیر اتصال و مدل زمانی، جریان اوج، زمان نشست و نحوهٔ تبدیل انرژی مشخص نمی‌شوند.

منابع و مطالعهٔ بیشتر

بررسی منابع: ۲۵ مهر ۱۴۰۵. تحلیل‌ها و پیشنهادهای اجرایی، جمع‌بندی نویسنده‌اند.