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

۱. قرارداد مدل: میلی‌ولت، کد صحیح و بازسازی

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

مدل انتخابی ما یک کوانتیزه‌ساز ایده‌آل با ۲۵۶ کد صحیح از منفی ۱۲۸ تا مثبت ۱۲۷ است. گام بازسازی ده میلی‌ولت تعریف می‌شود و تابع بازسازی هر کد را در ده ضرب می‌کند. پس پایین‌ترین سطح بازسازی منفی ۱۲۸۰ و بالاترین سطح مثبت ۱۲۷۰ میلی‌ولت است. عبارت «هشت‌بیتی علامت‌دار» در این درس به همین محدوده اشاره دارد؛ قالب ارسال بایت یا رجیستر سخت‌افزار پیاده‌سازی نمی‌شود.

تابع اصلی ابتدا ورودی میلی‌ولت را بر گام تقسیم، نزدیک‌ترین عدد صحیح را انتخاب و نتیجه را به محدودهٔ مجاز محدود می‌کند. قرارداد تساوی فاصله نیز باید مشخص باشد: در نیمهٔ دو کد، کد زوج انتخاب می‌شود. از پیش فرض نکرده‌ایم که تمام مبدل‌های واقعی همین قرارداد را دارند. این مشخصات متعلق به مثال خودمان است و باید پیش از اتصال مدل به دادهٔ حسگر، با نیاز سامانه مقایسه شود.

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

۲. اعداد گویا و رفتار دقیق در نیمهٔ دو سطح

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

برای ساختن ورودی دو و نیم میلی‌ولت، از Fraction(5, 2) استفاده می‌کنیم. این انتخاب به ما اجازه می‌دهد فاصله‌ها و خطاهای این مدل را با کسرهای دقیق مقایسه کنیم. مستندات Fraction تفاوت ساخت کسر از صورت و مخرج با تبدیل یک float را توضیح می‌دهد. تبدیل float لزوماً همان کسر اعشاریِ مورد نظر نویسنده را بازسازی نمی‌کند؛ در این درس اصلاً چنین ورودی‌ای وارد تابع نمی‌شود.

برای ورودی پنج میلی‌ولت، نسبت به گام برابر یک‌دوم است؛ کد صفر انتخاب و مقدار بازسازی صفر می‌شود. ورودی پانزده میلی‌ولت میان کدهای یک و دو قرار می‌گیرد و کد دو را می‌گیرد. در طرف منفی نیز منفی پنج به صفر و منفی پانزده به کد منفی دو می‌رود. این چهار مثال نشان می‌دهند که «همیشه رو به بالا» یا «همیشه دور از صفر» توصیف این قرارداد نیست.

راهنمای round پایتون قرارداد واگذاری به روش گرد کردن شیء و انتخاب زوج در تساوی را بیان می‌کند. اینجا round روی Fraction اجرا می‌شود. بنابراین بحث رفتار مرزها را با تلورانس تقریبی float مخلوط نمی‌کنیم. اگر مسئلهٔ دیگری واقعاً ممیز شناور می‌خواهد، درس آزمون expm1 و انتخاب تلورانس مکمل مناسبی است؛ هر نمایش عددی باید با قرارداد خودش آزموده شود.

۳. کد کامل و مرجع مستقل با شمارش ۲۵۶ سطح

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

کد زیر با کتابخانهٔ استاندارد پایتون ۳٫۱۴٫۵ اجرا شده است. فایل را با نام quantizer_properties.py ذخیره و دستور python quantizer_properties.py را اجرا کنید. دادهٔ آزمون شامل ۱۲۰۱ ورودی از منفی ۱۵۰۰ تا مثبت ۱۵۰۰ میلی‌ولت، با فاصلهٔ دقیق دو و نیم میلی‌ولت است. این مجموعهٔ محدود شامل سطح‌های بازسازی، نیمهٔ فاصله‌ها و ورودی‌های اشباع می‌شود؛ تولید تصادفی یا کتابخانهٔ آزمون خارجی در کار نیست.

