...

суббота, 3 апреля 2021 г.

Проект Fedora официально выступил против Столлмана и прекратил все отношения с FSF

2 апреля 2021 года проект Fedora официально выступил против Ричарда Столлмана (RMS) и прекратил все отношения с Фондом свободного программного обеспечения (FSF).
Совет проекта Fedora пояснил, что организация поддерживает инклюзивные, доброжелательные и открытые сообщества разработчиков. Fedora против гонений, оскорблений, преследований, издевательств или другим форм злоупотреблений. Совет Fedora был ошеломлен тем, что FSF позволил Ричарду Столлману вернуться в совет директоров, учитывая его трансфобные высказывания и досаждающее поведение.

Совет проекта Fedora пояснил, что он обычно не участвует в управлении другими проектами. Однако это исключительный случай из-за того, что FSF контролирует развитие семейства лицензий GPL, которые имеют решающее значение для работы проекта.

Fedora, в соответствии с внутренними принципами и ценностями, прекращает предоставлять финансирование или участвовать в любых мероприятиях, спонсируемых FSF, и любых мероприятиях, на которых Ричард Столлман является ведущим докладчиком или экспонентом. Это также относится к любой организации, где он играет руководящую роль (проекта GNU).

25 марта Red Hat объявила, что против возвращения Столлмана. Компания прекратила финансирование Фонда свободного программного обеспечения (FSF). Ее сотрудники, а также многие разработчики и активисты СПО, поддерживающие решение Red Hat, не будут больше принимать участие в мероприятиях этой организации.

С 23 марта, после возвращения RMS в Фонд СПО, работа организации практически парализована из-за объявления бойкота и прекращения финансирования со стороны многих основных партнеров организации и ментейнеров, включая Fedora, Red Hat, Open Source Initiative (OSI), Software Freedom Conservancy (SFC), Европейского Фонда СПО, EFF (Electronic Frontier Foundation), Mozilla, Tor Project, FreeDOS, GNOME Foundation, X.org Foundation, HardenedBSD Foundation, MidnightBSD, Open Life Science, Open Source Diversity, Document Foundation (куратор LibreOffice), Creative Commons, GNU Radio, OBS Project, SUSE и даже создателя Rust Грейдона Хоара. Они обвиняют Столлмана в том, что он показал себя женоненавистником, эйблистом и трансфобом.

Против RMS уже подписали письмо многие ключевые команды разработчиков и ментейнеров. Сторонники Столлмана также сплотились, причем сейчас их голосов в письме в поддержку RMS больше практически на треть.

Публикация на Хабре "Перевод статьи «В защиту Ричарда Столлмана»".

Let's block ads! (Why?)

EDSAC (только для самых суровых)

Что приходит Вам в голову, когда Вы слышите “низкоуровневое программирование”? Может быть, C++? Непрекращающийся контроль указателей, попытки оптимизации быстродействия, потребляемой памяти? Или, вероятно, вы представляете инструкции ассемблера какой-нибудь популярной ныне архитектуры?

Если же Вы вспоминаете, как перфоратором прокалывали перфоленту, одно отверстие за другим, в течение многих часов, то эта статья для Вас! Добро пожаловать под кат! :)

Lasciate ogni speranza, voi ch'entrate
— Dante Alighieri, La Divina Commedia

EDSAC — «Electronic Delay Storage Automatic Calculator», был создан в Кембридже в 1949 для военных нужд. Ныне в забвении, вспоминают о нём лишь в образовательных целях в N-й губернии в малоизвестной Alma Mater, дочери Петербурхского государственного унивеситета.
Существует также проект The EDSAC Replica Project, где могучая кучка энтузиастов создаёт эмулятор и гайды по EDSAC для сохранения памяти об этом удивительном устройстве.
One of the nicer features of the EDSAC is that it is conceptually a very simple machine.
— Tutorial Guide to the EDSAC Simulator

Выглядит дрындулет вот так:

Рис 1.1. — EDSAC Simulator вблизи.

Рис 1.2. — EDSAC Simulator вдали.

Скачать его можно здесь.

Слева Вы можете видеть текстовый редактор для инструкций. Инструкции бывают двух типов: Initial Orders 1 (далее IO1) и Initial Orders 2 (мега продвинутые). Измененную строку редактор подсвечивает жёлтым, зелёным — сохранённые. Комментарии заключаются в []. Редактор имеет память на несколько шагов назад, поэтому можно откатывать программу, нажимая Ctrl+Z. Всё это раньше набирали на перфоленте, это новодел. Первая инструкция в IO1 — T N S, где N — адрес последней строки с инструкцией + 1. Простейшая программа на EDSAC имеет вид:

Листинг 1. — EDSAC order code, простейшая программа.

Она не делает ничего. Как и большинство моих программ. Отсчёт начинается с 31, т. к. ячейки с 0..30 изначально используются для запуска самой машины, вспоследствии их можно использовать как ячейки памяти.

Кстати, по умолчанию симулятор настроен на IO2, чтобы переключить его на IO1, нажмите на врехней панели EDSAC -> IO1. Запускается по кнопке Start, останавливается по Stop, Single E. P. — пошаговая отладка, для этого надо написать вначале Z0S/ZS/Z0F/ZF.

Справа Вы можете симулятор и содержимое памяти вычислительной машины. Слова 17-битные, всего 1024 ячеек памяти (или 35-битные, 512 ячеек памяти, как будет удобнее, посередине — таинственный «sandwich-digit»). Еще есть аккумулятор (оперативная память), 71-бит, и 35-битный регистр умножения. Симулятор поддерживает ввод с помощью «дискового телефона», вывод на печать. Включив hints, вы можете смотреть на содержимое ячеек памяти, наводя на них мышкой. Сверху будет появляться число в 10СС. Также можно смотреть содержимое аккумулятора и регистра умножения.


Рис 2. — Формат машинного слова.

Рис 3. — Формат машинной инструкции.

О числах. Число записывается в память в дополнительном коде с помощью инструкции: P N S или P N L, где N — множитель перед двойкой. Например, P0S = 2*0+0=0, P0L = 2*0+1=1, P1S = 2, P1L = 3 и т. д. Отрицательные числа записываются в дополнительном коде, об этом можно почитать в туториале. EDSAC также работает с дробными числами. Символ короткого слова S (F для IO2) и L (D). Не только числа, но и инструкции меняют свой смысл в зависимости от этого.

Есть пара гайдов, основной и сокращённый, рекомендую последний т. к. там меньше буков понятнее изложен материал, работающие примеры. Чтобы сделать приведенный в них код работающим, надо заменить первую строку ZOS/ZS/Z0F/ZF на X0S/X0F. Инструкции смотрите там, либо уже в комментариях к коду.

В течение нескольких месяцев было изготовлено множество версий процедуры печати десятичных чисел. Если программист был непроходимо глуп, или был полным идиотом и совершенным «лозером», то подпрограмма конверсии отняла бы у него около сотни команд. Но любой хакер, стоивший своего имени, мог уместить ее в меньший объем. В конечном счете, попеременно убирая инструкции то в одном, то в другом месте, процедура была уменьшена до примерно пятидесяти инструкций.
— Стивен Леви, Хакеры: Герои компьютерной революции

Итак, моим заданием было написать программу по выводу n-й строки треугольника Паскаля.
Листинг 2. — Kotlin, вывод 3-й строки треугольника Паскаля.
fun main(args: Array<String>) {
    var a = 1
    var row = 3
    row += 1
    for (i in 1..row) {
        print(a)
        print(" ")
        a = a * (row - i) / i
    }
}


Здесь будет вложенный цикл, т. к. деление нужно реализовывать вручную.
Листинг 3. — EDSAC order code, целочисленное деление, округление вниз.
[31] T56S
[32] E37S [безусловный переход через константы]
[33] P3L [делимое = 5]
[34] P1L [делитель = 2]
[35] P0S [ну для подсчета частного, 0]
[36] P0L [1]
[37] A35S [выгружаю ну для подсчета частного]
[38] T2S [частное будет во 2 ячейке]
[39] A33S [загружаю делимое в аккумулятор]
[40] T1S [записываю делимое в 1 ячейку]
[41] A1S [читаю содержимое делимого]
[42] S34S [вычитаю делитель]
[43] T1S [записываю новое значение делимого в ячейку 1]
[44] A2S [загружаю частное]
[45] A36S [прибавляю 1]
[46] T2S [записываю увеличенное на 1 частное]
[47] A1S [выгружаю в аккумулятор делимое]
[48] G50S [если делимое < 0, стоп]
[49] T1S [чищу содержимое аккумулятора]
[50] E41S[иначе повторять цикл]
[51] T0S [чищу содержимое аккумулятора]
[52] A2S [выгружаю частное]
[53] S36S [вычитаю 1]
[54] T2S [загружаю частное обратно]
[55] Z0S [иначе стоп]


Как Вы могли заметить, адресация абсолютная, что доставляет головную боль, агрессию, вызывает скрип зубов, ненависть, разочарование, злобу, сложности (это пофиксили во 2 версии Initial Orders). При сдвиге одной строки сдвигается сразу МНОГО. И надо переписывать адреса всех «поплывших» строк.

Наконец решение:

