Calculated Variables ==================== | | by: Piotr Nikiel, Paris Moschovakos | Created Oct-Nov 2018 | Updated September 2022 | Updated July 2023 Overview and end-user documentation ----------------------------------- Preface ------- | The concepts and work embodied in the quasar's CalculatedVariables module draw significant inspiration from the "CalculatedItems" concept in the CANopen OPC UA server, originally developed by V.Filimonov. This influence is gratefully acknowledged. However, it's important to note that the CalculatedVariables module has been designed entirely from scratch, utilizing an original architecture and different concepts and technologies. Rationale --------- | A quasar-based OPC UA server is based on a model of a system it is made for. This model is called a design. The model explains (in terms of quasar classes, variables etc.) what sources and sinks of information the system can publish or digest. | Often it is practical to add supplementary sources of information - like Calculated Variables - on top of what the model already provides. The reasons for doing this are often the following: - the system already provides information which is expressed in quantities different than requested by users. Example: an OPC UA server is built for a device which measures period of a repetitive process, but the users prefer frequency instead. Solution: a Calculated Variable might be added to the server with a simple reciprocal (y=1/x) formula to compute frequency in terms of period. - the system already provides information but in another unit. - the system provides raw (e.g. uncalibrated) information. Example: an OPC UA server publishes data from Analog to Digital Converter. This is raw information though. Solution: a Calculated Variable might be added to provide for gain and offset calibration. - similarly to the example above, the ADC might have the value from a conversion of e.g. a temperature sensor. A Calculated Variable might be attached with a formula that recomputes the ADC value (in volts) into temperature. Design-based variables vs CalculatedVariables --------------------------------------------- +-----------------------+-----------------------+-----------------------+ | Aspect | Design-based variable | Calculated Variable | | | (cache-variable or | | | | source-variable) | | +=======================+=======================+=======================+ | Instantiation | The variable always | The variable is | | | belongs to an object | defined in the | | | of a quasar class | configuration file | | | which is defined by | which is loaded at | | | the model (Design). | runtime. | | | Therefore it's the | | | | design which | | | | determines its type, | | | | behaviour, | | | | limitations etc. | | +-----------------------+-----------------------+-----------------------+ | Source of information | Comes from inside of | Is a result of an | | | the server, typically | analytical expression | | | from user supplied | evaluation. The | | | Device logic. Any | inputs to the | | | method available by | expression can be: | | | C++ programming can | | | | be used. | - literal constants | | | | (e.g. 1000.0 or | | | | \_pi) | | | | - scalar cache | | | | variables which | | | | output numbers | | | | (i.e. are of any | | | | numeric quasar | | | | data type) | | | | - other calculated | | | | variables | | | | - free variables | | | | - config entries of | | | | numeric data-type | +-----------------------+-----------------------+-----------------------+ | Output type | Any supported by | Double or boolean | | | quasar, including | scalar. | | | arrays. | | +-----------------------+-----------------------+-----------------------+ | Adding new variable | Yes | No | | per quasar class | | | | requires | | | | recompilation? | | | +-----------------------+-----------------------+-----------------------+ Feature list ------------ +-----------------------------------+-----------------------------------+ | **Feature** | **State** | +-----------------------------------+-----------------------------------+ | Supports all quasar numerical | Yes | | types | | +-----------------------------------+-----------------------------------+ | Formulas with N inputs | Yes | +-----------------------------------+-----------------------------------+ | Good/Bad/WaitingForInitialData | Yes | | support | | +-----------------------------------+-----------------------------------+ | Separate formula for status | Yes | | evaluation | | +-----------------------------------+-----------------------------------+ | Support for delegated | Yes | | cache-variables | | +-----------------------------------+-----------------------------------+ | Tracing in separate LogIt | Yes | | component | | +-----------------------------------+-----------------------------------+ | open62541 compatibility | Yes | +-----------------------------------+-----------------------------------+ | Initial value support | Yes | +-----------------------------------+-----------------------------------+ | Evaluation as boolean | Yes | +-----------------------------------+-----------------------------------+ | Atomic passing of value and | Yes | | status, thread safety | | +-----------------------------------+-----------------------------------+ | Optimizing out variables not used | Yes | | in any expression | | +-----------------------------------+-----------------------------------+ | Formula templates | Yes | +-----------------------------------+-----------------------------------+ | Formula inputs from | Scalar+numeric: cache-variables, config-entries, free-variables and other calculated variables | +-----------------------------------+-----------------------------------------------------------------------------------------------+ Manual Update and Auto Update Control ------------------------------------- | An enhancement to the CalculatedVariables module has been introduced. This enhancement enables the automatic update of calculated variables to be controlled and allows for manual triggering of recalculation. | This functionality proves beneficial in scenarios where the calculated variable is computationally expensive and doesn't need to be updated as frequently, or where more control over when the recalculation occurs is desired. +------------------------+--------------------------------------------------+ | Method | Description | +========================+==================================================+ | setAutoUpdate(bool) | This function allows the automatic update of a | | | calculated variable to be enabled or disabled. | | | When called with true, the calculated variable | | | will automatically update whenever any of its | | | dependencies change. When called with false, | | | the calculated variable will not update | | | automatically, and manual triggering of | | | recalculation is required. The default value is | | | true. | +------------------------+--------------------------------------------------+ | triggerRecalculation() | This function manually triggers the | | | recalculation of a calculated variable. It is | | | useful when automatic updates are disabled and | | | a recalculation is needed. When this function | | | is called, the calculated variable will | | | recompute its value based on the current values | | | of its dependencies. | +------------------------+--------------------------------------------------+ Operators and built-in functions -------------------------------- | A summary of functions, operators and constants which mu::Parser supports is pasted here for reference. | Built-in functions +-----------------------+-----------------------+-----------------------+ | **Name** | **Argc.** | **Explanation** | +-----------------------+-----------------------+-----------------------+ | ``sin`` | 1 | sine function | +-----------------------+-----------------------+-----------------------+ | ``cos`` | 1 | cosine function | +-----------------------+-----------------------+-----------------------+ | ``tan`` | 1 | tangens function | +-----------------------+-----------------------+-----------------------+ | ``asin`` | 1 | arcus sine function | +-----------------------+-----------------------+-----------------------+ | ``acos`` | 1 | arcus cosine function | +-----------------------+-----------------------+-----------------------+ | ``atan`` | 1 | arcus tangens | | | | function | +-----------------------+-----------------------+-----------------------+ | ``sinh`` | 1 | hyperbolic sine | | | | function | +-----------------------+-----------------------+-----------------------+ | ``cosh`` | 1 | hyperbolic cosine | +-----------------------+-----------------------+-----------------------+ | ``tanh`` | 1 | hyperbolic tangens | | | | function | +-----------------------+-----------------------+-----------------------+ | ``asinh`` | 1 | hyperbolic arcus sine | | | | function | +-----------------------+-----------------------+-----------------------+ | ``acosh`` | 1 | hyperbolic arcus | | | | tangens function | +-----------------------+-----------------------+-----------------------+ | ``atanh`` | 1 | hyperbolic arcur | | | | tangens function | +-----------------------+-----------------------+-----------------------+ | ``log2`` | 1 | logarithm to the base | | | | 2 | +-----------------------+-----------------------+-----------------------+ | ``log10`` | 1 | logarithm to the base | | | | 10 | +-----------------------+-----------------------+-----------------------+ | ``log`` | 1 | logarithm to base e | | | | (2.71828...) | +-----------------------+-----------------------+-----------------------+ | ``ln`` | 1 | logarithm to base e | | | | (2.71828...) | +-----------------------+-----------------------+-----------------------+ | ``exp`` | 1 | e raised to the power | | | | of x | +-----------------------+-----------------------+-----------------------+ | ``sqrt`` | 1 | square root of a | | | | value | +-----------------------+-----------------------+-----------------------+ | ``sign`` | 1 | sign function -1 if | | | | x<0; 1 if x>0 | +-----------------------+-----------------------+-----------------------+ | ``rint`` | 1 | round to nearest | | | | integer | +-----------------------+-----------------------+-----------------------+ | ``abs`` | 1 | absolute value | +-----------------------+-----------------------+-----------------------+ | ``min`` | var. | min of all arguments | +-----------------------+-----------------------+-----------------------+ | ``max`` | var. | max of all arguments | +-----------------------+-----------------------+-----------------------+ | ``sum`` | var. | sum of all arguments | +-----------------------+-----------------------+-----------------------+ | ``avg`` | var. | mean value of all | | | | arguments | +-----------------------+-----------------------+-----------------------+ | ``pow`` | 2 | x^y | +-----------------------+-----------------------+-----------------------+ | | Built-in operators +-----------------------+-----------------------+-----------------------+ | **Operator** | **Description** | **Priority** | +=======================+=======================+=======================+ | ``=`` | assignement | -1 | +-----------------------+-----------------------+-----------------------+ | ``&&`` | logical and | 1 | +-----------------------+-----------------------+-----------------------+ | ``||`` | logical or | 2 | +-----------------------+-----------------------+-----------------------+ | ``<=`` | less or equal | 4 | +-----------------------+-----------------------+-----------------------+ | ``>=`` | greater or equal | 4 | +-----------------------+-----------------------+-----------------------+ | ``!=`` | not equal | 4 | +-----------------------+-----------------------+-----------------------+ | ``==`` | equal | 4 | +-----------------------+-----------------------+-----------------------+ | ``>`` | greater than | 4 | +-----------------------+-----------------------+-----------------------+ | ``<`` | less than | 4 | +-----------------------+-----------------------+-----------------------+ | ``+`` | addition | 5 | +-----------------------+-----------------------+-----------------------+ | ``-`` | subtraction | 5 | +-----------------------+-----------------------+-----------------------+ | ``*`` | multiplication | 6 | +-----------------------+-----------------------+-----------------------+ | ``/`` | division | 6 | +-----------------------+-----------------------+-----------------------+ | ``^`` | raise x to the power | 7 | | | of y | | +-----------------------+-----------------------+-----------------------+ | | Common mathematical constants | \_pi, \_e Configuration file schema regarding Calculated Variables -------------------------------------------------------- | The XML element type is called CalculatedVariable and it has the following attributes: +-----------------+-----------------+-----------------+-----------------+ | **Name | **Obligatory? | **XSD Type** | **Meaning | | ** | ** | | ** | +=================+=================+=================+=================+ | name | Yes | xs:string | Name of this | | | | | calculated | | | | | variable. Note | | | | | that the full | | | | | address that | | | | | this variable | | | | | obtains will be | | | | | the name | | | | | prefixed by the | | | | | address of | | | | | position in the | | | | | Address Space | | | | | where the | | | | | variable gets | | | | | instantiated. | +-----------------+-----------------+-----------------+-----------------+ | value | Yes | xs:string | Value formula, | | | | | that is: an | | | | | analytical | | | | | expression used | | | | | to evaluate | | | | | value of this | | | | | variable. Some | | | | | examples will | | | | | be given below. | +-----------------+-----------------+-----------------+-----------------+ | initialValue | No | xs:double | Initial value, | | | | | i.e. the value | | | | | that this | | | | | variable will | | | | | hold BEFORE | | | | | first | | | | | evaluation | | | | | happens (which | | | | | normally is | | | | | when all | | | | | formula | | | | | ingredients | | | | | receive the | | | | | initial | | | | | update). If | | | | | initialValue is | | | | | not given then | | | | | the variable | | | | | will hold NULL | | | | | along | | | | | BadWaitin | | | | | gForInitialData | | | | | status. | +-----------------+-----------------+-----------------+-----------------+ | isBoolean | No | xs:boolean | Evaluate and | | | | | present as | | | | | boolean. The | | | | | final result | | | | | will be | | | | | OpcUa_True if | | | | | the calculation | | | | | result is | | | | | non-zero. | +-----------------+-----------------+-----------------+-----------------+ | status | No | xs:string | Status formula, | | | | | that is: an | | | | | analytical | | | | | expression used | | | | | to evaluate | | | | | OPC UA | | | | | status-code of | | | | | this variable. | | | | | The status-code | | | | | will be | | | | | OpcUa_Good if | | | | | the formula | | | | | evaluates to | | | | | non-zero | | | | | otherwise | | | | | OpcUa_Bad. If | | | | | status formula | | | | | is not used | | | | | then by default | | | | | the variable is | | | | | OpcUa_Good when | | | | | all input | | | | | arguments are | | | | | in good status, | | | | | or OpcUa_Bad | | | | | otherwise | +-----------------+-----------------+-----------------+-----------------+ | The XML element CalculatedVariable can be attached under any quasar object declaration as well as on global scope. Meta-functions and meta-operators (dollar signs in the formulas) ---------------------------------------------------------------- | It is often practical to perform some sort of elaboration of configured formulas before they are given to be compiled by the formula parser. Such elaboration steps are achieved by placing dollar-sign operators and functions in the formulas. For all examples below, the following quasar design diagram is used: | |image1| $thisObjectAddress ~~~~~~~~~~~~~~~~~~ | ``$thisObjectAddress`` evaluates to the string address of the object under which the calculated variable was instantiated. It finds a very practical application to build generalized formulas, which can be applied "under" multiple places in the address-space, so ``$thisObjectAddress`` serves as the relative pointer to the object address. | Using the design as above, the following config file shows a sample application: .. code:: mycode $_ ~~ ``$_`` is an abbreviation for ``$thisObjectAddress`` which comes practical for long, complex formulas. $parentObjectAddress(numLevelsUp=N) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ``$parentObjectAddress`` is a generalization of ``$thisObjectAddress``. For N=0 it evaluates to ``$thisObjectAddress``, for N=1 to its parent object and so on. | Using the design as above, the following config file shows a sample application: .. code:: mycode $applyGenericFormula(formula) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ``$applyGenericFormula`` is used in the context of generalized function templates and `documented there <#Generalized_formula_templates>`__. Generalized formula templates ----------------------------- | Multiple sensors of same type are likely to use same formulas (with possibly different calibration constants). Thus it is economical to share formulas between them if configuration file readability/clarity would profit. | The basic application of generalized formula templates is composed of the following steps: - defining the generalized formula at the top of the configuration file using the CalculatedVariableGenericFormula XML element - applying the formula at the point of use using ``$applyGenericFormula`` meta-function. | Technically, the job done by quasar for applying the formula at the point of use boils down to pasting the formula in place of the meta-function. In the future, extending this operation by optional arguments, might be considered. | An example of the generalized formula template from a real system (CERN - ATLAS DCS - New Small Wheel project, courtesy of P. Tzanis) is given. The generalized formula is put at the top of the configuration file: .. code:: mycode | | As can be seen, the formula profits from $thisObjectAddress meta-function which enables its reuse at any place of the configuration (so, consequently, the address-space) which has a sibling variable called "value" (which, in the case of the particular application, is the converted voltage expressed in volts). | Then, the application of the formula is done in the following way: .. code:: mycode CalculatedVariables logging and tracing --------------------------------------- | CalculatedVariables module has its own LogIt component called ``CalcVars``. | As it's the case with any LogIt logging component, its log levels can be configured via the address-space as well as in the configuration file. The latter is often needed because most of potential issues (formula errors) would happen at the server initialization, i.e. before it is possible and practical to raise verbosity using the address-space. | Thus, in case of issues with formulas, it is advised to put the CalcVars log level to TRC, for instance by means of the XML configuration: .. code:: mycode Escaping variable names containing dashes ("-") and slashes ("/") ----------------------------------------------------------------- | Users of quasar-based servers sometimes choose to name their quasar objects (i.e. the ``name`` attribute of XML elements in the configuration files) with names containing dashes or slashes. | This is legit in the quasar world. However, it poses some problems if CalculatedVariables inputs connect to such named objects (i.e. its variables). | Imagine the following config file: .. code:: mycode | | Such a config file is fine; among different address-space entities instantiated it'd have the CalculatedVariable under address "Bus1/Device2-A.calibrationConstant". | However, now imagine that somewhere "later" in the config file, another CalculatedVariable would be introduced and it would refer to the calibrationConstant: .. code:: mycode | | A problem is clearly seen: in the formula, it is impossible to distinguish if the dashes "-" and slashes "/" refer to input variable names or the subtraction and/or division operators (in simpler cases like in this example one could "guess" the meaning but in general quasar architecture prefers to be more explicit rather than to guess). Note that due to the grammar imposed by the parser engine, the precedence of dashes and slashes will always be given to operators rather than operands. | Therefore one needs to escape the dash and slash signs in case these are to refer to variable names. Thus, the aforementioned example would be fixed this way: .. code:: mycode   | | **Note** that those using $thisObjectAddress and/or $parentObjectAddress to derive the input variable address do not have to do anything because both of the meta-functions will escape dashes and slashes behind the scenes. Examples -------- NTC sensors: converting resistance into temperature in Celsius and Fahrenheit degrees ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | Imagine that a device can measure resistance of a connected resistor. If the resistor happens to be a NTC temperature probe, then one can find the temperature in function of resistance: | T = T0 \* B / (T0 \* ln(R/R0) + B) | where T0 is typically 298.15K (that is, +25 deg C in Kelvin degrees), B is the so called B constant of a NTC probe (often 3977K) and R0 is the resistance at T0. | The variable in the example is R, and that is the cache-variable that gets updated by your OPC UA server device logic. | Let's assume that the OPC UA address of the variable is NTC1.resistance | Therefore, anywhere below NTC1 declaration in your config file, you can instantiate a CalculatedVariable that will recompute the measured resistance into temperature expressed in Kelvin degrees. In the example below we also add some CalculatedVariables to hold B, T0 and R0 constants. .. code:: mycode | We also add two Calculated Variables that will recompute Kelvins into Celsius degrees and Fahrenheit degrees: .. code:: mycode | In addition, we can add a boolean variable which subjectively indicates whether it's warm enough. It's an example of usage of logical operators as well as ``isBoolean`` attribute: .. code:: mycode CalculatedVariable attached to multiple different quasar entities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | This example shall illustrate that a CalculatedVariable can be attached (i.e. its inputs might be) different quasar entities such as: cache-variables, free-variables, other calculated-variables and even config-entries (if they are of some numeric data-type). .. code:: mycode | As can be seen, the last calculated variable is a function computed of values of many different quasar entities which all corresponds to address-space variables. Counter-examples ---------------- Place no white-space between unary operation (e.g. a function) and the parenthesis around its operand ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | Note that it is illegal (i.e. will be refused at configuration loading) to put any whitespace between unary operation (function?) and the operands, e.g. this is legal: .. code:: mycode | | and this is illegal: .. code:: mycode | Advanced documentation for quasar developers -------------------------------------------- Selection of expression parser ------------------------------ | There exist many open-source parsers potentially suitable for the feature. At the time of writing, a good overview was present at https://github.com/ArashPartow/math-parser-benchmark-project . | The author has evaluated three parsers from the list: - `ExprTk `__ It made an excellent overall impression. However, due to very intensive use of templates, the compilation time has been significant (i.e. its inclusion would triple(!!) the whole compilation time of a simple quasar server). That unfavourable property has made the quasar team to look for another solution. - `ATMSP `__ The initial code review has shown that the parser uses setjmp()/longjmp() which has been considered unfavourable for quasar servers. - `muParser `__ muParser demonstrated decent performance while it has all the features required by the Calculated Variables feature. Overview of feature implementation ---------------------------------- | An UML class diagram is presented below. | |UML| Classes rationale ----------------- - ChangeNotifyingVariable - can emit notifications whenever the variable changed value. Applicable to any data type. Can be used with multiple notification receivers. Can be used for applications different than CalculatedVariables. - ParserVariable - stores current numeric variable value as a plain double type, and therefore can be coupled as a mu::Parser variable. (Sidenote: mu::Parser doesn't know anything about OPC UA and without such arrangement it wouldn't know how to access a double from UaVariant, neither to know whether the value is correct, etc.). - - notifyingVariable - is the pointer to a ChangeNotifyingVariable which notifies this particular ParserVariable on change, - notifiedVariables - the list of all CalculatedVariables that use this particular ParserVariable in formulas. - CalculatedVariable - it's the OPC UA variable defined by a formula. It's a subclass of ChangeNotifyingVariable because its output can in turn be used as an input to another Calculated Variable (so it must be able to emit notifications on change). - Engine - puts all things together. It supplies methods for usage in Configuration module: - - instantiateCalculatedVariable - called whenerver CalculatedVariable() entry is found in the config file, - registerVariableForCalculatedVariables - called whenever any cache-variable of suitable design properties (numeric and scalar) is inserted into the OPC UA address-space Overview of information flow ---------------------------- #. All cache-variables instantiated by quasar Configuration module are of ChangeNotifyingVariable type or its subclasses. #. When quasar Configuration determines that given cache-variable variable looks suitable to be used as a formula input (i.e. is numeric and it's scalar), it would add a ChangeListener and a corresponding ParserVariable. The ChangeListener will (once potentially invoked in future) call setValue() on given ParserVariable. #. When device logic or an OPC UA client writes to a suitable cache-variable, the setValue() of ParserVariable bound to the cache-variable will be called. It will store the new value and status in corresponding fields and then call update() on relevant (i.e. those which use given parser variable as an input) CalculatedVariable variables. Synchronization, re-entrance, multi-threading --------------------------------------------- | The CalculatedVariables module is closely tied to the AddressSpace of a quasar-based server. | For instance, the recalculation of an associated calculated variable is done within the call to a setter of a variable that it depends on. | It must be emphasized that AddressSpace is brutally multi-threaded. At the same time, the following thread families would be doing work on AddressSpace objects: - sampling threads which sample current values of cache-variables to which any client subscribes. Those threads are run by chosen OPC UA backend and their number is highly dependent on backend's configuration (i.e. ServerConfig.xml) as well as possibly on number of connected clients and the set of data they subscribe to. - server's OPC UA requests processing threads. Those threads are run by chosen OPC UA backend and similarly to sampling threads, their number depends on many factors. Those threads process e.g. Write service requests, so that an OPC UA client can write to given variable. - device logic (or other user threads). Those threads are instantiated by server developers and configured by end-users. They typically push data to the address-space. | | In the context of Calculated Variables, there are two obvious critical section types: - possible calls to variable setters of the same variable coming from different threads. The worries here are the following: - - there might be a clash in storage of value and status, as both of them are necessary to perform the calculation and (to author's knowledge) such an assignment is never atomic by default. So a recalculation might take value stored by one thread and status from another, or on a 32-bit machine (since double is 64-bits) even take partially stored value. - it's not entirely clear if calls to mu::Parser::Eval are re-entrant. - possible concurrent calls from different threads to variable setters of different variables which are used in the same formula. The worries here are the following: - - the parser might attempt to use the value when it is being assigned to (and that is not atomic) - it's not entirely clear if calls to mu::Parser::Eval are re-entrant. | Having analyzed the problem and trying to propose a guaranteed dead-lock free solution, the author proposes to form disjoint subgraphs of the calculation graph and synchronize per each subgraph. | Let's look at an example for which the calculation graph is like in the picture below. | |Synchronization example| | PV stands for ParserVariable, those are all variables that can be used as inputs in a CalculatedVariable formula. | CV stands for CalculatedVariable. Note that every CV is also a PV because the output of one formula can be used as an input to another formula. Case 1: ignore CV4 (violet node and arrows) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | Case 1 would happen if we defined the following Calculated Variables in the config file (the particular operators - e.g. addition, multiplication - do not matter): | CV1 = PV1 + PV2 | CV2 = CV1*PV3 + PV2 | CV3 = 3.14 \* PV4 | In this case the implementation will form two domains of mutual exclusion (called synchronizers): - 1st one, which will provide exclusive access to setters of PV1, PV2 and PV3 (e.g. if any thread would enter setter of any of {PV1, PV2, PV3} all other threads willing to do the same would need to wait) - 2nd one, which will provide exclusive access to setter of PV4 | PV5 would not get a synchronizer because it's output is not used by anything; in fact PV5 would be optimized out after the configuration process is finished. Case 2: CV4 is added ~~~~~~~~~~~~~~~~~~~~ | Now let's add CV4 to the picture. | This (apparently) small extension actually does change a lot in the multi-threading schema: now one mutual exclusion domain gets formed which covers all possible setters. | Though such a scenario is rather unlikely to be seen, server developers and users should be aware of this relation. Supplementary notes on certain design decisions ----------------------------------------------- Why constants from config entries propagate into ParserVariables rather than being declared using muParser::DefineConst? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | Benchmarks ---------- | Some benchmarks have been performed. The base has been pre-1.3.1 release of quasar. The benchmarks have been performed with UA-SDK 1.5.5 as the OPC UA backend. Aspect +-----------------+-----------------+-----------------+-----------------+ | | quasar w/o | quasar w | Diff | | | Calculated | Calculated | | | | Variables | Variables | | | | support | support | | | | | (note: no Calc | | | | | Vars declared!) | | +-----------------+-----------------+-----------------+-----------------+ | Build time of a | 55s | 1m15s | 18% longer | | simple, | 56s | 1m3s | | | one-class | 54s | 59s | | | server | AVG = 55s | AVG = 65s | | +-----------------+-----------------+-----------------+-----------------+ | Build time of a | 4m15s | 4m25s | 3.9% longer | | complex server | | | | | (here: SCA) | | | | +-----------------+-----------------+-----------------+-----------------+ | Time to publish | 32793 ms | 32917 ms | 1.4% more | | 100M random | 32892 ms | 33313 ms | overhead | | doubles via a | 32623 ms | 33460 ms | | | cache-variable | AVG = 32768 ms | AVG = 33230 ms | | +-----------------+-----------------+-----------------+-----------------+ | Valgrind info | ==6591== HEAP | ==5861== HEAP | 0.1% more | | (publishing 1M | SUMMARY: | SUMMARY: | allocs | | random doubles) | ==6591== in use | ==5861== in use |   | | | at exit: 27,753 | at exit: 28,458 | note "bytes | | | bytes in 209 | bytes in 213 | allocated" has | | | blocks | blocks | no relation to | | | ==6591== total | ==5861== total | the actual size | | | heap usage: | heap usage: | of RSS memory | | | 1,031,151 | 1,032,466 | of a running | | | allocs, | allocs, | process! | | | 1,030,942 | 1,032,253 | | | | frees, | frees, | | | | 72,543,037 | 75,191,342 | | | | bytes allocated | bytes allocated | | +-----------------+-----------------+-----------------+-----------------+ | muParser distribution model --------------------------- | The muParser is distributed along quasar in an amalgamated way. | In quasar repo, you can go to: | CalculatedVariables/ext_components/ | where you will find a script "clone_and_amalgamate_muparser.sh" which will perform cloning of muParser and then amalgamation. | Note that the particular version of muParser as well as accompanying amalgamation utility is fixed so there is no reason to run the script without changing the version. .. |image1| image:: images/sample_design.png :width: 20.0% .. |UML| image:: images/CalculatedVariablesClassDiagram.png :width: 1098px :height: 765px .. |Synchronization example| image:: images/SynchronizationExample.png :width: 614px :height: 364px