دو مجموعهٔ تأخیر می‌توانند میانگینِ برابر داشته باشند و شکلشان متفاوت باشد. برای دیدنِ این تفاوت، فقط کم‌کردنِ میانگین‌ها کافی نیست. این درس فاصلهٔ Wasserstein-1 را برای دو توزیعِ متناهیِ ساختگی با پایتون محاسبه می‌کند: جرم‌ها جدا نرمال می‌شوند، فاصله با حسابِ گویا به دست می‌آید و مرجعِ دیگری همان جواب را از جابه‌جاییِ جرم می‌سازد. دادهٔ سرویس، درخواستِ شبکه یا زمان‌سنجیِ واقعی نداریم.

۱. دو توزیعِ تأخیر، دو جرمِ نرمال‌شده

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

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

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

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

در مستندِ رسمیِ Wasserstein-1 در SciPy، داده می‌تواند مشاهده یا محلِ تکیه‌گاه باشد و وزن‌ها سهمِ آن محل‌ها را تعیین می‌کنند. ما فقط تعریفِ یک‌بعدی و رابطهٔ آن با CDF را می‌گیریم. SciPy در برنامه وارد یا اجرا نمی‌شود؛ حدودِ ورودی و نمایشِ خروجی، قراردادِ همین پیاده‌سازیِ مستقل‌اند.

۲. مساحتِ فاصلهٔ دو CDF با واحدِ میلی‌ثانیه

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

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

برای بازهٔ چپ تا راست، کد جرمِ محل‌های کوچک‌تر یا مساویِ چپ را می‌شمارد. خودِ نقطهٔ پرش، طولی ندارد و مساحتی اضافه نمی‌کند. پیش از کمترین محل، هر دو CDF صفر و پس از بزرگ‌ترین محل، هر دو یک‌اند؛ بیرونِ این ناحیه سهمی نداریم. اگر فقط یک محلِ مشترک باشد، فهرستِ بازه‌ها تهی و فاصله صفر است.

جرم و اختلافِ CDF بی‌بُعدند؛ طولِ بازه میلی‌ثانیه است. پس جواب نیز میلی‌ثانیه دارد، نه میلی‌ثانیهٔ مربع و نه درصد. هزینهٔ این فاصله از قدرمطلقِ جابه‌جایی استفاده می‌کند؛ مربعِ فاصله، سؤالِ دیگری می‌سازد. در درسِ صدکِ تأخیر یک نقطهٔ رتبه‌ای را گزارش می‌کردیم؛ اینجا همهٔ شکلِ توزیع در مساحت دخالت دارد.

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

۳. میانگینِ برابر، فاصلهٔ مثبت و جهتِ نامعلوم

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

شاهدِ اصلی، توزیعِ A با یک سهم در صفر و یک سهم در بیست و توزیعِ B با دو سهم در ده است. میانگینِ هر دو ده میلی‌ثانیه است. روی بازهٔ صفر تا ده، فاصلهٔ CDFها یک‌دوم و مساحت پنج است؛ روی ده تا بیست نیز مساحت پنج می‌شود. جوابِ دستی ده میلی‌ثانیه است، با اینکه اختلافِ میانگین‌ها صفر است.

فاصله جهت ندارد: عوض‌کردنِ A و B همان ده را می‌دهد. از آن نمی‌فهمیم کدام نسخه سریع‌تر است، یا تغییر به سودِ کاربر بوده است. نمونهٔ ساختگی حتی دو نسخهٔ واقعی را اندازه نگرفته است. درسِ مقایسهٔ جفت‌شدهٔ تأخیر، اختلافِ درونِ جفت و فرضِ آزمون را جدا بررسی می‌کند؛ این فاصله نه جفت‌بندی می‌سازد و نه احتمالِ دنباله می‌دهد.

