Система СИ и взаимосвязи между единицами физических величин
Система СИ и взаимосвязи между единицами физических величин

Это третья, заключительная статья серии, посвящённой библиотеке KotUniL языка Котлин для работы с физическими и иными величинами. В ней мы рассмотрим, как фундаментальные математические структуры, которые природа “заложила” в систему СИ, предопределили дизайн DSL. А также о том, как возможности и ограничения Котлина повлияли на разработку этой библиотеки.

Вот список статей серии:

  1. Магия размерностей и магия Котлина. Часть первая: Введение в KotUniL  

  2. Магия размерностей и магия Котлина. Часть вторая: Продвинутые возможности  KotUniL

  3. Магия размерностей и магия Котлина. Часть третья: Смешение магий

В каком мире живем?

Давайте для начала вспомним основы. Физический мир, в котором мы живём, весьма успешно описывается формулами с участием физических величин. Физическая величина характеризуется числовым значением и размерностью. (Эту размерность не надо путать с геометрической размеренностью нашего пространства). 

Для так называемых базовых физических величин люди договорились и выбрали единицы. Например, вначале это были физические артефакты, названные эталонами: эталон метра, килограмма и т.д. 

С единицей физической величины можно сравнить любую другую величину того же рода и выразить их отношение в виде числа. Таким образом физическая величина - это числовое значение (отношение к выбранной единице) и указание на тип величины в рамках выбранной системы физических величин

 Систем физических величин несколько, но все они концептуально близки друг к другу и конечны, т.е. состоят из небольшого числа размерностей или, что программистам понятнее - типов. 

Размерности или типы принято обозначать заглавными латинскими буквами. Так, например, классическая система размерностей, на которой базируется система СИ содержит семь размерностей: LMTIΘNJ (длина, масса, время и т.д.).

Получается, вся физическая информация может быть измерена, рассчитана, предсказана и т.д. в виде набора «размерных величин» - пар из числа и размерности. При этом размерность записывается в виде (если мы работаем с LMTIΘNJ) Lp1Mp2Tp3… где pi -  степень размерности. 

Например, размерность ускорения L1T-2, а ускорение свободного падения тела у поверхности Земли - 9,8 м/сек2.

Другими словами, мы живём в мире, в котором любая объективная физическая информация представима в виде точки из  восьмимерного пространства R*D где R - это число, отображающие значения физической величины, а D - пространство семимерных векторов реальных чисел, представляющих степени при соответствующих размерностях.

Забегая вперёд, отмечу, что ничто не мешает нам эти вектора “удлинить”, добавив туда собственные размерные величины, например цену в рублях или субъективную привлекательность партнёра, измеряемую по десятибалльной шкале. 

Но для простоты мы далее сосредоточимся на пространстве семимерных векторов.

Какова математическая структура пространства размерностей?

Когда физики производят какое-то математическое действие над физическими величинами, они по разному работают с числовыми значениями величин и их размерностями. Работа с числовыми значениями в нашем контексте тривиальна и неинтересна. Сосредоточимся на вопросе, как операции над физическими величинами отображаются на операции над размерностями.

Базовое множество пространства размерностей (в системе СИ) - это вектора длины 7. Элементы вектора - вещественные числа. С каждым элементом (номером элемента) раз и навсегда связана определённая физическая размерность. 

Над этим множеством определены операции сравнения и классические арифметические операции. Но очень специфическим образом.

Отношение порядка, определено только над одинаковыми векторами размерности. Мы можем сравнивать две величины скорости, но не можем сравнивать скорость и расстояние.

Операции + и - на паре D*D имеют очень ограниченную область определения: операции определены только если d1 == d2 (на так называемой диагонали произведения D*D).

Результат операций тоже очень необычен:

d1 + d2 = d1 - d2 = d1 = d2

Например, складывая расстояния или вычитая длины, мы всегда остаёмся в размерности L1

Тем самым наше пространство является кандидатом на звание абелевой группы, да поправят меня математики среди наших читателей, если это не так. 

