~vbursian/research-assistant/intervers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
////////////////////////////////////////////////////////////////////////////////
/*! @file Units.h   Единицы измерения физвеличин.
- Part of RANet - Research Assistant Net Library (based on ANSI C++).
- Copyright(C) 2007-2011, Viktor E. Bursian, St.Petersburg, Russia.
                     Viktor.Bursian@mail.ioffe.ru
*///////////////////////////////////////////////////////////////////////////////
#ifndef Units_H
#define Units_H
#include "General.h"
#include "Storable.h"
#include <map>
#include <list>
#include <cmath>
#include <valarray>
namespace RA {
//------------------------------------------------------------------------------

ANNOUNCE_CLASS(sUnit)
ANNOUNCE_CLASS(sUnits)
ANNOUNCE_CLASS(sPhysValue)

//------------------------------------------------------------------------------
/*! Константа "пустые единицы".

Используется в определениях переменных или членов, когда нужно создать заведомо
безразмерную переменную или член типа sPhysValue или sPhysRange. Введена по двум
причинам.  Во-первых, C++  не даёт использовать в этих местах выражение
@a sUnits() - думает, что это объявление функции.  Во-вторых, просто
улучшает читаемость.
*/
RANet_EXPORT extern csUnits   _Unitsless_;


//------------------------------------------------------------ sMetricPrefix ---
/*! Provides transformations between metric prefixes and scale multipliers

in accordance with the following table:
@code
        Metric Prefixes                         USA         old British
 10^24  Y   yotta              йотта            septillion
 10^21  Z   zetta              зетта            sextillion  (trilliard)
 10^18  E   exa            Э   экса(экза)       quintillion (trillion)
 10^15  P   peta           П   пета             quadrillion (billiard)
 10^12  T   tera           Т   терра            trillion    (billion)
 10^9   G   giga           Г   гига             billion     (milliard)
 10^6   M   mega           М   мега             million
 10^3   k   kilo           к   кило
 10^2   h   hecto(hekto)   г   гекто
 10^1   da  deca(deka)     да  дека
 10^-1  d   deci           д   деци
 10^-2  c   centi          с   санти
 10^-3  m   milli          м   милли
 10^-6  µ   micro          мк  микро
 10^-9  n   nano           н   нано
 10^-12 p   pico           п   пико
 10^-15 f   femto          ф   фемто
 10^-18 a   atto           а   атто              one quintillionth
 10^-21 z   zepto              зопто             one sextillionth
 10^-24 y   yocto(yokto)       йокто             one septillionth
@endcode
*/
class RANet_EXPORT  sMetricPrefix
{
  public://static
    static sString            Symbol (int  Log);
    static sString            CombinedSymbol (int  Log);
    static int                Exp (sString  Prefix);
    static real               Multiplier (int  Log);
    static real               Multiplier (sString  Prefix)
                                { return Multiplier(Exp(Prefix)); }
    static sString            Name (int  Log);
    static csString           StandardAllowance;
    static csString           NoPrefixAllowed;
};

//-------------------------------------------------------------------- sUnit ---
/*! Единица измерения физвеличин (и система единиц).

Для каждой единицы создаётся @b один объект класса sUnit.
Он помещается в регистр (статический член sUnit::UnitList) и в дальнейшем
используются только указатели на него.

@note
Поскольку все конструкторы класса приватны (кроме спрятанного в макро STORABLE
конструктора ввода из потока), новые единицы появляются и одновременно
регистрируются одним из следующих способов:
- посредством вызова статической функции-члена sUnit::Register;
- при разборе строки в конструкторе sUnits::sUnits(sString) класса составной
  единицы;
- при вводе из потока объекта, ранее созданного и сохранённого.

Ядро RA не навязывает никакой системы единиц.  Пользователь сам вводит удобные
лично ему единицы и их взаимосвязи.  Однако некоторые плагин-пакеты
инструментов могут либо мягко предлагать
(в sInstrumentalPackage::InstallDefaults()), либо навязывать (лучше всего это
делать в конструкторе производного от sInstrumentalPackage класса) единицы,
необходимые для их работы.  Например, драйвер вольтметра скорее всего будет
навязывать "V" как базовую единицу.

@note
Драйверо-писателям настоятельно рекомендуется снабжать результаты измерений
какими-то единицами, если только измеряемая величина не является принципиально
безразмерной.  Последняя оговорка применима крайне редко!  Чаще бывает так, что
некая вполне размерная физ.величина измеряется в неких условных единицах,
например, в квантах АЦП, и не может быть пересчитана в разумные физические
единицы, поскольку чувсвительность либо неизвестна, либо вообще меняется от раза
к разу, т.е. зависит от условий эксперимента.  Тем не менее, даже в этом случае,
рекомендуется (@b настоятельно!) выдавать результат "в попугаях" ("ADC_quantum",
"photon/s", ...).  Можно не придумывать длинные имена, а использовать
какой-нибудь экзотический символ Юникода (знак карточной масти, например).
Важно, чтобы придуманная единица была раз и навсегда связана с конкретным
измерительным трактом, измеряющим конкретное физическое свойство.
@note
Это положительно повлияет на последующее поведение: показ кривых на графиках,
вычитание и деление спектров, и т.п.
@note
Кроме того, впоследствии, например, проградуировав прибор, можно будет
объявить, что один попугай равен 0.033 удава, а один удав равен
5.67e12 Spin/Oe<sup>2</sup>, и снятые ранее на разных установках спектры ЭПР
приобретут единый количественный смысл.

Будучи раз введённой тем или иным способом, единица сохраняется в
конфигурационной части базы знаний RA.  Таким образом там формируется система
единиц.  Пользователь может её просматривать и редактировать.

Единица имеет символьное представление (symbol), представляющее собой строку из
букв и других символов Юникода, но не содержащую пробелы, цифры и знаки
арифметических операций.  Например, спектроскописты оценят возможность ввести
@b базовую единицу @a cm<sup>-1</sup> -- надо только изобразить степень
символами U+207B (Superscript Minus) и U+00B9 (Superscript One).

@note
Единица считается базовой, если для неё не задано определение (definition)
через какие-то другие единицы.

Единицу можно сделать производной, то есть снабдить определением через
физическую константу, выраженную в других единицах -- базовых, производных
(других, конечно) или @ref sUnits "составных".  Например, определить "eV" как
8065.54468cm<sup>-1</sup> (здесь имеются в виду обратные сантиметры, набранные
специальными символами, как указано выше).  Это сделает возможным перевод
физвеличин как из электрон-вольтов в обратные сантиметры, так @b и @b обратно.

@note
Не рекомендуется устраивать цикличные определения.  Например, в дополнение к
вышеуказанному, определить ещё и обратные сантиметры через электрон-вольты.
Это ввергнет RA в состояние глубокой задумчивости!

RA понимает и самостоятельно применяет метрические префиксы при единицах
(см. sMetricPrefix) для более наглядного представления чисел.  Это
потрясающе удобно.  Причём, как для юзера, так и для программера.
Последнему не надо думать о количестве знаков, положении точки, домножать на
десять-в-степени при выводе, делить при вводе,..
Но и тому, и другому надо учитывать ряд нюансов:

-# Как упомянуто выше, новые единицы автоматически регистрируются в плагинах
или в диалоге с пользователем. И, например, "ms" будет зарегистрирована как
базовая, если до этого не регистрировалась "s". Если же "s" зарегистрирована до
первого появления "ms", то "ms" распознаётся как milli-"s".
@note
Последнее, впрочем, не мешает всяким извращенцам, в дополнение к единице "s",
явно зарегистрировать ещё и базовую единицу "ms" со смыслом, отличным от
milli-"s". Тогда "ms" будет означать, например, единицу маразма, "Mms" -
мегамаразм, но "ks" и "ns" останутся производными от "s".

-# К некоторым единицам не принято применять некоторые (или никакие)
метрические префиксы. Или у пользователя специфический вкус. Например, значение
длины волны 1270nm программой RA будет представлено в виде 1.270 микрометров, а
шаг сканирования 0.0033nm - в виде 3.3pm, что может быть неудобно. Проблема
почти всегда решается указанием допустимых префиксов для таких единиц (см.
prefix_allowance).  В данном случае, правильным выходом будет задать "m", как
базовую единицу, и запретить для неё префиксы micro и pico.  Остальные лучше
оставить разрешёнными - тогда, например, значение толщины образца тоже будет
выглядеть красиво.
-# Если стандартных префиксов не хватает RA может начать комбинировать
префиксы, например, "kYm", что означает kilo-yotta-meter. (Интересно, что в не
очень старых статьях можно найти частоту в kMHz, видимо, префикс giga- тогда
ещё не был введён в обиход.)

@sa sUnits, sPhysValue, sPhysRange.

*/
class RANet_EXPORT  sUnit : public sStorable
{
  STORABLE(sUnit)
  public://types
    typedef bool              fRegisterDialog (rsString);
    typedef fRegisterDialog * pfRegisterDialog;

