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

برای نمونهٔ i، یک همسایه در چپ و یکی در راست لازم داریم. فاصلهٔ چپ hs برابر زمان مرکزی منهای زمان قبلی و فاصلهٔ راست hd برابر زمان بعدی منهای زمان مرکزی است. هر دو باید مثبت باشند؛ همزمانی دو نمونه برای این قرارداد مجاز نیست. برخلاف میانگین روی یک بازه، نتیجه را به زمان همان نمونهٔ مرکزی نسبت میدهیم، نه به وسطِ فاصلهٔ دو همسایه.
ورودی زمان برحسب ثانیه و ولتاژ برحسب ولت است؛ تقسیم اختلاف ولتاژ بر اختلاف زمان، ولتبرثانیه میدهد. زمانهای منفی هم مجازند، چون مبدأ نسبی را میتوان جابهجا کرد. این مقدارها تاریخ تقویمی یا زمان تهران نیستند. اگر داده با میلیثانیه آمده، پیش از فراخوانی باید بر هزار تقسیم شود؛ تغییر نام واحد، مقیاس عددی را اصلاح نمیکند.
برای دو انتهای دنباله خروجی نمیسازیم، چون دو همسایهٔ لازم وجود ندارد. پس N ورودی، N منهای دو برآورد داخلی میدهد و اندیس اصلی کنار هر نتیجه ثبت میشود. این سیاست با خروجی هماندازه و فرمولهای مرزی numpy.gradient یکسان نیست. مستندات آن، زمینهٔ گام نامساوی را توضیح میدهد؛ برنامهٔ حاضر جایگزین سازگار آن API معرفی نمیشود.
۲. شیبهای دو طرف را با وزن درست ترکیب کنیم

ابتدا شیب چپ را از اختلاف ولتاژ مرکزی و قبلی تقسیم بر hs و شیب راست را از اختلاف ولتاژ بعدی و مرکزی تقسیم بر hd میگیریم. برآورد مرکزی، مجموع شیب چپ با وزن hd تقسیم بر مجموع گامها و شیب راست با وزن hs تقسیم بر مجموع گامهاست. گام بزرگترِ یک طرف در وزنِ شیب طرف مقابل ظاهر میشود؛ جابهجایی این دو وزن، خاصیت بازتولید درجهٔ دو را میشکند.
این بیان، از نظر جبری مشتق چندجملهای درجهٔ دوِ گذرنده از سه نقطه است. بخش Notes مستندات NumPy، ضرایب متناظر را با بسط تیلور و فرض همواری توضیح میدهد. این فرض را برای اندازهگیری دلخواه یا سیگنال پرنویز خودکار برقرار نمیدانیم. اگر گامها مساوی شوند، ترکیب به تفاضل مرکزی آشنای دو همسایه روی دو برابر گام کاهش مییابد.
نمونهٔ دستی تابع v(t) برابر دو t مربع منهای سه t بهعلاوهٔ یک است؛ ضریبها به ترتیب واحد ولتبرثانیهٔ مربع، ولتبرثانیه و ولت دارند. مشتق تحلیلی چهار t منهای سه، با واحد ولتبرثانیه است. زمانهای صفر، نیم، یکونیم، سه و چهار ثانیه، ولتاژهای یک، صفر، یک، ده و بیستویک ولت میدهند. پاسخ داخلی باید منفی یک، سه و نه باشد.
در زمان نیم ثانیه، hs نیم و hd یک ثانیه است. شیب چپ منفی دو و شیب راست یک ولتبرثانیهاند؛ وزنهای دوسوم و یکسوم، منفی یک میدهند. تقسیم اختلاف ولتاژ دو همسایه بر فاصلهٔ کل، در همین نقطه صفر میدهد. آن عدد شیب بازهٔ همسایههاست و در گام نامساوی لزوماً مشتقِ نمونهٔ مرکزی نیست.
۳. کد کامل و مرجع کسریِ مشتق لاگرانژ