Операция возведения физической величины, имеющей размерность со степенью m в степень n отображается в умножении степеней, а взятие корня в деление первого на второго.

Операции умножения и деления определены повсеместно, т.е. над любыми парами элементов из D. 

Умножение и деление взаимообратимы. Однако нуля, на который запрещено делить, в нашем случае нет. Делить можно на любую размерность. 

Операция умножения размерностей приводит к сложению значений соответствующих векторов. А операция деления означает вычитание значений делителя из значений делимого. 

Вот по умножению/делению наше пространство физических размерностей вроде бы точно абелева группа.  Тем самым получается, что пространство физических размерностей с определёнными арифметическими операциями является алгебраическим полем  в смысле этого определения.  

Если я не прав, опять-таки попрошу математиков из числа читателей меня поправить.

Следует также упомянуть о применении функций типа синус или интеграл к физическим величинам. Здесь всё просто. Они применяются только к числовым величинам, а вектор размерности не меняют.

Абелевы группы и алгебраические поля с их причудливо определёнными арифметическими операциями нужны нам не только любопытства ради, но и по вполне прагматическим причинам. И вот почему.

Рискну сформулировать лемму:

Лемма: Все физические формулы (по крайней мере, нужные на практике:-) можно представить в виде ориентированного вычислительного графа, у которого узлы имеют один предок и один либо два потомка и интерпретируется как одна из операций с потомками из набора операций; +,-, *, /,** (возведение в степень). 

Использование в формулах функций типа синуса или интеграла мы опускаем, поскольку они определены над безразмерными величинами. 

Магия языков программирования заключается в том, что компиляторы умеют строить эти самые ориентированные вычислительные графы. А отдельные языки, например Котлин, позволяют не только определять новые объекты, но и определять над ними операции, которые применяются к обычным числам - всё те же +,-, *, /,** (возведение в степень). 

Это означает, что после этого мы можем новоопределённые объекты так же использовать в формулах, как мы привыкли это делать с числами. 

Разумеется, не все синтаксически правильные формулы имеют физический смысл. Но это уже другая проблема, которая должна быть решена при реализации объектов и операций моделируемого мира (в нашем случае мира физических величин).

Используемые в формулах операции, как уже отмечалось, могут быть корректными или некорректными с точки зрения использования размерностей. При этом факт корректности формулы совершенно не зависит от используемых в ней числовых значений физических переменных. Другими словами, с точки зрения размерностей формула либо всегда верна, либо всегда неверна. Если это фундаментальное свойство размерностей удастся реализовать в коде, то для проверки корректности формулы с точки зрения размерностей достаточно одного юнит теста, “пробежавшего” по ней. 

Забегая вперёд, скажу, что мне кажется, при создании KotUniL этого удалось достичь.

Блеск и нищета Котлин и дизайн DSL

Итак, мне хотелось разработать специальный Domain Specific Language (DSL) для манипулирования физическими и иными размерными величинами.

Функционально он должен обеспечить работу с размерными величинами с помощью математических формул максимально похоже на использование аналогичных формул в технических статьях и документации.

DSL бывают самостоятельные и встроенные в языки программирования. Мы хотим создать встроенное подмножество Котлина без использования препроцессоров и генераторов. Другими словами, все конструкции нашего DSL должны быть синтаксически корректными конструкциями Котлина со своей специфической семантикой.

Разумеется, это ограничение сильно «подрезает крылья» в дизайне языка.

Но постараемся придумать такие конструкции, которые понятны людям с техническим образованием и в тоже время будут восприняты компилятором Котлина. 

Начнём с простейших школьных формул:

x = 1 m

y = 1.5 m

s = x * y

Вроде всё предельно просто и знакомо с пятого класса.

Но присмотримся повнимательнее к выражению

x = 1 m

Что на самом деле это значит? Как это можно реализовать на языке программирования?

Фактически эта запись подразумевает использование функции от двух переменных 

f : R, N -> М(L)