شاهدِ وزن‌دار، A با شمارش‌های سه در صفر و یک در بیست و B با دو در صفر و شش در بیست است. مجموع‌ها چهار و هشت‌اند، اما جرم‌های تجمعیِ بازه به‌ترتیب سه‌چهارم و یک‌چهارم‌اند. اختلافِ یک‌دوم در طولِ بیست، باز هم ده میلی‌ثانیه می‌دهد. مقایسهٔ مستقیمِ شمارشِ سه و دو، جایِ نرمال‌سازیِ مستقل را نمی‌گیرد.

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

۴. کدِ کامل و مرجعِ مستقلِ جابه‌جاییِ جرم

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

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

"""Exact finite synthetic latency distributions; no service or SciPy execution.

Own profile: exact tuple of1..8 exact(ms,count) tuples, exact int milliseconds
0..10000 and exact positive int counts1..16. Duplicate locations combine.
Each distribution is independently normalized to probability mass1.
"""
from fractions import Fraction
from itertools import permutations, product
import json
import platform
import sys


def checked_profile(records):
    if type(records) is not tuple or not 1 <= len(records) <= 8:
        raise ValueError('one_to_eight_exact_tuple_rows')
    counts = {}
    for row in records:
        if type(row) is not tuple or len(row) != 2:
            raise ValueError('exact_ms_count_pair')
        milliseconds, count = row
        if type(milliseconds) is not int or not 0 <= milliseconds <= 10000:
            raise ValueError('exact_integer_ms_0_to10000')
        if type(count) is not int or not 1 <= count <= 16:
            raise ValueError('exact_positive_count_1_to16')
        counts[milliseconds] = counts.get(milliseconds, 0) + count
    return tuple(sorted(counts.items()))


def cdf_distance(a_records, b_records):
    """Integrate absolute CDF gap over adjacent combined support intervals."""
    a, b = checked_profile(a_records), checked_profile(b_records)
    total_a, total_b = sum(w for _, w in a), sum(w for _, w in b)
    support = sorted({x for x, _ in a} | {x for x, _ in b})
    distance = Fraction(0)
    intervals = []
    for left, right in zip(support, support[1:]):
        cdf_a = Fraction(sum(w for x, w in a if x <= left), total_a)
        cdf_b = Fraction(sum(w for x, w in b if x <= left), total_b)
        gap = abs(cdf_a - cdf_b)
        area = gap * (right - left)
        distance += area
        intervals.append({'left_ms': left, 'right_ms': right,
                          'cdf_a': cdf_a, 'cdf_b': cdf_b,
                          'absolute_cdf_gap': gap, 'area_ms': area})
    return {
        'unit': 'ms', 'distance_ms': distance,
        'input_count_totals': (total_a, total_b),
        'normalized_a': tuple((x, Fraction(w, total_a)) for x, w in a),
        'normalized_b': tuple((x, Fraction(w, total_b)) for x, w in b),
        'intervals': tuple(intervals),
    }


def transport_reference(a_rows, b_rows):
    """Independent ordered mass transfer; trusted finite fixtures only.

    No production checker, CDF table, interval support union or production
    distance function builds this reference. Repeated locations are allowed.
    """
    total_a, total_b = sum(w for _, w in a_rows), sum(w for _, w in b_rows)
    a = sorted((x, Fraction(w, total_a)) for x, w in a_rows)
    b = sorted((x, Fraction(w, total_b)) for x, w in b_rows)
    i = j = 0
    left_a, left_b = a[0][1], b[0][1]
    cost = Fraction(0)
    while i < len(a) and j < len(b):
        moved = min(left_a, left_b)
        cost += moved * abs(a[i][0] - b[j][0])
        left_a -= moved
        left_b -= moved
        if left_a == 0:
            i += 1
            if i < len(a):
                left_a = a[i][1]
        if left_b == 0:
            j += 1
            if j < len(b):
                left_b = b[j][1]
    assert i == len(a) and j == len(b)
    return cost


def mean_reference(rows):
    return Fraction(sum(x * w for x, w in rows), sum(w for _, w in rows))


def rejected(value):
    try:
        cdf_distance(value, ((0, 1),))
    except ValueError:
        return True
    return False