    ANNOUNCE_CLASS(sSystem)
    class  sSystem
    {
      public:
                                  ~sSystem ();
                                  sSystem ();
      public://fields
        map<sString,psUnit>       UnitList;

//      friend class sUnit;
    };

  public://static
    static psUnit             Register (sString     symbol
                                       ,sString     prefix_allowance
                                       ,sPhysValue  definition);
    static psUnit             Register (sString  symbol
                                       ,sString  prefix_allowance);
    static psUnit             Register (sString     symbol
                                       ,sPhysValue  definition);
    static psUnit             Register (sString  symbol);
    static psUnit             Find (sString  symbol);
    static bool               ParseUnitWithPrefix (sString   lexem
                                                  ,psUnit &  unit
                                                  ,int &     prefixlg);

  public://static fields
    static sSystem            System;
    static psUnit             _1_;
    static pfRegisterDialog   RegisterDialog;

  public:
    sString                   Symbol () const
                                { return TheSymbol; }
    bool                      PrefixAllowed (int  prefix_index) const;
    sString                   PrefixAllowance ()
                                { return ThePrefixAllowance; }
    psPhysValue               Definition () const
                                { return TheDefinition; }
    bool                      IsBasic () const
                                { return (TheDefinition != NULL); }

  private:
                              sUnit (); //!< makes _1_
                              sUnit (sString      symbol
                                    ,sString      prefix_allowance
                                    ,psPhysValue  definition);
                              sUnit (rcsUnit);
    rsUnit                    operator = (rcsUnit);