где R - множество в общем случае вещественных чисел

N - множество имён физических единиц

М(L)  - множество физических единиц длины 

Разумеется, в языке Котлин есть функции, но если мы вместо

x = 1 m

напишем что то вроде

f(1, „m“) 

это будет не очень понятно техническим людям.

В Котлине есть инфиксные функции, но они должны иметь два аргумента.

Т.е. можно написать  m   и использовать её в виде:

2 m 5

но нельзя написать функцию инфиксную функцию m чтобы использовать её как 

1 m

В итоге, похоже, нам остаётся один вариант: использовать функции - расширения. Тогда пример

x = 1 m

y = 1.5 m

s = x * y

на нашем языке можно будет записать как:

x = 1.m
y = 1.5.m
s = x * y

А можно сразу

s = 1.m * 1.5.m

Про префиксы, суффиксы и инфиксы в программировании

По ходу дела я попытался привести в порядок свои мысли относительно функций, операций, арностей/валентностей и префиксных, инфиксных и суффиксных функций. Возможно, и вам это будет интересно.

Может это и не совсем точные определения, но я вижу дело так.

Отталкиваемся от фундаментального понятия отображения одного множества пространств в другие. 

Количество параметров, являющихся входом (инпутом) нашего отображения называет его арностью. Соответственно говорят об нуль-арных, унарных, бинарных, тернарных и т.д. отображениях. Иногда вместо арности говорят про валентность отображения.

Больше всего термин «арность» употребляется применительно к функциям и их аргументам. 

В соответствии с определением под функциями понимаются однозначные отображения. А некоторые функции с хорошо определенным результатом принято называть операциями. 

Как я понимаю, выделение некоторых функций в специальную категорию операций имело в основном исторические причины. В частности операциями принято называть знакомые со школы плюс, минус, умножение и т.д.

В школе нас научили записывать функции в скобочной нотации:

y = f(x1, x2, …xn)

Бинарные функции можно записывать в нотации, которая называется инфиксной:

y = x1 f x2

Знакомые арифметические операции почти всегда так и записывают, например

y = x1 + x2

z = a*y

Унарные операции можно записывать скобочно

y = f(x)

А можно в префиксной нотации

y = f x 

или в постфиксной

y = x f

Известные примеры постфиксных операций это вычисление процентов (20%) или факториала (5!).

Примеры префиксных операций, реализованных в языках программирования: логическое отрицание not a или !а

а также операции с assign: 

y += 2 или y = ++x
У них есть и постфиксный вариант: y = x++ с немного другой семантикой.

Арифметические операции в префиксной, инфиксной и постфиксной нотации (пред)определены в большинстве языков программирования. 

Тонкий вопрос, можно ли определять собственные подобные операции.

Получается, что создатели языков благосклоннее относятся к инфиксным операциям нежели к префиксным и суффиксным. Так, например, в Котлине можно определять инфиксные собственные функции, а вот префиксные и постфиксные - нет (см. здесь)

Так что пока в KotUniL обходимся без постфиксных функций.

В программировании принято говорить о типах или классах, объектах или инстанциях. Говорить про полторы или половину инстанции не принято, но в реальной жизни это встречается постоянно. 

Например, можно купить 500 грамм вина. Если выпьете из них 200 грамм, у вас останется 300 грамм. 

Чтобы удовлетворить этим требованиям преступаем ограничения фундаменталистов от объектно-ориентированного программирования и реализуем арифметические операции над размерными величинами:

val rest = 1.5.kg - 200.g 

В этой записи мы имеем три разных инстанции одного типа. 

А в результате умножения или деления получается составной тип, отличающийся от двух исходных. Я в своей библиотеке называю это Expression хотя в это всегда произведение, как и следует из приведённой выше теории. 

Разработчики Котлина большие молодцы, что разрешили использовать в идентификаторах большинство символов Юникода. Это позволило без труда отобразить требования системы СИ к обозначениям некоторых размерностей и размерных префиксов в код.