فایل irregular_derivative.py با Python 3.14.5 اجرا شده است. دو فهرست هماندازه با دستکم سه عضو لازم است. عضوها باید int یا float دقیق و پس از تبدیل متناهی باشند؛ bool، رشته، مختلط، NaN و بینهایت رد میشوند. زمانهای تبدیلشده باید اکیداً صعودی بمانند؛ دو عدد صحیح بزرگ که به یک float گرد شوند نیز ورودی معتبر نمیسازند.
"""Offline interior three-point derivative for finite irregular synthetic samples."""
from fractions import Fraction
import json
import math
import platform
def finite_sequence(values, name):
if type(values) not in (list, tuple):
raise ValueError(name + '_list_or_tuple')
converted = []
for value in values:
if type(value) not in (int, float):
raise ValueError(name + '_exact_real_type')
try:
number = float(value)
except OverflowError:
raise ValueError(name + '_float_range') from None
if not math.isfinite(number):
raise ValueError(name + '_finite')
converted.append(number)
return tuple(converted)
def interior_derivative(times, voltages, time_unit='s', value_unit='V'):
if type(time_unit) is not str or time_unit != 's' or type(value_unit) is not str or value_unit != 'V':
raise ValueError('units_must_be_s_and_V')
t, v = finite_sequence(times, 'times'), finite_sequence(voltages, 'voltages')
if len(t) != len(v) or len(t) < 3:
raise ValueError('matching_lengths_at_least_three')
if not all(left < right for left, right in zip(t, t[1:])):
raise ValueError('strictly_increasing_represented_times')
output = []
for index in range(1, len(t) - 1):
left_step, right_step = t[index] - t[index - 1], t[index + 1] - t[index]
span = left_step + right_step
if not all(math.isfinite(x) and x > 0 for x in (left_step, right_step, span)):
raise ArithmeticError('steps_and_span_must_remain_finite_positive')
differences = (v[index] - v[index - 1], v[index + 1] - v[index])
if not all(math.isfinite(x) for x in differences):
raise ArithmeticError('voltage_difference_not_finite')
try:
left_slope, right_slope = differences[0] / left_step, differences[1] / right_step
if not all(math.isfinite(x) for x in (left_slope, right_slope)):
raise ArithmeticError('slope_not_finite')
estimate = math.fsum(((right_step / span) * left_slope,
(left_step / span) * right_slope))
except (OverflowError, ValueError):
raise ArithmeticError('derivative_intermediate_not_finite') from None
if not math.isfinite(estimate):
raise ArithmeticError('derivative_not_finite')
output.append({'index': index, 'time_s': t[index], 'left_step_s': left_step,
'right_step_s': right_step, 'derivative_V_per_s': estimate})
return output
def lagrange_fraction_reference(times, voltages):
"""Exact derivative of each local quadratic interpolant, not slope blending."""
t = tuple(Fraction.from_float(float(value)) for value in times)
v = tuple(Fraction.from_float(float(value)) for value in voltages)
output = []
for index in range(1, len(t) - 1):
nodes, values = t[index - 1:index + 2], v[index - 1:index + 2]
target = t[index]
derivative = Fraction(0)
for j in range(3):
other = [k for k in range(3) if k != j]
numerator_derivative = (target - nodes[other[0]]) + (target - nodes[other[1]])
denominator = (nodes[j] - nodes[other[0]]) * (nodes[j] - nodes[other[1]])
derivative += values[j] * numerator_derivative / denominator
output.append(float(derivative))
return output
def estimates(rows):
return [row['derivative_V_per_s'] for row in rows]
def close_lists(left, right):
return len(left) == len(right) and all(math.isclose(a, b, rel_tol=1e-12, abs_tol=1e-12)
for a, b in zip(left, right, strict=True))
def rejects(call, error=ValueError):
try:
call()
except error:
return True
return False
def run_example():
times = [0, 0.5, 1.5, 3, 4]
voltages = [1, 0, 1, 10, 21] # v(t)=2*t^2-3*t+1 V, t in s.
result = interior_derivative(times, voltages)
hand = [-1.0, 3.0, 9.0]
unweighted_secants = [(voltages[i + 1] - voltages[i - 1]) / (times[i + 1] - times[i - 1])
for i in range(1, len(times) - 1)]
reference_fixtures = [([0, 0.5, 1.5, 3], [1, -2, 3, 0]),
([-2, -0.5, 1, 4], [4, 0.25, 1, 16]),
([0, 1, 3], [0, 1, 27]), (times, voltages)]
polynomial_ok = True
polynomial_cases = 0
for a in (-2, 0, 0.5, 2):
for b in (-3, 0, 1):
values = [a * t * t + b * t + 0.75 for t in times]
polynomial_ok = polynomial_ok and close_lists(estimates(interior_derivative(times, values)),
[2 * a * t + b for t in times[1:-1]])
polynomial_cases += 1
coarse_t, fine_t = [0, 1, 3], [0.5, 1, 2]
coarse = estimates(interior_derivative(coarse_t, [t ** 3 for t in coarse_t]))[0]
fine = estimates(interior_derivative(fine_t, [t ** 3 for t in fine_t]))[0]
checks = {}
checks['hand_quadratic_derivatives_at_three_interior_times'] = close_lists(estimates(result), hand)
checks['interior_indices_steps_and_no_endpoint_estimates'] = [r['index'] for r in result] == [1, 2, 3] and [(r['left_step_s'], r['right_step_s']) for r in result] == [(0.5, 1.0), (1.0, 1.5), (1.5, 1.0)]
checks['independent_exact_lagrange_derivative_four_fixtures'] = all(close_lists(estimates(interior_derivative(t, v)), lagrange_fraction_reference(t, v)) for t, v in reference_fixtures)
checks['constant_voltage_derivative_is_zero'] = estimates(interior_derivative(times, [2.0] * len(times))) == [0.0, 0.0, 0.0]
checks['affine_voltage_is_reproduced_on_irregular_grid'] = close_lists(estimates(interior_derivative(times, [-2.5 * t + 0.75 for t in times])), [-2.5] * 3)
checks['twelve_quadratic_polynomials_match_analytic_reference'] = polynomial_cases == 12 and polynomial_ok
checks['unweighted_spanning_secant_is_not_center_derivative'] = unweighted_secants == [0.0, 4.0, 8.0] and unweighted_secants != hand
checks['stretching_time_by_two_halves_derivatives'] = close_lists(estimates(interior_derivative([2 * t for t in times], voltages)), [v / 2 for v in hand])
checks['voltage_scale_and_dc_shift_transform_derivative'] = close_lists(estimates(interior_derivative(times, [2 * v + 7 for v in voltages])), [2 * v for v in hand])
checks['time_origin_translation_preserves_derivatives'] = close_lists(estimates(interior_derivative([t + 8 for t in times], voltages)), hand)
checks['authored_cubic_half_step_error_ratio_four'] = close_lists([coarse, fine, (coarse - 3.0) / (fine - 3.0)], [5.0, 3.5, 4.0])
checks['three_points_produce_only_one_center_record'] = interior_derivative([0, 1, 3], [1, 0, 10])[0]['index'] == 1 and len(interior_derivative([0, 1, 3], [1, 0, 10])) == 1
saved_t, saved_v = times[:], voltages[:]
checks['tuple_list_repeatable_and_no_input_mutation'] = interior_derivative(tuple(times), tuple(voltages)) == result and times == saved_t and voltages == saved_v
checks['rejects_lengths_shapes_and_iterator_input'] = all(rejects(call) for call in (
lambda: interior_derivative([], []), lambda: interior_derivative([0, 1], [1, 2]),
lambda: interior_derivative([0, 1, 2], [1, 2]), lambda: interior_derivative(iter(times), voltages),
lambda: interior_derivative(times, [[1]] * len(times))))
checks['rejects_bad_times_duplicates_order_and_float_collapse'] = all(rejects(lambda x=x: interior_derivative([0, x, 2], [0, 1, 2])) for x in (True, '1', float('nan'), float('inf'), 10 ** 400)) and rejects(lambda: interior_derivative([0, 0, 1], [0, 1, 2])) and rejects(lambda: interior_derivative([0, 2, 1], [0, 1, 2])) and rejects(lambda: interior_derivative([2 ** 53, 2 ** 53 + 1, 2 ** 53 + 2], [0, 1, 2]))
checks['rejects_bad_voltage_types_and_nonfinite_values'] = all(rejects(lambda x=x: interior_derivative([0, 1, 2], [0, x, 2])) for x in (False, '1', 1j, None, float('nan'), -float('inf'), 10 ** 400))
checks['explicit_units_and_millisecond_conversion'] = rejects(lambda: interior_derivative(times, voltages, time_unit='ms')) and rejects(lambda: interior_derivative(times, voltages, value_unit='mV')) and close_lists(estimates(interior_derivative([t / 1000 for t in [0, 500, 1500, 3000, 4000]], voltages)), hand)
smallest = math.ulp(0.0)
checks['rejects_overflowed_span_difference_or_slope'] = all(rejects(call, ArithmeticError) for call in (
lambda: interior_derivative([-1e308, 0, 1e308], [0, 1, 2]),
lambda: interior_derivative([0, 1, 2], [-1e308, 1e308, 0]),
lambda: interior_derivative([0, smallest, 2 * smallest], [0, 1, 2])))
if len(checks) != 18 or not all(type(value) is bool and value for value in checks.values()):
raise AssertionError(checks)
return {'python_version': platform.python_version(), 'runtime': 'Python standard library only',
'units': {'time': 's', 'voltage': 'V', 'derivative': 'V/s'},
'times_s': times, 'voltages_V': voltages, 'authored_function': 'v(t)=(2 V/s^2)*t^2-(3 V/s)*t+1 V',
'analytic_derivative': '(4 V/s^2)*t-3 V/s', 'hand_derivatives_V_per_s': hand,
'result': result, 'unweighted_spanning_secants_V_per_s': unweighted_secants,
'cubic_fixture': {'center_s': 1, 'analytic_V_per_s': 3, 'coarse_V_per_s': coarse,
'both_steps_halved_V_per_s': fine,
'actual_absolute_error_ratio': (coarse - 3.0) / (fine - 3.0),
'hand_expected_absolute_error_ratio': 4.0},
'polynomial_cases': polynomial_cases, 'checks': checks, 'unique_checks': 18,
'limitations': ['Interior estimates only; no endpoint formula, interpolation of missing data, smoothing, hardware or NumPy execution.',
'Weighted adjacent slopes are algebraically the local quadratic derivative; arbitrary functions/data are approximations.',
'Tiny steps amplify data/rounding errors; finite guards and fixture tolerance do not guarantee universal conditioning/accuracy.',
'Fraction oracle is exact for represented float inputs; original measurements/decimals are not recovered.']}
if __name__ == '__main__':
print(json.dumps(run_example(), ensure_ascii=False, indent=2, allow_nan=False))
مرجع دوم، مشتق پایههای چندجملهای لاگرانژ را با Fraction روی سه گرهٔ محلی محاسبه میکند؛ از تابع ترکیب شیب کمک نمیگیرد. حساب کسری، مقدار دقیقِ float موجود را نگه میدارد، نه دادهٔ اعشاریِ پیش از ورود به برنامه را. این مرجع و پاسخ تحلیلیِ تألیفی، دو راه متفاوت برای یافتن اشتباه وزن یا اندیساند.
هجده گروه بررسی اجرا شدهاند. چهار fixture با مرجع مستقل، دوازده تابع درجهٔ دو یا کمتر با مشتق تحلیلی، دادهٔ ثابت و خطی، تغییر مبدأ زمان و تغییر مقیاس ولتاژ آزموده میشوند. دوازده تابع یک گروه است و اجرای دوباره تعداد آزمون مستقل را زیاد نمیکند. تلورانس نسبی و مطلق ده به توان منفی دوازده برای این دادههای کوچک انتخاب شده؛ بخش مطلق واحد ولتبرثانیه دارد.
۴. نتیجهٔ اجرا و مرزهای استحکام عددی