def run_example():
    a, b = ((0, 1), (20, 1)), ((10, 2),)
    result = cdf_distance(a, b)
    weighted_a, weighted_b = ((0, 3), (20, 1)), ((0, 2), (20, 6))
    weighted = cdf_distance(weighted_a, weighted_b)
    fractional = cdf_distance(((0, 1), (10, 2)), ((0, 1), (10, 1)))
    profiles = [tuple((x, count) for x, count in enumerate(counts) if count)
                for counts in product(range(3), repeat=4) if any(counts)]
    oracle_ok = True
    oracle_pairs = 0
    distances = {}
    for i, left in enumerate(profiles):
        for j, right in enumerate(profiles):
            value = cdf_distance(left, right)['distance_ms']
            oracle_ok = oracle_ok and value == transport_reference(left, right)
            distances[i, j] = value
            oracle_pairs += 1
    triangle_indices = tuple(range(0, len(profiles), 10))
    triangle_cases = 0
    triangle_ok = True
    for i, j, k in product(triangle_indices, repeat=3):
        triangle_ok = triangle_ok and distances[i, k] <= distances[i, j] + distances[j, k]
        triangle_cases += 1
    permuted = all(cdf_distance(left, right) == weighted
                   for left in permutations(weighted_a)
                   for right in permutations(weighted_b))
    split_a = ((0, 1), (0, 2), (20, 1))
    scale_a = tuple((x, 2 * count) for x, count in weighted_a)
    scale_b = tuple((x, 2 * count) for x, count in weighted_b)
    shifted = cdf_distance(tuple((x + 100, count) for x, count in a),
                           tuple((x + 100, count) for x, count in b))
    stretched = cdf_distance(tuple((3 * x, count) for x, count in a),
                             tuple((3 * x, count) for x, count in b))

    class IntChild(int):
        pass

    class TupleChild(tuple):
        pass

    invalid = ((), [], '0,1', (((0, 1),),), ((0,),), ([0, 1],),
               ((True, 1),), ((0.0, 1),), ((-1, 1),), ((10001, 1),),
               ((IntChild(1), 1),), ((0, True),), ((0, 1.0),),
               ((0, 0),), ((0, -1),), ((0, 17),), ((0, IntChild(1)),),
               (TupleChild((0, 1)),), TupleChild(((0, 1),)),
               tuple((x, 1) for x in range(9)))
    maximum = cdf_distance(tuple((10000, 16) for _ in range(8)), ((0, 16),))
    before_a, before_b = tuple(a), tuple(b)
    changed = cdf_distance(a, b)
    changed['intervals'][0]['area_ms'] = Fraction(999)
    n, d = fractional['distance_ms'].as_integer_ratio()
    checks = {
        'equal_mean_bimodal_hand_two_areas5_plus5_equals10ms':
            mean_reference(a) == mean_reference(b) == 10
            and result['distance_ms'] == 10
            and [(row['left_ms'], row['right_ms'], row['area_ms']) for row in result['intervals']]
                == [(0, 10, Fraction(5)), (10, 20, Fraction(5))],
        'unequal_count_totals_normalized_and_fractional_hand_answer':
            weighted['input_count_totals'] == (4, 8)
            and weighted['distance_ms'] == 10
            and weighted['intervals'][0]['cdf_a'] == Fraction(3, 4)
            and weighted['intervals'][0]['cdf_b'] == Fraction(1, 4)
            and fractional['distance_ms'] == Fraction(5, 3),
        'independent_sorted_mass_transfer_all_small_profiles_ONE_family':
            oracle_ok and len(profiles) == 80 and oracle_pairs == 6400,
        'identity_uses_normalized_distribution_not_raw_sample_total':
            all(distances[i, i] == 0 for i in range(80))
            and cdf_distance(a, ((0, 4), (20, 4)))['distance_ms'] == 0,
        'symmetric_unsigned_distance_all_small_profile_pairs':
            all(distances[i, j] == distances[j, i] for i in range(80) for j in range(80)),
        'common_translation_preserves_distance_changes_each_mean':
            shifted['distance_ms'] == result['distance_ms']
            and mean_reference(tuple((x + 100, w) for x, w in a)) == mean_reference(a) + 100,
        'physical_duration_scale_in_same_ms_scales_distance':
            stretched['distance_ms'] == 3 * result['distance_ms'] == 30,
        'input_row_permutations_preserve_complete_normalized_result': permuted,
        'splitting_duplicate_support_count_preserves_distribution':
            cdf_distance(split_a, weighted_b) == weighted,
        'independent_count_scaling_does_not_change_probability_distance':
            cdf_distance(scale_a, weighted_b)['distance_ms'] == weighted['distance_ms']
            and cdf_distance(weighted_a, scale_b)['distance_ms'] == weighted['distance_ms'],
        'finite_triangle_inequality_witnesses': triangle_ok and triangle_cases == 512,
        'singletons_and_closed_resource_endpoints_have_manual_bounds':
            cdf_distance(((10, 7),), ((13, 2),))['distance_ms'] == 3
            and maximum['distance_ms'] == 10000
            and maximum['input_count_totals'] == (128, 16)
            and cdf_distance(((0, 1),), ((0, 16),))['distance_ms'] == 0,
        'exact_input_shape_types_positive_weights_and_caps_rejected': all(rejected(value) for value in invalid),
        'inputs_preserved_fresh_output_exact_ratio_and_explicit_ms':
            a == before_a and b == before_b and cdf_distance(a, b) == result
            and changed['intervals'][0]['area_ms'] == 999
            and (n, d) == (5, 3) and d > 0
            and all(value['unit'] == 'ms' for value in (result, weighted, fractional, maximum)),
    }
    assert len(checks) == 14 and all(type(value) is bool and value for value in checks.values()), checks
    return {
        'python': platform.python_version(), 'dependencies': 'Python standard library only',
        'scope': 'Exact weighted1D finite synthetic milliseconds; W1 unsigned distribution distance, not bias, significance or service speed.',
        'equal_mean_fixture': {'a_ms_count': a, 'b_ms_count': b,
                               'mean_a_ms': mean_reference(a), 'mean_b_ms': mean_reference(b), 'result': result},
        'unequal_totals_fixture': {'a_ms_count': weighted_a, 'b_ms_count': weighted_b, 'result': weighted},
        'fractional_distance_fixture': fractional,
        'independent_transport_ONE_family': {'profiles': len(profiles), 'ordered_pairs': oracle_pairs},
        'triangle_triples_inside_one_group': triangle_cases,
        'limits': {'rows_per_distribution': [1, 8], 'integer_ms': [0, 10000],
                   'positive_count_per_row': [1, 16], 'max_input_count_total': 128,
                   'combined_support_points_at_most': 16, 'intervals_at_most': 15,
                   'zero_weight_policy': 'rejected; own educational policy', 'duplicate_support_policy': 'combine counts'},
        'scipy_executed': False, 'actual_service_requests': 0, 'actual_timer_measurements': 0,
        'population_inference_or_deployment_decision': False, 'checks': checks,
    }