Также сильным ходом при дизайне языка было разрешение использовать в качестве идентификаторов произвольные символы, заключённые в специальные кавычки: 

val prise = 52.`€`/m2

Это позволило элегантно решить целый ряд проблем, в том числе с коллизией идентификаторов.

Немного личных впечатлений


Заканчивая тему магии физических размерностей, хотелось бы хотелось бы поделиться своим пониманием процесса их становления и даже восхищением этим процессом.

Итак, уже в античные времена при торговле, взимании податей и строительстве были нужны меры веса и длины. Появились эталонные локальные  артефакты в качестве эталонов. В средние века в Европе на каждой ярмарке и в каждом городе были свои меры длины и веса (иногда - объёма), что при межгородской торговле создавало неудобства. 

Науке тоже потребовались точные эталоны. Их, вместе со многими другими полезными вещами, принесла Великая Французская Революция. Французы создали и начали заботливо хранить очень точные эталоны в виде физических артефактов. 

Но прошло полтора столетия, прежде чем остальной мир более-менее присоединился к стандарту.

Стандарт СИ совершенствовался и развивался. Об этом хорошо написано в Википедии.

Меня лично поразили два последних изменения стандарта. 

Особенно предпоследнее, принятое Генеральной Конференцией по Мерам и Весам в 2018 году и вступившее в силу в 2019. 

За два последние века наука нашла много физических процессов, которые всегда приводят к одним и тем же результатам. Например - скорость света постоянна и равна 299 792 458 м/с.

И таких физических констант много. Измеряли их с помощью измерительных инструментов, построенных на базе физических эталонов. 

А в 2018 году решили сделать обратное преобразование. Не константы определять с помощью эталонов, а отказаться от физических артефактов  и измерять физические единицы на базе природных констант. Картинка из Википедии, помещённая в начале статьи, объясняет новую структуру процесса определения единиц системы СИ.

Лично мне потребовалось определённое время, чтобы постичь философское значение этого решения.

Ну а последнее решение Генеральной Конференции по  Мера и Весам не такое фундаментальное, но примечательное. И произошло оно уже после того, как я начал работать над своей библиотекой. 

Совсем недавно точность выражения результатов расчётов и  измерений, выражаемая  префиксами (милли-, микро, кило, гекто…) была расширена из-за практических потребностей с интервала степеней (-24, +24) до (-30, +30). Напомню, что префикс «нано», это «всего» -9. Это фантастика, насколько далеко в Космос и глубоко внутрь элементов материи проникла своими измерениями современная наука.

И последнее. Я сделал официальное предложение расширить функциональность Котлина библиотекой типа описанной в этой серии статей. Если Вам она показалось нужной, Вы можете поучаствовать в обсуждении.

Иллюстрация: История и структура процесса определения единиц системы СИ. Источник: Википедия 

