Skip to content

Formulas and Calculated Items

A Calculated Item is defined by a Formula over other Items. plintz supports a subset of the Formula language. The Formula's result determines the kind of Calculated Item:

  • a Signal makes a Calculated Signal, such as $a + $b * 2;
  • a Condition makes a Calculated Condition, such as $a > 20;
  • a time-independent value makes a Calculated Scalar, such as 10.

A Calculated Item belongs to the Analysis you create it in. You need Write access to the Analysis to create or edit one.

Writing a Formula

Variables

A Formula refers to other Items through $variables. A variable's name is a $, then letters, digits and underscores, and it doesn't start with a digit: $a, $flow_rate, $T2. Each variable is bound to one Item: a Stored Signal, or a Calculated Signal, Condition or Scalar of the same Analysis. A Formula may have no variables, such as 10 or days().

Syntax

  • Numbers are decimal: 10, 10.0, .1. A number can have a unit: 1h, 30 min, 5kW, 10m/s.
  • Strings are in single or double quotes. A backslash escapes a backslash or a quote.
  • Arithmetic is +, -, * and /, with unary minus.
  • Comparisons are <, <=, >, >=, == and !=. They bind looser than + and -.
  • Conditions combine with and, or and not. They bind looser than comparisons, not tightest and or loosest.
  • Parentheses group, and // and /* */ start comments.
  • Functions are written either way: $a.move(1h) or move($a, 1h).

Hexadecimal and octal numbers, such as 0x1F and 013, aren't supported. Write a decimal number such as 13 instead.

The Formula box

The Formula box highlights the syntax as you type, in both the light and the dark theme: $variables, function names, numbers and their units, strings, comments and operators (+, -, *, /, the comparisons, and, or, not and the . of $a.move(1h)) each have their own color. When the caret is next to a parenthesis, it and its partner are marked. Typing ( adds the ) as well, and typing the ) yourself steps over it.

The box is a text box named "Formula", in the tab order: Tab leaves it for the next control, and Enter starts a new line. Highlighting follows the syntax above, so a Formula can be highlighted and still fail on Execute, for example with a function or unit plintz doesn't know.

The Formula editor with a Signal variable and a comparison Formula.

Function suggestions

The box suggests function names as you type, and with Ctrl+Space, which opens the list without a letter typed. Each suggestion shows its parameters, and the selected one its description. Enter or Tab accepts the selected suggestion, the arrow keys choose another and Escape closes the list. Tab leaves the box as before when no list is open.

The list holds only the functions plintz implements, the ones in the table below, so every suggestion works. It is read from the server once for each session. If it can't be read, the box still highlights and works, but suggests no functions.

After a ., as in $a., it suggests the functions you can call that way on the variable: those whose first parameter takes that kind of value. $a. offers move() if $a is a Signal, and removeLongerThan() if it is a Condition, but not the other way round. Choose the variable's Item first, as the kind comes from it. Where the kind isn't known, for example after $a.move(1h)., every function that can be called that way is offered. The functions +, - and not call, such as add() and inverse(), aren't suggested: write the operator. intersect() and union(), which and and or call, are suggested, and so is subtract() of Conditions.

While the caret is inside a call, a hint above it shows the call's parameters, with the one the caret is on in bold. A function with several forms, such as periods(), shows each form that has a parameter there. A parameter is named by what it takes: Signal, Condition, Scalar for a number, Duration for a time such as 1h, String and so on, so move() shows as move(Signal, Duration). In $a.move(1h), $a counts as the first parameter, so 1h is the second.

Variable and unit suggestions

After a $, the box suggests the names of the variable rows above it, narrowed by the letters you type after the $, in the order of the rows. Each shows the kind of its Item, Signal, Condition or Scalar, once the Item is chosen. The list follows the rows as you add, rename or remove them. It opens as you type, and Enter or Tab accepts the selected name, as for functions. It works when the function list can't be read.

