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

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

در هر زمان، تازهترین رکورد با زمانِ کوچکتر یا مساویِ آن را انتخاب میکنیم. مستنداتِ merge_asof در pandas 3.0.6، جهتِ backward را از forward و nearest جدا میکند و مرتببودنِ کلید و تحملِ فاصله را توضیح میدهد. اینجا pandas اجرا نمیشود؛ اصلِ انتخابِ رکوردِ قبلی را با کتابخانهٔ استاندارد پیاده میکنیم.
پس از انتخاب، مقدارِ قبلی را تا نمونهٔ بعدی ثابت فرض میکنیم، اما مدتِ اعتبار نامحدود نیست. سنِ مجازِ ولتاژ ۸۰ و جریان ۴۰ میلیثانیه است؛ بازهٔ اعتبار از زمانِ نمونه آغاز و در کمینهٔ زمانِ نمونهٔ بعدی و مهلتِ سن تمام میشود. انتهای بازه خارج است. این آستانهها سیاستِ تألیفیِ همین مثالاند؛ سلامتِ حسگر یا دقتِ واقعیِ نگهداریِ مقدار را ثابت نمیکنند.
قبل از اولین نمونه، مقدار نامعلوم است. بعد از پایانِ اعتبار نیز نمونه کهنه است؛ هیچکدام را صفر نمیگذاریم. در زمانِ دقیقِ نمونهٔ تازه، همان نمونه انتخاب میشود؛ در پایانِ اعتبار، اگر جایگزین نداریم، مقدار کهنه است. اصطلاحِ previous در مرجعِ interp1d در SciPy 1.18.0 نیز به مقدارِ قبلی اشاره دارد. آن کلاس legacy است؛ این استناد تعریفِ مفهوم است، نه توصیهٔ اجرای آن یا ادعای تقلیدِ همهٔ قواعدِ API.
۳. انرژیِ بخشِ پوششدادهشده را حساب کنیم

در فاصلهای که هر دو مقدار معتبر و ثابتاند، سهمِ انرژی برابرِ ولتاژ ضربدر جریان ضربدر مدت است. یک میلیولت ضربدر یک میلیآمپر، یک میکرووات است؛ ضرب در یک میلیثانیه، یک نانوژول میدهد. پس میتوان سهمها را با عددِ صحیحِ نانوژول جمع کرد و برای ژول، بر یکمیلیارد تقسیم کرد. این دقتِ حسابی، فرضِ ثابتبودنِ سیگنال را دقیق نمیکند.
از صفر تا ۲۰، جریان نداریم. از ۲۰ تا ۵۰، توانِ مدل شش وات و سهمِ ۳۰ میلیثانیه، ۰٫۱۸ ژول است. از ۵۰ تا ۶۰، ۱۲ وات در ده میلیثانیه، ۰٫۱۲ ژول میدهد. از ۶۰ تا ۹۰، ۲۴ وات در ۳۰ میلیثانیه، ۰٫۷۲ ژول داریم. در ۹۰، جریانِ زمانِ ۵۰ کهنه میشود؛ تا ۱۱۰، سهمِ انرژی را نامعلوم نگه میداریم.
از ۱۱۰ تا ۱۲۰، جریانِ منفیِ ۰٫۲۵ آمپر در ۲۴ ولت، توانِ منفیِ شش وات و سهمِ منفیِ ۰٫۰۶ ژول دارد. جمعِ سهمهای معلوم، ۰٫۹۶ ژول است. فقط ۸۰ میلیثانیه پوشش داریم؛ ۲۰ میلیثانیه ابتدا نامعلوم و ۲۰ میلیثانیه کهنه است. این جمع را انرژیِ تمامِ پنجره یا میانگینِ توانِ کلِ ۱۲۰ میلیثانیه معرفی نمیکنیم.
نمونهٔ جریانِ دقیقاً در ۱۲۰، سهمِ زمانی در پنجره ندارد. همچنین نباید مانند مدلِ خطیتکهایِ انتگرالِ ولتاژ، دو انتها را خودکار با خط وصل کنیم؛ اینجا فرضِ نگهداریِ مقدارِ قبلی و دو کانال داریم. انتخابِ مدلِ دیگری میتواند عددِ دیگری بسازد، حتی اگر هر دو کد بدون خطا اجرا شوند.
۴. کد کامل و مرجعِ مستقلِ میلیثانیهها