Комментарии (12)


  1. torgeek
    16.12.2022 21:18

    Можно ли реализовать мультиязыковой вариант библиотеки?

    Например, одновременно иметь

    val rest = 1.5.kg - 200.g 
    и
    val остаток = 1.5.кг - 200.г 


    1. visirok Автор
      16.12.2022 21:26
      +1

      Идентификаторы на национальном языке (например - на русском) позволяет делать сам Котлин.

      Реализовать версию библиотеки, когда функции -расширения будут, например, на русском, очень просто.

      Если вам нужно использовать немного величин - проше сделать их вручную по образу и подобию существующих. Если вы хотите повторить на русском всю библиотеку - посмотрите как она генерируется (пакет generator).

      Будут вопросы - обращайтесь ко мне в личке.


  1. firegurafiku
    17.12.2022 03:24
    +2

    В итоге, похоже, нам остаётся один вариант: использовать функции - расширения.

    Или использовать операцию умножения, которая здесь очень подходит по смыслу. Например, если понимать обозначение «кг» как синоним величины «1 кг», а «м³» — как синоним «1 м³» (то есть «1 м ⋅ 1 м ⋅ 1 м»), то можно весьма вольно обращаться с числами и единицами измерения:

    20*(kg/m3) == (20*kg)/m3 == (20*kg/m)/m2

    Так, кстати, сделано в Boost.Units.


    1. visirok Автор
      17.12.2022 11:38
      +1

      В KotUniL это тоже можно.

      Так что добавляется ещё и 20.kg/m3 == 20*(kg/m3)


  1. janatem
    17.12.2022 20:01
    +1

    Тем самым наше пространство является кандидатом на звание абелевой группы, да поправят меня математики среди наших читателей, если это не так.

    Это пространство действительно является абелевой группой, но только по умножению. Такие группы называют мультипликативными (хотя это не определение, а договоренность — если обозначаем групповую операцию как умножение, нейтральный элемент как единицу и т. д., то принято говорить «мультипликативная»). Поскольку складывать можно не любые элементы (а только элемент сам с собой), то группы по сложению не образуется, и, следовательно, никакое поле тоже не получается.


    1. visirok Автор
      17.12.2022 20:46

      Спасибо. Прояснили ситуацию.


  1. janatem
    17.12.2022 20:10
    +1

    Использование в формулах функций типа синуса или интеграла мы опускаем, поскольку они определены над безразмерными величинами.

    Синус и другие тригонометрические функции, действительно, можно применять только к безразмерным величинам, но вот интегрирование и дифференцирование работает как, соответственно, умножение и деление. Дифференциал действует как аддитивная операция, поэтому размерности не меняет (dx имеет ту же размерность, что и x). Подынтегральное выражение «чисто случайно» записывается как умножение одной величины на дифференциал другой. Аналогично, запись дифференцирования тоже «чисто случайно» записывается как отношение дифференциалов двух величин. На самом деле такая нотация конечно же не случайна, в этом есть большой смысл.

    Также размерной является операция скалярного умножения векторов, для этого необходимо, чтобы все компоненты вектора имели одинаковый тип (но у перемножаемых векторов типы могут быть разными).


    1. visirok Автор
      17.12.2022 20:54

      Спасибо. На эту тему есть тикет https://github.com/vsirotin/si-units/issues/4

      Если Вы там сформулируете требования,буду очень признателен. А если и имплементируете, будет совсем замечательно.


      1. janatem
        18.12.2022 22:05

        Не знаю, как работает конкретно Котлин, но сюда просится параметрический полиморфизм: для любой функции типа R->R строится естественное отображение в функцию, переводящую величину нулевой размерности в величину того же типа. В худшем случае это синтаксически будет выражаться как (map' f v), где map' — это вышеупомянутое отображение (и только его нужно реализовать), хотя, конечно, изящней было бы писать просто (f v), но для этого, кажется, придется каждую функцию f: R->R перегружать, что явно неудобно.


        1. visirok Автор
          18.12.2022 23:26

          Одно из важнейших требований к библиотеке - формулы должны выглядеть максимально похожи на формулы в учебниках или справочниках по физике или экономике.

          Так что необходимо балансировать между желаниями и возможностями.


  1. janatem
    17.12.2022 20:17
    +1

    Получается, что создатели языков благосклоннее относятся к инфиксным операциям нежели к префиксным и суффиксным. Так, например, в Котлине можно определять инфиксные собственные функции, а вот префиксные и постфиксные - нет (см. здесь)

    Так что пока в KotUniL обходимся без постфиксных функций.

    Тогда как это работает? Насколько я понял, писать (1 m) не получится, потому что нет пользовательских постфиксных операций, но как работает синтаксис (1.m)? Я бы сказал, что такой записи вижу постфиксную операцию m, примененную к числовому литералу (1.). А если вместо литерала будет переменная числового типа?


    1. visirok Автор
      17.12.2022 20:50
      +1

      1 m это применение постфиксной функции m к аргументу 1. А 1.m - это расширение типа Int новой функцией m. Почти так и сделано в KotUniL. Только расширен не Int а его предок Number.