After a number, the box suggests units. Type a letter after 10, as in 10 k, to see kW, kWh and other units that begin with that letter. If you type an alias such as degc for °C, the box shows the matching unit. Press Ctrl+Space after a number to open the full list. Unit names are case-sensitive. Suggestions matching the case you typed appear first, followed by those that differ only in case. The server loads the unit names and aliases accepted by plintz once per session. If it can't load them, the box suggests no units but otherwise works as usual. Suggestions appear only for a unit immediately after a number, not for the second unit in a compound such as m/s.

A $variable in the Formula that has no variable row is underlined, as a syntax error is, with the message "Variable '$b' is not defined". It is marked half a second after you stop typing, and again when you add, rename or remove a row. A $variable with a row but no Item chosen is not marked: Execute asks you to choose one.

A syntax error is underlined as you type. Half a second after you stop typing, plintz checks the Formula and underlines errors where they occur. Hover over an underline to read the error message, such as "Unexpected input at '*', line=1, column=6". This check validates syntax only. It doesn't read data, require an Item for each $variable or create anything. Errors that depend on variable types, such as adding a Signal and a Condition, an unknown function or a variable row without an Item, appear only when you click Execute. If the syntax check fails, for example because the server is unavailable, plintz doesn't underline the Formula. The editor continues to work, and Execute still moves the caret to an error.

Calculations

You want Write Makes
Arithmetic on Signals and Scalars $a + $b * 2, $a / 60 a Signal
A comparison $a > $threshold, $a >= 20 a Condition
Combine Conditions $high and $running, $high or $low, not $high, $high.intersect($running), $high.union($low), $high.subtract($running) a Condition
Drop Capsules that are too long $high.removeLongerThan(1h), $high.setMaximumDuration(1h) a Condition
Find when a Signal meets a criterion $a.valueSearch(1d, isGreaterThan(5 kW)), $a.valueSearch(1h, isLessThanOrEqualTo($b)), $a.valueSearch(1d, isBetween(2.5 kW, 7.5 kW), 4s), $a.valueSearch(1d, isGreaterThan(5 kW), 4s, true, isGreaterThan(15 kW), 3s, true) a Condition
Drop short Capsules, or ignore short gaps $high.removeShorterThan(5min), $high.merge(30s) a Condition
Leave out a Signal's invalid Samples $a.validValues() a Signal
Capsules from the calendar days(), hours(), days('Europe/Paris'), periods(10min, 1h), weeks('Monday'), months('Europe/Paris'), daysOfWeek('Monday,Friday') a Condition
One Sample for each Capsule $a.aggregate(average(), $high, startKey()) a Signal
The total duration or the count of a Condition's Capsules in each Capsule $running.aggregate(totalDuration('h'), $weeks, startKey()), $running.aggregate(count(), $weeks, middleKey()) a Signal
Shift a Signal in time $a.move(1h), $a.move(-3h) a Signal
Change a Signal's unit $a.convertUnits('ft'), $a.setUnits('ft'), $a.reduceUnits() a Signal
Change how a Signal is drawn $a.setMaxInterpolation(2h), $a.toLinear(), $a.toStep(2h), $a.toPiLinear(), $a.toDiscrete() a Signal

