فهرست مقاله
نامِ Simpson بهتنهایی نمیگوید با نمونههای نامنظم چه کنیم. فرمولِ آشنای یک، چهار، یک به فاصلههای برابر وابسته است؛ کپیکردن آن روی سه زمان نابرابر میتواند حتی یک تابع درجهٔ دو را نادرست انتگرال بگیرد. این درس برای پنلهای سهگرهیِ تألیفی، فرضِ درجهٔ دو را صریح میکند، وزنها را میسازد و با انتگرالِ دقیقِ چندجملهای میسنجد. دادهٔ واقعی، شکلِ ناشناختهٔ سیگنال و اجرای SciPy در آزمایش نداریم.
۱. سه گره، یک سهمی و یک فرض تازه

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

گام چپ را h0، گام راست را h1 و مجموعشان را H مینامیم. ضریبِ ولتاژِ نخست H*(2-h1/h0)/6 است؛ ضریبِ میانی H**3/(6*h0*h1) و ضریبِ آخر H*(2-h0/h1)/6 میشود. این سه ضریب واحدِ ثانیه دارند؛ وزنِ احتمال یا میانگینِ بیبُعد نیستند. جمعشان باید عرضِ کلِ پنل را بازتولید کند.
برای گامهای یک و سه ثانیه، ضرایب منفی دوسوم، سیودو نهم و دهنهم ثانیهاند. ضرب در صفر، یک و شانزده ولت، شصتوچهارسوم ولتثانیه میدهد. اگر فرمولِ یک، چهار، یک را با عرض چهار به کار ببریم، چهلسوم به دست میآید؛ مشکل، فاصلهٔ نابرابرِ گرهٔ میانی است، نه یک رقمِ گردکردن.
صورتِ معمولِ Simpson در DLMF، گرهٔ میانی را وسطِ دو انتها میگذارد و برای قاعدهٔ مرکب نیز شبکهٔ برابرگام دارد. در حدِ h0 برابر h1، ضرایبِ ما به همان نسبتِ یک، چهار، یک میرسند. حدِ خطای نوشتهشده برای فرضهای همواری و شبکهٔ آن منبع را به این دادهٔ دلخواه و گام نامساوی منتقل نمیکنیم.
اگر گام راست بیش از دو برابرِ چپ شود، ضریبِ نخست منفی است. روی زمانهای صفر، یک و چهار، نمونههای یک، صفر و صفر ولت، انتگرالِ منفی دوسوم میسازند. همهٔ نمونهها نامنفیاند، اما سهمیِ درونیاب بین بعضی گرهها زیر صفر میرود. این نتیجه، منفی بودنِ سیگنالِ واقعی را اثبات نمیکند؛ نشان میدهد مدلِ انتخابی تضمینِ حفظِ نامنفی بودن ندارد.
۳. کد کاملِ پنلهای زوجبازه