def fraction_json(value):
    if type(value) is Fraction:
        n, d = value.as_integer_ratio()
        return {'numerator': n, 'denominator': d}
    raise TypeError('unsupported JSON value')


if __name__ == '__main__':
    text = json.dumps(run_example(), ensure_ascii=True, indent=2, allow_nan=False, default=fraction_json) + '\n'
    sys.stdout.buffer.write(text.encode('utf-8'))

مرجعِ مستقل، محل‌های هر طرف را جدا مرتب می‌کند و از کوچک‌ترین محل‌ها جرم جابه‌جا می‌کند. هر گام، کمترین جرمِ باقی‌ماندهٔ دو محل را با هزینهٔ قدرمطلقِ فاصله منتقل می‌کند. این مسیر از جدولِ CDF، اجتماعِ محل‌ها، اعتبارسنج یا تابعِ اصلی جواب نمی‌گیرد. دو نمایشِ محاسباتی، تعریفِ فاصله را از دو طرف کنترل می‌کنند؛ حسابِ گویا هم مشترکاً ابزارِ عددیِ آن‌هاست.

برای خانوادهٔ متناهی، چهار محلِ صفر تا سه و شمارش‌های صفر، یک یا دو می‌سازیم؛ خانهٔ صفر حذف و بردارِ کاملاً صفر کنار گذاشته می‌شود. هشتاد پروفایل و ۶۴۰۰ جفتِ مرتب با مرجع مقایسه می‌شوند؛ همه داخلِ یک گروه‌اند. چهارده گروهِ نام‌دار، شاهدِ دستی، تقارن، جابه‌جاییِ مشترک، مقیاسِ مدت، ترکیبِ محلِ تکراری، وزن‌ها، مرزها و حفظِ ورودی را نیز بررسی می‌کنند.