What each does:

  • Arithmetic between Signals has a Sample at every Sample of any input, each input interpolated there by its Interpolation Method. An input with a gap, where its Samples are further apart than its Maximum Interpolation, has no value there, so the result has none either. A value that isn't a finite number, such as a division by zero, is left out. With a Scalar, the Signal's own Samples are kept. For example, $a - $a.move(1h) compares a Signal with its value an hour before.
  • Units convert in +, - and comparisons. The right operand converts to the left operand's unit, so 1m + 1ft is 1.3048m. A unitless operand takes the other operand's unit. Incompatible units cause an error, such as °C is not compatible with kW. * and / create compound units such as kW·h.
  • Comparisons make a Condition with a Capsule for each stretch of time where the comparison holds. Each Signal is read with its Interpolation Method, so a Linear Signal's Capsule starts where its line crosses the other value. Where a Signal has no value, or a Sample's value isn't a valid number, the comparison doesn't hold, so a gap ends a Capsule. A comparison that holds only at one instant, such as == where a Linear Signal crosses a value, has no Capsule.
  • and, or and not require Conditions with a Maximum Duration. Comparisons produce Conditions without one. Set a Maximum Duration with removeLongerThan() or setMaximumDuration(), as in ($a > 20).removeLongerThan(1h) and ($b > 3).removeLongerThan(1h). Without a Maximum Duration, the Formula fails with "condition must have a maximum duration. Consider using removeLongerThan() to apply a maximum duration". A and B has a Capsule for each overlap between a Capsule of A and one of B. A or B has the Capsules of both, merged where they overlap or touch. not A holds in the gaps between A's Capsules. A and B has the smaller input Maximum Duration. A or B and not A have no Maximum Duration. Before using either result in and, or, not or aggregate(), use removeLongerThan() to give it a Maximum Duration.
  • intersect(a, b), union(a, b) and subtract(a, b) combine two Conditions. Each also works as a method, such as $a.intersect($b). intersect() is equivalent to and, and union() is equivalent to or. They produce the same Capsules and Maximum Durations as those operators. You can nest method calls to combine more than two Conditions, such as $a.intersect($b).intersect($c). subtract() returns the parts of $a that $b doesn't cover. A Capsule of $a that $b only touches stays whole. A Capsule fully covered by $b is dropped, and a Capsule that contains a Capsule of $b is split in two. Like $a and not $b, subtract() requires a Maximum Duration for both Conditions and uses $a's Maximum Duration for its result. Its Uncertainty matches and. The - operator applies to Signals and Scalars, not Conditions. Use subtract() to subtract Conditions. See Composite Condition for the Tool that writes these functions. Other functions such as touches() and join() aren't available.
  • removeLongerThan(d) and setMaximumDuration(d) drop the Capsules longer than the duration d and give the Condition that Maximum Duration.
  • valueSearch(d, criterion) creates a Capsule for each stretch of time when a Signal meets the criterion. It drops Capsules longer than d, a positive duration that becomes the Condition's Maximum Duration. Criteria include isLessThan(v), isLessThanOrEqualTo(v), isGreaterThan(v), isGreaterThanOrEqualTo(v), isEqualTo(v), isNotEqualTo(v), isBetween(lo, hi) and isNotBetween(lo, hi). Each value can be a number with a unit or another Signal. isBetween(lo, hi) holds from lo, inclusive, to hi, exclusive. isNotBetween(lo, hi) holds below lo or at and above hi. The function uses the same units, interpolation and gap behavior as a comparison. For example, $a.valueSearch(1d, isGreaterThan(5 kW)) is equivalent to $a > 5 kW, except that it drops Capsules longer than a day. A criterion alone doesn't create an Item. To compare Signals directly, write $a > 5, not isGreaterThan($a, 5). See Value Search for the Tool that writes this Formula.
  • valueSearch(d, criterion, t) creates a Capsule only when the criterion holds for at least t. For example, $a.valueSearch(1d, isBetween(2.5 kW, 7.5 kW), 4s) requires the Signal to stay in that range for at least four seconds. The Capsule covers the whole stretch, from when the criterion starts to hold until it stops. It doesn't start after t. A stretch exactly t long counts.
  • valueSearch(d, entry, t, ignoreGaps, exit, exitT, exitIgnoreGaps) starts a Capsule when the entry criterion begins a stretch that lasts at least t. It ends the Capsule when the exit criterion begins a stretch that lasts at least exitT. For example, $a.valueSearch(1d, isGreaterThan(5 kW), 4s, true, isGreaterThan(15 kW), 3s, true) requires the entry criterion to hold for four seconds and the exit criterion to hold for three seconds. The exit search begins at the Capsule's start, so a stretch that began earlier counts from the Capsule's start. A Capsule may have no length. The entry criterion doesn't need to keep holding until the exit. The next Capsule requires a new entry stretch that starts after the previous Capsule ends. The function drops Capsules longer than d. A Capsule without an exit is in progress until the data reaches the exit criterion. Durations can be time values or 0. The true or false value after each duration controls how the function handles invalid Samples for that criterion. true ignores invalid Samples, as validValues() does and the Tool's Ignore signal gaps option does.
  • removeShorterThan(d) drops the Capsules shorter than d, a positive duration, so a Capsule of exactly d stays. A Capsule still in progress stays. The Condition keeps its Maximum Duration, and needs none.
  • merge(d) merges the Capsules that overlap, touch or start within d, zero or more, after the end of another, so it ignores gaps shorter than d: ($a > 5).setMaximumDuration(1h).merge(1min) is one Capsule where the Signal dips below 5 for a minute or less. A gap of exactly d is ignored too. Like or, it requires a Maximum Duration and makes a Condition without one, so write .setMaximumDuration() after it to bound the merged Capsules.
  • validValues() leaves out Signal Samples whose values aren't valid numbers, so the Signal is interpolated across them.
  • periods(d, period, offset, timeZone) has a Capsule d long every period from offset. Only d is required. period defaults to d, so the Capsules are back to back. offset is a time such as '2025-01-01T06:00:00', and timeZone an IANA time zone such as 'America/New_York', UTC unless given. hours() and days() are back-to-back hours and days from midnight, in UTC or in the time zone you give. In a time zone, a duration in days is a calendar day, so a day is 23 or 25 hours long when the clocks change.
  • weeks(startDay, timeZone, shift), workWeeks(timeZone, shift), weekends(timeZone, shift), months(timeZone, shift), quarters(timeZone, shift) and years(timeZone, shift) create Capsules for weeks, work weeks, weekends, months, quarters or years in the given time zone, or UTC if none is given. weeks() requires startDay, a weekday name such as 'Monday'. A week lasts seven days, a work week lasts five days from Monday to Friday, and a weekend lasts two days from Saturday to Sunday. Work weeks and weekends have gaps between them. A month, quarter or year follows the calendar. A quarter lasts three months, starting in January, April, July or October. The optional shift follows the time zone and moves each Capsule's start and end by a duration such as 6h or -1d. Capsules start at the same local time across clock changes. A week or month that includes a clock change is an hour shorter or longer.
  • daysOfWeek(days, timeZone, shift), monthsOfYear(months, timeZone, shift) and quartersOfYear(quarters, timeZone, shift) have a day-, month- or quarter-long Capsule only for the ones listed: a string of names separated by commas, 'Monday,Friday', 'January,March' or 'Q1,Q3', where the names are English, in full and with a capital. The time zone and the shift are optional, as above. Neighbors listed together, such as Saturday and Sunday, have a Capsule each.
  • The calendar functions give the Condition the Maximum Duration its Capsules need, so it can be used in and, or, not and aggregate() without setting one.
  • aggregate(statistic, condition, key) has one Sample for each Capsule of condition, the bounding Condition, that has data. The input is a Signal or a Condition, and the statistic must fit it. For a Signal, the statistics are average(), minValue(), maxValue(), sum() and count(). For a Condition, they are totalDuration(unit) and count(). The keys, which place the Sample, are startKey(), middleKey() and endKey(). The bounding Condition must have a Maximum Duration, and so must an input Condition. A Capsule with no data in it has no Sample. average() is time-weighted. The result is drawn PILinear, with the Signal's unit, except count(), which is unitless. To draw it another way, call one of the interpolation functions below on it, as the Signal from Condition Tool does.
  • totalDuration(unit), for a Condition input of aggregate(), is the sum of the durations of its Capsules inside each bounding Capsule, converted to the time unit, a string such as 's', 'min', 'h' or 'hours', which is the unit of the result: $running.aggregate(totalDuration('h'), $weeks, startKey()) is the hours a Condition holds in each week. A Capsule only partly inside a bounding Capsule counts for the part inside it. A unit that isn't a time unit fails with Invalid unit '…', or with a message that totalDuration() takes a time unit. unit is required. count() of a Condition is the number of its Capsules that overlap each bounding Capsule, so a Capsule across two bounding Capsules counts in both. A bounding Capsule that no Capsule of the input overlaps has no Sample, not a zero, as for a Signal. totalDuration() of a Signal or average(), minValue(), maxValue() or sum() of a Condition fails with a message that the statistic is of the other.
  • move(d) shifts each Sample later by d, or earlier if it is negative. The Signal keeps its Interpolation Method, Maximum Interpolation and unit.
  • convertUnits('ft') converts values to a unit. setUnits('ft') assigns a unit without changing values, and setUnits('') removes the unit. reduceUnits() reduces a compound unit to the first unit in its dimension. An unknown unit fails with Invalid unit '…'. plintz accepts the unit names and aliases available in the Formula editor.
  • setMaxInterpolation(d), toLinear(), toStep(), toPiLinear() and toDiscrete() draw a Signal with another Maximum Interpolation or Interpolation Method in this Analysis only. toLinear(d), toStep(d) and toPiLinear(d) also set the Maximum Interpolation to d. d is a positive duration. To change a Stored Signal for every Analysis, an Administrator uses an override: see Item properties.