سه خروجی در زمان نیم، یکونیم و سه ثانیه به جواب دستی نزدیکاند. مقدار میانیِ ثبتشده ۳٫۰۰۰۰۰۰۰۰۰۰۰۰۰۰۰۴ است؛ آن را در دادهٔ اجرا به سه تبدیل نکردهایم. راهنمای math پایتون معنای isclose، متناهی بودن و جمع fsum را جدا توضیح میدهد. fsum خطای تفریق یا ضربی را که قبلاً رخ داده، به دقت نامحدود تبدیل نمیکند.
{
"python_version": "3.14.5",
"runtime": "Python standard library only",
"units": {
"time": "s",
"voltage": "V",
"derivative": "V/s"
},
"times_s": [
0,
0.5,
1.5,
3,
4
],
"voltages_V": [
1,
0,
1,
10,
21
],
"authored_function": "v(t)=(2 V/s^2)*t^2-(3 V/s)*t+1 V",
"analytic_derivative": "(4 V/s^2)*t-3 V/s",
"hand_derivatives_V_per_s": [
-1.0,
3.0,
9.0
],
"result": [
{
"index": 1,
"time_s": 0.5,
"left_step_s": 0.5,
"right_step_s": 1.0,
"derivative_V_per_s": -1.0
},
{
"index": 2,
"time_s": 1.5,
"left_step_s": 1.0,
"right_step_s": 1.5,
"derivative_V_per_s": 3.0000000000000004
},
{
"index": 3,
"time_s": 3.0,
"left_step_s": 1.5,
"right_step_s": 1.0,
"derivative_V_per_s": 9.0
}
],
"unweighted_spanning_secants_V_per_s": [
0.0,
4.0,
8.0
],
"cubic_fixture": {
"center_s": 1,
"analytic_V_per_s": 3,
"coarse_V_per_s": 5.0,
"both_steps_halved_V_per_s": 3.4999999999999996,
"actual_absolute_error_ratio": 4.0000000000000036,
"hand_expected_absolute_error_ratio": 4.0
},
"polynomial_cases": 12,
"checks": {
"hand_quadratic_derivatives_at_three_interior_times": true,
"interior_indices_steps_and_no_endpoint_estimates": true,
"independent_exact_lagrange_derivative_four_fixtures": true,
"constant_voltage_derivative_is_zero": true,
"affine_voltage_is_reproduced_on_irregular_grid": true,
"twelve_quadratic_polynomials_match_analytic_reference": true,
"unweighted_spanning_secant_is_not_center_derivative": true,
"stretching_time_by_two_halves_derivatives": true,
"voltage_scale_and_dc_shift_transform_derivative": true,
"time_origin_translation_preserves_derivatives": true,
"authored_cubic_half_step_error_ratio_four": true,
"three_points_produce_only_one_center_record": true,
"tuple_list_repeatable_and_no_input_mutation": true,
"rejects_lengths_shapes_and_iterator_input": true,
"rejects_bad_times_duplicates_order_and_float_collapse": true,
"rejects_bad_voltage_types_and_nonfinite_values": true,
"explicit_units_and_millisecond_conversion": true,
"rejects_overflowed_span_difference_or_slope": true
},
"unique_checks": 18,
"limitations": [
"Interior estimates only; no endpoint formula, interpolation of missing data, smoothing, hardware or NumPy execution.",
"Weighted adjacent slopes are algebraically the local quadratic derivative; arbitrary functions/data are approximations.",
"Tiny steps amplify data/rounding errors; finite guards and fixture tolerance do not guarantee universal conditioning/accuracy.",
"Fraction oracle is exact for represented float inputs; original measurements/decimals are not recovered."
]
}
برای تابع مکعبیِ ساختگی، مشتق در یک ثانیه سه ولتبرثانیه است. با گام چپ یک و راست دو، برآورد پنج میشود؛ با نصف کردن هر دو گام حول همان مرکز، حدود سهونیم به دست میآید. خطای این fixture از دو به نیم کاهش مییابد و نسبت ثبتشده تقریباً چهار است. این مشاهدهٔ محدود، تضمین همگرایی با کاهش بیپایان گام در حضور نویز یا گردکردن نیست.
گام بسیار کوچک، اختلاف کوچک داده را بر عدد کوچک تقسیم میکند و حساسیت را بالا میبرد. کد گام، مجموع گامها، اختلاف ولتاژ، شیب و خروجی نامتناهی را رد میکند. ورودی متناهی بهتنهایی کافی نیست: تفریق دو ولتاژ بزرگ یا جمع فاصلهها میتواند سرریز کند. عبور از این کنترلها نیز گواه شرط عددی مناسب یا دقت عمومی برای هر داده نیست.
انتگرال ذوزنقهای نمونههای نامنظم مساحت با واحد ولتثانیه را بررسی میکند؛ اینجا نرخ تغییر با واحد ولتبرثانیه داریم. درونیابی خطی کالیبراسیون نیز قرارداد مرز را روشن میکند. این برنامه دادهٔ گمشده را بازسازی، هموارسازی یا کیفیت حسگر را ارزیابی نمیکند.
۵. چهار پرسش پاسخدار پیش از استفاده