{
  "python": "3.14.5",
  "dependencies": "Python standard library only",
  "scope": "Exact weighted1D finite synthetic milliseconds; W1 unsigned distribution distance, not bias, significance or service speed.",
  "equal_mean_fixture": {
    "a_ms_count": [
      [
        0,
        1
      ],
      [
        20,
        1
      ]
    ],
    "b_ms_count": [
      [
        10,
        2
      ]
    ],
    "mean_a_ms": {
      "numerator": 10,
      "denominator": 1
    },
    "mean_b_ms": {
      "numerator": 10,
      "denominator": 1
    },
    "result": {
      "unit": "ms",
      "distance_ms": {
        "numerator": 10,
        "denominator": 1
      },
      "input_count_totals": [
        2,
        2
      ],
      "normalized_a": [
        [
          0,
          {
            "numerator": 1,
            "denominator": 2
          }
        ],
        [
          20,
          {
            "numerator": 1,
            "denominator": 2
          }
        ]
      ],
      "normalized_b": [
        [
          10,
          {
            "numerator": 1,
            "denominator": 1
          }
        ]
      ],
      "intervals": [
        {
          "left_ms": 0,
          "right_ms": 10,
          "cdf_a": {
            "numerator": 1,
            "denominator": 2
          },
          "cdf_b": {
            "numerator": 0,
            "denominator": 1
          },
          "absolute_cdf_gap": {
            "numerator": 1,
            "denominator": 2
          },
          "area_ms": {
            "numerator": 5,
            "denominator": 1
          }
        },
        {
          "left_ms": 10,
          "right_ms": 20,
          "cdf_a": {
            "numerator": 1,
            "denominator": 2
          },
          "cdf_b": {
            "numerator": 1,
            "denominator": 1
          },
          "absolute_cdf_gap": {
            "numerator": 1,
            "denominator": 2
          },
          "area_ms": {
            "numerator": 5,
            "denominator": 1
          }
        }
      ]
    }
  },
  "unequal_totals_fixture": {
    "a_ms_count": [
      [
        0,
        3
      ],
      [
        20,
        1
      ]
    ],
    "b_ms_count": [
      [
        0,
        2
      ],
      [
        20,
        6
      ]
    ],
    "result": {
      "unit": "ms",
      "distance_ms": {
        "numerator": 10,
        "denominator": 1
      },
      "input_count_totals": [
        4,
        8
      ],
      "normalized_a": [
        [
          0,
          {
            "numerator": 3,
            "denominator": 4
          }
        ],
        [
          20,
          {
            "numerator": 1,
            "denominator": 4
          }
        ]
      ],
      "normalized_b": [
        [
          0,
          {
            "numerator": 1,
            "denominator": 4
          }
        ],
        [
          20,
          {
            "numerator": 3,
            "denominator": 4
          }
        ]
      ],
      "intervals": [
        {
          "left_ms": 0,
          "right_ms": 20,
          "cdf_a": {
            "numerator": 3,
            "denominator": 4
          },
          "cdf_b": {
            "numerator": 1,
            "denominator": 4
          },
          "absolute_cdf_gap": {
            "numerator": 1,
            "denominator": 2
          },
          "area_ms": {
            "numerator": 10,
            "denominator": 1
          }
        }
      ]
    }
  },
  "fractional_distance_fixture": {
    "unit": "ms",
    "distance_ms": {
      "numerator": 5,
      "denominator": 3
    },
    "input_count_totals": [
      3,
      2
    ],
    "normalized_a": [
      [
        0,
        {
          "numerator": 1,
          "denominator": 3
        }
      ],
      [
        10,
        {
          "numerator": 2,
          "denominator": 3
        }
      ]
    ],
    "normalized_b": [
      [
        0,
        {
          "numerator": 1,
          "denominator": 2
        }
      ],
      [
        10,
        {
          "numerator": 1,
          "denominator": 2
        }
      ]
    ],
    "intervals": [
      {
        "left_ms": 0,
        "right_ms": 10,
        "cdf_a": {
          "numerator": 1,
          "denominator": 3
        },
        "cdf_b": {
          "numerator": 1,
          "denominator": 2
        },
        "absolute_cdf_gap": {
          "numerator": 1,
          "denominator": 6
        },
        "area_ms": {
          "numerator": 5,
          "denominator": 3
        }
      }
    ]
  },
  "independent_transport_ONE_family": {
    "profiles": 80,
    "ordered_pairs": 6400
  },
  "triangle_triples_inside_one_group": 512,
  "limits": {
    "rows_per_distribution": [
      1,
      8
    ],
    "integer_ms": [
      0,
      10000
    ],
    "positive_count_per_row": [
      1,
      16
    ],
    "max_input_count_total": 128,
    "combined_support_points_at_most": 16,
    "intervals_at_most": 15,
    "zero_weight_policy": "rejected; own educational policy",
    "duplicate_support_policy": "combine counts"
  },
  "scipy_executed": false,
  "actual_service_requests": 0,
  "actual_timer_measurements": 0,
  "population_inference_or_deployment_decision": false,
  "checks": {
    "equal_mean_bimodal_hand_two_areas5_plus5_equals10ms": true,
    "unequal_count_totals_normalized_and_fractional_hand_answer": true,
    "independent_sorted_mass_transfer_all_small_profiles_ONE_family": true,
    "identity_uses_normalized_distribution_not_raw_sample_total": true,
    "symmetric_unsigned_distance_all_small_profile_pairs": true,
    "common_translation_preserves_distance_changes_each_mean": true,
    "physical_duration_scale_in_same_ms_scales_distance": true,
    "input_row_permutations_preserve_complete_normalized_result": true,
    "splitting_duplicate_support_count_preserves_distribution": true,
    "independent_count_scaling_does_not_change_probability_distance": true,
    "finite_triangle_inequality_witnesses": true,
    "singletons_and_closed_resource_endpoints_have_manual_bounds": true,
    "exact_input_shape_types_positive_weights_and_caps_rejected": true,
    "inputs_preserved_fresh_output_exact_ratio_and_explicit_ms": true
  }
}