  private://static
    void                      MakeVeryBasicDefinitions ();

  private://fields
    bool                      Is_1_Flag;
    bool                      IsObsoleteFlag;
    sString                   TheSymbol;
    sString                   ThePrefixAllowance;
    psPhysValue               TheDefinition;
//    sString                   TheComment;

  friend class sSystem;
};

//------------------------------------------------------------------- sUnits ---
/*! Составные единицы измерения (то есть выражение над несколькими
    @ref sUnit "базовыми или производными единицами" с операциями умножения,
    деления и возведения в степень).

Объекты класса содержат выражение в разобранном виде, но при написании
предметно-ориентированных частей программы, а также при вводе пользователем
они порождаются из строки, в которой показатели степеней пишутся "в строчку":

@code
"kV/cm"
"kg*m2*ms-2"
"kg*m2/ms2"  // эквивалентно предыдущему
"km2*kg/s2"  // то же, но лишь в том случае, если "g", "m" и "s" объявлены
             // ранее как как единицы (базовые или производные).
"Gg/s2*m2"   // то же, с той же оговоркой
             // (N!B! порядок операций: "m2" - в числителе!)
"MJ"   // то же, если определена единица "J" = "kg*m2/s2"
@endcode

Класс довольно умный: умеет
- разбирать строковые представления (см. sUnits::sUnits(sString));
- умножать и делить составные единицы друг на друга;
- подставлять определения производных единиц, переводя всё в базовые (это
  делается не всегда, а при необходимости, см. ToBasic());
- упрощать выражение, сокращая сомножители (см. SimplifyProduct(int));
- сортировать сомножители, отправляя сомножители с отрицательными степенями
  в конец (см. SmartSort());
- "растворять" порядок числа в метрических префиксах (см. DissolveOrder(...));
- а в будущем, возможно, даже подбирать приятный глазу вид представления,
  пробуя разные обратные подстановки - из комбинаций базовых единиц
  в производные.

В предметно-ориентированных частях программы нет нужды использовать
б<i>о</i>льшую часть этих свойств класса sUnits непосредственно. Обычно
используется алгебра физвеличин, ради которой всё это и понаписано.
Фактически вне ядра RA используются первые два свойства.

Для определения безразмерных физвеличин в конструкторах sPhysValue в качестве
единиц указывается константа RA::_Unitsless_.

В конструкторах sPhysValue единицы можно задавать и непосредственно
в строковом представлении.

Примерчик:
@code
  sUnits          WeightUnit ("pound");
  sUnits          SizeUnit ("\""); //кавычки как символ дюйма
  sUnits          AccelerationUnit ("m/s2");
  //...
  sPhysValue      Pressure (WeightUnit/(SizeUnit*SizeUnit));
  sPhysValue      ForseMoment1 (WeightUnit*SizeUnit);
  sPhysValue      ForseMoment2 ("kg*m/s2*m");
  sPhysValue      Ratio (_Unitsless_);
  //...
  Ratio = ForseMoment1 / ForseMoment2;
@endcode

@sa sUnit, sPhysValue, sPhysRange.

@bug
Разбор строкового представления ломается, если строка содержит символы Юникода,
в UTF-8 представлении которых содержатся байты '*', '/', '+', '-',
'0', ... '9'.

*/
class RANet_EXPORT  sUnits : public sStorable
{
  STORABLE(sUnits)