پرسش اول: چرا در زمان نیم ثانیه، شیب کل بازه صفر ولی پاسخ مرکزی منفی یک است؟ پاسخ: فاصلهٔ چپ و راست برابر نیست. شیب کل بازه به محل دیگری از تابع درجهٔ دو مربوط میشود؛ وزندهیِ گامها مشتق را در مرکزِ انتخابشده بازتولید میکند.
پرسش دوم: زمانها دو برابر و ولتاژها ثابت بمانند، مشتق چه میشود؟ پاسخ: هر گام دو برابر و هر شیب نصف میشود؛ نسبت وزنها ثابت میماند، پس برآورد نیز نصف است. واحد همچنان ولتبرثانیه است.
پرسش سوم: چرا برای اولین و آخرین نمونه عددی نداریم؟ پاسخ: سیاست این درس فقط سهنقطهای داخلی است. ساخت فرمول یکطرفهٔ مرزی نیازمند قرارداد و آزمون جداست؛ صفر گذاشتن در دو انتها، مشاهده یا محاسبهٔ مشتق نیست.
پرسش چهارم: آیا ریزتر کردن گام همیشه جواب را بهتر میکند؟ پاسخ: خیر؛ خطای داده و نمایش عددی ممکن است غالب شود یا دو زمان یکسان نمایش داده شوند. منابع در ۲۴ مهر ۱۴۰۵ بازخوانی شدهاند؛ موفقیت هجده بررسی و مثال مکعبی، دقت اندازهگیری واقعی یا همهٔ دنبالهها را اثبات نمیکند.
منابع و مطالعهٔ بیشتر
- NumPy 2.4؛ gradient، گام نامساوی و شرایط تقریب داخلی
- Python 3.14؛ isfinite، isclose و fsum
- Python 3.14؛ Fraction و مقدار دقیق float ذخیرهشده
بررسی منابع: ۲۴ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