تابع دو tuple واقعیِ هماندازه با شمارِ فردِ سه تا هفده عضو میگیرد. تعداد بازهها زوج است تا پنلِ ناقص باقی نماند. زمانها در بازهٔ منفی هشت تا هشت ثانیه و دقیقاً مضربِ یکشانزدهم ثانیهاند؛ باید اکیداً صعودی باشند. این شبکه، دامنهٔ محدودِ تمرین است، نه ادعای پشتیبانی از تمامِ timestampها یا محدودیتِ SciPy.
"""Original odd-grid quadratic-panel integral and exact polynomial oracle."""
from fractions import Fraction
import itertools
import json
import math
import platform
ABS, REL = 1e-10, 1e-12
def simpson_panels(times_s, voltage_v):
if type(times_s) is not tuple or type(voltage_v) is not tuple:
raise TypeError("actual time and voltage tuples required")
count = len(times_s)
if not 3 <= count <= 17 or count % 2 != 1 or len(voltage_v) != count:
raise ValueError("matching odd sample count in 3..17 required")
for values, bound in ((times_s, 8), (voltage_v, 64)):
for value in values:
if type(value) not in (int, float):
raise TypeError("exact int/float required, no bool or coercion")
if not -bound <= value <= bound or not math.isfinite(value):
raise ValueError("finite bounded samples required")
times = tuple(map(float, times_s))
if any(t * 16 != round(t * 16) for t in times):
raise ValueError("chosen time grid is exact multiples of 1/16 second")
if any(b <= a for a, b in zip(times, times[1:])):
raise ValueError("strictly increasing time, no hidden sorting")
panels = []
for start in range(0, count - 2, 2):
a, center, b = times[start:start + 3]
left, right = center - a, b - center
width = left + right
coefficients = (width / 6 * (2 - right / left),
width / 6 * width * width / (left * right),
width / 6 * (2 - left / right))
value = math.fsum(c * float(y) for c, y in zip(coefficients, voltage_v[start:start + 3]))
panels.append({"start_index": start, "time_coefficients_s": coefficients,
"integral_v_s": value})
return {"integral_v_s": math.fsum(p["integral_v_s"] for p in panels), "panels": panels}
def lagrange_integral_reference(times_s, voltage_v):
# Independent expansion/integration of three polynomial bases; no main weight formula.
total = Fraction()
for start in range(0, len(times_s) - 2, 2):
xs = tuple(map(Fraction, times_s[start:start + 3]))
ys = tuple(map(Fraction, voltage_v[start:start + 3]))
lower, upper = xs[0], xs[-1]
for j in range(3):
others = [xs[k] for k in range(3) if k != j]
q, r = others
denominator = (xs[j] - q) * (xs[j] - r)
# Integral of (t-q)(t-r) = t**3/3 -(q+r)t**2/2 +q*r*t.
primitive_difference = ((upper ** 3 - lower ** 3) / 3
- (q + r) * (upper ** 2 - lower ** 2) / 2
+ q * r * (upper - lower))
total += ys[j] * primitive_difference / denominator
return total
def close(a, b):
return math.isclose(float(a), float(b), abs_tol=ABS, rel_tol=REL)
def rejected(*args):
try:
simpson_panels(*args)
except (TypeError, ValueError):
return True
return False
def run_checks():
times, values = (0, 1, 4), (0, 1, 16)
result = simpson_panels(times, values)
checks = {}
checks["literal_nonuniform_quadratic_integral_and_weights"] = (
close(result["integral_v_s"], Fraction(64, 3))
and all(close(a, b) for a, b in zip(result["panels"][0]["time_coefficients_s"],
(Fraction(-2, 3), Fraction(32, 9), Fraction(10, 9))))
)
negative = simpson_panels(times, (1, 0, 0))
checks["nonnegative_samples_can_have_negative_interpolant_integral"] = close(negative["integral_v_s"], Fraction(-2, 3))
checks["unequal_steps_do_not_allow_uniform_one_four_one_weights"] = (
close((4 / 6) * (values[0] + 4 * values[1] + values[2]), Fraction(40, 3))
and not close(result["integral_v_s"], Fraction(40, 3))
)
cases, oracle_ok, max_error = 0, True, 0.0
for left, right in itertools.product((1 / 4, 1 / 2, 1, 2), repeat=2):
xs = (0, left, left + right)
for ys in itertools.product((-2, 0, 3), repeat=3):
actual = simpson_panels(xs, ys)["integral_v_s"]
reference = lagrange_integral_reference(xs, ys)
oracle_ok = oracle_ok and close(actual, reference)
max_error = max(max_error, abs(actual - float(reference)))
cases += 1
checks["independent_lagrange_432_panels_one_group"] = oracle_ok and cases == 432
quadratic_ok, polynomial_cases = True, 0
for grid in ((0, 1 / 4, 1, 2, 4), (-2, -1, 0, 1 / 2, 2), (0, 1, 2)):
for a, b, c in ((1, 0, 0), (0, 2, -1), (1, -1, 2), (-1, 1, 0)):
ys = tuple(a * t * t + b * t + c for t in grid)
exact = Fraction(a, 3) * (Fraction(grid[-1]) ** 3 - Fraction(grid[0]) ** 3) + Fraction(b, 2) * (Fraction(grid[-1]) ** 2 - Fraction(grid[0]) ** 2) + c * (Fraction(grid[-1]) - Fraction(grid[0]))
quadratic_ok = quadratic_ok and close(simpson_panels(grid, ys)["integral_v_s"], exact)
polynomial_cases += 1
checks["twelve_degree_at_most_two_functions_one_group"] = quadratic_ok and polynomial_cases == 12
cubic_uniform = simpson_panels((0, 1, 2), (0, 1, 8))["integral_v_s"]
cubic_unequal = simpson_panels((0, 1, 3), (0, 1, 27))["integral_v_s"]
checks["uniform_cubic_exact_but_unequal_cubic_has_literal_error"] = close(cubic_uniform, 4) and close(cubic_unequal, Fraction(45, 2)) and close(cubic_unequal - Fraction(81, 4), Fraction(9, 4))
composite_t, composite_y = (0, 1 / 2, 2, 3, 4), (1, 3, -1, 2, 0)
composite = simpson_panels(composite_t, composite_y)
checks["composite_shared_endpoints_integrate_two_adjacent_panels"] = (
len(composite["panels"]) == 2
and close(composite["integral_v_s"], lagrange_integral_reference(composite_t, composite_y))
and close(composite["integral_v_s"], simpson_panels(composite_t[:3], composite_y[:3])["integral_v_s"] + simpson_panels(composite_t[2:], composite_y[2:])["integral_v_s"])
)
shifted = simpson_panels(tuple(t - 2 for t in times), values)
checks["time_origin_shift_preserves_integral"] = close(shifted["integral_v_s"], result["integral_v_s"])
checks["voltage_offset_and_gain_have_declared_effect"] = close(simpson_panels(times, tuple(y + 3 for y in values))["integral_v_s"], result["integral_v_s"] + 12) and close(simpson_panels(times, tuple(-2 * y for y in values))["integral_v_s"], -2 * result["integral_v_s"])
checks["time_scale_changes_integral_in_proportion"] = close(simpson_panels(tuple(t * 2 for t in times), values)["integral_v_s"], 2 * result["integral_v_s"])
checks["constant_voltage_and_maximum_odd_sample_count"] = close(simpson_panels(tuple(range(-8, 9)), (64,) * 17)["integral_v_s"], 1024)
checks["odd_count_shape_and_no_even_end_fallback"] = all([
rejected(list(times), values), rejected(times, list(values)), rejected((), ()),
rejected((0, 1), (0, 1)), rejected((0, 1, 2, 3), (0, 1, 4, 9)),
rejected(times, values[:2]), rejected(tuple(v / 16 for v in range(19)), (0,) * 19),
])
checks["time_grid_order_range_and_numeric_contracts"] = all([
rejected((0, 0, 1), values), rejected((1, 0, 2), values),
rejected((0, 0.1, 1), values), rejected((-9, 0, 1), values),
rejected((0, 1, 9), values),
*[rejected((0, 1, bad), values) for bad in (True, "2", 1j, math.nan, math.inf, 10 ** 500)],
*[rejected(times, (0, 1, bad)) for bad in (True, "2", 1j, math.nan, -math.inf, 65, -65)],
])
assert all(type(v) is bool and v for v in checks.values())
return {"python": platform.python_version(), "dependencies": "standard library only", "scipy_executed": False,
"time_grid_seconds": "exact multiples of 1/16 within [-8,8]", "voltage_bound_v": 64,
"units": {"times": "s", "samples": "V", "coefficients": "s", "integral": "V*s"},
"absolute_tolerance_v_s": ABS, "relative_tolerance": REL, "hand_times_s": times,
"hand_voltage_v": values, "hand_result": result, "hand_exact_integral_v_s": "64/3",
"negative_weight_witness_integral_v_s": negative["integral_v_s"],
"uniform_cubic_integral_v_s": cubic_uniform, "unequal_cubic_estimate_v_s": cubic_unequal,
"unequal_cubic_true_integral_v_s": "81/4", "unequal_cubic_error_v_s": "9/4",
"composite_example": composite, "lagrange_oracle_cases_inside_one_group": cases,
"known_polynomials_inside_one_group": polynomial_cases,
"maximum_lagrange_oracle_absolute_difference_v_s": max_error,
"checks": checks, "unique_check_groups": len(checks), "all_passed": True,
"limits": ["Integrates paired quadratic interpolants; unknown physical waveform between samples is not established",
"Odd sample counts only; no hidden even-grid end correction, extrapolation, smoothing or adaptive rule",
"Unequal-spacing coefficients can be negative and do not preserve positivity of samples",
"No signal quality, measured uncertainty, energy-in-joules or global error-bound claim"]}
if __name__ == "__main__":
print(json.dumps(run_checks(), ensure_ascii=False, indent=2))
نمونههای ولتاژ، int یا float دقیقِ متناهی با قدرِ حداکثر ۶۴ ولتاند. bool، رشته، مختلط، NaN و بینهایت تبدیل نمیشوند. گرهها را مرتب، تکرارها را حذف یا نمونهٔ انتهایی را با ذوزنقه اصلاح نمیکنیم؛ تعداد زوج یا زمانِ نامعتبر خطا میدهد. زمانِ منفی فقط جابهجاییِ مبدأ نسبی است و ارتباطی با تاریخِ تقویمی ندارد.
برای هر پنل، اندیسِ آغاز، سه ضریبِ زمانی و سهمِ انتگرال ذخیره میشوند. جمعِ حاصلضربها و جمعِ پنلها با fsum است؛ خطای ضرب و تقسیمِ قبلی همچنان باقی میماند. اجرای واقعی با Python 3.14.5 و کتابخانهٔ استاندارد ثبت شده است. SciPy و MATLAB اجرا نشدهاند و نمودارِ مفهومیِ مقاله، نتیجهٔ عددیِ این فایل معرفی نمیشود.
مرجع simpson در SciPy 1.18.0، نمونههای دارای مختصاتِ صریح و تفاوتِ دقتِ چندجملهای در فاصلهٔ برابر و نابرابر را بیان میکند. کدِ حاضر رابطِ عمومیِ آن نیست: محورهای چندبعدی، دادهٔ زوج و سیاستهای انتهاییِ کتابخانه را پیاده نمیکند. نامِ مشابه، گواهیِ سازگاریِ همهٔ حالتهای یک API نیست.
۴. مرجع لاگرانژ و شاهد شکستِ درجهٔ سه