"""Exact rational verification of an authored ideal quantizer, not an ADC measurement."""
from fractions import Fraction as F
import json
import platform

STEP_MV = 10
LOW_CODE, HIGH_CODE = -128, 127


def quantize_mv(value):
    if type(value) not in (int, F):
        raise TypeError('Use integer or Fraction millivolts, not float or bool')
    return max(LOW_CODE, min(HIGH_CODE, round(F(value) / STEP_MV)))


def decode_mv(code):
    if type(code) is not int or not LOW_CODE <= code <= HIGH_CODE:
        raise ValueError('Signed eight-bit code required')
    return code * STEP_MV


def exhaustive_reference(value):
    # No rounding or clamping operation: enumerate every permitted reconstruction level.
    return min(range(LOW_CODE, HIGH_CODE+1),
               key=lambda c: (abs(c*STEP_MV-value), c % 2))


def rejects(call, exception):
    try:
        call()
    except exception:
        return True
    return False


def run_example():
    values = [F(i*5, 2) for i in range(-600, 601)]
    codes = [quantize_mv(v) for v in values]
    inside = [v for v in values if -1280 <= v <= 1270]
    symmetric = [v for v in values if -1270 <= v <= 1270]
    errors = [abs(F(decode_mv(quantize_mv(v)))-v) for v in inside]
    checks = {
        'all_codes_in_range': all(LOW_CODE <= c <= HIGH_CODE for c in codes),
        'monotonic': all(a <= b for a, b in zip(codes, codes[1:])),
        'all_inputs_match_enumerated_reference': all(quantize_mv(v) == exhaustive_reference(v) for v in values),
        'half_step_bound_in_declared_interval': max(errors) <= F(STEP_MV, 2),
        'reconstruction_requantization_idempotent': all(quantize_mv(decode_mv(c)) == c for c in codes),
        'every_code_roundtrips': all(quantize_mv(decode_mv(c)) == c for c in range(-128, 128)),
        'tie_positive_even': quantize_mv(5) == 0 and quantize_mv(15) == 2,
        'tie_negative_even': quantize_mv(-5) == 0 and quantize_mv(-15) == -2,
        'symmetry_only_in_selected_interval': all(quantize_mv(-v) == -quantize_mv(v) for v in symmetric),
        'positive_clipping': quantize_mv(1500) == 127,
        'negative_clipping': quantize_mv(-1500) == -128,
        'half_step_bound_fails_outside': abs(decode_mv(quantize_mv(1500))-1500) == 230,
        'global_odd_symmetry_counterexample': quantize_mv(-1500) != -quantize_mv(1500),
        'float_rejected': rejects(lambda: quantize_mv(0.1), TypeError),
        'bool_rejected': rejects(lambda: quantize_mv(True), TypeError),
        'out_of_range_code_rejected': rejects(lambda: decode_mv(128), ValueError),
    }
    assert all(checks.values()), checks
    return {'python': platform.python_version(), 'model': 'ideal_signed8_nearest_even_saturating',
            'input_unit': 'mV', 'step_mv': STEP_MV, 'code_range': [LOW_CODE, HIGH_CODE],
            'fixture_points': len(values), 'input_grid_mv': '5/2',
            'interior_max_absolute_error_mv': str(max(errors)),
            'positive_overload_error_mv': 230, 'checks': checks}


if __name__ == '__main__':
    print(json.dumps(run_example(), indent=2))

تابع مرجع، فرمول گرد کردن تابع اصلی را تکرار نمی‌کند. همهٔ ۲۵۶ کد مجاز را می‌گردد و کمترین فاصلهٔ بازسازی تا ورودی را انتخاب می‌کند؛ اگر فاصله برابر باشد، اولویت زوج بودن کد را می‌سنجد. به این ترتیب مسیر محاسبهٔ مرجع با مسیر «تقسیم، گرد کردن و محدودسازی» متفاوت است. البته هر دو هنوز به همان قرارداد نوشته‌شده وابسته‌اند؛ استقلال روش محاسبه، جای بررسی درستی خودِ مشخصات را نمی‌گیرد.

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