نسخهٔ اجرای ثبت‌شده Python 3.14.5 است؛ مستنداتِ خوانده‌شدهٔ Python نسخهٔ 3.14.8 و راهنمای SciPy نسخهٔ 1.18.0 دارند. دامنه‌های فنی در ۲۰ اکتبرِ ۲۰۲۶، برابر با ۲۸ مهرِ ۱۴۰۵، کامل خوانده و ذخیره شده‌اند. انتشارِ اولیهٔ این صفحه‌های زنده معلوم نیست و تاریخِ دسترسی را جایِ آن نمی‌گذاریم. این آموزش خبرِ انتشار یا آزمونِ اجرای SciPy نیست.

۵. چهار تمرین برای خواندنِ درستِ فاصله

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

تمرینِ اول: برای A در صفر و بیست با وزنِ برابر و B متمرکز در ده، میانگینِ برابر چه فاصله‌ای می‌دهد؟ پاسخ: میانگین‌ها ده‌اند، اما هر بازهٔ ده‌میلی‌ثانیه‌ای مساحتِ پنج دارد؛ مجموع ده میلی‌ثانیه است. مرکزِ برابر، شکلِ برابر نمی‌سازد و اختلافِ میانگین جایِ این مساحت را نمی‌گیرد.

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

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

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

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

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