مرجعِ مستقل، سه پایهٔ لاگرانژ را مستقیم بسط میدهد. برای پایهٔ متناظر با گرهٔ j، حاصلضربِ زمان منهای دو گرهٔ دیگر بر دو فاصلهٔ ثابتِ مخرج تقسیم میشود. تابعِ اولیهٔ صورت از جملههای درجهٔ سه، دو و یک ساخته و در دو انتهای پنل تفریق میشود. این مسیر، فرمولِ سه ضریبِ تابعِ اصلی را فراخوانی یا دوباره استفاده نمیکند.
Fraction روی fixtureهای کوچکِ صحیح و دودویی، انتگرالِ دقیقِ همان چندجملهای را میدهد. تابع اصلی در مثالِ درجهٔ دو، ۲۱٫۳۳۳۳۳۳۳۳۳۳۳۳۳۳۶ ثبت کرده است؛ جوابِ کسری شصتوچهارسوم است. تلورانسِ مطلق ده به توان منفی ده ولتثانیه و نسبی ده به توان منفی دوازده، انتخابِ مقایسهٔ این دادههاست، نه کرانِ اثباتشدهٔ خطای همهٔ سیگنالها.
برای تابعِ معلومِ v(t) = (1 V/s³) t³، نمونههای صفر، یک و دو ثانیه جوابِ درستِ چهار ولتثانیه میدهند. اما در زمانهای صفر، یک و سه با نمونههای صفر، یک و ۲۷، برنامه ۲۲٫۵ میدهد؛ انتگرالِ تحلیلی ۸۱ چهارم و اختلاف نهچهارم ولتثانیه است. بازتولیدِ درجهٔ سه روی شبکهٔ برابر را نمیتوان برای گام نابرابر وعده داد.
{
"python": "3.14.5",
"dependencies": "standard library only",
"scipy_executed": false,
"time_grid_seconds": "exact multiples of 1/16 within [-8,8]",
"voltage_bound_v": 64,
"units": {
"times": "s",
"samples": "V",
"coefficients": "s",
"integral": "V*s"
},
"absolute_tolerance_v_s": 1e-10,
"relative_tolerance": 1e-12,
"hand_times_s": [
0,
1,
4
],
"hand_voltage_v": [
0,
1,
16
],
"hand_result": {
"integral_v_s": 21.333333333333336,
"panels": [
{
"start_index": 0,
"time_coefficients_s": [
-0.6666666666666666,
3.5555555555555554,
1.1111111111111112
],
"integral_v_s": 21.333333333333336
}
]
},
"hand_exact_integral_v_s": "64/3",
"negative_weight_witness_integral_v_s": -0.6666666666666666,
"uniform_cubic_integral_v_s": 4.0,
"unequal_cubic_estimate_v_s": 22.5,
"unequal_cubic_true_integral_v_s": "81/4",
"unequal_cubic_error_v_s": "9/4",
"composite_example": {
"integral_v_s": 6.777777777777777,
"panels": [
{
"start_index": 0,
"time_coefficients_s": [
-0.3333333333333333,
1.7777777777777777,
0.5555555555555556
],
"integral_v_s": 4.444444444444444
},
{
"start_index": 2,
"time_coefficients_s": [
0.3333333333333333,
1.3333333333333333,
0.3333333333333333
],
"integral_v_s": 2.333333333333333
}
]
},
"lagrange_oracle_cases_inside_one_group": 432,
"known_polynomials_inside_one_group": 12,
"maximum_lagrange_oracle_absolute_difference_v_s": 1.7763568394002505e-15,
"checks": {
"literal_nonuniform_quadratic_integral_and_weights": true,
"nonnegative_samples_can_have_negative_interpolant_integral": true,
"unequal_steps_do_not_allow_uniform_one_four_one_weights": true,
"independent_lagrange_432_panels_one_group": true,
"twelve_degree_at_most_two_functions_one_group": true,
"uniform_cubic_exact_but_unequal_cubic_has_literal_error": true,
"composite_shared_endpoints_integrate_two_adjacent_panels": true,
"time_origin_shift_preserves_integral": true,
"voltage_offset_and_gain_have_declared_effect": true,
"time_scale_changes_integral_in_proportion": true,
"constant_voltage_and_maximum_odd_sample_count": true,
"odd_count_shape_and_no_even_end_fallback": true,
"time_grid_order_range_and_numeric_contracts": true
},
"unique_check_groups": 13,
"all_passed": true,
"limits": [
"Integrates paired quadratic interpolants; unknown physical waveform between samples is not established",
"Odd sample counts only; no hidden even-grid end correction, extrapolation, smoothing or adaptive rule",
"Unequal-spacing coefficients can be negative and do not preserve positivity of samples",
"No signal quality, measured uncertainty, energy-in-joules or global error-bound claim"
]
}
سیزده گروه نامدار اجرا شدهاند. ۴۳۲ پنلِ کوچک یک گروهِ مرجعِ لاگرانژ و دوازده تابعِ درجهٔ دو یا کمتر، یک گروهِ تحلیلیاند. بیشینهٔ اختلافِ مرجعِ پنلها حدود ۱٫۷۸ ضربدر ده به توان منفی پانزده ولتثانیه بود. دو پنلِ همسایه، جابهجایی مبدأ، بهره و آفستِ ولتاژ، کشیدگیِ زمان و مرزهای قرارداد نیز جدا آزموده شدند؛ شمارِ اعضای حلقه به گروههای مستقل اضافه نشده است.
۵. چهار پرسش و مرزِ تفسیر انتگرال