  public://static
    static sUnits             Parse (sString);

  public:
                              ~sUnits ();
                              sUnits ();
                              sUnits (psUnit unit
                                     ,int    power     = 1
                                     ,int    prefix_lg = 0);
                              sUnits (sString);
                              sUnits (rcsUnits);
    bool                      Is_1_ () const;
    sString                   Text (eTextFormat  F = Plain) const;
                                //!< visual representation for user
    sPhysValue                ToBasic () const;

  public://operators
    rsUnits                   operator = (rcsUnits);
    sPhysValue                operator * (rcsUnits) const;
    sPhysValue                operator / (rcsUnits) const;

  private://types
    ANNOUNCE_CLASS(sTerm)
    class sTerm
    {
      public:
                                  sTerm (psUnit unit ,int power ,int prefix_lg)
                                      :Unit(unit)
                                      ,Power(power)
                                      ,PrefixLg(prefix_lg)
                                    {}
        bool                      operator < (rcsTerm T) const
                                    { return Unit->Symbol()
                                              < T.Unit->Symbol();
                                    }
      public://fields
        psUnit                    Unit;
        int                       Power;
        int                       PrefixLg;
    };

    typedef std::list<sTerm>    tProduct;
    typedef std::valarray<int>  tPrefixes;

  private://static
    static void               SubstituteDefinitions (real &   coeff
                                                    ,rsUnits  units);

  private:
    int                       MultBy (rcsUnits);
    int                       DivideBy (rcsUnits);
//    bool                      Comparable (rcsUnits  units
//                                         ,real &    ratio
//                                         ,bool &    power_of_ten) const;
    bool                      Comparable (rcsUnits  units
                                         ,real &    ratio) const;
    int                       DissolveOrder (unsigned short int  level
                                            ,int  log_shift = 0
                                            ,int  min_rest_expected = 0
                                            ,int  max_rest_expected = -1);
    int                       SimplifyProduct (int  log_shift = 0);
//    void                      OrderPushIn (int  log_shift);
    void                      SmartSort ();
    void                      FindBestChoice (tPrefixes &  best_choice
                                             ,int       &  best_weight
                                             ,int       &  best_rest_order
                                             ,int          min_rest_expected
                                             ,int          max_rest_expected
                                             ,tPrefixes    considered_product
                                             ,int          weight
                                             ,int          rest_order
                                             ,size_t       term_to_vary
                                             ,unsigned short int  level);

  private://fields
    tProduct                  Product;
    bool                      InBasicTerms;
    bool                      Refined;

  friend class sPhysValue;
  //! @todo{PhysValues} friend class sPhysRange у sUnits - хорошо ли?
  friend class sPhysRange;
};

//------------------------------------------------------------------------------
} //namespace RA
#endif