کد، زمانِ رویداد و پایانِ اعتبارِ مؤثر را به نقاطِ شکست اضافه میکند؛ در هر فاصله، حالتِ دو کانال تغییر نمیکند. مرجعِ bisect_right، نقطهٔ درجِ بعد از مقدارهای مساوی را توضیح میدهد؛ یک اندیس کمتر، تازهترین زمانِ کوچکتر یا مساوی را میدهد. دادهٔ مرتب پیششرط است و پیش از اولین رکورد، اندیسِ منفی نباید به آخرین نمونه تبدیل شود.
فایلِ asof_energy.py مستقل و بدونِ وابستگیِ خارجی است. هر کانال، tuple دقیق با حداکثر ۶۴ رکوردِ دوعضوی است؛ زمانها int دقیقِ صعودی از صفر تا دههزار میلیثانیهاند. ولتاژ از صفر تا ۶۰۰۰۰ میلیولت و جریان از منفیِ ۲۰۰۰۰ تا ۲۰۰۰۰ میلیآمپر پذیرفته میشود. زمانِ تکراری، ترتیبِ کاهشی، bool و تبدیلِ خودکار رد میشوند. این کرانها برای آزمایشاند، نه ردهبندیِ تجهیزات.
"""Synthetic asynchronous DC logs: covered sample-and-hold energy, not a meter."""
from bisect import bisect_right
from fractions import Fraction
from itertools import combinations, product
import json
import platform
def checked_events(events, minimum, maximum):
if type(events) is not tuple:
raise TypeError('Exact tuple of events required')
if len(events) > 64:
raise ValueError('At most64 events per channel in this teaching profile')
previous = -1
for row in events:
if type(row) is not tuple or len(row) != 2:
raise TypeError('Exact(time_ms,value) tuple required')
time, value = row
if type(time) is not int or type(value) is not int:
raise TypeError('Exact int time and value; no bool or subclasses')
if not 0 <= time <= 10000 or time <= previous:
raise ValueError('Strictly increasing timestamps0..10000ms required')
if not minimum <= value <= maximum:
raise ValueError('Value outside declared physical-unit profile')
previous = time
def held_state(events, times, at_ms, max_age_ms):
"""Internal validated data: latest past-or-present event, never future fill."""
index = bisect_right(times, at_ms) - 1
if index < 0:
return 'unknown', None, None
stamp, value = events[index]
if at_ms >= stamp + max_age_ms:
return 'stale', stamp, None
return 'fresh', stamp, value
def energy_report(voltage, current, start_ms, end_ms, voltage_age_ms, current_age_ms):
"""Assume a common event-time clock and a constant held value, not accuracy."""
checked_events(voltage, 0, 60000)
checked_events(current, -20000, 20000)
if any(type(v) is not int for v in (start_ms, end_ms, voltage_age_ms, current_age_ms)):
raise TypeError('Exact integer window and age limits required')
if not 0 <= start_ms < end_ms <= 10000:
raise ValueError('Window must satisfy0<=start<end<=10000ms')
if not all(1 <= age <= 10000 for age in (voltage_age_ms, current_age_ms)):
raise ValueError('Positive age limits1..10000ms required')
breaks = {start_ms, end_ms}
for events, age in ((voltage, voltage_age_ms), (current, current_age_ms)):
for index, (stamp, _) in enumerate(events):
next_stamp = events[index + 1][0] if index + 1 < len(events) else end_ms
expiry = min(stamp + age, next_stamp)
if start_ms < stamp < end_ms:
breaks.add(stamp)
if start_ms < expiry < end_ms:
breaks.add(expiry)
times_v, times_i = tuple(t for t, _ in voltage), tuple(t for t, _ in current)
points = sorted(breaks)
rows = []
durations = {'covered': 0, 'initial_unknown': 0, 'stale': 0}
positive_nj = negative_nj = 0
for left, right in zip(points, points[1:]):
vstate, vtime, mv = held_state(voltage, times_v, left, voltage_age_ms)
istate, itime, ma = held_state(current, times_i, left, current_age_ms)
if vstate == istate == 'fresh':
status = 'covered'
contribution = mv * ma * (right - left)
positive_nj += max(contribution, 0)
negative_nj += min(contribution, 0)
else:
status = 'initial_unknown' if 'unknown' in (vstate, istate) else 'stale'
contribution = None
durations[status] += right - left
rows.append({'interval_ms': [left, right], 'status': status,
'voltage_state': vstate, 'current_state': istate,
'voltage_source_time_ms': vtime, 'current_source_time_ms': itime,
'voltage_mV': mv, 'current_mA': ma,
'covered_energy_nJ': contribution})
net_nj = positive_nj + negative_nj
return {'window_ms': [start_ms, end_ms], 'intervals': rows,
'durations_ms': durations, 'uncovered_ms': end_ms - start_ms - durations['covered'],
'positive_covered_energy_nJ': positive_nj,
'negative_covered_energy_nJ': negative_nj,
'net_covered_energy_nJ': net_nj,
'net_covered_energy_J_exact': str(Fraction(net_nj, 1000000000)),
'model_covers_entire_window': durations['covered'] == end_ms - start_ms}
def millisecond_cell_oracle(voltage, current, start, end, vage, iage):
"""Already validated integer-grid fixtures; no bisect, breaks or held helper.
Every event/expiry has an integer-ms boundary. State is constant inside each
unit cell, so midpoint enumeration integrates the chosen step model exactly.
This does not infer an unknown continuous physical waveform.
"""
duration = {'covered': 0, 'initial_unknown': 0, 'stale': 0}
positive = negative = 0
for tick in range(start, end):
midpoint = Fraction(2 * tick + 1, 2)
values, states = [], []
for channel, age in ((voltage, vage), (current, iage)):
candidates = [row for row in channel if row[0] <= midpoint]
if not candidates:
states.append('unknown'); values.append(None)
else:
chosen = max(candidates, key=lambda row: row[0])
if midpoint - chosen[0] >= age:
states.append('stale'); values.append(None)
else:
states.append('fresh'); values.append(chosen[1])
if states == ['fresh', 'fresh']:
state = 'covered'; energy = values[0] * values[1]
positive += max(energy, 0); negative += min(energy, 0)
else:
state = 'initial_unknown' if 'unknown' in states else 'stale'
duration[state] += 1
return duration, positive, negative
def summary(report):
return report['durations_ms'], report['positive_covered_energy_nJ'], report['negative_covered_energy_nJ']
def rejects(error, *args):
try:
energy_report(*args)
except error:
return True
return False
def run():
voltage = ((0, 12000), (60, 24000))
current = ((20, 500), (50, 1000), (110, -250), (120, 9000))
result = energy_report(voltage, current, 0, 120, 80, 40)
hand_rows = [([0, 20], 'initial_unknown', None), ([20, 50], 'covered', 180000000),
([50, 60], 'covered', 120000000), ([60, 90], 'covered', 720000000),
([90, 110], 'stale', None), ([110, 120], 'covered', -60000000)]
checks = {}
checks['literal_interval_contributions_and_signed_known_energy_24_over25_J'] = (
[(r['interval_ms'], r['status'], r['covered_energy_nJ']) for r in result['intervals']] == hand_rows
and result['net_covered_energy_J_exact'] == '24/25'
and result['durations_ms'] == {'covered': 80, 'initial_unknown': 20, 'stale': 20}
and result['uncovered_ms'] == 40 and result['model_covers_entire_window'] is False)
checks['end_cutoff_sample_has_zero_duration_no_future_backfill'] = (
energy_report(voltage, current[:-1], 0, 120, 80, 40) == result
and result['intervals'][0]['current_mA'] is None
and energy_report(voltage, current + ((200, -10000),), 0, 120, 80, 40) == result)
zero = energy_report(((0, 12000),), ((0, 0),), 0, 10, 20, 20)
absent = energy_report(((0, 12000),), (), 0, 10, 20, 20)
checks['observed_zero_current_is_covered_zero_not_missing_zero_substitution'] = (
zero['durations_ms']['covered'] == 10 and zero['net_covered_energy_nJ'] == 0
and absent['durations_ms']['covered'] == 0 and absent['uncovered_ms'] == 10
and absent['intervals'][0]['covered_energy_nJ'] is None)
expiry = energy_report(((0, 12000),), ((0, 1000),), 0, 10, 100, 4)
checks['expiry_splits_interval_even_without_new_event_and_equality_is_stale'] = (
expiry['durations_ms'] == {'covered': 4, 'initial_unknown': 0, 'stale': 6}
and expiry['net_covered_energy_J_exact'] == '6/125'
and energy_report(((0, 12000),), ((0, 1000),), 4, 5, 100, 4)['durations_ms']['covered'] == 0)
renewed = energy_report(((0, 12000),), ((0, 1000), (4, 0)), 0, 8, 100, 4)
checks['event_on_expiry_replaces_old_sample_at_left_boundary'] = (
renewed['durations_ms']['covered'] == 8 and renewed['net_covered_energy_nJ'] == 48000000
and renewed['intervals'][-1]['current_mA'] == 0)
left = energy_report(voltage, current, 0, 60, 80, 40)
right = energy_report(voltage, current, 60, 120, 80, 40)
checks['split_horizon_additivity_uses_past_state_at_each_new_start'] = (
left['net_covered_energy_nJ'] + right['net_covered_energy_nJ'] == result['net_covered_energy_nJ']
and all(left['durations_ms'][k] + right['durations_ms'][k] == result['durations_ms'][k] for k in result['durations_ms'])
and energy_report(voltage, current, 30, 70, 80, 40)['net_covered_energy_J_exact'] == '12/25')
flipped = energy_report(voltage, tuple((t, -i) for t, i in current), 0, 120, 80, 40)
checks['current_sign_reversal_negates_net_not_coverage_and_swaps_signed_parts'] = (
flipped['net_covered_energy_nJ'] == -result['net_covered_energy_nJ']
and flipped['durations_ms'] == result['durations_ms']
and flipped['positive_covered_energy_nJ'] == -result['negative_covered_energy_nJ'])
shifted = energy_report(tuple((t + 500, v) for t, v in voltage), tuple((t + 500, i) for t, i in current), 500, 620, 80, 40)
stretched = energy_report(tuple((2*t, v) for t, v in voltage), tuple((2*t, i) for t, i in current), 0, 240, 160, 80)
checks['time_origin_and_explicit_time_scale_have_different_unit_effects'] = (
summary(shifted) == summary(result)
and stretched['net_covered_energy_nJ'] == 2 * result['net_covered_energy_nJ']
and stretched['durations_ms'] == {k: 2*v for k, v in result['durations_ms'].items()})
doubled_v = energy_report(tuple((t, 2*v) for t, v in voltage), current, 0, 120, 80, 40)
checks['voltage_scaling_doubles_energy_with_same_coverage'] = (
doubled_v['net_covered_energy_nJ'] == 2 * result['net_covered_energy_nJ']
and doubled_v['durations_ms'] == result['durations_ms'])
profiles = [()]
for size in (1, 2):
for times in combinations(range(4), size):
for values in product((0, 1, 2), repeat=size):
profiles.append(tuple(zip(times, values)))
cases = 0
for va, ia in product(profiles, repeat=2):
signed_i = tuple((t, i-1) for t, i in ia)
vage, iage = cases % 3 + 1, (cases // 3) % 3 + 1
actual = energy_report(va, signed_i, 0, 4, vage, iage)
assert summary(actual) == millisecond_cell_oracle(va, signed_i, 0, 4, vage, iage)
cases += 1
checks['independent_rational_midpoint_fullscan_oracle_ONE_family'] = cases == len(profiles)**2
checks['main_fixture_matches_independent_cell_reference'] = (
summary(result) == millisecond_cell_oracle(voltage, current, 0, 120, 80, 40))
class IntChild(int):
pass
class TupleChild(tuple):
def __len__(self):
raise AssertionError('Excluded tuple hooks must not execute')
valid = (((0, 12000),), ((0, 1000),), 0, 10, 20, 20)
malformed = [([ (0,12000) ], valid[1], 0, 10, 20, 20),
(TupleChild(valid[0]), valid[1], 0, 10, 20, 20),
(((0,True),), valid[1], 0, 10, 20, 20),
(((0,IntChild(1)),), valid[1], 0, 10, 20, 20),
(valid[0], valid[1], False, 10, 20, 20),
(valid[0], valid[1], 0, 10, 20.0, 20)]
wrong_values = [(((0,1),(0,2)), valid[1], 0, 10, 20, 20),
(((2,1),(1,2)), valid[1], 0, 10, 20, 20),
(((0,-1),), valid[1], 0, 10, 20, 20),
(valid[0], ((0,20001),), 0, 10, 20, 20),
(valid[0], valid[1], 0, 0, 20, 20),
(valid[0], valid[1], 0, 10, 0, 20),
(tuple((t,1) for t in range(65)), valid[1], 0, 10, 20, 20)]
checks['strict_profile_rejects_duplicate_time_bad_order_types_and_bounds'] = (
all(rejects(TypeError, *args) for args in malformed)
and all(rejects(ValueError, *args) for args in wrong_values))
extreme = energy_report(((0,60000),), ((0,20000),), 0, 10000, 10000, 10000)
checks['inclusive_unit_domain_endpoints_max_records_and_empty_channel_policy'] = (
extreme['net_covered_energy_J_exact'] == '12000'
and energy_report(tuple((t,1) for t in range(64)), (), 0, 10000, 1, 1)['uncovered_ms'] == 10000
and energy_report((), (), 0, 10, 1, 1)['durations_ms']['initial_unknown'] == 10)
checks['inputs_and_literal_fixture_output_preserved_after_all_checks'] = (
voltage == ((0,12000),(60,24000)) and current == ((20,500),(50,1000),(110,-250),(120,9000))
and energy_report(voltage,current,0,120,80,40) == result)
assert all(type(v) is bool and v for v in checks.values()), checks
return {'python': platform.python_version(), 'implementation': platform.python_implementation(),
'dependencies': 'standard library only; Pandas and SciPy not executed',
'model': 'Assumed common event-time clock; zero-order hold on half-open fresh intervals, not physical reconstruction or clock synchronization',
'units': {'time':'ms','voltage':'mV','current':'mA positive into load','integer_energy':'nJ','one_mV_mA_ms_in_J':'1/1000000000'},
'voltage_events_mV': voltage, 'current_events_mA': current,
'max_age_ms': {'voltage':80,'current':40}, 'result': result,
'unknown_intervals_are_not_inferred_zero_or_total_window_energy': True,
'independent_oracle_cases_inside_ONE_family': cases,
'checks': checks, 'unique_check_groups':len(checks)}
if __name__ == '__main__':
print(json.dumps(run(), ensure_ascii=True, indent=2, allow_nan=False))
مرجعِ مستقل، نقاطِ شکست یا جستوجوی دودویی را استفاده نمیکند. هر سلولِ یکمیلیثانیهای را در نقطهٔ میانیِ کسری بررسی و تمامِ رکوردها را برای آخرین زمانِ مجاز میگردد. چون تمامِ زمانها و پایانهای اعتبار روی شبکهٔ صحیحاند، حالت درونِ هر سلول ثابت است؛ این شمارش، انتگرالِ دقیقِ مدلِ انتخابی را میدهد. این استدلال دربارهٔ موجِ پیوستهٔ ناشناخته یا زمانِ اعشاریِ خارج از قرارداد نیست.
{
"python": "3.14.5",
"implementation": "CPython",
"dependencies": "standard library only; Pandas and SciPy not executed",
"model": "Assumed common event-time clock; zero-order hold on half-open fresh intervals, not physical reconstruction or clock synchronization",
"units": {
"time": "ms",
"voltage": "mV",
"current": "mA positive into load",
"integer_energy": "nJ",
"one_mV_mA_ms_in_J": "1/1000000000"
},
"voltage_events_mV": [
[
0,
12000
],
[
60,
24000
]
],
"current_events_mA": [
[
20,
500
],
[
50,
1000
],
[
110,
-250
],
[
120,
9000
]
],
"max_age_ms": {
"voltage": 80,
"current": 40
},
"result": {
"window_ms": [
0,
120
],
"intervals": [
{
"interval_ms": [
0,
20
],
"status": "initial_unknown",
"voltage_state": "fresh",
"current_state": "unknown",
"voltage_source_time_ms": 0,
"current_source_time_ms": null,
"voltage_mV": 12000,
"current_mA": null,
"covered_energy_nJ": null
},
{
"interval_ms": [
20,
50
],
"status": "covered",
"voltage_state": "fresh",
"current_state": "fresh",
"voltage_source_time_ms": 0,
"current_source_time_ms": 20,
"voltage_mV": 12000,
"current_mA": 500,
"covered_energy_nJ": 180000000
},
{
"interval_ms": [
50,
60
],
"status": "covered",
"voltage_state": "fresh",
"current_state": "fresh",
"voltage_source_time_ms": 0,
"current_source_time_ms": 50,
"voltage_mV": 12000,
"current_mA": 1000,
"covered_energy_nJ": 120000000
},
{
"interval_ms": [
60,
90
],
"status": "covered",
"voltage_state": "fresh",
"current_state": "fresh",
"voltage_source_time_ms": 60,
"current_source_time_ms": 50,
"voltage_mV": 24000,
"current_mA": 1000,
"covered_energy_nJ": 720000000
},
{
"interval_ms": [
90,
110
],
"status": "stale",
"voltage_state": "fresh",
"current_state": "stale",
"voltage_source_time_ms": 60,
"current_source_time_ms": 50,
"voltage_mV": 24000,
"current_mA": null,
"covered_energy_nJ": null
},
{
"interval_ms": [
110,
120
],
"status": "covered",
"voltage_state": "fresh",
"current_state": "fresh",
"voltage_source_time_ms": 60,
"current_source_time_ms": 110,
"voltage_mV": 24000,
"current_mA": -250,
"covered_energy_nJ": -60000000
}
],
"durations_ms": {
"covered": 80,
"initial_unknown": 20,
"stale": 20
},
"uncovered_ms": 40,
"positive_covered_energy_nJ": 1020000000,
"negative_covered_energy_nJ": -60000000,
"net_covered_energy_nJ": 960000000,
"net_covered_energy_J_exact": "24/25",
"model_covers_entire_window": false
},
"unknown_intervals_are_not_inferred_zero_or_total_window_energy": true,
"independent_oracle_cases_inside_ONE_family": 4489,
"checks": {
"literal_interval_contributions_and_signed_known_energy_24_over25_J": true,
"end_cutoff_sample_has_zero_duration_no_future_backfill": true,
"observed_zero_current_is_covered_zero_not_missing_zero_substitution": true,
"expiry_splits_interval_even_without_new_event_and_equality_is_stale": true,
"event_on_expiry_replaces_old_sample_at_left_boundary": true,
"split_horizon_additivity_uses_past_state_at_each_new_start": true,
"current_sign_reversal_negates_net_not_coverage_and_swaps_signed_parts": true,
"time_origin_and_explicit_time_scale_have_different_unit_effects": true,
"voltage_scaling_doubles_energy_with_same_coverage": true,
"independent_rational_midpoint_fullscan_oracle_ONE_family": true,
"main_fixture_matches_independent_cell_reference": true,
"strict_profile_rejects_duplicate_time_bad_order_types_and_bounds": true,
"inclusive_unit_domain_endpoints_max_records_and_empty_channel_policy": true,
"inputs_and_literal_fixture_output_preserved_after_all_checks": true
},
"unique_check_groups": 14
}
خروجی، سهمِ مثبت و منفی، جمعِ علامتدار، مدتِ پوشش و مدتِ نامعلوم را جدا ثبت میکند. مقایسهٔ مرجع روی پروفایلهای کوچک، یکی از گروههای نامدار است؛ شمارِ جفتها تعدادِ مقاله یا اجرای مستقل نیست. آزمونهای جدا، پایانِ اعتبار بدون رویداد تازه، جایگزینی روی مرز، جریانِ صفرِ معتبر، بریدنِ پنجره و کشیدگیِ محورِ زمان با حفظِ واحد را بررسی میکنند. نسخهٔ اجرای واقعی در خروجی است؛ مستنداتِ خواندهشده با اجرای pandas یا SciPy برابر نیستند.
۵. چهار پرسش دربارهٔ زمان و انرژیِ نامعلوم

پرسش اول: چرا دادهٔ زمانِ ۲۰ را به ابتدای پنجره نبریم؟ پاسخ: مقدارِ جریانِ قبل از ۲۰ داده نشده و فرضِ ما نگهداریِ آخرین نمونهٔ قبلی است. پرکردن از آینده، مدل را عوض میکند. مجهولبودنِ این بخش با جریانِ مشاهدهشدهٔ صفر فرق دارد؛ دومی میتواند مدتِ پوششِ معتبر و انرژیِ صفر داشته باشد.
پرسش دوم: اگر بعد از ۵۰ هیچ رویدادی تا ۱۱۰ نیاید، چه چیزی در ۹۰ تغییر میکند؟ پاسخ: پایانِ اعتبارِ جریان رخ میدهد. الگوریتم باید این زمان را نیز بشکند؛ فقط پیمایشِ زمانهای دریافتشده، بازهٔ کهنه را پنهان میکند. صفرکردنِ جریان در این فاصله نیز برآوردِ انرژی نیست؛ عددِ ناشناخته را به عددِ معلوم تبدیل کردهایم.
پرسش سوم: نمونهٔ دقیقاً روی پایانِ پنجره چه اثری دارد؟ پاسخ: در پنجرهٔ نیمهباز، مدتِ مثبتی برای آن باقی نیست. حذفِ نمونهٔ ۱۲۰ یا افزودنِ نمونهای بعدتر، گزارشِ پنجرهٔ قبلی را عوض نمیکند. اگر پنجرهٔ تازه از ۱۲۰ آغاز شود، همان رکورد میتواند طبقِ سیاستِ سن، مقدارِ آغازِ بخشِ تازه باشد.
پرسش چهارم: آیا ۰٫۹۶ ژول را میتوان انرژیِ واقعیِ بار دانست؟ پاسخ: خیر؛ این جمعِ علامتدارِ بخشِ پوششدادهشده در مدلِ نگهداری است. شکلِ موجِ میانِ نمونهها، خطای زمان، کالیبراسیون و انرژیِ ۴۰ میلیثانیهٔ فاقدِ پوشش تعیین نشدهاند. منابع در ۲۸ مهر ۱۴۰۵ خوانده شدهاند؛ تاریخِ نخستین انتشارِ صفحات معلوم نیست و این آموزش، خبر یا نتیجهٔ آزمایشِ دستگاه نیست.
منابع و مطالعهٔ بیشتر
- pandas3.0.6؛ اتصالِ زمانیِ backward و مرزِ فاصله
- SciPy1.18.0؛ معنای previous و محدودیتِ کلاسِ legacy
- Python3.14.8؛ bisect_right و آخرین کلیدِ کوچکتر یا مساوی
بررسی منابع: ۲۸ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