پرسش اول: چرا فرمولِ یک، چهار، یک روی زمانهای صفر، یک و چهار معتبر نیست؟ پاسخ: یک، وسطِ صفر و چهار نیست؛ دو گام یک و سهاند. ضرایبِ درست برای فرضِ درجهٔ دو، منفی دوسوم، سیودو نهم و دهنهماند و جواب شصتوچهارسوم است. برابر بودنِ تعداد سه گره، برابریِ فاصله را ثابت نمیکند.
پرسش دوم: انتگرالِ منفی از سه نمونهٔ نامنفی یعنی کد خراب است؟ پاسخ: لزوماً خیر؛ در شاهد یک، صفر، صفر، مدلِ سهمی بخشِ منفی دارد و جواب منفی دوسوم است. اگر حفظِ نامنفی بودن نیازِ مسئله باشد، این انتخابِ مدل ممکن است مناسب نباشد. بریدنِ خروجی به صفر، همان انتگرالِ تعریفشده را نگه نمیدارد.
پرسش سوم: آیا درجهٔ سه همیشه دقیق است؟ پاسخ: خیر؛ شاهدِ گام نابرابر، نهچهارم ولتثانیه اختلاف دارد. آزمون مشتقِ گام نامساوی هم نقشِ فرضِ محلی را نشان میدهد، اما خروجیِ آن ولتبرثانیه است؛ اینجا انتگرالِ پنل، واحد ولتثانیه دارد و آزمون یا فرمولِ مشتق تکرار نشده است.
پرسش چهارم: جواب، انرژیِ مصرفی یا کیفیتِ نمونهبرداری است؟ پاسخ: خیر؛ نه جریان و مدلِ بار داریم و نه شکلِ واقعیِ بین نمونهها را میدانیم. منابع در ۲۶ مهر ۱۴۰۵ خوانده شدند؛ DLMF نسخهٔ 1.2.8 با تاریخِ انتشارِ نسخهٔ ۱۵ سپتامبر ۲۰۲۶، از تاریخِ نامعلومِ بخشِ منبع جدا ثبت است. این آموزش، اندازهگیریِ سختافزار یا خبرِ پژوهشیِ تازه نیست.
منابع و مطالعهٔ بیشتر
- SciPy 1.18.0؛ Simpson با نمونههای برابر و نابرابر
- NIST DLMF 1.2.8؛ قاعدهٔ Simpson با شبکهٔ برابر
- NIST DLMF 1.2.8؛ وزنهای انتگرالِ درونیابی لاگرانژ
بررسی منابع: ۲۶ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