۴. خروجی اجرا: حد پنج میلی‌ولت کجا معتبر است؟

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

در بازهٔ اعلام‌شدهٔ منفی ۱۲۸۰ تا مثبت ۱۲۷۰ میلی‌ولت، بیشترین خطای مطلقِ ثبت‌شده روی شبکهٔ آزمون پنج میلی‌ولت است. استدلال هندسی این قرارداد نیز روشن است: بین دو سطح مجاور با فاصلهٔ ده میلی‌ولت، فاصله تا نزدیک‌ترین سطح از پنج بیشتر نمی‌شود. ما این حد را فقط برای همین بازه مطرح می‌کنیم؛ دربارهٔ تمام ورودی‌های خارج آن، ادعای یکسان نداریم.

{
  "python": "3.14.5",
  "model": "ideal_signed8_nearest_even_saturating",
  "input_unit": "mV",
  "step_mv": 10,
  "code_range": [
    -128,
    127
  ],
  "fixture_points": 1201,
  "input_grid_mv": "5/2",
  "interior_max_absolute_error_mv": "5",
  "positive_overload_error_mv": 230,
  "checks": {
    "all_codes_in_range": true,
    "monotonic": true,
    "all_inputs_match_enumerated_reference": true,
    "half_step_bound_in_declared_interval": true,
    "reconstruction_requantization_idempotent": true,
    "every_code_roundtrips": true,
    "tie_positive_even": true,
    "tie_negative_even": true,
    "symmetry_only_in_selected_interval": true,
    "positive_clipping": true,
    "negative_clipping": true,
    "half_step_bound_fails_outside": true,
    "global_odd_symmetry_counterexample": true,
    "float_rejected": true,
    "bool_rejected": true,
    "out_of_range_code_rejected": true
  }
}

مثلاً ورودی مثبت ۱۵۰۰ به کد ۱۲۷ محدود و به ۱۲۷۰ میلی‌ولت بازسازی می‌شود؛ خطای مطلق آن ۲۳۰ میلی‌ولت است. در منفی ۱۵۰۰، بازسازی منفی ۱۲۸۰ و خطا ۲۲۰ میلی‌ولت خواهد بود. نامتقارن بودن دو انتهای کد باعث می‌شود تقارن فرد را برای تمام ورودی‌ها ادعا نکنیم. در کد، این ویژگی فقط روی بازهٔ انتخابی منفی ۱۲۷۰ تا مثبت ۱۲۷۰ بررسی شده است.

همهٔ شانزده بررسی در اجرای ثبت‌شده موفق بوده‌اند، اما ۱۲۰۱ ورودی تمام اعداد گویا نیستند. همچنین بررسی بازگشت همهٔ ۲۵۶ کد، پوشش کامل فضای کد همین مدل را می‌دهد، نه پوشش کامل هر ورودی و هر خرابی سامانه. نتیجهٔ این اجرا دربارهٔ نویز، خطی بودن واقعی مبدل، نرخ نمونه‌برداری، تفکیک مؤثر یا دقت مدل یادگیری ماشین چیزی ثابت نمی‌کند. این موارد در مثال اصلاً مدل نشده‌اند.

۵. تمرین‌های پاسخ‌دار و گسترش مسئولانهٔ آزمون

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

تمرین اول: ورودی بیست‌وپنج میلی‌ولت چه کدی می‌گیرد؟ پاسخ: نسبت دو و نیم است و میان کدهای دو و سه، کد زوج دو انتخاب می‌شود؛ بازسازی بیست و خطا پنج میلی‌ولت است. تمرین دوم: آیا می‌توان حد پنج میلی‌ولت را برای ورودی ۱۵۰۰ به کار برد؟ پاسخ: خیر؛ ورودی بیرون بازهٔ اعلام‌شده است و اشباع، خطای ۲۳۰ میلی‌ولت ایجاد می‌کند.

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

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

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

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