A Calculated Item inherits the uncertainty of its most uncertain input, so it draws dashed, or hollow, where its inputs are uncertain.

A Calculated Scalar has no Samples. It is a single value, such as a threshold. One that isn't a finite number, such as 1/0, has no value and draws nothing.

Any other function call fails with "Function 'name' was not found or was archived". A call with unsupported arguments fails with "No variant of function 'name' consumes the parameters (…)". Only the functions in the table above are available, and they are the ones the Formula box suggests.

Creating a Calculated Item

  1. Open the Tools tab of the tool panel. It lists the Tools, and the Formula Tool, the Formula editor, is selected.
  2. Type a Name.
  3. For each $variable in the Formula, click Add variable. Type its name, such as $a, and then choose its Item in the list under it. The list offers the Items in the Details Pane. Type in Search Items to find another Stored Signal or a Calculated Item of the Analysis by name instead. Once chosen, its name shows on a button: click it to choose another Item. × removes the variable.
  4. Type the Formula in the Formula box, such as $a + $b * 2, accepting the function suggestions it offers. See The Formula box.
  5. Click Execute, or press Ctrl+S (Cmd+S on a Mac).

plintz creates the Calculated Item in the Analysis, adds it to the Details Pane and clears the editor for the next one. A Calculated Signal then trends like a Stored Signal. A Calculated Condition shows its Capsules, and a Calculated Scalar is a horizontal line in a Lane of its own. See Signals and the Trend.