Листинг 4. — EDSAC order code, IO1, n-я строка треугольника Паскаля.
[31] T154S [указатель на последнюю строку программы +1]
[32] E84S [безусловный переход к 84 строке, начало тела программы]
[33] PS [следующая цифра в десятичной СС]
[34] PS [степень десятки]
[35] P10000S [для корректного вывода чисел, степени 10, 10^4]
[36] P1000S [для корректного вывода чисел, степени 10, 10^3]
[37] P100S [для корректного вывода чисел, степени 10, 10^2]
[38] P10S [для корректного вывода чисел, степени 10, 10^1]
[39] P1S [для корректного вывода чисел, степени 10, 10^0]
[40] QS [для формирования чисел]
[41] #S [перевод на цифровой регистр]
[42] A40S [загрузка в аккумулятор символа формирования чисел]
[43] !S [символ пробела]
[44] &S [символ переноса строки на новую]
[45] @S [символ возврата каретки]
[46] O43S [печать телепринтером пробельного символа]
[47] O33S [печать телепринтером следующей цифры числа в 10СС]
[48] PS [число для вывода]
[49] A46S [загружаю в аккумулятор команду печати пробела для исполнения из 46 ячейки]
[50] T65S [загружаю в ячейку памяти 65, обнуляю аккумулятор]
[51] T300S [обнуляю аккумулятор]
[52] A35S [загружаю в аккумулятор число из ячейки памяти 35, 10000<<1]
[53] T34S [загружаю его в ячейку памяти 34, обнуляю аккумулятор]
[54] E61S [если число в аккумуляторе >= 0, перехожу к строке 61]
[55] T48S [загружаю новое значение числа для вывода в ячейку 48, обнуляю аккумулятор]
[56] A47S [загружаю число из 47 ячейки в аккумулятор]
[57] T65S [загружаю число из аккумулятора в 65, обнуляю аккумулятор]
[58] A33S [загружаю число из 33 ячейки в аккумулятор]
[59] A40S [прибавляю число из 40 ячейки к содержимому аккумулятора]
[60] T33S [загружаю число из аккумулятора в 33, обнуляю аккумулятор]
[61] A48S [загружаю число для вывода из ячейки памяти 48 в аккумулятор]
[62] S34S [вычитаю текущую степень десятки]
[63] E55S [если число >= 0, повторяю, перехожу к строке 55]
[64] A34S [загружаю степень десятки из ячейки 34 в аккумулятор]
[65] PS [цифра или пробел, в зависимости от ситуации]
[66] T48S [загружаю число из аккумулятора в ячейку 48, там число для вывода]
[67] T33S [загружаю число из аккумулятора в ячейку 33, там следующая цифра
десятичной записи числа]
[68] A52S [загружаю инструкцию из ячейки 52 в аккумулятор]
[69] A4S [прибавляю 1]
[70] U52S [записываю содержимое аккумулятора в ячейку 52]
[71] S42S [вычитаю из аккумулятора символ формирования чисел]
[72] G51S [если число в аккумуляторе < 0, перехожу к 51 ячейке]
[73] A103S [загружаю инструкцию из ячейки 103 в аккумулятор]
[74] T52S [загружаю содержимое аккумулятора в ячейку 52, обнуляю аккумулятор]
[75] PS [ячейка памяти для хранения инструкции возврата]
[76] PS [ячейка памяти для хранения индекса цикла]
[77] PS [const = 0]
[78] PS [const = 0]
[79] PS [const = 0]
[80] E100S [переход к строке 100]
[81] E104S [переход к строке 104]
[82] P5S [показатель треугольника Паскаля, вводится просто как число между P и S,
по правилам обычной арифметики в 10СС]
[83] E123S [переход к выводу переноса строки, строка программы 123]
[84] A110S [загружаю в аккумулятор число 2]
[85] T30S [выгружаю в ячейку памяти 30, обнуляю аккумулятор]
[86] O41S [перевод на цифровой регистр телепринтера]
[87] T300S [обнуляю аккумулятор]
[88] O44S [печатаю переноса строки на новую]
[89] O44S [печатаю переноса строки на новую]
[90] A76S [загружаю в аккумулятор индекс цикла]
[91] A4S [увеличиваю его на 1]
[92] T76S [перезаписываю изменённый индекс, обнуляю аккумулятор]
[93] E113S [безусловный переход к ячейке 113]
[94] T300S [обнуляю аккумулятор]
[95] A30S [загружаю в аккумулятор число из 30 ячейки, обнуляю аккумулятор]
[96] T48S [выгружаю в ячейку памяти 48, обнуляю аккумулятор]
[97] A80S [загружаю в аккумулятор инструкцию перехода из ячейки 80]
[98] T75S [загружаю её в 75 ячейку, обнуляю аккумулятор]
[99] E49S [перехожу к печати текущего числа]
[100] A81S [загружаю в аккумулятор инструкцию перехода из ячейки 81]
[101] T75S [выгружаю в ячейку памяти 75, обнуляю аккумулятор]
[102] E49S [перехожу к печати текущего числа]
[103] A35S [перехожу к печати текущего числа]
[104] A76S [загружаю в аккумулятор индекс цикла из ячейки 76]
[105] S82S [вычитаю показатель треугольника Паскаля]
[106] S110S [вычитаю 2]
[107] G87S [если полученное число <0, перехожу к строке 87]
[108] X0S [пустая команда]
[109] ZS [конец программы, сигнальный звонок]
[110] P1S [const = 2]
[111] P2S [const = 4]
[112] P0L [const = 1]
[113] T300S [обнуляю аккумулятор]
[114] A76S [загружаю в аккумулятор содержимое 76 ячейки]
[115] S111S [вычитаю содержимое 111 ячейки, 4]
[116] E124S [если число в аккумуляторе >=0, перехожу к 124 ячейке]
[117] T300S [иначе обнуляю аккумулятор]
[118] A30S [загружаю в аккумулятор число из 30 ячейки]
[119] T48S [загружаю в ячейку памяти 48, число для вывода, обнуляю аккумулятор]
[120] A83S [загружаю в аккумулятор инструкцию из адреса 83]
[121] T75S [загружаю ее в 75 ячейку]
[122] E49S [перехожу к печати числа из памяти]
[123] O44S [печатаю переноса строки на новую]
[124] O44S [печатаю переноса строки на новую]
[125] T300S [обнуляю аккумулятор]
[126] A82S [загружаю в аккумулятор показатель треугольника Паскаля]
[127] A110S [добавляю 2]
[128] S76S [вычитаю индекс цикла]
[129] T29S [записываю содержимое аккумулятора в ячейку 29, обнуляю аккумулятор]
[130] H29S [загружаю в умножающий регистр число из 29 ячейки]
[131] V30S [умножаю на число из 30 ячейки, результат в аккумуляторе]
[132] L64S [сдвигаю аккумулятор влево на 8 ячеек]
[133] L32S [сдвигаю аккумулятор влево на 7 ячеек]
[134] T30S [выгружаю число из аккумулятора в 30 ячейку памяти]
[135] T2S [выгружаю частное в ячейку памяти 2, обнуляю аккумулятор]
[136] A30S [загружаю делимое в аккумулятор из ячейки 30]
[137] T1S [записываю делимое в 1 ячейку, обнуляю аккумулятор]
[138] A1S [читаю содержимое делимого из ячейки памяти 1, начало цикла]
[139] S76S [вычитаю делитель из ячейки памяти 76]
[140] T1S [записываю новое значение делимого в ячейку 1, обнуляю аккумулятор]
[141] A2S [загружаю частное из ячейки 2]
[142] A110S [прибавляю 1 из ячейки памяти 110]
[143] T2S [записываю увеличенное на 1 частное в ячейку 2, обнуляю аккумулятор]
[144] A1S [выгружаю в аккумулятор делимое из ячейки 1]
[145] G147S [если делимое < 0, стоп, переход на строку 147]
[146] T1S [иначе записываю содержимое аккумулятора в ячейку 1, обнуляю аккумулятор]
[147] E138S[если делимое >= 0, повторять цикл деления, переход на 138 строку, конец цикла]
[148] T300S [обнуляю аккумулятор]
[149] A2S [выгружаю частное из ячейки 2]
[150] S110S [вычитаю 1 из ячейки памяти 110]
[151] U2S [загружаю частное обратно в ячейку 2]
[152] T30S [загружаю частное в ячейку 30, обнуляю аккумулятор]
[153] E94S [безусловный переход к строке 94, подготовка к выводу числа]


Что сказать про IO2? Поддерживает подпрограммы, относительную адресацию, набор команд расширен.
Та же программа на IO2:
Листинг 5. — EDSAC order code, IO2, n-я строка треугольника Паскаля.
..PK
T56K
[P6, подпрограмма для вывода чисел из стандартной библиотеки]
GKA3FT25@H29@VFT4DA3@TFH30@S6@T1F
V4DU4DAFG26@TFTFO5FA4DF4FS4F
L4FT4DA1FS3@G9@EFSFO31@E20@J995FJF!F
..PZ
[программа для нахождения n-й строки треугольника Паскаля]
GK [фиксирую абсолютное значение ячейки памяти для относительной индексации]
[0] XF [для отладки, нулевая инструкция]
[1] O34@ [вывод переведён на цифровой регистр, загрузка данных для печати из ячейки 34]
[2] O35@ [вывод новой строки, загрузка данных для печати из ячейки 35]
[3] O36@ [возврат каретки телепринтера, загрузка данных для печати из ячейки 36]
[4] TF [обнуление аккумулятора]
[5] A27@ [значение из ячейки 27 добавляется в аккумулятор]
[6] TF [запись этого значения в 0 ячейку для вывода, обнуление аккумулятора]
[7] A7@ [загрузка замкнутой подпрограммы по выводу числа]
[8] G56F [печать числа с использованием P6]
[9] T20@ [зануляю 20 ячейку памяти]
[10] A28@ [загружаю в аккумулятор индекс цикла]
[11] A31@ [добавляю 1]
[12] U28@ [обновляю значение индекса цикла в ячейке 28]
[13] T20@ [записываю значение индекса цикла в ячейку 20, обнуляю аккумулятор]
[14] A33@ [загружаю показатель в аккумулятор]
[15] A31@ [+1]
[16] S28@ [вычитаю текущий индекс из ячейки 28]
[17] T20@ [записываю значение индекса цикла в ячейку 20, обнуляю аккумулятор]
[18] E37@ [безусловный переход к умножению, ячейка 37]
[19] PD [const = 1]
[20] XF [нулевая инструкция]
[21] XF [нулевая инструкция]
[22] A33@ [загружаю показатель из ячейки 33 в аккумулятор]
[23] A31@ [+1]
[24] S28@ [вычитаю содержимое 28 ячейки, индекс]
[25] E2@ [если >= 0, повторяю цикл, переход к строке 2]
[26] ZF [конец программы, сигнальный звонок]
[27] PD [число для вывода]
[28] PF [индекс цикла]
[29] PD [используется в подпрограмме для печати]
[30] PD [используется в подпрограмме для печати]
[31] PD [const = 1]
[32] P1F [символ перехода на новую строку]
[33] P2D [показатель треугольника Паскаля, записывается по правилам EDSAC]
[34] #F [символ перевода вывода на цифровой регистр]
[35] @F [символ возврата каретки]
[36] &F [символ переноса строки на новую]
[37] H20@ [загружаю в умножающий регистр число из 20 ячейки]
[38] V27@ [умножаю на число из 27 ячейки, результат в аккумуляторе]
[39] L1024F [сдвигаю аккумулятор влево на 12 ячеек]
[40] L4F [сдвигаю аккумулятор влево на 4 ячеек]
[41] T20@ [выгружаю число из аккумулятора в 20 ячейку памяти, обнуляю аккумулятор]
[42] T102@ [выгружаю частное в ячейку памяти 102, обнуляю аккумулятор]
[43] A20@ [загружаю делимое в аккумулятор из ячейки 20]
[44] T101@ [записываю делимое в 101 ячейку, обнуляю аккумулятор]
[45] A101@ [читаю содержимое делимого из ячейки памяти 101, начало цикла]
[46] S28@ [вычитаю делитель из ячейки памяти 28]
[47] T101@ [записываю новое значение делимого в ячейку 101, обнуляю аккумулятор]
[48] A102@ [загружаю частное из ячейки 102]
[49] A31@ [прибавляю 1 из ячейки памяти 31]
[50] T102@ [записываю увеличенное на 1 частное в ячейку 102, обнуляю аккумулятор]
[51] A101@ [выгружаю в аккумулятор делимое из ячейки 101]
[52] G54@ [если делимое < 0, стоп, переход на строку 54]
[53] T101@ [иначе записываю содержимое аккумулятора в ячейку 101, обнуляю аккумулятор]
[54] E45@ [если делимое >= 0, повторять цикл деления, переход на 45 строку, конец цикла]
[55] T200@ [чищу содержимое аккумулятора]
[56] A102@ [выгружаю частное]
[57] S31@ [вычитаю 1 из ячейки памяти 102]
[58] U102@ [загружаю частное обратно в ячейку 102]
[59] T27@ [загружаю частное в ячейку 27, обнуляю аккумулятор]
[60] E22@ [безусловный переход к строке 22, подготовка к выводу числа]
[61] EZPF [указатель на исполнение кода]



Рис 4. — Вывод для показателя = 5.

Надеюсь, Вам понравилась эта статья и Вы прониклись истоками программирования. Когда пишешь подобный код, начинаешь ценить все те уровни абстракции, которые появились впоследствии, понимаешь, какой же большой путь прошли ЯП, чтобы стать такими, какие они есть. Спасибо за Ваше время!

P. S. Милейшие друзья из губернии N, не стоит благодарности за любезно предоставленный мной код, если у Вас совпал со мной вариант. Удачи с RISC-V!

P. P. S. Прекрасные примеры кода для EDSAC IO2 приведены здесь.

P. P. P. S. Код публикую под лицензией MIT.
Copyright 2021, ALEKSEI VASILEV
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the «Software»), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED «AS IS», WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Let's block ads! (Why?)

Суд признал, что Apple знала о дефекте Flexgate в продаваемых в 2016-2017 годах ноутбуках MacBook Pro

Пример дефекта шлейфа матрицы экрана в моделях MacBook Pro 2016 и 2017 года. Из-за разрыва короткого и тонкого кабеля подсветка в нижней части дисплея работает нештатно (эффект «сценического света»). Через некоторое время она полностью выходит из строя.

По информации MacRumors, суд признал, что Apple знала о дефекте Flexgate в продаваемых в 2016-2017 годах ноутбуках MacBook Pro. Cудья окружного суда Северной Калифорнии посчитал, что производитель намеренно продавал в течение полутора лет неисправные устройства, так как все продукты Apple проходят несколько этапов тестирования до попадания на прилавок.

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

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

