Формати журналів годинника
src/clock_log.rs читає виміряний журнал годинника в один нормалізований ряд фази: рівномірну сітку відліків (time_s, phase_s) з інтервалом відліків tau0, заданим або знайденим, з підрахованими всіма проміжками та названими одиницями журналу. Читається вісім форматів, кожен як задокументована підмножина. Усе поза підмножиною відхиляється з помилкою, що називає знайдене, а не читається приблизно.
Скорочення. RINEX — це Receiver Independent Exchange Format. TICC — лічильник часових інтервалів TAPR (Tucson Amateur Packet Radio). ADEV — перекривна девіація Аллана, TDEV — девіація часу, а MTIE — максимальна похибка часового інтервалу. tau0 — інтервал відліків у секундах.
Де можна використати журнал#
telecom-timing:[series.clock_log]замістьseries.samplesабоseries.csv_path. Фаза журналу в нс — це ряд похибки часу, який перевіряється за масками, а звіт отримує блокclock_log, що пояснює, як його прочитано.- Вид clock: таблиця
[clock_log]додає до прогону блокmeasured: як прочитано журнал, SHA-256 таблиці (input_sha256;scenario_hashпрогону не охоплює таблицю) і відліків фази, а також перекривні ADEV, TDEV і MTIE за октавними коефіцієнтами усереднення. Потрібенruns = 1. Симульований holdover не змінюється.
Без таблиці обидва види розбираються, хешуються й повідомляються точно як раніше.
[clock_log] # or [series.clock_log] in a telecom-timing scenario
format = "ticc" # "rinex-clk", "timelab", "ticc", "ptp4l", "phc2sys", "ts2phc",
# "chrony-tracking" or "chrony-statistics"
path = "logs/ticc.txt" # or text = "..." inline (the only choice in WebAssembly)
channel = "chA" # TICC: needed when the log holds more than one channel
gaps = "linear" # "refuse" (default), "longest" or "linear"
# clock = "G08" # RINEX, linuxptp, chrony: needed when the log holds more than one clock or source
# tau0_s = 1.0 # overrides a header and the detected spacing
# units = "ns" # TimeLab phase units: s (default), ms, us, ns, ps
# data = "phase" # TimeLab: phase (default) or frequency
Формати#
rinex-clk: файли годинників RINEX, версії 2,00 і від 3,00 до 3,04 (зокрема дев'ятисимвольне поле імені 3,04). Запис RINEX VERSION / TYPE перевіряється, а будь-яка інша версія відхиляється з назвою знайденої версії: IGS публікує формат годинника (rinex_clock300.txt, 302, 304) до 3,04, а документи RINEX 4,00, 4,01 і 4,02 визначають лише файли спостережень, навігації та метеорології, тож версії 4,00 файла годинника для читання немає. Заголовок читається лише заради TIME SYSTEM ID і пропускається до END OF HEADER. З записів даних зберігаються лише записи AS (супутник) і AR (приймач або станція) одного годинника, вибраного за іменем. Їхнє перше поле значення, зміщення годинника в секундах, є фазою. Рядки продовження та записи CR, DR і MS пропускаються. Епохи — це календарні поля запису, у секундах від 1980-01-06 00:00:00 у власній шкалі часу файла, без перетворення.
timelab: ASCII-журнали фази або частоти в стилі TimeLab, простий текстовий вигляд, який експортує TimeLab і читають його ASCII-імпорт та Stable32. Одне значення на рядок: або один стовпець за рівномірного tau0, або два стовпці time value. Стовпці можуть розділятися пробілами, комами або крапками з комою.
- Рядки, що починаються з
#,;або%, є коментарями. Коментар у виглядіkey = value(абоkey: value) задаєtau0(с),units(s,ms,us,nsабоps, для фази),data(phaseабоfrequency) чиtime_units(sабоmjd, для першого з двох стовпців). - Один початковий нечисловий рядок сприймається як заголовок стовпців.
- Дані частоти (відносної частоти) приймаються лише як один стовпець. Вони інтегруються у фазу як
x[i+1] = x[i] + y[i]·tau0(NIST SP 1065, 2008, §4,1). - Двійкові файли TimeLab і файли проєктів
.timне читаються.
ticc: журнали лічильника TAPR TICC (посібник з експлуатації TICC, редакції від 23 листопада 2016 і 15 грудня 2025, «режими вимірювання»). Кожне поле даних — це секунди з 12 десятковими розрядами (за замовчуванням 11 з прошивки 20251101: читається будь-яка кількість до 15). Позначка часу читається з десяткового тексту як ціле число фемтосекунд, а не як двійкове число з рухомою комою, тож фаза журналу, знятого через дні після запуску лічильника, зберігає власну роздільність лічильника: позначка часу f64 за 3.0e5 с має крок 5.8e-11 с, приблизно 60 пс лічильника.
- Рядки режиму міток часу (
<seconds> chA,<seconds> chB) і канал псевдомітки режиму TimeLab (chC) — це мітки часу подій. Фаза вибраного каналу — це його мітка часу мінус номінальний такт:x[k] = t[k] − t[0] − n[k]·tau0, деn[k]— ціле числоtau0від першої події. Пропущена подія є проміжком. - Рядки режиму інтервалів (
<seconds> TI(A->B)або будь-яка мітка, що починається зTI) читаються безпосередньо як фаза, по одному значенню наtau0. Сценарій має вказуватиtau0_s, бо рядок не містить часу. - Режим парних позначок часу друкує ті самі рядки в порядку каналів і читається як режим позначок часу; режим 3-Corner Hat додає синтезований
chC, що читається як вище. - Вивід у режимах періоду та налагодження відхиляється. Канал, чиї показання не зростають, повідомляється як режим періоду. Двійковий режим позначок часу не є текстом, і файл у ньому відхиляється з повідомленням про це.
- Рядки, що починаються з
#, і рядки, які не мають вигляду<number> <label>(наприклад, меню запуску), пропускаються й підраховуються.
Журнали linuxptp і chrony#
Ці п'ять форматів читають те, що працюючий демон часу записує про власний годинник. Вони призначені для перевірки ланцюга часу постфактум (граничний годинник, PHC гросмейстера, клієнт chrony) тими самими оцінювачами, що й інші журнали. Вони не читають конфігурацію демона, і ніщо тут не спілкується з демоном.
ptp4l, phc2sys, ts2phc: рядки серво linuxptp, кожен формат читає лише рядки власної програми. Значення — це рядок вигляду
ptp4l[5003.000]: master offset 40 s2 freq +1480 path delay 497
phc2sys[7000.000]: CLOCK_REALTIME phc offset -30 s2 freq +100 delay 1200
ts2phc[8000.000]: /dev/ptp0 offset 3 s2 freq -2
- Мітка часу — це перше
[seconds.fraction]у рядку (монотонний годинник демона). Її також читають після префікса syslog, де ідентифікатор процесу на кшталтptp4l[812]:не має дробової частини й не береться. - Фаза — це записане
offsetу наносекундах, перетворене на секунди. Це виміряна демоном похибка часу його підлеглого годинника відносно еталона, як вона записана: додатна, коли підлеглий випереджає. Це похибка, що лишається під керуванням серво, а не дрейф вільно ходового годинника. - Слова між міткою часу та
offsetназивають джерело (master,eth0 masterдля префікса[eth0],CLOCK_REALTIME phc,/dev/ptp0). Журнал з більш ніж одним джерелом потребуєclock = "<that text>"; помилка їх перелічує. - Значення до першого повідомлення серво
s2(заблоковано) пропускаються й підраховуються вn_skipped_lines:s0іs1— це незаблокований стан і стан кроку, чиї зсуви є перехідними процесами втягування. Серво, яке ніколи не досягаєs2, відхиляється. Рядок без поля стану читається як є. - Підсумкові рядки (
rms ... max ...), повідомленняdelayіselected best master, а також рядки інших програм пропускаються й підраховуються. - Мітки часу, що йдуть назад (перезапущений демон), відхиляються; розділіть журнал.
chrony-tracking, chrony-statistics: файли log tracking і log statistics chrony, що читаються за позицією стовпця. Значення — це рядок щонайменше з 14 стовпців (tracking) або 13 (statistics), що починається з дати й часу UTC YYYY-MM-DD HH:MM:SS. Рядок заголовка стовпців пропускається й підраховується; лінія ==== під ним пропускається без підрахунку.
- Tracking: стовпець 7, локальний зсув під час оновлення годинника, у секундах. IP-адреса або ідентифікатор еталона змінюється разом із вибраним джерелом, тож без
clockкожен рядок читається як один локальний годинник.clock = "<id>"зберігає один ідентифікатор еталона. - Statistics: стовпець 5, оцінений зсув джерела, у секундах. На кожну адресу джерела припадає один ряд, а журнал потребує
clock = "<address>", якщо містить більше одного. - Епохи — це секунди від 1980-01-06 00:00:00 UTC, без обробки високосних секунд. Chrony записує рядок за кожне оновлення або вибірку джерела, тому інтервал нерівномірний.
tau0— це медіанний інтервал; значення менш ніж за половинуtau0після збереженого перед ним відкидається й підраховується вn_duplicates, а довший інтервал є проміжком, про який повідомляєgaps = "refuse". Задайтеtau0_sіgaps = "linear"для журналу зі сталим інтервалом опитування.
Стовпці chrony і розкладки рядків linuxptp взято з публічної документації демонів. Фікстури в tests/fixtures/clock_log/linuxptp_chrony/ написано для Kshana з вигаданими значеннями (див. її NOTICE.md); жодного реального журналу не читали, тож розкладка, що різниться між версіями демонів, усе ще може бути відхилена або прочитана неправильно. Рядок, що не збігається, пропускається й підраховується, ніколи не вгадується.
tau0 і проміжки#
Коли журнал містить мітки часу, а tau0 не задано, tau0 — це медіанний інтервал між послідовними мітками часу, округлений до чотирьох значущих цифр, і джерело повідомляється як median-spacing. Інтервал лічильника відрізняється від номінального на зсув частоти еталона, частин у 10^6 або менше. tau0_s у сценарії завжди має перевагу (джерело input), далі йде коментар заголовка tau0 (джерело header).
Відліки розміщуються на сітці t0 + i·tau0, а відсутня епоха є проміжком. Значення, що відхиляються від сітки більш ніж на 1 % від tau0, відкидаються й підраховуються, як і дублікати (зберігається перший). Політика проміжків — одна з:
refuse(за замовчуванням): будь-який проміжок є помилкою, що підраховує проміжки;longest: зберегти найдовший відрізок без проміжків;linear: заповнити внутрішні проміжки лінійною інтерполяцією й повідомити кількість заповнених.
Оцінювачам потрібен запис без проміжків, тому проміжки розв'язуються тут.
Ваш власний годинник#
Таблиця [clock_log] на типі clock повідомляє, наскільки стабільним був ваш власний годинник, поряд із симуляцією: scenarios/clock-measured-log.toml (kshana example clock-measured-log) — це clock-holdover.toml із 96-показниковою вибіркою годинника супутника IGS як вбудованим журналом TimeLab. Замініть text на path і format для вашого власного файла. Блок measured у результаті містить, як прочитано журнал (формат, tau0 і звідки він, кожен пропуск), та його ADEV, TDEV і MTIE; він не змінює симуляцію.
Керування holdover вашим власним годинником: [clock_fit]#
[clock_log] # the measured series, as above
format = "ticc"
path = "my-clock.txt"
channel = "chA"
[clock_fit]
replaces = "classical" # or "quantum": the simulated clock whose noise the log replaces
Журнал підганяється за процедурою картки пристрою з src/clock_library/card.rs (кондиціонування, вилучення квадратичної складової, ідентифікація шуму за лагом 1, зважений метод найменших квадратів у білому PM, білому FM, flicker FM і FM випадкового блукання за часів усереднення щонайменше з 30 ступенями свободи). Названий годинник тоді бере q_wf = h_0 / 2, q_rw = 2 pi^2 h_-2, flicker_floor = sqrt(2 ln 2 h_-1) і підігнаний drift; y0 лишається таким, як у сценарії, бо журнал несе зміщення свого еталона, а не годинника після дисциплінування. Білий фазовий шум підганяється й повідомляється, але не симулюється. Результат отримує блок fitted (коефіцієнти, точки підгонки, значення, які використав прогін, що було відкинуто, SHA-256 журналу), а годинник несе ідентифікатор measured:<12 hex of the phase hash>. За runs > 1 ансамбль бере вибірки з підігнаного годинника (блок measured потребує runs = 1). Підгонка без частотного шуму, надто короткий для підгонки журнал і replaces, відмінний від quantum чи classical, є помилками, які про це кажуть. scenarios/clock-fitted-log.toml — приклад (6 годин годинника супутника IGS).
Що показує порівняння на відомому шумі (tests/fixtures/clock_fit/RESULTS.md) і що слід пам'ятати:
- Досягнуто: рівень білого FM відновлюється в межах 5 % на кожному синтетичному записі, що його має; підігнана картка відтворює справжню девіацію Аллана в межах множника 1,35 за кожного часу усереднення, за якого її підігнано; показник шуму, який ідентифікує підгонка, дорівнює показнику allantools у кожній точці (на записі 5071A, коли крок фази, який вилучає кондиціонування, вилучено також із входу allantools).
- Не досягнуто й закріплено тестом: FM випадкового блукання відновлюється лише тоді, коли воно домінує на найдовших часах усереднення запису, і навіть тоді в одному з трьох записів ряду з 131 072 с із перетином у 3 000 с підгонка віддала його flicker FM і дрейфу. Розподіл між FM випадкового блукання та flicker FM ненадійний за одним записом. Прогін відтворює стабільність журналу за часів усереднення, які журнал охоплює; за їхніми межами обидва вибори розходяться (flicker FM — стала підлога, FM випадкового блукання зростає як квадратний корінь часу усереднення).
- MODELLED: сценарій «що, якщо» для шуму цього годинника, як його показує журнал, а не калібрування годинника й не передбачення його майбутньої поведінки.
Спотворений журнал є помилкою, що називає знайдене (рядок, версію, відсутній вибірник), ніколи не частковим результатом: tests/clock_log_real_allantools.rs закріплює 24 таких випадків.
Докази#
MODELLED. Це розбирач, і його вихід живить оцінювачі, перевірені деінде за allantools. Модульні тести в src/clock_log.rs використовують невеликі журнали, написані вручну, а для форматів linuxptp і chrony — фікстури в tests/fixtures/clock_log/linuxptp_chrony/. tests/clock_log_allantools.rs виконує порівняння, попередньо зареєстроване в tests/fixtures/clock_log/PREREGISTRATION.md: три синтетичні журнали із зерном, по одному на формат, перечитані незалежним читачем, написаним за цією сторінкою тим самим автором, з ADEV, TDEV і MTIE, перевіреними за allantools 2024,6; це порівняння охоплює лише перші три формати, а читачі linuxptp і chrony мають тести розбирача, але не порівняння з оракулом. Журнали цього порівняння синтетичні, тож ніщо там не описує справжній годинник чи лічильник; порівняння на виміряних рядах описано нижче.
На виміряних даних#
tests/clock_log_real_allantools.rs виконує порівняння, попередньо зареєстроване в tests/fixtures/clock_log_real/PREREGISTRATION.md (зафіксоване до будь-якої фікстури чи прогону), на чотирьох виміряних рядах: PHASE.DAT Stable32, перші 65 536 показань цезієвого годинника 5071A проти водневого мазера, а також годинники супутника IGS G01 (30 с) і станції ABMF (300 с) з фінального годинника IGS за 2026-08-02, як вендорений зріз із його NOTICE. Перші два не мають надання на поширення: журнали, записані з них, лежать у realdata-cache/, що ігнорується git (scripts/gen_clock_log_real_ref.py), і тест про це каже та проходить, коли їх немає, якщо не задано KSHANA_REQUIRE_REALDATA=1. Кожен журнал читається рушієм і незалежним читачем, а ADEV, TDEV і MTIE порівнюються з allantools 2024,6. Записано в tests/fixtures/clock_log_real/RESULTS.md: на поставленому читачі журнали TICC, версія заголовка та п'ять повідомлень про помилки не досягли попередньо зареєстрованих порогів; їх виправлено, а порівняння повторено без змін. Досягнуто, крім однієї пари: MTIE частотного журналу відрізняється від allantools, бо allantools інтегрує частоту мінус її середнє (закріплено як NOT MET; порівняння за рівних умов досягнуто). Рядок лишається MODELLED: журнали TICC — це виміряні дані годинника, перекодовані в розмітку лічильника нашим скриптом (жодного справжнього захоплення TICC не знайдено), а еталонний читач має того самого автора, що й читач рушія.