If the Formula has an error, plintz creates nothing. It shows the error under the box and moves the caret to it. Fix the error and click Execute again. If a variable has no Item, plintz asks you to "Choose an Item for" it.

For example, to see when a Signal named Temperature is above a threshold of 80:

  1. Create a Calculated Scalar named Limit with no variables and the Formula 80.
  2. Create a Calculated Condition named Too hot with $t bound to Temperature and $limit bound to Limit, and the Formula $t > $limit.

You can use a Calculated Item as an input to another Formula. To change a threshold in one place, create a Calculated Scalar and edit its Formula.

Editing a Calculated Item

Click ƒ on the Item's row in the Details Pane. The Tools tab opens as Edit Formula, with the Item's name, variables and Formula. You can change the variables and the Formula, but not the name. Click Execute to save, or Cancel. The Calculated Items that use it read the change the next time they are computed, and the trend updates.

plintz rejects an edit, and keeps the old Formula, when:

  • the Formula has an error;
  • the Formula would make another kind of Calculated Item. A Calculated Signal stays a Signal, a Calculated Condition a Condition and a Calculated Scalar a Scalar;
  • the Calculated Item would depend on itself, directly or through other Calculated Items. The error reads "Unable to set Formula because a circular dependency was found…";
  • a Calculated Item that depends on it would fail with the new Formula. The error names it.

To see a Calculated Item's Formula without editing it, use ⓘ: see Item properties. The same dialog lists its Item Usages, the Calculated Items that would be affected by an edit.

If you have Read access, the Formula editor and ƒ aren't shown.