В групповом иске против Apple истцы требуют возместить им все расходы, понесенные за ремонт бракованных ноутбуков, так как он выполнялся не по гарантии, а также компенсировать все судебные издержки. Также в иске указано, что Apple должна бесплатно отремонтировать все остальные проданные MacBook Pro 2016 и 2017 года, фактически начав глобальную отзывную кампанию. Ранее Apple бесплатна ремонтировала только MacBook Pro 13", которые были подвержены этой проблеме.

Let's block ads! (Why?)

VMware SD-WAN: обзор решения

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

Появление SD-WAN

До облачной революции, когда деревья были большими, а про облака говорили мало и редко, для размещения приложений большинство компаний использовали локальные ЦОДы. Доступ для сотрудников из удаленных офисов предоставлялся через выделенные каналы связи — MPLS. При таком подключении большинство пользователей из филиалов прямого пути в большую сеть не имели и выходили за пределы приватной сети через центральный офис, который мог находиться за тысячи километров от филиала и пользователя. 

Чуть позже компании, наконец, распробовали облачные технологии. Бизнес стал размещать свои приложения в облаках, а доступ к ним организовывать через интернет, без использования выделенных каналов. Требования к пропускной полосе и скорости обмена информацией постоянно росли. Если со старыми корпоративными приложениями, не относящимися к медиа или ПО реального времени, традиционный подход еще работал, то по мере появления видеохостингов и сервисов видеосвязи, взрывной рост популярности которых пришелся на начало 2010-х, стало очевидно: нужно что-то менять. 

Выделенные MPLS-каналы со своей низкой пропускной способностью и высокой стоимостью (за 1 Мбит на MPLS приходилось платить иногда в десятки раз больше, чем за такой же канал в интернет) банально перестали отвечать требованиям рынка.

Казалось бы, почему не перейти на интернет? Это дешево и сердито, а скорость доступа к облакам выше. Ответ прост: в случае с интернетом невозможно гарантировать качество сервиса. Да, MPLS дорогой и неудобный, но провайдеры как минимум на уровне контракта гарантируют качество. А в случае с интернетом обещать, что приложения, физически находящиеся бог знает где, через конкретного провайдера будут хорошо работать, нельзя. Сами сервисы находятся вне зоны ответственности провайдера. 

Иными словами, те, кто отвечают за канал связи, не отвечают за приложения, и наоборот. Заказчик оказывается между Сциллой и Харибдой.

Решения SD-WAN долгое время оставались уделом стартапов. В стадию зрелости технология перешла всего несколько лет назад, когда крупные компании начали активно приобретать специализированные проекты. VMware выкупила стартап Velocloud, который делил первое место на рынке с аналогичным проектом — Viptela, примерно в то же время купленным Cisco. Еще пару лет заняла консолидация: одни стартапы разорялись — не всем удавалось сохранить нужные темпы роста и найти свое место на рынке, наиболее перспективные и успешные переходили под крыло крупных вендоров. 

Чтобы нарастить конкурентные преимущества, каждой компании пришлось найти собственную уникальную нишу. Решение Velocloud создавалось для того, чтобы бизнес мог получать надежный и качественный доступ к приложениям независимо от того, где они находятся: в локальном дата-центре или в облаке, доступ в которое осуществляется через интернет. Спойлер: реализовать это удалось благодаря архитектурному компоненту решения — SD-WAN Cloud Gateways. Идея состоит в том, что облачные технологии можно использовать и в мире сетевых подключений. 

Как работает SD-WAN

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

Этот неудобный подход просуществовал вплоть до появления SDN. Технология программно-определяемых сетей позволила централизовать control plane и management plane, что дало существенный выигрыш в плане управляемости и масштабируемости сети. Стало возможным без труда управлять распределенной сетью из сотен и даже тысяч устройств, выросла скорость внесения изменений, а также появилась возможность более интеллектуальной обработки потоков данных.

Концепция SDN подразумевает, что «мозг» всех устройств объединяется единой сервисной платформой. Это может быть облачный сервис или физический компонент, который управляет работой устройств. Применение этих принципов к глобальным сетям позволило сделать сеть более гибкой и управляемой.

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

Далее мы подробно рассмотрим все компоненты VMware SD-WAN и поговорим о каждом из них отдельно.

Компоненты решения

VMware SD-WAN Orchestrator

SD-WAN Orchestrator — это облачный портал управления, мониторинга и диагностики сети. С его помощью можно буквально в один клик запустить виртуальные сетевые службы в филиале, облаке или в корпоративном ЦОДе. Фактически, это веб-портал управления сетью SD-WAN, который можно получить как SaaS или развернуть в локальном ЦОД.

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

  • Мониторинг. Данные о состоянии узлов сети, SLA по каналам, сетевая активность по приложениям и клиентам, а также продвинутая система построения отчетов

  • Zero-Touch Provisioning (ZTP). Активация оконечного устройства в филиале не требует выезда отдельного специалиста на площадку. Весь процесс укладывается в несколько простых шагов. Сотрудник в удаленном офисе подключает к устройству все кабели и включает его. Через интерфейс оркестратора создается новый Edge и высылается email со ссылкой для активации. Там «зашиты» нужные параметры для настройки WAN-интерфейса оборудования. Сотрудник в филиале, подключившись через провод или WiFi с планшета, переходит по этой ссылке, а устройство получает автоматически связность с оркестратором, вне зависимости от типа подключения к интернету.

  • Диагностика сети. В панели управления можно посмотреть состояние протоколов маршрутизации, портов, таблиц ARP и NAT, запустить Ping или Traceroute и многое другое без необходимости подключения к устройствам через CLI/SSH.

  • Журнал событий. Изменения и уведомления со всех устройств доступны для поиска и просмотра на SD-WAN Orchestrator.

  • Автоматизация через API. SD-WAN Orchestrator предоставляет REST API, примеры использования можно посмотреть на SD-WAN Dev Center.

Посмотрим, как это выглядит.

SD-WAN Dashboard — общий вид на всю сеть:

Список филиалов и их расположение на карте:

Статистика по приложениям и клиентам:

Информация о качестве каналов — Quality of Experience:

Детальные характеристики каналов:

VMware SD-WAN Gateway

SD-WAN Gateways — это сетевые шлюзы, развернутые в высоконадежных ЦОД по всему миру. В первую очередь, они выполняют функцию Control Plane, но в зависимости от задачи могут брать на себя сразу обе плоскости — и control plane, и data plane. Эти шлюзы позволяют соединить между собой разрозненные офисы и обеспечивают оптимальный путь от филиала к данным и приложениям в ЦОД и облаках. Потребность в установке мощных концентраторов или устройств координации непосредственно в филиалах отпадает.

На уровне Control Plane обеспечивается и обмен маршрутной информацией между филиалами, так как Gateway выполняет роль рефлектора маршрутов. Филиалы получают обновления маршрутов от Control Plane, благодаря чему настраивать протоколы маршрутизации между устройствами Edge не требуется.

Ещё одна важная роль SD-WAN Gateway — помощь в определении характеристик каналов связи. При активации устройства Edge выполняется подключение к Gateway и замер полосы пропускания канала, также автоматически определяется MTU.

По своему принципу работы эти шлюзы мультитенантные, то есть предназначены для обработки данных большого количества клиентов сразу. Благодаря тому, что сеть этих мультитенантных шлюзов распределена по множеству дата-центров, SD-WAN легко масштабировать от пары площадок до нескольких тысяч. Об отказоустойчивости можно не беспокоиться — каждое устройство Edge подключается как минимум к двум шлюзам в разных регионах.

При этом приложения, с которыми работают пользователи, могут быть развернуты как в облаке провайдера, так и на собственной площадке клиента. Если инфраструктура компании  — филиалы, офисы — распределена географически, каждый офис будет подключаться к ближайшему SD-WAN Gateway.

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

Хороший пример — использование облачных сервисов совместной работы, например, Microsoft Office 365, Dropbox, Zoom, Skype for business и других. В месте их фактического расположения установить SD-WAN Edge невозможно, но получить преимущества SD-WAN Overlay (подробнее — в разделе о протоколе VCMP) нужно. SD-WAN Gateway выполняет функцию шлюза в «большой» интернет с балансировкой между каналами, компенсацией ошибок и выбором наиболее оптимального канала для передачи пакетов конкретного приложения.

VMware SD-WAN Edges

VMware SD-WAN Edge — это физическое или виртуальное устройство с функциями безопасности. Фактически, это маршрутизатор, который может как заменить уже размещенные в филиалах устройства, так и работать с ними в тандеме. Он поддерживает функции сетевого шлюза, Site-to-Site VPN, DHCP-сервера, NAT и файрвола, а также различные протоколы маршрутизации для интеграции с физической сетью.

SD-WAN Edge в виртуальном исполнении подойдет тем, кто планирует использовать концепцию Software-Defined Branch или обеспечивать доступ к своим приложениям в ЦОД через SD-WAN. Виртуальный Edge в ЦОД подходит как в качестве связующего звена ресурсов ЦОД и удаленных площадок, так и в роли хаба для агрегации и распределения трафика внутри сети SD-WAN.

SD-WAN Edge поддерживает основные варианты резервирования — есть возможность объединения в отказоустойчивую пару или подключение по протоколу VRRP.

И если компоненты решения — руки, ноги и голова технологии VMware SD-WAN, то его сердце — технология DMPO. Именно она и позволяет реализовать уникальные фишки по обработке сетевых пакетов при передаче данных (между филиалами и ЦОД, филиалами и облаками) и обеспечивает высокое качество связи. Поговорим о ней подробнее.

Технология DMPO

Технология DMPO (Dynamic Multi-Path Optimization) позволяет нам за счет гибкого управления трафиком на уровне приложений агрегировать потоки и обеспечивать высокое качество связи. DMPO работает на уровне отдельных пакетов, однако учитывает, к каким приложениям эти пакеты относятся. Транзакционным приложениям, таким как веб-сайты, инструменты передачи файлов, не принципиально, придет пакет вовремя или чуть позже, главное, чтобы он вообще дошел. А приложениям реального времени (стриминговые сервисы, приложения для голосовых вызовов и т.п.) очередность пакетов важна. Из-за мелких неполадок может «рассыпаться» картинка на экране или собеседник не расслышит слово. Поэтому важно, чтобы SD-WAN устройства понимали, что это за приложение. В этом помогает движок распознавания приложений (Deep Application Recognition), работающий на каждом устройстве SD-WAN Edge.

Приложений существует огромное множество, 3000+ из них есть во встроенной базе DPI VMware SD-WAN. Кроме того, можно добавить и кастомные (самописные) сигнатуры, если вдруг какого-то приложения не хватает. Посмотрим, какие функции выполняет DMPO.

Непрерывный мониторинг качества каналов

Для мониторинга состояния WAN-каналов в реальном времени стандартные сетевые технологии вроде BFD или ICMP Probes не совсем подходят, т.к. тестовые пакеты отличаются от пользовательского трафика (другие классы DSCP, другой размер пакетов), а интервал проб слишком высок, чтобы достоверно и быстро определить ухудшение качества. Например, даже при замерах BFD или ICMP каждую секунду за время между этими замерами у обычного пользователя с ноутбуком передается 200+ пакетов (проверьте сами, сняв дамп трафика). Чтобы зафиксировать уровень потери в 2%, потребуется не менее 50 BFD пакетов (50 с). Если использовать более «агрессивные» значения, например, 100 мс, все равно потребуется не менее 5 секунд, чтобы обнаружить потери в 2% на канале. За это время тысячи пакетов с пользовательскими данными могут быть потеряны.

Поэтому в решении Velocloud используется собственный протокол инкапсуляции — Velocloud Multipath Protocol (VCMP), который добавляет к каждому пакету специальные заголовки, помогающие отследить время его прохождения (Latency, Jitter) и факт доставки. Это позволяет обнаружить ухудшение качества каналов практически мгновенно, а затем перенаправить пакеты на канал с лучшими показателями незаметно для пользователя. 

За точное время в сети отвечает Control Plane — уже известные нам SD-WAN Gateways. Устройства SD-WAN Edge синхронизируют свое время с центром управления сетью. Таким образом можно гарантировать, что все временные метки будут достоверны. В стандартных сетевых протоколах такие возможности отсутствуют.

