#*************************************************************************
# Copyright (c) 2007 UChicago Argonne LLC, as Operator of Argonne
#     National Laboratory.
# Copyright (c) 2002 The Regents of the University of California, as
#     Operator of Los Alamos National Laboratory.
# EPICS BASE is distributed subject to a Software License Agreement found
# in file LICENSE that is included with this distribution.
#*************************************************************************

=title Calculation Record (calc)

The calculation or "Calc" record is used to perform algebraic, relational,
and logical operations on values retrieved from other records. The result
of its operations can then be accessed by another record so that it can
then be used.

=head2 Parameter Fields

The record-specific fields are described below, grouped by functionality.

=recordtype calc

=cut

recordtype(calc) {

=head3 Scan Parameters

The Calc record has the standard fields for specifying under what
circumstances the record will be processed.
These fields are described in L<Scan Fields|dbCommonRecord/Scan Fields>.

=fields SCAN, PHAS, EVNT, PRIO, PINI

=head3 Read Parameters

The read parameters for the Calc record consist of 12 input links INPA,
INPB, ... INPL. The fields can be database links, channel access links, or
constants. If they are links, they must specify another record's field or a
channel access link. If they are constants, they will be initialized with
the value they are configured with and can be changed via C<dbPuts>. They
cannot be hardware addresses.

See L<Address
Specification|https://docs.epics-controls.org/en/latest/guides/EPICS_Process_Database_Concepts.html#address-specification>
for information on how to specify database links.

=fields INPA, INPB, INPC, INPD, INPE, INPF, INPG, INPH, INPI, INPJ, INPK, INPL

=head3 Expression

At the core of the Calc record lies the CALC and RPCL fields. The CALC field
contains the infix expresion which the record routine will use when it
processes the record. The resulting value is placed in the VAL field and
can be accessed from there. The CALC expression is actually converted to
opcode and stored as Reverse Polish Notation in the RPCL field. It is this
expression which is actually used to calculate VAL. The Reverse Polish
expression is evaluated more efficiently during run-time than an infix
expression. CALC can be changed at run-time, and a special record routine
calls a function to convert it to Reverse Polish Notation.

The infix expressions that can be used are very similar to the C expression
syntax, but with some additions and subtle differences in operator meaning
and precedence. The string may contain a series of expressions separated by
a semi-colon character ";" any one of which may actually provide the
calculation result; however, all of the other expressions included must
assign their result to a variable. All alphabetic elements described below
are case independent, so upper and lower case letters may be used and mixed
in the variable and function names as desired. Spaces may be used anywhere
within an expression except between characters that make up a single
expression element.

The range of expressions supported by the calculation record are separated
into literals, constants, operands, algebraic operators, trigonometric operators,
relational operators, logical operators, the assignment operator,
parentheses and commas, and the question mark or '?:' operator.

=fields CALC, RPCL

=head3 Literals

=over 1

=item *
Standard double precision floating point numbers

=item *
Inf: Infinity

=item *
Nan: Not a Number

=back

=head3 Constants

=over 1

=item *
PI: returns the mathematical constant E<pi>

=item *
D2R: evaluates to E<pi>/180 which, when used as a multiplier, converts an
angle from degrees to radians

=item *
R2D: evaluates to 180/E<pi> which as a multiplier converts an angle from
radians to degrees

=back

=head3 Operands

The expression uses the values retrieved from the INPx links as operands,
though constants can be used as operands too. These values retrieved from
the input links are stored in the A-L fields. The values to be used in the
expression are simply referenced by the field letter. For instance, the
value obtained from INPA link is stored in the field A, and the value
obtained from INPB is stored in field B. The field names can be included in
the expression which will operate on their respective values, as in A+B.
Also, the RNDM nullary function can be included as an operand in the
expression in order to generate a random number between 0 and 1.

=fields A, B, C, D, E, F, G, H, I, J, K, L

The keyword VAL returns the current contents of the VAL field (which can be
written to by a CA put, so it might I<not> be the result from the last time
the expression was evaluated).

=head3 Algebraic Operators

=over 1

=item *
ABS: Absolute value (unary)

=item *
SQR: Square root (unary)

=item *
MIN: Minimum (any number of args)

=item *
MAX: Maximum (any number of args)

=item *
FINITE: returns non-zero if none of the arguments are NaN or Inf (any
number of args)

=item *
ISNAN: returns non-zero if any of the arguments is NaN or Inf (any number
of args)

=item *
CEIL: Ceiling (unary)

=item *
FLOOR: Floor (unary)

=item *
FMOD: Floating point modulo (binary)  Added in 7.0.8

=item *
LOG: Log base 10 (unary)

=item *
LOGE: Natural log (unary)

=item *
LN: Natural log (unary)

=item *
EXP: Exponential function (unary)

=item *
^ : Exponential (binary)

=item *
** : Exponential (binary)

=item *
+ : Addition (binary)

=item *
- : Subtraction (binary)

=item *
* : Multiplication (binary)

=item *
/ : Division (binary)

=item *
% : Modulo (binary)

=item *
NOT: Negate (unary)

=back

=head3 Trigonometric Operators

=over 1

=item *
SIN: Sine

=item *
SINH: Hyperbolic sine

=item *
ASIN: Arc sine

=item *
COS: Cosine

=item *
COSH: Hyperbolic cosine

=item *
ACOS: Arc cosine

=item *
TAN: Tangent

=item *
TANH: Hyperbolic tangent

=item *
ATAN: Arc tangent

=back

=head3 Relational Operators

=over 1

=item *
C<<< >= >>> : Greater than or equal to

=item *
C<<< > >>> : Greater than

=item *
C<<< <= >>> : Less than or equal to

=item *
C<<< < >>> : Less than

=item *
C<<< # >>> : Not equal to

=item *
C<<< = >>> : Equal to

=back

=head3 Logical Operators

=over 1

=item *
C<&&> : And

=item *
C<||> : Or

=item *
C<!> : Not

=back

=head3 Bitwise Operators

=over 1

=item *
C<|> : Bitwise Or

=item *
C<&> : Bitwise And

=item *
OR : Bitwise Or

=item *
AND : Bitwise And

=item *
XOR : Bitwise Exclusive Or

=item *
C<~> : One's Complement

=item *
C<<< << >>> : Arithmetic Left Shift

=item *
C<<< >> >>> : Arithmetic Right Shift

=item *
C<<<< >>> >>>> : Logical Right Shift

=back

=head3 Assignment Operator

=over 1

=item *
C<:=> : assigns a value (right hand side) to a variable (i.e. field)

=back

=head3 Parantheses, Comma, and Semicolon

The open and close parentheses are supported. Nested parentheses are
supported.

The comma is supported when used to separate the arguments of a binary
function.

The semicolon is used to separate expressions. Although only one
traditional calculation expression is allowed, multiple assignment
expressions are allowed.

=head3 Conditional Expression

The C language's question mark operator is supported. The format is:
C<condition ? True result : False result>

=head3 Expression Examples

=head3 Algebraic

C<A + B + 10>

=over 1

=item *
Result is C<A + B + 10>

=back

=head3 Relational

C<<< (A + B) < (C + D) >>>

=over 1

=item *
Result is 1 if C<<< (A + B) < (C + D) >>>

=item *
Result is 0 if C<<< (A + B) >= (C + D) >>>

=back

=head3 Question Mark

C<<< (A + B) < (C + D) ? E : F + L + 10 >>>

=over 1

=item *
Result is C<E> if C<<< (A + B) < (C + D) >>>

=item *
Result is C<F + L + 10> if C<<< (A + B) >= (C + D) >>>

=back

Prior to Base 3.14.9 it was legal to omit the : and the second (else) part
of the conditional, like this:

C<(A + B)<(C + D) ? E>

=over 1

=item
Result is E if (A + B)<(C + D)

=item
Result is unchanged if (A + B)>=(C + D)

From 3.14.9 onwards, this expresion must be written as
C<(A + B) < (C + D) ? E : VAL>

=back

=head3 Logical

C<A & B>

=over 1

=item *
Causes the following to occur:

=over 1

=item *
Convert A to integer

=item *
Convert B to integer

=item *
Bitwise And A and B

=item *
Convert result to floating point

=back

=back

=head3 Assignment

C<sin(a); a:=a+D2R>

=over 1

=item *
Causes the Calc record to output the successive values of a sine curve in
1 degree intervals.

=back

=head3 Operator Display Parameters

These parameters are used to present meaningful data to the operator. These
fields are used to display VAL and other parameters of the calculation
record either textually or graphically.

The EGU field contains a string of up to 16 characters which is supplied by
the user and which describes the values being operated upon. The string is
retrieved whenever the routine C<get_units> is called. The EGU string is
solely for an operator's sake and does not have to be used.

The HOPR and LOPR fields only refer to the limits of the VAL, HIHI, HIGH,
LOW and LOLO fields. PREC controls the precision of the VAL field.

See L<Fields Common to All Record Types|dbCommonRecord/Operator Display
Parameters> for more on the record name (NAME) and description (DESC) fields.

=fields EGU, PREC, HOPR, LOPR, NAME, DESC

=head3 Alarm Parameters

The possible alarm conditions for the Calc record are the SCAN, READ,
Calculation, and limit alarms. The SCAN and READ alarms are called by the
record support routines. The Calculation alarm is called by the record
processing routine when the CALC expression is an invalid one, upon which
an error message is generated.

The following alarm parameters which are configured by the user, define the
limit alarms for the VAL field and the severity corresponding to those
conditions.

The HYST field defines an alarm deadband for each limit.

The AFTC field sets the time constant on a low-pass filter that delays the
reporting of limit alarms until the signal has been within the alarm range for
that number of seconds (the default AFTC value of zero retains the previous
behavior).

See L<Alarm Specification|https://docs.epics-controls.org/en/latest/guides/EPICS_Process_Database_Concepts.html#alarm-specification>
for a complete explanation of record alarms and of the standard fields.
L<Alarm Fields|dbCommonRecord/Alarm Fields> lists other fields related
to alarms that are common to all record types.

=fields HIHI, HIGH, LOW, LOLO, HHSV, HSV, LSV, LLSV, HYST, AFTC

=head3 Monitor Parameters

These paramaeters are used to determine when to send monitors for the value
fields. These monitors are sent when the value field exceeds the last
monitored field by the appropriate deadband, the ADEL for archiver monitors
and the MDEL field for all other types of monitors. If these fields have a
value of zero, everytime the value changes, monitors are triggered; if they have a
value of -1, everytime the record is scanned, monitors are triggered. See
L<Monitor Specification> for a complete explanation of monitors.

=fields ADEL, MDEL

=head3 Run-time Parameters

These fields are not configurable using a configuration tool and none are
modifiable at run-time. They are used to process the record.

The LALM field is used to implement the hysteresis factor for the alarm
limits.

The LA-LL fields are used to decide when to trigger monitors for the
corresponding fields. For instance, if LA does not equal the value A,
monitors for A are triggered. The MLST and ALST fields are used in the same
manner for the VAL field.

=fields LALM, ALST, MLST, LA, LB, LC, LD, LE, LF, LG, LH, LI, LJ, LK, LL

=cut

	include "dbCommon.dbd"
	field(VAL,DBF_DOUBLE) {
		prompt("Result")
		promptgroup("50 - Output")
		asl(ASL0)
	}
	field(CALC,DBF_STRING) {
		prompt("Calculation")
		promptgroup("30 - Action")
		special(SPC_CALC)
		pp(TRUE)
		size(80)
		initial("0")
	}
	field(INPA,DBF_INLINK) {
		prompt("Input A")
		promptgroup("41 - Input A-F")
		interest(1)
	}
	field(INPB,DBF_INLINK) {
		prompt("Input B")
		promptgroup("41 - Input A-F")
		interest(1)
	}
	field(INPC,DBF_INLINK) {
		prompt("Input C")
		promptgroup("41 - Input A-F")
		interest(1)
	}
	field(INPD,DBF_INLINK) {
		prompt("Input D")
		promptgroup("41 - Input A-F")
		interest(1)
	}
	field(INPE,DBF_INLINK) {
		prompt("Input E")
		promptgroup("41 - Input A-F")
		interest(1)
	}
	field(INPF,DBF_INLINK) {
		prompt("Input F")
		promptgroup("41 - Input A-F")
		interest(1)
	}
	field(INPG,DBF_INLINK) {
		prompt("Input G")
		promptgroup("42 - Input G-L")
		interest(1)
	}
	field(INPH,DBF_INLINK) {
		prompt("Input H")
		promptgroup("42 - Input G-L")
		interest(1)
	}
	field(INPI,DBF_INLINK) {
		prompt("Input I")
		promptgroup("42 - Input G-L")
		interest(1)
	}
	field(INPJ,DBF_INLINK) {
		prompt("Input J")
		promptgroup("42 - Input G-L")
		interest(1)
	}
	field(INPK,DBF_INLINK) {
		prompt("Input K")
		promptgroup("42 - Input G-L")
		interest(1)
	}
	field(INPL,DBF_INLINK) {
		prompt("Input L")
		promptgroup("42 - Input G-L")
		interest(1)
	}
	field(EGU,DBF_STRING) {
		prompt("Engineering Units")
		promptgroup("80 - Display")
		interest(1)
		size(16)
		prop(YES)
	}
	field(PREC,DBF_SHORT) {
		prompt("Display Precision")
		promptgroup("80 - Display")
		interest(1)
		prop(YES)
	}
	field(HOPR,DBF_DOUBLE) {
		prompt("High Operating Rng")
		promptgroup("80 - Display")
		interest(1)
		prop(YES)
	}
	field(LOPR,DBF_DOUBLE) {
		prompt("Low Operating Range")
		promptgroup("80 - Display")
		interest(1)
		prop(YES)
	}
	field(HIHI,DBF_DOUBLE) {
		prompt("Hihi Alarm Limit")
		promptgroup("70 - Alarm")
		pp(TRUE)
		interest(1)
		prop(YES)
	}
	field(LOLO,DBF_DOUBLE) {
		prompt("Lolo Alarm Limit")
		promptgroup("70 - Alarm")
		pp(TRUE)
		interest(1)
		prop(YES)
	}
	field(HIGH,DBF_DOUBLE) {
		prompt("High Alarm Limit")
		promptgroup("70 - Alarm")
		pp(TRUE)
		interest(1)
		prop(YES)
	}
	field(LOW,DBF_DOUBLE) {
		prompt("Low Alarm Limit")
		promptgroup("70 - Alarm")
		pp(TRUE)
		interest(1)
		prop(YES)
	}
	field(HHSV,DBF_MENU) {
		prompt("Hihi Severity")
		promptgroup("70 - Alarm")
		pp(TRUE)
		interest(1)
		prop(YES)
		menu(menuAlarmSevr)
	}
	field(LLSV,DBF_MENU) {
		prompt("Lolo Severity")
		promptgroup("70 - Alarm")
		pp(TRUE)
		interest(1)
		prop(YES)
		menu(menuAlarmSevr)
	}
	field(HSV,DBF_MENU) {
		prompt("High Severity")
		promptgroup("70 - Alarm")
		pp(TRUE)
		interest(1)
		prop(YES)
		menu(menuAlarmSevr)
	}
	field(LSV,DBF_MENU) {
		prompt("Low Severity")
		promptgroup("70 - Alarm")
		pp(TRUE)
		interest(1)
		prop(YES)
		menu(menuAlarmSevr)
	}
	field(AFTC, DBF_DOUBLE) {
		prompt("Alarm Filter Time Constant")
		promptgroup("70 - Alarm")
		interest(1)
	}
	field(AFVL, DBF_DOUBLE) {
		prompt("Alarm Filter Value")
		special(SPC_NOMOD)
		interest(3)
	}
	field(HYST,DBF_DOUBLE) {
		prompt("Alarm Deadband")
		promptgroup("70 - Alarm")
		interest(1)
	}
	field(ADEL,DBF_DOUBLE) {
		prompt("Archive Deadband")
		promptgroup("80 - Display")
		interest(1)
	}
	field(MDEL,DBF_DOUBLE) {
		prompt("Monitor Deadband")
		promptgroup("80 - Display")
		interest(1)
	}
	field(A,DBF_DOUBLE) {
		prompt("Value of Input A")
		pp(TRUE)
	}
	field(B,DBF_DOUBLE) {
		prompt("Value of Input B")
		pp(TRUE)
	}
	field(C,DBF_DOUBLE) {
		prompt("Value of Input C")
		pp(TRUE)
	}
	field(D,DBF_DOUBLE) {
		prompt("Value of Input D")
		pp(TRUE)
	}
	field(E,DBF_DOUBLE) {
		prompt("Value of Input E")
		pp(TRUE)
	}
	field(F,DBF_DOUBLE) {
		prompt("Value of Input F")
		pp(TRUE)
	}
	field(G,DBF_DOUBLE) {
		prompt("Value of Input G")
		pp(TRUE)
	}
	field(H,DBF_DOUBLE) {
		prompt("Value of Input H")
		pp(TRUE)
	}
	field(I,DBF_DOUBLE) {
		prompt("Value of Input I")
		pp(TRUE)
	}
	field(J,DBF_DOUBLE) {
		prompt("Value of Input J")
		pp(TRUE)
	}
	field(K,DBF_DOUBLE) {
		prompt("Value of Input K")
		pp(TRUE)
	}
	field(L,DBF_DOUBLE) {
		prompt("Value of Input L")
		pp(TRUE)
	}
	field(LA,DBF_DOUBLE) {
		prompt("Prev Value of A")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LB,DBF_DOUBLE) {
		prompt("Prev Value of B")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LC,DBF_DOUBLE) {
		prompt("Prev Value of C")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LD,DBF_DOUBLE) {
		prompt("Prev Value of D")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LE,DBF_DOUBLE) {
		prompt("Prev Value of E")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LF,DBF_DOUBLE) {
		prompt("Prev Value of F")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LG,DBF_DOUBLE) {
		prompt("Prev Value of G")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LH,DBF_DOUBLE) {
		prompt("Prev Value of H")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LI,DBF_DOUBLE) {
		prompt("Prev Value of I")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LJ,DBF_DOUBLE) {
		prompt("Prev Value of J")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LK,DBF_DOUBLE) {
		prompt("Prev Value of K")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LL,DBF_DOUBLE) {
		prompt("Prev Value of L")
		special(SPC_NOMOD)
		interest(3)
	}
	field(LALM,DBF_DOUBLE) {
		prompt("Last Value Alarmed")
		special(SPC_NOMOD)
		interest(3)
	}
	field(ALST,DBF_DOUBLE) {
		prompt("Last Value Archived")
		special(SPC_NOMOD)
		interest(3)
	}
	field(MLST,DBF_DOUBLE) {
		prompt("Last Val Monitored")
		special(SPC_NOMOD)
		interest(3)
	}
	%#include "postfix.h"
	field(RPCL,DBF_NOACCESS) {
		prompt("Reverse Polish Calc")
		special(SPC_NOMOD)
		interest(4)
		extra("char	rpcl[INFIX_TO_POSTFIX_SIZE(80)]")
	}

=head2 Record Support

=head3 Record Support Routines

=head2 C<init_record>

For each constant input link, the corresponding value field is initialized
with the constant value if the input link is CONSTANT or a channel access
link is created if the input link is a PV_LINK.

A routine postfix is called to convert the infix expression in CALC to
Reverse Polish Notation. The result is stored in RPCL.

=head2 C<process>

See next section.

=head2 C<special>

This is called if CALC is changed. C<special> calls postfix.

=head2 C<get_units>

Retrieves EGU.

=head2 C<get_precision>

Retrieves PREC.

=head2 C<get_graphic_double>

Sets the upper display and lower display limits for a field. If the field
is VAL, HIHI, HIGH, LOW, or LOLO, the limits are set to HOPR and LOPR, else
if the field has upper and lower limits defined they will be used, else the
upper and lower maximum values for the field will be used.

=head2 C<get_control_double>

Sets the upper control and the lower control limits for a field. If the
field is VAL, HIHI, HIGH, LOW, or LOLO, the limits are set to HOPR and
LOPR, else if the field has upper and lower limits defined they will be
used, else the upper and lower maximum values for the field type will be
used.

=head2 C<get_alarm_double>

Sets the following values:

=over 1

upper_alarm_limit = HIHI

upper_warning_limit = HIGH

lower_warning_limit = LOW

lower_alarm_limit = LOLO

=back

=head3 Record Processing

Routine process implements the following algorithm:

=over 1

=item 1.
Fetch all arguments.

=item 2.
Call routine C<calcPerform>, which calculates VAL from the postfix version of
the expression given in CALC. If C<calcPerform> returns success UDF is set to
FALSE.

=item 3.
Check alarms. This routine checks to see if the new VAL causes the alarm
status and severity to change. If so, NSEV, NSTA, and LALM are set. It also
honors the alarm hysteresis factor (HYST). Thus the value must change by
at least HYST before the alarm status and severity changes.

=item 4.
Check to see if monitors should be invoked.

=back

=over 1

=item *
Alarm monitors are invoked if the alarm status or severity has changed.

=item *
Archive and values change monitors are invoked if ADEL and MDEL conditions
are met.

=item *
Monitors for A-L are checked whenever other monitors are invoked.

=item *
NSEV and NSTA are reset to 0.

=back

=over

=item 5.
Scan forward link if necessary, set PACT FALSE, and return.

=back

=cut

}
