Arithmetic and units

Expressions in comparisons, ?y = expr to compute a value, functions that run in any direction, and how units multiply, divide and get their names.

A comparison can compute: ?w * 2 > ?limit, or ?y = expr, which gives ?y the expression’s value (#5). These use resistors with a resistance, a current and the rail they sit on:

res(?p, ?r), cur(?p, ?i), ?v = ?i * ?r => ?p, ?v
?p?v
R15V
R25V
Facts: res("R1", 1000ohm); res("R2", 20ohm) cur("R1", 0.005A); cur("R2", 0.25A) rail("R1", 5V); rail("R2", 5V)

Functions run in any direction

Arithmetic is written in functions the standard library registers: num.add, num.sub, num.mul, num.div and num.neg. Each is a relation of its operands and its result, so ?v = ?i * ?r is num.mul(?i, ?r, ?v), and it runs from whichever two it has. Given the voltage and the current, the same expression finds the resistance, which is Ohm’s law used in either direction:

rail(?p, ?v), cur(?p, ?i), ?v = ?i * ?r => ?p, ?r
?p?r
R11000ohm
R220ohm
Facts: res("R1", 1000ohm); res("R2", 20ohm) cur("R1", 0.005A); cur("R2", 0.25A) rail("R1", 5V); rail("R2", 5V)

Where the expression is written doesn’t matter: a step waits until what it needs is bound, as a comparison does. Expressions nest with the usual precedence and parentheses:

res(?p, ?r), ?p = "R1", ?both = 1 / (1 / ?r + 1 / 1kohm) => ?both
?both
500ohm
Facts: res("R1", 1000ohm); res("R2", 20ohm) cur("R1", 0.005A); cur("R2", 0.25A) rail("R1", 5V); rail("R2", 5V)

and can call a function by name, in which case the function’s last argument is its result, so num.div(?r, 2) is ?r / 2. A host’s own functions work the same way, with name(args) calling the relation name with one more argument for the result:

res(?p, ?r), ?half = num.div(?r, 2) + 1ohm => ?p, ?half
?p?half
R1501ohm
R211ohm
Facts: res("R1", 1000ohm); res("R2", 20ohm) cur("R1", 0.005A); cur("R2", 0.25A) rail("R1", 5V); rail("R2", 5V)

A division by zero, or a value that isn’t a number, has no result, so that row has none rather than the query failing.

In the answer and in a rule’s head

An expression can be an answer column too, named as it’s written without its ?s, so ?i * ?r answers a column i * r (#206):

res(?p, ?r), cur(?p, ?i) => ?p, ?i * ?r, ?i * ?i * ?r
?p?i * r(?i * i) * )
R15V0.025W
R25V1.25W
Facts: res("R1", 1000ohm); res("R2", 20ohm) cur("R1", 0.005A); cur("R2", 0.25A)

and a rule’s head can compute an argument, as ?y = expr in its body would:

dissipates(?p, ?i * ?i * ?r) :- res(?p, ?r), cur(?p, ?i);
dissipates(?p, ?w), ?w > 1W => ?p, ?w
?p?w
R21.25W
Facts: res("R1", 1000ohm); res("R2", 20ohm) cur("R1", 0.005A); cur("R2", 0.25A)

A column like count(?n) stays an aggregate, so a host’s own function alone in a column is called through an expression around it, or by its dotted name.

Functions written as rules

A rule can be a function too. power below has no relation to read: its body computes ?p from ?v and ?i, which it takes from its caller (#208), so an expression calls it as power(?v, ?i) with the result filling its last argument:

power(?v, ?i, ?p) :- ?p = ?v * ?i;
rail(?x, ?v), cur(?x, ?i), ?w = power(?v, ?i) => ?x, ?w
?x?w
R10.025W
R21.25W
Facts: res("R1", 1000ohm); res("R2", 20ohm) cur("R1", 0.005A); cur("R2", 0.25A) rail("R1", 5V); rail("R2", 5V)

The call is written into its caller before the query runs, so power runs in any direction num.mul does, and called as a relation, power(?v, ?i, ?w), it means the same. Its signature marks as inputs the head’s earliest arguments its body can’t compute, here ?v and ?i, and a call that leaves num.mul too little to run from is refused. Like any rule taking arguments from its caller, a function has one rule and doesn’t reach itself.

Units

A number can carry a unit, and a query can write one after a number: 1kohm, 3.3V, 0.5A. A prefix is folded in, so 1kohm is 1000 ohm and compares with a source’s ohms:

res(?p, ?r), ?r < 1kohm => ?p
?p
R2
Facts: res("R1", 1000ohm); res("R2", 20ohm) cur("R1", 0.005A); cur("R2", 0.25A) rail("R1", 5V); rail("R2", 5V)

A rule’s head folds its numbers the same way, so standard(1kohm) holds 1000 ohm and joins with a source’s ohms (#224):

standard(1kohm) :- res(_, _);
res(?p, ?r), standard(?r) => ?p
?p
R1
Facts: res("R1", 1000ohm); res("R2", 20ohm)

A product’s unit multiplies and a quotient’s divides, and the result takes a unit’s name when the vocabulary has one: amps times ohms is volts, above. Adding or comparing needs one unit, and a bare number takes the other side’s. Volts plus amps has no answer:

rail(?p, ?v), cur(?p, ?i), ?x = ?v + ?i => ?p

No rows.

Facts: res("R1", 1000ohm); res("R2", 20ohm) cur("R1", 0.005A); cur("R2", 0.25A) rail("R1", 5V); rail("R2", 5V)

A unit nobody named is still a unit, written as what it is:

res(?p, ?r), ?p = "R1", ?x = ?r / 2s => ?x
?x
500ohm/s
Facts: res("R1", 1000ohm); res("R2", 20ohm) cur("R1", 0.005A); cur("R2", 0.25A) rail("R1", 5V); rail("R2", 5V)

The standard library names the SI prefixes, the seven SI base units (kg, m, s, A, K, mol, cd), the gram as a thousandth of a kg, and an electrical set (A, V, ohm and Ω, W, J, C, F, H, Hz) defined over the base units, and a host adds its own with Vocabulary.AddUnit: a base unit, a prefix, or a name for a product, such as speed for m/s. A unit’s first name is how answers write it. Since V is kg·m²/(A·s³), a unit a host names over kg, m and s meets the electrical ones: with N as kg*m/s^2, N*m is J. An unnamed unit is still written in volts where it can be (V/m, not kg*m/(A*s^3)), since the library writes kg through V (Vocabulary.WriteBaseThrough), and mass is in kg, so 5g is 0.005kg. A literal’s unit is letters only, so a compound one is written as arithmetic, 3m / 1s.

A prefix moves the decimal point, so 3300mV is the 3.3V a reading in volts holds, not the 3.3000000000000003 that multiplying by 0.001 gives.

To jaala a unit is a tag: two numbers order and aggregate together when their units are the same text, and what mV means is the vocabulary’s. So a source’s values are read in the unit it serves them in, and a host keeping lengths in millimetres writes a bare threshold in millimetres (#303):

track_width(?net, ?w), ?w < 0.1 => ?net, ?w
?net?w
SIG0.08mm
Facts: track_width("SIG", 0.08mm); track_width("PWR", 0.5mm)

A host whose source mixes scales for one quantity asks jaala to fold them as it reads them, with Vocabulary.FoldSourceUnits (#205). Then 1000mV from a source is read as 1V, so it joins, groups, dedups and orders with the source’s volts. A number whose text only spelled it reads as the folded number; text saying more, such as 5 mV, is kept as the data wrote it.

reading(?a, ?v), reading(?b, ?v), ?a < ?b => ?a, ?b
?a?b
TP1TP2
TP3TP4
Facts: reading("TP1", 1000mV); reading("TP2", 1V); reading("TP3", 3300mV); reading("TP4", 3.3V) read with their units folded (FoldSourceUnits)

A store that looks values up, evaluates a pushed conjunction or filters for such a host holds what it holds, but is asked by the folded value: a lookup for 1V has to find the 1000mV it serves.

A unit’s scale needn’t be a power of ten. A host names durations over seconds, or a binary prefix, with an exact factor, and a query’s literals read them:

v.AddUnit("min", "60*s")
v.AddUnit("h", "60*min")
v.AddUnit("B", "")
v.AddUnit("Ki", "1024") // a prefix: 2KiB is 2048 B

With those, build(?b, ?d), ?d > 1min, ?d < 1h reads 1min as 60 s and 1h as 3600 s, and a source serving 1.5 in min reads as 90 s once the vocabulary folds a source’s units.

What arithmetic can’t do yet

A rule can’t compute a value and feed it back into its own recursion, since n(?m) :- n(?n), ?m = ?n + 1 never stops:

count_up(?n) :- res(_, ?n);
count_up(?m) :- count_up(?n), ?m = ?n + 1;
count_up(?n) => ?n

query: rule "count_up" computes with arithmetic and reads count_up, which reaches it again, so it might never finish

Facts: res("R1", 1000ohm); res("R2", 20ohm) cur("R1", 0.005A); cur("R2", 0.25A) rail("R1", 5V); rail("R2", 5V)