Состояние каналов отслеживается даже в том случае, если пользовательского трафика на данный момент нет. Ведь когда этот трафик появится, его нужно сразу же отправить по оптимальному пути. Для этого устройства SD-WAN Edge обмениваются служебными пакетами VCMP, только без User Payload, каждые 100 или 500 мс. Как только появляется пользовательский трафик, система снова начинает отслеживать SLA для каждого пакета.

Динамическая балансировка трафика и агрегация каналов

DMPO позволяет определять, какие приложения используют сеть, при помощи политик манипулировать поведением SD-WAN и настраивать приоритеты. 

Решение VMware умеет автоматически (за счет работы overlay-протокола VCMP) выбирать наилучший способ обработки и доставки трафика приложений. К примеру, если конкретному приложению требуется ускорить передачу данных, то есть агрегировать несколько каналов, «умное» SD-WAN устройство начнет передавать данные сразу по нескольким путям. Для балансировки используются все доступные подключения.

В ряде случаев стандартная балансировка не имеет смысла, они сильно отличаются между собой по характеристикам: на одном задержка 5 мс, на другом — 100). Соответственно, балансировать трафик между ними бесполезно. Однако если «быстрый» канал окажется забит под завязку и на нем скопится большая очередь из пакетов, система определит, что каналы «сравнялись» по времени доставки пакетов. В этом случае в рассылке пакетов будут задействованы оба канала таким образом, чтобы пакеты по обоим доходили до точки назначение в одно и то же время. На принимающей стороне пакеты выстраиваются в порядке очереди так, чтобы клиенту или серверу за пределами сети SD-WAN не приходилось пересобирать их в требуемой последовательности. Соответственно, еще одна из функций, которую способно обеспечить устройство SD-WAN — автоматический реордеринг.

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

Из-за особенностей каналов связи трафик между точками А и В может идти неравномерно: в одну сторону с задержкой 10 мс, обратно — 20 мс. В то же самое время канал работает наоборот: «туда» — 20 мс, «сюда» — 10 мс задержки. По сети VMware SD-WAN такая сессия будет передаваться по обоим каналам сразу, в одну сторону по каналу с задержкой 10 мс, и так же обратно — по каналу с наилучшими характеристиками.

За счет того, что каждый пакет обрабатывается индивидуально алгоритмами DMPO, система выбирает наилучший путь для каждого пакета автоматически, принимая решения в реальном времени.

Раньше администраторам приходилось вручную определять эту логику, либо настраивать схемы вида: «каждые N секунд проверяем, какой канал и в какую сторону работает лучше и перестраиваемся при необходимости». Реализовать такую схему без разрывов пользовательских сессий практически невозможно. Ценность SD-WAN заключается в том, что вся эта запутанная логика делегирована алгоритмам — они быстрее и точнее, чем даже самые сложные проверочные скрипты. Логика VMware SD-WAN работает на разных типах сессий и на разных транспортных протоколах, никак не нарушая логику работы TCP/IP.

Технологии корректировки ошибок

Выше мы упомянули, что каждый входящий пакет «маркируется» своим номером в очереди. Соответственно, сетевые устройства знают, в какой последовательности пакеты должны приходить, их можно легко отбалансировать по разным каналам и пересобрать в правильном порядке, когда они дойдут до получателя. Также за счет этого легко обнаружить потери и в случае необходимости запросить у отправляющей стороны пропущенный пакет данных:

Получатель: Так, к нам идут пакеты. Первый, второй, третий, пятый… Стоп. Почему пятый? Требую выслать мне пакет №4 как можно скорее! Прием!

Отправитель: Посмотрим, к какому приложению относился этот ваш пакет. Ага, вот оно! Высылаю компенсацию, подтвердите получение!

В случае транзакционного приложения устройству-отправителю будет достаточно найти в буфере нужный пакет и повторить отправку — так работает технология NACK (Negative Acknowledgement). В устройствах SD-WAN используется серверная память гораздо большего объема, чем в обычных маршрутизаторах. Самая младшая модель имеет на борту 4 Гб, на старших — 32 Гб и выше. Это позволяет держать буфер данных и по необходимости досылать потерявшиеся пакеты.

С realtime-приложениями ситуация обстоит иначе. Повторно отправлять пакеты не имеет смысла, важно предотвратить потери в будущем. Поэтому устройство-отправитель включает упреждающую коррекцию ошибок (Forward Error Correction, FEC), и каждый пакет начинает передаваться в двух экземплярах. Таким образом повышается вероятность успешной доставки пакетов, а лишние автоматически отбрасываются на принимающей стороне. На практике это позволяет использовать телефонию и видеоконференции на каналах с потерями до 30-40%.

Под капотом у провайдера

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

Разделение зон ответственности

Первая задача, с которой столкнется компания при самостоятельном внедрении — построение облачной инфраструктуры SD-WAN: деплой контроллеров, виртуальных машин, выделение ресурсов, администрирование и многое другое. Построение такой инфраструктуры повлечет за собой значительные расходы в виде выделения или покупки дополнительных серверов и ПО, а также обеспечения условий Disaster Recovery. Эти сложности и расходы особенно трудно обосновать при постепенном внедрении SD-WAN. Во-вторых, для построения надежной отказоустойчивой сетевой связности требуется детально проработать целевую схему подключения, определить движение потоков данных, настроить политики безопасности.

Когда вы обращаетесь к провайдеру, все эти задачи ложатся на него. Он обеспечивает инфраструктуру, круглосуточный мониторинг, администрирование сети и защищенных распределенных ЦОД, уровень доступности и выполнение SLA. Провайдер поможет выбрать оптимальное оборудование и даст рекомендации по модернизации сети, чтобы она соответствовала всем требованиям качества и надежности. При этом клиенту остается только запросить доступ к облачному порталу управления сетью и создать учетные записи для администраторов. На любые вопросы по использованию услуги и настройке сервисов SD-WAN поможет ответить круглосуточная русскоязычная техническая поддержка.

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

Распределенная инфраструктура провайдера

Общая инфраструктура «ИТ-ГРАД» и #CloudMTS насчитывает 10 ЦОД в СНГ и множество точек присутствия по всему миру. Где бы ни находился филиал, он будет подключен к ближайшему SD-WAN Gateway, что позволит минимизировать задержки и оптимизировать доступ к любым приложениям — в локальных ЦОД и любых облаках. Наличие распределенной инфраструктуры также позволяет обеспечивать георезервирование для сети SD-WAN любого масштаба.

Инфраструктура «ИТ-ГРАД» и #CloudMTS на территории РФ

В каждом ЦОД резервируются каналы, обеспечивается высокопроизводительная связность шлюзов SD-WAN с внешним миром. Дата-центры подключены к глобальной опорно-магистральной сети МТС, которая обеспечивает доступ к мировым публичным точкам обмена трафиком — DE-CIX (Франкфурт), AMS-IX (Амстердам), HK-IX (Гонконг), LINX (Лондон), Equinix, NY-IX и т.д.

Кроме того, в распоряжении наших заказчиков:

  • PNI (Private Network Interconnect) с гиперскейлерами и крупными генераторами трафика. Среди них Amazon AWS, Microsoft, Google Cloud, Apple, Facebook, Valve, Twitch, Netflix, Twitter и прочие.

  • CDN-сети: Akamai, Google Cash и Google CDN, Cloudflare, Gcore labs, Microsoft и прочие. 

Интеграция с IaaS и другими сервисами

Использование SD-WAN позволяет получить надежный доступ к сервисам, расположенными в инфраструктуре облачного провайдера. Какие преимущества это дает:

  • VDI, Skype for Business — компенсация ошибок, умная балансировка пакетов между всеми каналами, подключенными к площадке. Использование решения обеспечивает гарантированное качество для сервисов реального времени за счет приоритезации на уровне приложений и компенсации ошибок на каналах. 

  • Резервное копирование — SD-WAN позволяет наладить максимально быструю и надежную транспортировку бэкапов по сети (с агрегацией каналов и обеспечением отказоустойчивости) по всем доступным каналам до облака.

  • S3 хранилище, облачный диск — при использовании сервисов блочного, объектного и файлового хранилищ подключение по SD-WAN позволяет передавать большие файлы на максимальных скоростях. Это достигается за счет отсутствия ретрансмитов при передаче данных и агрегирования каналов связи, подключенных к устройству SD-WAN.

  • GPU Super Cloud — высокоскоростная передача больших объемов данных для обработки в облачной среде.

Все чаще наши клиенты для подключения своих офисов к виртуальным дата-центрам «ИТ-ГРАД» и #CloudMTS вместо выделенных каналов выбирают SD-WAN и интернет-каналы. Клиенты, уже использующие выделенные каналы, применяют SD-WAN для агрегации существующих каналов, многократно повышая производительность и надежность подключения к облаку.

Будем рады пообщаться с вами в комментариях! В следующей статье мы проведем тестирование сервиса и на практике посмотрим, какие преимущества дают технология DMPO и протокол VCMP.

Let's block ads! (Why?)

JavaScript: Стек вызовов и магия его размера

Привет, Хабровчане!

Большинство разработчиков, которые использовали рекурсию для решения своих задач, видели такую ошибку:

 RangeError: Maximum call stack size exceeded. 

Но не каждый разработчик задумывался о том, а что означает "размер стэка вызовов" и каков же этот размер? А в чем его измерять?

Думаю, те, кто работают с языками, напрямую работающими с памятью, смогут легко ответить на этот вопрос, а вот типичный фронтэнд разработчик скорее всего задает себе подобные вопросы впервые. Что-ж, попробуем разобраться!

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

О чем ты вообще, автор?

Для статьи важно понимание таких понятий как Execution Stack, Execution Context. Если вы не знаете, что это такое, то советую об этом почитать. На данном ресурсе уже было достаточно хороших статей на эту тему. Пример - https://habr.com/ru/company/ruvds/blog/422089/

Когда возникает эта ошибка?

Разберем на простом примере - функция, которая рекурсивно вызывает сама себя.

const func = () => {
    func();
}

Если попытаться вызвать такую функцию, то мы увидим в консоли/терминале ошибку, о которой я упомянул выше.

А что если подглядеть, сколько же раз выполнилась функция перед тем, как возникла ошибка?

На данном этапе код запускается в Chrome DevTools последней версии на март 2021. Результат будет различаться в разных браузерах. В дальнейшем в статье я упомяну об этом.

Для эксперимента будем использовать вот такой код:

let i = 0;

const func = () => {
  i++;

  func();
};

try {
  func();
} catch (e) {
  // Словили ошибку переполнения стэка и вывели значение счетчика в консоль
  console.log(i);
}

Результатом вывода в консоль стало число в 13914. Делаем вывод, что перед тем, как переполнить стэк, наша функция вызвалась почти 14 тысяч раз.

Магия начинается тогда, когда мы начинаем играться с этим кодом. Допустим, изменим его вот таким образом:

let i = 0;

const func = () => {
  let someVariable = i + 1;
  i++;

  func();
};

try {
  func();
} catch (e) {
  console.log(i);
}

Единственное, что мы добавили, это объявление переменной someVariable в теле функции. Казалось бы, ничего не поменялось, но число стало меньше. На этот раз функция выполнилась 12523 раз. Что более чем на тысячу меньше, чем в прошлом примере. Чтобы убедиться, что это не погрешность, пробуем выполнять такой код несколько раз, но видим одни и те же результаты в консоли.

Почему же так? Что изменилось? Как понять, посмотрев на функцию, сколько раз она может выполниться рекурсивно?!

Магия раскрыта

Отличие второго примера от первого - наличие дополнительной переменной внутри тела функции. На этом все. Соответственно, можно догадаться, именно из-за этого максимальное количество рекурсивных вызовов стало меньше. Что-ж, а что, если у нас будет не одна, а четыре переменных внутри функции? По этой логике, количество рекурсивных вызовов станет еще меньше? Проверим: 

let i = 0;

const func = () => {
  let a = i + 1;
  let b = a + 1;
  let c = b + 1;
  let d = c + 1;
  let e = d + 1;
  i++;

  func();
};

try {
  func();
} catch (e) {
  console.log(i);
}

По этой логике, значение счетчика должно стать еще меньше. Выдыхая, видим вывод - 8945. Ага, оно стало еще меньше. Значит, мы на правильном пути. Хочу привести небольшую аналогию, чтобы даже самым маленьким стало понятно.

Execution Stack (Call Stack) - это емкость с водой. Изначально она пустая. Так получилось, что эта емкость с водой стоит прямо на электрической проводке. Как только емкость переполнится, вода проливается на провода, и мы видим ошибку в консоли. При каждом новом рекурсивном вызове функции в стэк падает капелька воды. Само собой, чем капелька воды крупнее, тем быстрее наполнится стэк.

Емкости бывают разные по размеру. И размер емкости в данном примере - это размер коллстэка. А точнее - количество байт, которое он максимально может в себе удержать. Как гласит статья про Call Stack (Execution Stack), которую я приложил в начале, на каждый вызов функции создается Execution Context - контекст вызова функции (не путать с this). И упрощая, в нем, помимо разных "подкапотных" штук, содержатся все переменные, которые мы объявили внутри функции. Как Execution Context, так и каждая переменная внутри него имеют определенный размер, который они занимают в памяти. Сложив эти два размера мы и получим "размер" капли, которая капает в кувшин при каждом рекурсивном вызове функции.

У нас уже достаточно много данных. Может быть, поэкспериментируем еще? А давайте попробуем вычислить, какой размер коллстэка в движке, который использует Chrome?

Математика все-таки пригодилась

Как мы выяснили, у нас есть две неизвестные, которые составляют размер функции (капельки, которая падает в емкость). Это размер самого Execution Stack, а так же сумма размеров всех переменных внутри функции. Назовем первую N, а вторую K. Сам же неизвестный размер коллстэка обозначим как X.

В итоге - количество байт, которое занимает функция (в упрощенном виде) будет:

FunctionSize = N + K * SizeOfVar

SizeOfVar в данном случае - количество байт, которые занимает переменная в памяти.

Учитывая, что мы знаем количество вызовов первой функции, в теле которой не объявляются переменные, размер коллстэка можно выразить как:

X = (N + 0 * SizeOfVar)* 13914 = N * 13914

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

X = (N + 5 * SizeOfVar) * 8945

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

N * 13914 = (N + 5 * SizeOfVar) * 8945

Выглядит неплохо. У нас тут две неизвестные переменные - N и SizeOfVar. Если N мы не можем откуда-то узнать, то что насчет SizeOfVar? Заметим, что во всех функциях, которые фигурировали выше, переменные хранили значение с типом "number", а значит, нужно просто узнать, сколько же байт в памяти занимает одна такая переменная.

С помощью великого гугла получаем ответ - "Числа в JavaScript представлены 64-битными значениями с плавающей запятой. В байте 8 бит, в результате каждое число занимает 64/8 = 8 байт." Вот она - последняя неизвестная. 8 байт. Подставляем ее в наше уравнение и считаем, чем равно N.

N * 13914 = (N + 5 * 8) * 8945

Упрощаем:

N * 13914 = N * 8945 + 40 * 8945

Если выразить отсюда N, то получим ответ: N равно приблизительно 72. В данном случае 72 байтам.

Теперь, подставив N = 72 в самое первое уравнение, получим, что размер коллстэка в Chrome равен... 1002128 байтов. Это почти один мегабайт. Не так уж и много, согласитесь.

Мы получили какое-то число, но как убедиться, что наши расчеты верны и число правильное? А давайте попробуем с помощью этого числа спрогнозировать, сколько раз сможет выполниться функция, внутри которой будет объявлено 7 переменных типа 'number'. 

Считаем: Ага, каждая функция будет занимать (72 + 7 * 8) байт, это 128. Разделим 1002128 на 128 и получим число... 7829! Согласно нашим расчетам, такая функция сможет рекурсивно вызваться именно 7829 раз! Идем проверять это в реальном бою...

Мы были очень даже близки. Реальное число отличается от теоретического всего на 3. Я считаю, что это успех. В наших расчетах было несколько округлений, поэтому результат мы посчитали не идеально, но, очень-очень близко. Небольшая погрешность в таком деле - нормально.

Получается, что мы посчитали все верно и можем утверждать, что размер пустого ExecutionStack в Chrome равен 72 байтам, а размер коллстэка - чуть меньше одного мегабайта.

Отличная работа!

Важное примечание

Размер стэка разнится от браузера к браузеру. Возьмем простейшую функцию из начала статьи. Выполнив ее в Сафари получим совершенно другую цифру. Целых 45606 вызовов. Функция с пятью переменными внутри выполнилась бы 39905 раз. В NodeJS числа очень близки к Chrome по понятным причинам. Любопытный читатель может проверить это самостоятельно на своем любимом движке JavaScript. 

А что с непримитивами?

Если с числами все вроде бы понятно, то что насчет типа данных Object? 

let i = 0;

const func = () => {
  const a = {
    key: i + 1,
  };
  i++;

  func();
};

try {
  func();
} catch (e) {
  console.log(i);
}

Простейший пример на ваших глазах. Такая функция сможет рекурсивно вызваться 12516. Это практически столько же, сколько и функция с одной переменной внутри. Тут в дело вступает механизм хранения и передачи объектов в JS'е - по ссылке. Думаю, большинство уже знают об этом.

А что с этим? А как поведет себя вот это? А что с *?

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

Итоги:

  • Количество рекурсивных вызовов функции до переполнения стэка зависит от самих функций.

  • Размер стэка измеряется в байтах.

  • Чем "тяжелее" функция, тем меньше раз она может быть вызвана рекурсивно.

  • Размер стэка в разных движках различается.

Вопрос особо любознательным: А сколько переменных типа "number" должно быть объявлено в функции, чтобы она могла выполниться рекурсивно всего два раза, после чего стэк переполнится?

Let's block ads! (Why?)

Кватернионы. Решение одной навигационной задачи

История

Некоторое время назад я занимался одной интересной задачей, относящейся к спутниковой навигации. Используя фазовый фронт сигнала, объект навигации (ОНВ) измеряет координаты навигационных спутников (НС) в своей системе координат (локальная система, ЛСК). Также ОНВ получает значения положений НС в глобальной системе координат (ГСК), и измеряет время получения сигнала НС (рис. 1). Требовалось вычислить координаты ОНВ в ГСК и системное время, то есть решить навигационную задачу.

Рис. 1. Системы координат
Рис. 1. Системы координат

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

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

Но оно, конечно, существует. Мне удалось найти решение этой задачи, используя свойства кватернионов. В этом материале я хочу описать саму задачу, ход и её решение, уделяя внимание ориентации и координатам ОНВ, и пока оставляя за рамками измерения координат по фазовому фронту.

Входные данные

Итак, входные данные:

\mathbf R_i = \begin{bmatrix} x_i & y_i & z_i \end{bmatrix}^T: вектор-столбец положения i-го НС в ГСК, i = 1, 2, 3: номер НС,
\mathbf R_i ^{'} = \begin{bmatrix} x_i^{'} & y_i^{'} & z_i^{'} \end{bmatrix}^T: вектор-столбец положения i-го НС в координатах ЛСК; векторы \mathbf R_iи \mathbf R_i^{'} полагаем известными; ГСК, ЛСК: правые декартовы системы координат в 3-х мерном евклидовом пространстве E^3 с разнонаправленными базисами (\mathbf e_x, \mathbf e_y, \mathbf e_z) и ( \mathbf e_x^{'}, \mathbf e_y^{'}, \mathbf e_z^{'})соответственно; начала координат ГСК и ЛСК не совпадают. Нижний индекс дальше будет обозначать номер вектора, верхний - номер элемента в векторе, если только не указано явно, что это степень.

Задача

Рис. 2. Основные величины
Рис. 2. Основные величины

Нужно найти:

\mathbf R_a = \begin{bmatrix} x_a & y_a & z_a \end{bmatrix}^T: вектор-столбец положения ОНВ в ГСК,

\mathbf M: оператор перехода от ЛСК к ГСК, который я условно назвал "оператором ориентации" (рис. 2)

Решение

Теперь ход моих рассуждений и решения.

Разложение векторов \mathbf R_i и \mathbf R_i^{'} в своих базисах: \mathbf R_i = x_i \mathbf e_x + y_i \mathbf e_y + z_i \mathbf e_z,\quad \mathbf R_i^{'} = x_i^{'} \mathbf e_x^{'} + y_i^{i} \mathbf e_y^{'} + z_i^{'} \mathbf e_z^{'}, где i=1,2,3 - номер НС. Базисные векторы ЛСК

\begin{cases} \mathbf e_x^{'} = a_1^1 \mathbf e_x + a_1^2 \mathbf e_y + a_1^3 \mathbf e_z,\\ \mathbf e_y^{'} = a_2^1 \mathbf e_x + a_2^2 \mathbf e_y + a_2^3 \mathbf e_z,\\ \mathbf e_z^{'} = a_3^1 \mathbf e_x + a_3^2 \mathbf e_y + a_3^3 \mathbf e_z, \end{cases}

где \lbrace a_j^i: a_j^i \in \mathcal R \rbrace, \mathcal R - множество вещественных чисел, i = 1, 2, 3. Выписывая эти коэффициенты в матрицу

\mathbf M = \begin{bmatrix} a_1^1 & a_2^1 & a_3^1\\ a_1^2 & a_2^2 & a_3^2\\ a_1^3 & a_2^3 & a_3^3 \end{bmatrix},

получаем

\mathbf e_x^{'} = \mathbf M \mathbf e_x,\; \mathbf e_y^{'} = \mathbf M \mathbf e_y,\; \mathbf e_z^{'} = \mathbf M \mathbf e_z,\;

полагая, что \mathbf e_x = \begin{bmatrix} 1 & 0 & 0 \end{bmatrix}^T, \mathbf e_y = \begin{bmatrix} 0 & 1 & 0 \end{bmatrix}^T, \mathbf e_z = \begin{bmatrix} 0 & 0 & 1 \end{bmatrix}^T. Следовательно, координаты вектора \mathbf R_i^{'}в базисе ГСК \mathbf R_i^{''} = \mathbf M \mathbf R_i^{'}. Можно сказать, что \mathbf M является матрицей некоторого линейного оператора (или "оператора ориентации"), такого, что E^3 \to E^3, определённого в базисе ГСК. Такой матрицей может быть матрица направляющих косинусов или любая из матриц поворотов.

Свойства произвольного евклидового пространства позволяют записать уравнение для вычисления вектора положения ОНВ:

\mathbf R_a = \mathbf R_i - \mathbf M \mathbf R_i^{'}. (1)

Неудачный поиск решения

Уравнение (1) содержит две неизвестные матричные величины \mathbf R_a и \mathbf M, и имеет поэтому бесконечное число решений. Аналогичное соотношение для трёх различных НС в виде

\mathbf R = \mathbf R_a + \mathbf M \mathbf R^{'},\qquad (2)

где

\mathbf R = \begin{bmatrix} x_1 & x_2 & x_3\\ y_1 & y_2 & y_3\\ z_1 & z_2 & z_3 \end{bmatrix},\quad \mathbf R_1 = \begin{bmatrix} x_1^{'} & x_2^{'} & x_3^{'}\\ y_1^{'} & y_2^{'} & y_3^{'}\\ z_1^{'} & z_2^{'} & z_3 ^ {'} \end{bmatrix} -

квадратные невырожденные матрицы, также содержат две неизвестные матричные величины \mathbf R_a и \mathbf M. Можно переписать (1) и (2) так, чтобы избавиться от величины \mathbf R_a:

\mathbf R_a = \mathbf R_i - \mathbf M \mathbf R_i^{'} = \mathbf R_k - \mathbf M \mathbf R_k^{'},\quad i \ne k;\quad i,k=1,2,3,

откуда

(x_k^{'} - x_i^{'}) \mathbf M \mathbf e_x + (y_k^{'} - y_i^{'}) \mathbf M \mathbf e_y + (z_k^{'} - z_i^{'}) \mathbf M \mathbf e_z =\\ = (x_k - x_i) \mathbf M \mathbf e_x + (y_k - y_i) \mathbf M \mathbf e_y + (z_k - z_i) \mathbf M \mathbf e_z,

или

\mathbf M \mathbf R_{ki}^{'} = \mathbf R_{ki}, \qquad (3)

где \mathbf R_{ki}^{'} = \mathbf R_k^{'} - \mathbf R_i^{'}.

Если бы я смог как-нибудь найти \mathbf M из (3) и подставить в (1), то задача была бы решена. К примеру, была сделана попытка расписать (3) по трём НС аналогично с (2), но в итоге матрицы получались вырожденные и уравнение единственного решения поэтому не имело. Попытки расписать и решить систему уравнение вроде

\begin{cases} \mathbf M \mathbf R_{12}^{'} = \mathbf R_{12},\\ \mathbf M \mathbf R_{13}^{'} = \mathbf R_{13} , \end{cases} \qquad (4)

приводили к тому самому нагромождению синусов и косинусов, о котором я упомянул во вступлении.

Здесь я и подумал, а получится ли найти \mathbf M, если (3) или (4) записать в кватернионном виде. В итоге получилось, но продолжу по порядку.

Теория о кватернионе поворота

Два теоретических момента, которые, думаю, стоит упомянуть.

Кватернионом, как мы знаем, является математический объект вида \dot q = q^0 + iq^1 + jq^2 + kq^3 = q^0 + \dot{ \mathbf q}, где q^0, q^1, q^2, q^3\in \mathcal R, q^0- скалярная часть (множитель вещественной единицы), \dot{\mathbf q} - векторная часть; 1, i, j, k - вещественная и три разные мнимые единицы с таблицей умножения:

\begin{split} 1^2 = 1,\quad 1i = i1,\quad 1j = j1,\quad 1k = k1,\quad i^2 = j^2 = k^2 = -1,\\ ij = -ji = k,\quad jk = -kj = i,\quad ki = -ik = j. \end{split}

Кватернион даёт удобную возможность представления трёхмерных преобразований (вращений), определяя одновременно и ось поворота, и угол вращения. Если взять некоторый кватернион \dot q = q^0 + \dot{\mathbf q} = q^0 + iq^1 + jq^2 + kq^3, такой, что \Vert \dot q \Vert = 1, то можно записать, что \Vert \dot q \Vert = q^{(0)^2} + \Vert \dot {\mathbf q } \Vert = 1, и значит \Vert \dot q \Vert = \cos^2 \gamma + \sin^2 \gamma = 1, где \cos^2 \gamma = q^{(0)^2}, \sin^2 \gamma = \Vert \dot { \mathbf q} \Vert = q^{(1)^2} + q^{(2)^2} + q^{(3)^2}. Здесь индекс в скобках обозначает номер элемента, а верхний индекс без скобки - возведение в степень. Если вектор \dot { \mathbf q} представить как

\mathbf{ \dot q} = \frac{1}{\Vert \dot {\mathbf q} \Vert} (q^{(1)^2} + q^{(2)^2} + q^{(3)^2}) \Vert \dot {\mathbf q} \Vert = (q^{(x)^2} + q^{(y)^2} + q^{(z)^2}) \Vert \dot {\mathbf q} \Vert = (q^{(x)^2} + q^{(y)^2} + q^{(z)^2}) \sin^2 \gamma,

где q^x = \frac{q^1}{\vert \dot {\mathbf q} \vert}, q^y = \frac{q^2}{\vert \dot {\mathbf q} \vert}, q^z = \frac{q^3}{\vert \dot {\mathbf q} \vert}, то кватернион \dot q запишется так:

\dot q = \cos \gamma + \dot {\mathbf q}_n \sin \gamma,

где \dot {\mathbf q}_n = iq^x + jq^y + kq^z. Если теперь взять произвольный кватернион \dot R с нулевой скалярной частью и вектором \dot {\mathbf R}, то результатом операции

\dot R^{'} = \dot q \circ \dot R \circ \dot q^{-1} \qquad (5)

будет вектор \dot{\mathbf R}^{'} с той же длиной, что и \dot {\mathbf R}, но повёрнутый на угол 2 \gamma против часовой стрелки вокруг оси, направляющим вектором которой является \dot {\mathbf q}_n. Дальше будут встречаться фразы вроде "кватернион \dot q выполняет поворот вектора \dot R", которые, конечно, подразумевают применение кватерниона \dot q к вектору \dot{\mathbf R} и получение \dot{\mathbf R}^{'} в соответствии с (5).

Последний теоретический момент. Для обозначения разных видов умножения используются такие общеизвестные значки: \dot {\mathbf q} \dot {\mathbf r} или \dot {\mathbf q} \cdot \dot {\mathbf r}: скалярное произведение,\dot q \circ \dot r: кватернионное произведение,\dot {\mathbf q} \times \dot {\mathbf r}: векторное произведение.

На этом с теорией всё.

Описание решения с кватернионами

Вернёмся к векторам \mathbf R_{ki}^{'} и \mathbf R_{ki}. Три замечания об их характере, которые потребуются дальше. Примем пока k = 1 , i = 2.

Нужные замечания

Во-первых, эти векторы являются свободными векторами, которые можно перемещать в пространстве, соблюдая параллельность перемещений.

Во-вторых, они неколлинеарны. Действительно, если бы они были коллинеарными, то (3) обращалось бы в истинное высказывание только при единичной матрице \mathbf M и задачу решать не нужно было.

И, в-третьих, \vert \mathbf R_{12}^{'} \vert = \vert \mathbf R_{12} \vert. В самом деле, так как \mathbf M - это ортогональная матрица, которая не меняет длину вектора \mathbf R_{12}^{'}, то из (3) следует, что длины векторов \mathbf R_{12}^{'} и \mathbf R_{12} равны.

Tак как в (3) длины векторов не имеют значения, для упрощения записей и решения дальше заменю векторы \mathbf R_{12}^{'} и \mathbf R_{12} их нормированными эквивалентами:

\mathbf r_1 \equiv \frac{\mathbf R_{12}}{\vert \mathbf R_{12} \vert} = \begin{bmatrix} r_1^1 & r_1^2 & r_1^3 \end{bmatrix}^T, \qquad \mathbf p_1 \equiv \frac{\mathbf R_{12}^{'}}{\vert \mathbf R_{12}^{'} \vert} = \begin{bmatrix} p_1^1 & p_1^2 & p_1^3 \end{bmatrix}^T,

и в виде кватернионов:

\dot r = 0 + \mathbf{\dot r}_1 = 0 + ir_1^1 + jr_1^2 + kr_1^3, \quad \dot p = 0 + \mathbf{\dot p}_1 = 0 + ip_1^1 + jp_1^2 + kp_1^3.

Кватернионная форма основных уравнений

Выражение (3) в кватернионной форме выглядит теперь так:

\dot q \circ \dot r_1 \circ \dot q^{-1} = \dot p_1,\qquad (6)

где \dot q- неизвестный кватернион, эквивалент \mathbf M, а выражение (1) так:

\dot R_a = \dot R_1 - \dot q \circ \dot R_1 \circ \dot q^{-1}, \qquad (7)

где \dot R_a = 0 + ix_a + jy_a + kz_a, \dot R_1 = 0 + ix_1 + jy_1 + kz_1.

Рис. 3. Основные векторы
Рис. 3. Основные векторы

Так как векторы \dot {\mathbf r}_1 и \dot {\mathbf p}_1 свободны, неколлинеарны, то можно совместить их концы таким образом, чтобы \dot {\mathbf r}_1 и \dot {\mathbf p}_1 образовали угол \gamma_1 на плоскости, натянутой на эти векторы (пусть эта плоскость будет \lambda_1). Начало координат поместим в точку, общую для \dot {\mathbf r}_1 и \dot {\mathbf p}_1 (рис. 3), и будем полагать, что значение \gamma_1 известно.

Уравнение (6), аналогично уравнению (3), по-прежнему имеет бесконечное множество решений: можно найти сколько угодно различных \dot q, которые удовлетворяют (6). Геометрически это обозначает, что можно найти бесконечное число различных прямых, вокруг которых можно выполнить вращение, совмещающее \dot {\mathbf r}_1 с \dot {\mathbf p}_1. На рис. 3 приведён пример, в котором поворот выполняется по кратчайшему пути вокруг прямой с направляющим вектором \dot {\mathbf d}_1, ортогональным плоскости \lambda_1. Очевидно, что \gamma_{d_1}\ = \gamma_1.

На рис. 4, а) и б) приведён пример, в котором вокруг некоторой оси с направляющим вектором \dot {\mathbf q}_1 построен круговой конус \tau_1 с направляющими прямыми \dot {\mathbf r}_1 и \dot {\mathbf p}_1. Ясно, что вокруг такой оси можно сделать поворот, совмещающий \dot {\mathbf r}_1 с \dot {\mathbf p}_1, который будет выполнен не по кратчайшему пути (не в плоскости \lambda_1), и угол \gamma_{q_1}, измеряемый в плоскости основания конуса \tau_1, не будет равен \gamma_1, измеряемый в плоскости \lambda_1.

Рис. 4. Поворот r к p вокруг произвольной оси
Рис. 4. Поворот r к p вокруг произвольной оси

Чтобы найти неизвестное значение \dot q, я добавил два новых объекта \dot r_2 = \dot{R}_3^{'} - \dot{R}_1^{'} и \dot p_2 = \dot R_3 - \dot R_1, получаемые из векторов \mathbf R_3 и \mathbf R_1. Выражение (6), аналогично (4), расширяется до системы уравнений

\begin{cases} \dot q \circ \dot r_1 \circ \dot q^{-1} = \dot p_1,\\ \dot q \circ \dot r_2\circ \dot q^{-1} = \dot p_2. \end{cases} \qquad (8)

Теперь можно утверждать, что кватернион \dot q, найденный из этой системы уравнений (8), является единственным решением, и он выполняет такой поворот в пространстве, который совмещает тройку векторов базиса ЛСК с векторами ГСК. Следовательно, его можно подставить в выражение (7) и вычислить искомое R_a - положение ОНВ.

Когда я записал (8), отчего-то стало ясно, что здесь безразмерная куча тригонометрии может не возникнуть, и аналитическое решение поэтому можно будет найти более-менее просто.

Геометрия задачи

Рис. 5. Геометрия задачи
Рис. 5. Геометрия задачи

Геометрически задача теперь выглядит так. Нужно найти в пространстве такую ось, вокруг которой можно выполнить поворот, совмещающий вектор \dot {\mathbf r}_1 с вектором \dot {\mathbf p}_1, и вектор \dot {\mathbf r}_2 с вектором \dot {\mathbf p}_2. Прямые, направляющими векторами которых являются \dot {\mathbf r}_1 (или \dot {\mathbf p}_1) и \dot {\mathbf r}_2 (или \dot {\mathbf p}_2), образуют два различных круговых конуса \tau_1, \tau_2. Эти конусы имеют общую ось, направляющим вектором которой является искомый \dot {\mathbf q}. При этом повороты \dot {\mathbf r}_i к \dot {\mathbf p}_i, i = 1, 2, будут выполняться не по кратчайшему пути, и поэтому углы \gamma_{q_i}, измеренные в плоскости оснований конусов \tau_i, будут отличаться от углов \gamma_i (рис. 5).

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

Утверждение 1. Угол поворота вокруг биссектрисы \dot {\mathbf b}_1 такого, который совмещает \dot {\mathbf r}_1 с \dot {\mathbf p}_1, равен \pi (рис. 4, в), г)).

Утверждение 2. Поворот, который совмещает \dot {\mathbf r}_i с \dot {\mathbf p}_i, i = 1, 2, вокруг некоторой прямой можно сделать тогда и только тогда, когда эта прямая лежит в плоскости, натянутой на \dot {\mathbf d}_i и \dot {\mathbf b}_i (плоскость \delta_i), рис. 6). Вокруг любой другой прямой такой поворот выполнить нельзя.

Рис. 6. Плоскости векторов b и d
Рис. 6. Плоскости векторов b и d

Для доказательства этих утверждений нужно рассмотреть свойства кругового конуса \tau_i, образованного линией, направляющий вектор которой равен \dot {\mathbf r}_i (или \dot {\mathbf p}_i), а ось лежит в плоскости \delta_i.

Очевидно, что ось, вокруг которой можно сделать такой поворот, который совместит \dot {\mathbf r}_i с \dot {\mathbf p}_i , i = 1, 2, будет одновременно принадлежать обеим плоскостям \delta_i, то есть будет совпадать с линией их пересечения. Поэтому вектор \dot {\mathbf q} будем искать из уравнения линии пересечения плоскостей \delta_i (рис. 7).

Рис. 7. Линия пересечения плоскостей
Рис. 7. Линия пересечения плоскостей

Эта прямая не будет совпадать с прямыми, определяемые направляющими векторами \dot {\mathbf d}_i. Проходя через начало координат, она образует некоторый угол \beta_1 с вектором \dot {\mathbf d}_1, и угол \beta_2 с вектором \dot {\mathbf d}_2 (рис. 8). Оба этих угла нам пока неизвестны и \beta_1 \ne \beta_2.

Рис. 8. Все векторы
Рис. 8. Все векторы

Посмотрим на рис. 8. Если взять некоторый кватернион \dot e_{d_1}, который поворачивает вектор \dot {\mathbf r}_1 на \pi в плоскости \lambda_1, т.е.

\dot e_{d_1} = 0 + \dot {\mathbf s}_1, \qquad (9)

и повернуть его вокруг начала координат на угол \beta_1 в плоскости \delta_1, то из

\dot e_{q_1}(\beta_i) = \dot h_1(\beta_1) \circ \dot e_{d_1} \circ \dot h_1^{-1}(\beta_1) \qquad (10)

мы получим вектор \dot {\mathbf e}_{q_1}(\beta_1), который в свою очередь является кватернионом поворота на угол \pi для вектора \dot {\mathbf r}_1, причём \dot {\mathbf r}_1 при этом будет описывать дугу в пространстве. Поворот \dot {\mathbf e}_{d_1} к \dot {\mathbf e}_{q_1} может быть выполнен кватернионом \dot h_1(\beta_1), который мы найдём чуть позже из условия его ортогональности к плоскости \delta_1.

Из утверждения 2 следует, что вектор \dot {\mathbf r}_1 может быть совмещён с \dot {\mathbf p}_1 вращением вокруг оси с направляющим вектором \dot {\mathbf e}_{q_1}(\beta_1) на некоторый угол \gamma_{q_1}, который, очевидно, функционально зависит от \beta_1. Поэтому, зная \beta_1 и зависимость \gamma_{q_1} = f(\beta_1), мы сможем построить из кватерниона \dot {\mathbf e}_{q_i} кватернион \dot q, являющийся решением задачи.

Зависимость \gamma_{q_1} = f(\beta_1) мы найдём немного позже из простых тригонометрических соотношений.

Угол \beta_1 будем искать из следующих соображений. Так как \dot {\mathbf q} ортогонален одновременно \dot {\mathbf h}_1 и \dot {\mathbf h}_2, то результат векторного произведения \dot {\mathbf h}_1(\beta_1) \times \dot {\mathbf h}_2(\beta_2) будет сонаправлен с \dot {\mathbf q}. Обозначим

\dot {\mathbf q}_e \equiv \dot {\mathbf h}_1(\beta_1) \times \dot {\mathbf h}_2(\beta_2) \qquad (11)

и запишем такое скалярное произведение: \dot {\mathbf e}_{q_1}(\beta_1) \dot {\mathbf q}_e. Отметим, что направление \dot {\mathbf q}_e не зависит ни от \beta_1, ни от \beta_2. Поэтому для вычисления \dot {\mathbf q}_e примем \dot h_1 = \dot h_1 (\beta_1) \vert _{\beta_1 = \pi} и \dot h_2 = \dot h_2 (\beta_2) \vert _{\beta_2 = \pi}, то есть угол, при котором \vert \dot {\mathbf h}_i \vert = 1. Следовательно, в записанном выше скалярном произведении остаётся одна переменная \beta_1, которую можно вычислить, решив уравнение

\dot {\mathbf e}_{q_1}(\beta_1) \frac{\dot {\mathbf q}_e}{ \vert \dot {\mathbf q}_e \vert } = \dot {\mathbf e}_{q_1}(\beta_1) \dot{\mathbf q}_{e_{n}} = 1, \qquad (12)

полагая, что \vert \dot {\mathbf e}_{q_1} (\beta_1) \vert = 1 . Теперь, зная \beta_1, зависимость \gamma_{q_1} = f(\beta_1) и вектор \dot {\mathbf e}_{q_1}(\beta_1), искомый кватернион \dot {\mathbf q} будет выглядеть так:

\dot {\mathbf q} = \cos \frac{\gamma_{q_1}}{2} + \dot {\mathbf e}_{q_1} \sin \frac{\gamma_{q_1}}{2}. \qquad (13)

Осталось найти каждый из описанных выше элементов, чтобы решить задачу. Заметим, что объекты \dot e_{d_2 }, \dot e_{q_2}, \dot h_2, \beta_2, \gamma_2, \gamma_{q_2} определяются аналогично. Поэтому далее нижний индекс "1" или "2" буду заменять на " i ", подразумевая, что i = 1, 2 .

Кватернионы, которые будем искать

Ещё раз перечислим кватернионы и векторы, которые мы сейчас будем строить для получения решения:

  • кватернион \dot d_i: совмещает вектор \dot{\mathbf r}_i с \dot{\mathbf p}_i по кратчайшей траектории,

  • кватернион \dot b_i: направляющий вектор биссектрисы угла между векторами \dot{\mathbf r}_i и \dot{\mathbf p}_i; векторы \dot{\mathbf d}_i и \dot{\mathbf b}_i определяют плоскость \delta_i, в которой лежит искомый кватернион \dot q,

  • кватернион \dot e_{d_i}: сонаправлен с \dot d_i; модуль векторной части \dot e_{d_i} равен 1,

  • кватернион \dot h_i(\beta_i): поворачивает вектор \dot{\mathbf e}_{d_i} в плоскости \delta_i на угол \beta_i ; значение \beta_i неизвестно,

  • зависимость \gamma_{q_i} = f(\beta_i): вычисляет \gamma_{q_i} для построения искомого кватерниона \dot q из \dot e_{q_i},

  • кватернион \dot e_{q_i} (\beta_i): получен поворотом вектора \dot{\mathbf e}_{d_i} в плоскости \delta_i на угол \beta_i; из \dot e_{q_i} будет получено решение,

  • кватернион \dot q_e: нужен для вычисления угла \beta_i,

  • и, наконец, результирующий кватернион \dot q: получается из \dot e_{q_i}, учитывая найденные \beta_i и \gamma_{q_i}.

Кватернион \dot d_i

Найдём кватернион \dot d_i, который совмещает \dot {\mathbf r}_i с \dot {\mathbf p}_i по кратчайшей траектории. Вектор \dot {\mathbf r}_i перемещается в плоскости \lambda_i, а ось поворота перпендикулярна \lambda_i и проходит через точку начала координат (рис. 3). Направляющий вектор этой оси может быть равен \dot { \mathbf r}_i \times \dot {\mathbf p}_i . Так как \vert \dot {\mathbf r}_i \vert = \vert \dot {\mathbf p}_i \vert = 1, и \vert \dot {\mathbf r}_i \times \dot {\mathbf p}_i \vert = \vert \dot {\mathbf r}_i \vert \vert \dot {\mathbf p}_i \vert \sin \gamma_i, то:

\dot {\mathbf r}_i \times \dot {\mathbf p}_i = \frac{ \dot {\mathbf r}_i \times \dot {\mathbf p}_i }{ \vert \dot {\mathbf r}_i \times \dot {\mathbf p}_i \vert } \vert \dot {\mathbf r}_i \times \dot {\mathbf p}_i \vert = \dot {\mathbf s}_i \sin \gamma_i, \qquad (14)

где \dot {\mathbf s}_i - единичный вектор, равный

\dot {\mathbf s}_i = \frac{1}{\sin \gamma_i} \begin{vmatrix} i & j & k \\ r_i^1 & r_i^2 & r_i^3 \\ p_i^1 & p_i^2 & p_i^3 \end{vmatrix} = is_i^x + js_i^y + ks_i^z, \qquad (15)

где

s_i^x = \frac{r_i^2 p_i^3 - r_i^3 p_i^2}{\sin \gamma_i}, \quad s_i^y = \frac{r_i^3 p_i^1 - r_i^1 p_i^3}{\sin \gamma_i}, \quad s_i^z = \frac{r_1^1 p_1^2 - r_i^2 p_i^1}{\sin \gamma_i}. \qquad (16)

Произведение \dot {\mathbf s}_i \sin \gamma_i может представлять собой векторную часть некоторого кватерниона \dot s_i, который выполняет поворот вектора \dot {\mathbf r}_i на угол 2 \gamma_i в плоскости \lambda_i: \dot s_1 = \cos \gamma_i + \dot {\mathbf s}_i \sin \gamma_i. Поэтому кватернион поворота \dot {\mathbf d}_i на угол \gamma_i равен

\dot d_i = \cos \frac{\gamma_i}{2} + \dot {\mathbf s}_i \sin \frac{\gamma_i}{2} \qquad (17)

и

\dot p_i = \dot d_i \circ \dot r_i \circ \dot d_i^{-1}.

Кватернион \dot b_i

Найдём кватернион \dot b_i. Он может быть получен из преобразования

\dot b_i = \dot d_{b_i} \circ \dot r_i \circ \dot d_{b_i}^{-1}, \qquad (18)

где \dot d_{b_i} - кватернион, выполняющий поворот вектора \dot {\mathbf r}_i на угол \frac{\gamma_i}{2}. Учитывая (17), он равен

\dot d_{b_i} = \cos \frac{\gamma_i}{4} + \dot {\mathbf s}_1 \sin \frac{\gamma_a}{4}. \qquad (19)

Подставим \dot d_{b_i} из (19) в (18), выполним кватернионные умножения и, учитывая (14), получим:

\begin{split} \dot b_i = \dot {\mathbf r}_i \cos^2 \frac{\gamma_i}{4} - \dot {\mathbf r}_i \circ \dot {\mathbf s}_i \sin \frac{\gamma_i}{4} \cos \frac{\gamma_i}{4} + \dot {\mathbf s}_i \circ \dot {\mathbf r}_i \sin \frac{\gamma_i}{4} \cos \frac{\gamma_i}{4} - \dot {\mathbf s}_i \circ \dot {\mathbf r}_i \circ \dot {\mathbf s}_i \sin^2 \frac{\gamma_i}{4} = \\ = \dot {\mathbf r}_i \cos^2 \frac{\gamma_i}{4} - \dot {\mathbf r}_i \times (\dot {\mathbf r}_i \times \dot {\mathbf p}_i) \frac{\sin \frac{\gamma_i}{2}}{\sin \gamma_i} - ((\dot {\mathbf r}_i \times \dot {\mathbf p}_i) \times \dot {\mathbf r}_i ) \times (\dot {\mathbf r}_i \times \dot {\mathbf p}_i ) \frac{\sin^2 \frac{\gamma_i}{4}}{\sin^2 \gamma_i}. \end{split}

Применяя правило "БАЦ минус ЦАБ" и упрощая, получаем

\dot b_i =0 + (\dot {\mathbf r}_i + \dot {\mathbf p}_i) \frac{\sin \frac{\gamma_i}{2}}{\sin \gamma_i}. \qquad (20)

Заметим, что \vert \dot {\mathbf b}_i \vert = 1.

Зависимость \gamma_{q_1} = f(\beta_1)

Рис. 9.
Рис. 9.

Найдём зависимость \gamma_{q_1} = f(\beta_1). Возьмём конус \tau_1 из рис. 6, для удобства развернём его основанием вниз и изобразим все основные векторы и углы (рис. 9). Также добавим угол \alpha_i = \frac{\pi}{2} - \beta_1.

Будем находить зависимость угла \gamma_{q_1}, соответствующего углу поворота вокруг \dot {\mathbf q}_{e_1} от \dot {\mathbf r}_1 к \dot {\mathbf p}_1, от значения угла \beta_1, то есть от угла наклона \dot {\mathbf q}_{e_1} к плоскости векторов \dot {\mathbf r}_1, \dot {\mathbf p}_1 (т.е. к плоскости \lambda_1). Заметим, что при \beta_1 = 0 поворот выполняется по кратчайшей траектории вокруг \mathbf{\dot d}_1.

Из соотношений прямоугольных треугольников запишем:

\begin{split} \vert RR^{'} \vert = \vert \dot {\mathbf r} \vert \sin \frac{\gamma_1}{2}, \quad \vert \dot {\mathbf b}_1 \vert = \vert \dot {\mathbf r} \vert \cos \frac{\gamma_i}{2}, \quad \vert R^{'}O^{'} \vert = \vert \dot {\mathbf b}_1 \vert \sin \alpha_1, \end{split}

откуда \vert R^{'}O^{'} \vert = \vert \dot {\mathbf r}_1 \vert \cos \frac{\gamma_1}{2} \sin \alpha_1. Так как \tan \frac{\gamma_{q_1}}{2} = \frac{\vert RR^{'} \vert }{\vert R^{'}O^{'} \vert} ), то

\tan \frac{\gamma_{q_1} }{2} = \vert \dot {\mathbf r}_1 \vert \sin \frac{\gamma_1}{2} \frac{1}{\cos \frac{\gamma_1}{2} \sin \alpha_1} = \frac{1}{\sin \alpha_1} \tan \frac{\gamma_i}{2},

откуда

\gamma_{q_1} = 2 \arctan (\frac{1}{\cos \beta_1} \tan \frac{\gamma_1}{2}). \qquad (21)

Из выражения (21) видно, что при \beta_1 = 0 значение \gamma_{q_1} = \gamma_1, а при \beta_1 = \frac{\pi}{2} значение \gamma_{q_1} = \pi, что согласуется с утверждением 1. Заметим также, что угол \gamma_{q_1} не зависит от длин векторов, а угол \gamma_i полагается известным.

Кватернион \dot h_i(\beta_i)

Продолжим. Построим кватернион \dot h_i. Поскольку он выполняет поворот \dot{\mathbf e}_{d_i} на угол \beta_i в плоскости \delta_i, то

\dot h_i(\beta_i) = \cos \frac{\beta_i}{2} + \dot{\mathbf e}_{d_i} \times \dot{\mathbf b}_i \sin \frac{\beta_i}{2}, \qquad (22)

полагая, что \vert \dot{\mathbf e}_{d_i} \vert = 1, \vert \dot{\mathbf b}_i \vert = 1. Учитывая (9), (18), выполнив некоторые преобразования, получим

\begin{split} \dot{\mathbf e}_{d_i} \times \dot{\mathbf b}_i = \dot{\mathbf r}_i (\tan^{-2} \gamma_i \sin \frac{\gamma_i}{2} - \tan^{-1} \gamma_i \cos \frac{\gamma_i}{2} - \frac{\sin \frac{\gamma_i}{2}}{\sin^2 \gamma_i}) + \dot{\mathbf p}_i (\frac{\cos \frac{\gamma_i}{2}}{\sin \gamma_i} - \frac{\cos \gamma_i}{\sin^2 \gamma_i} \sin \frac{\gamma_i}{2} + \frac{\sin \frac{\gamma_i}{2}}{\sin^2 \gamma_i}). \end{split}

После упрощающих тригонометрических преобразований, подставляя в (22), получаем

\dot h_i(\beta_i) = \cos \frac{\beta_i}{2} + (\dot{\mathbf p}_i - \dot{\mathbf r}_i) \frac{\cos \frac{\gamma_i}{2}}{\sin \gamma_i} \sin \frac{\beta_i}{2}, \qquad (23)

при этом \vert \dot h_i(\beta_i) \vert = 1.

Кватернион \dot e_{q_i}(\beta_i)

Найдём кватернион \dot e_{q_i}. Выше мы записали выражение (9), которое определяет \dot e_{q_i}. Приведём его здесь ещё раз: \dot e_{q_i}(\beta_i) = \dot h_i(\beta_i) \circ \dot e_{d_i} \circ \dot h_i^{-1}(\beta_i). Подставляя сюда полученный результат (23) и выражение (9), после преобразований получим:

\dot e_{q_i} = C(A^2 \dot{\mathbf r}_i \times \dot{\mathbf p}_i -2AB(\dot{\mathbf r}_i + \dot{\mathbf p}_i)(\cos \gamma_i -1) - 2B^2 \dot{\mathbf r}_i \times \dot{\mathbf p}_i (1-\cos \gamma_i)),

где A \equiv \cos \frac{\beta_i}{2}, B \equiv \frac{\cos \frac{\gamma_i}{2}}{\sin \gamma_i } \sin \frac{\beta_i}{2}, C \equiv \frac{1}{\sin \frac{\gamma_i}{2}}. Упрощая, получаем:

\dot e_{q_i} = \frac{1}{\sin \gamma_i} (\dot{\mathbf r}_i \times \dot{\mathbf p}_i \cos \beta_i + (\dot{\mathbf r}_i + \dot{\mathbf p}_i) \sin \frac{\gamma_i}{2} \sin \beta_i), \qquad (24)

при этом \vert \dot e_{q_i} \vert = 1.

Кватернион \dot q_e

Получим кватернион \dot q_e. Как мы указывали выше, вектор этого кватерниона ортогонален каждому из векторов \dot{\mathbf h}_1 и \dot{\mathbf h}_2, то есть \dot {\mathbf q}_e \equiv \dot {\mathbf h}_i(\beta_i) \times \dot {\mathbf h}_i(\beta_i) (выражение (11)), и при этом \vert \dot{\mathbf q}_e \vert \ne 1. Также мы отмечали, что угол \beta_i не меняет направление вектора \dot{\mathbf h}_i, и поэтому можно выбрать такое \beta_i, при котором длина \dot{\mathbf h}_i максимальна. Учитывая (23) при \beta_i = \pi, получаем:

\dot h_1 = (\dot{\mathbf p}_1 - \dot{\mathbf r}_1) \frac{\cos \frac{\gamma_1}{2}}{\sin \gamma_1}, \quad \dot h_2 = (\dot{\mathbf p}_2 - \dot{\mathbf r}_2) \frac{\cos \frac{\gamma_2}{2}}{\sin \gamma_2}, \qquad (25)

следовательно,

\dot{\mathbf q}_e = (\dot{\mathbf p}_1 - \dot{\mathbf r}_1) \times (\dot{\mathbf p}_2 - \dot{\mathbf r}_2) \frac{\cos \frac{\gamma_1}{2}}{\sin \gamma_1} \frac{\cos \frac{\gamma_2}{2}}{\sin \gamma_2}. \qquad (26)

Нормированное значение

\dot{\mathbf q}_{e_n} = \frac{ (\dot{\mathbf p}_1 - \dot{\mathbf r}_1) \times (\dot{\mathbf p}_2 - \dot{\mathbf r}_2) }{\vert (\dot{\mathbf p}_1 - \dot{\mathbf r}_1) \times (\dot{\mathbf p}_2 - \dot{\mathbf r}_2) \vert}. \qquad (27)

Угол \beta_i

Подставляем результаты в выражение (12) и получаем уравнение, которое нужно решить относительно \beta_1:

\frac{1}{\sin \gamma_1} (\dot{\mathbf r}_1 \times \dot{\mathbf p}_1 \cos \beta_1 + (\dot{\mathbf r}_1 + \dot{\mathbf p}_1) \sin \frac{\gamma_1}{2} \sin \beta_1) \frac{ (\dot{\mathbf p}_1 - \dot{\mathbf r}_1) \times (\dot{\mathbf p}_2 - \dot{\mathbf r}_2) }{\vert (\dot{\mathbf p}_1 - \dot{\mathbf r}_1) \times (\dot{\mathbf p}_2 - \dot{\mathbf r}_2) \vert} = 1. \qquad (28)

После преобразований получаем это уравнение в форме A \cos \beta_1 + B \sin \beta_1 = 1, которое и решаем:

\beta_1 = \arcsin \frac{1}{\sqrt{A^2 + B^2}} - \arcsin \frac{A}{\sqrt{A^2 + B^2}}, \qquad (29)

где

\begin{split} A = \frac{\dot{\mathbf r}_1 \times \dot{\mathbf p}_1}{\sin \gamma_1} \frac{ (\dot{\mathbf p}_1 - \dot{\mathbf r}_1) \times (\dot{\mathbf p}_2 - \dot{\mathbf r}_2) }{\vert (\dot{\mathbf p}_1 - \dot{\mathbf r}_1) \times (\dot{\mathbf p}_2 - \dot{\mathbf r}_2) \vert}, \quad B = \frac{\sin \frac{\gamma_1}{2}}{\sin \gamma_1} (\dot{\mathbf r}_1 + \dot{\mathbf p}_1) \frac{ (\dot{\mathbf p}_1 - \dot{\mathbf r}_1) \times (\dot{\mathbf p}_2 - \dot{\mathbf r}_2) }{\vert (\dot{\mathbf p}_1 - \dot{\mathbf r}_1) \times (\dot{\mathbf p}_2 - \dot{\mathbf r}_2) \vert} \end{split}.

Решение

Зная \beta_1, можно построить кватернион \dot e_{q_1} из (24), подставить его в (13) и записать результат в виде:

 \dot q =\cos \frac{\gamma_{q_1}}{2} + \frac{1}{\sin \gamma_1} (\dot{\mathbf r}_1 \times \dot{\mathbf p}_1 \cos \beta_1 + (\dot{\mathbf r}_1 + \dot{\mathbf p}_1) \sin \frac{\gamma_1}{2} \sin \beta_1) \sin \frac{\gamma_{q_1}}{2}, \qquad (30)

где

\gamma_{q_1} = 2 \arctan (\frac{1}{\cos \beta_1} \tan \frac{\gamma_1}{2}), \qquad (31)

а \beta_1 вычисляется из выражения (29).

Послесловие

Задача решена. Я попытался упростить итоговое выражение (30), но пока не получилось. В моделях оно работает, поэтому пока оставлю, как есть. На рис. 10 приведён простой пример нахождения ориентации ЛСК для того, чтобы затем определить R_a.

Рис. 10. Вращение ЛСК
Рис. 10. Вращение ЛСК

Вначале положение ЛСК (x^{'}, y^{'}, z^{'}) неизвестно, и мы полагаем, что оно может быть любым или, например, совпадающим с ГСК (то есть, мы "думаем", что оно совпадает, а на самом деле это не так). Измеренные координаты НС1', НС2' и НС3' существенно отличаются от истинных положений НС1, НС2 и НС3 (рис. 10, а)). Зная координаты НС в ГСК, вычисляется кватернион поворота \dot q по (30) и выполняется вращение. На рис. 10, б) показано дискретное вращение с интервалом 10 градусов от старого положения ЛСК к истинному. При этом измеренные координаты НС (показаны светло-серым цветом) всё более приближаются к истинным. На рис. 10, в) показано вычисленное истинное положение ЛСК, и мы теперь можем определить R_a, то есть решить навигационную задачу (точнее, часть навигационной задачи, связанной с определением координат).

На этом пока всё. Отмечу только, что в ближайшем будущем я попробую поработать над одним недостатком выражения (30). При \gamma_1 близким к нулю, то есть когда ориентации ГСК и ЛСК мало отличаются, кватернион \dot q вычисляется с ошибкой из-за множителя \frac{1}{ \sin \gamma_1}. Это может приводить к значительным ошибкам вычисления ориентации ЛСК и, как результат, ошибкам определения положения R_a. Об этом в следующем материале.

Let's block ads! (Why?)