Writing calculations#

A handcalcs calculation is ordinary Python plus comments. This page covers the pieces you use to turn a block of code into a readable calculation. The examples use the Markdown renderer (the Jupyter default); every built-in renderer supports the same features.

Headings and prose#

A comment starting with ## is a heading. ## is the top level, ### the next, and so on. Other comment lines are prose; consecutive comment lines join into one paragraph.

## Beam design
### Loads
# The beam carries a uniformly distributed load from the floor above.
# Self-weight is included in w.
w = 12.5  # kN/m
Output (MarkdownRenderer)

Beam design

Loads

The beam carries a uniformly distributed load from the floor above. Self-weight is included in w.

\[\begin{aligned} w &= 12.5 \quad\text{(kN/m)} \end{aligned}\]
# Beam design

## Loads

The beam carries a uniformly distributed load from the floor above. Self-weight is included in w.

$$\begin{aligned} w &= 12.5 \quad\text{(kN/m)} \end{aligned}$$

Naming variables#

A variable’s name is typeset as a math symbol, so name variables the way you’d write them on paper:

You write

Shown as

Rule

I_y

Iy

_ starts a subscript

N_pl_Rd

Npl,Rd

each further _ adds a subscript, separated by a comma

sigma_max, Gamma

σmax, Γ

a Greek letter’s name becomes the letter (lamb or lambda_ for λ)

ΔH, σ_R

ΔH, σR

Unicode Greek letters work too

DeltaH, gammaM0, alpha2

ΔH, γM0, α2

a Greek name followed by an uppercase letter or a digit

x_hat, x_bar, V_dot, x_ddot

x̂, x̄, V̇, ẍ

accents over the part before them

x̄, ŷ_2, σ̂

x̄, ŷ2, σ̂

Unicode letters with a combining accent (hat, bar, dot, double dot)

f_prime_c, M_star

f′c, M*

a prime, a superscript *

Annual__Rate

Annual Rate

__ is a space

a___b

a_b

___ is a literal underscore

A lowercase continuation never splits off a Greek letter: metal, beta and Pin stay as they are. A subscript of more than one letter is set upright.

N_pl_Rd = 1250  # kN
gammaM0 = 1.0
DeltaT = 35  # K
f_prime_c = 30  # MPa
Output (MarkdownRenderer)
\[\begin{aligned} N_{\mathrm{pl,Rd}} &= 1250 \quad\text{(kN)} \\ \gamma \mathrm{M0} &= 1 \\ \Delta T &= 35 \quad\text{(K)} \\ f'_{c} &= 30 \quad\text{(MPa)} \end{aligned}\]
$$\begin{aligned} N_{\mathrm{pl,Rd}} &= 1250 \quad\text{(kN)} \\ \gamma \mathrm{M0} &= 1 \\ \Delta T &= 35 \quad\text{(K)} \\ f'_{c} &= 30 \quad\text{(MPa)} \end{aligned}$$

Nested subscripts#

handcalcs v1 nested each further subscript (x_a_b → xab). To get that back, set the subscripts option to "nested", either for the rest of the calculation with a comment command or for the session with set_option:

# hc: subscripts = "nested"
x_a_b = 2
Output (MarkdownRenderer)
\[\begin{aligned} x_{a_{b}} &= 2 \end{aligned}\]
$$\begin{aligned} x_{a_{b}} &= 2 \end{aligned}$$
import handcalcs
handcalcs.set_option("subscripts", "nested")

Display symbols#

When a name can’t express the symbol you want, map the variable to a display spec with the symbols option. The spec uses a small TeX-like syntax that every renderer understands:

# hc: symbols = {"N_plRd": "N_{pl,Rd}", "V_flow": "dot(V)", "dH": "ΔH^{*}"}
N_plRd = 1250  # kN
V_flow = 2.5  # m³/s
dH = 4.2  # kJ
Output (MarkdownRenderer)
\[\begin{aligned} N_{\mathrm{pl,Rd}} &= 1250 \quad\text{(kN)} \\ \dot{V} &= 2.5 \quad\text{(m³/s)} \\ \Delta H^{*} &= 4.2 \quad\text{(kJ)} \end{aligned}\]
$$\begin{aligned} N_{\mathrm{pl,Rd}} &= 1250 \quad\text{(kN)} \\ \dot{V} &= 2.5 \quad\text{(m³/s)} \\ \Delta H^{*} &= 4.2 \quad\text{(kJ)} \end{aligned}$$

Spec

Meaning

N_{pl,Rd}

subscripts in braces, separated by commas; N_Rd takes the run after _

M^{*}, x^2

superscripts, the same way

x_{a_{b}}

a subscript with its own subscript

Delta H, ΔH, sigma_{c}

spaces separate words; Greek names or Unicode letters

hat(x), bar(x)_{m}, dot(V), ddot(x)

an accent over the base

f'_c

a prime

Set symbols for the whole session with handcalcs.set_option("symbols", {...}). A spec that can’t be parsed raises an error that says where.

Indexing#

An index or key lookup shows the element it looks up in the substitution. A name indexed by integers, short strings or other names is set with the index as a subscript, the way a hand calculation numbers its values:

M_Ed = [90, 120, 140]  # kNm, load cases
loads = {"dead": 5.0, "live": 3.0}  # kN/m
i = 2
eta = M_Ed[i] / 150
w = 1.25 * loads["dead"] + 1.5 * loads["live"]
Output (MarkdownRenderer)
\[\begin{aligned} M_{\mathrm{Ed}} &= [90, 120, 140] \quad\text{(kNm, load cases)} \\ \mathrm{loads} &= \{\text{"dead"}\colon 5, \text{"live"}\colon 3\} \quad\text{(kN/m)} \\ i &= 2 \\ \eta &= \frac{M_{\mathrm{Ed},i}}{150} = \frac{140}{150} = 0.93333 \\ w &= 1.25 \cdot \mathrm{loads}_{\mathrm{dead}} + 1.5 \cdot \mathrm{loads}_{\mathrm{live}} = 1.25 \cdot 5 + 1.5 \cdot 3 = 10.75 \end{aligned}\]
$$\begin{aligned} M_{\mathrm{Ed}} &= [90, 120, 140] \quad\text{(kNm, load cases)} \\ \mathrm{loads} &= \{\text{"dead"}\colon 5, \text{"live"}\colon 3\} \quad\text{(kN/m)} \\ i &= 2 \\ \eta &= \frac{M_{\mathrm{Ed},i}}{150} = \frac{140}{150} = 0.93333 \\ w &= 1.25 \cdot \mathrm{loads}_{\mathrm{dead}} + 1.5 \cdot \mathrm{loads}_{\mathrm{live}} = 1.25 \cdot 5 + 1.5 \cdot 3 = 10.75 \end{aligned}$$

Each index follows the name’s own subscripts (M_Ed[i] → MEd,i), and A[i, j] gives Ai,j. Other lookups, such as slices (xs[1:3]) and indexes that are expressions (xs[i + 1]), are shown in brackets. The ASCII renderer always uses brackets.

handcalcs finds the element without running any code again: the index must be a literal, a name or an attribute (or a slice or tuple of those), and the container a list, tuple, string, dict, numpy array, pandas object or units-library quantity. Otherwise the substitution shows the lookup with its index substituted (xs[2 + 1]), followed by the result. To show every lookup in brackets, see the recipe.

Several targets#

Tuple unpacking and chained assignment are shown as written, with their values:

x = [3.0, 4.0]
a, b = x
c = d = a * b
Output (ASCIIRenderer)
   x = [3, 4]
a, b = x = [3, 4] = 3, 4
   c = d = a * b = 3 * 4 = 12

Inline comments#

A comment at the end of a line is shown next to that line. Use it for units, sources or short notes:

L = 6000  # Span, in mm (drawing S-201)
E = 200000  # MPa
Output (MarkdownRenderer)
\[\begin{aligned} L &= 6000 \quad\text{(Span, in mm (drawing S-201))} \\ E &= 200000 \quad\text{(MPa)} \end{aligned}\]
$$\begin{aligned} L &= 6000 \quad\text{(Span, in mm (drawing S-201))} \\ E &= 200000 \quad\text{(MPa)} \end{aligned}$$

Values inside comments#

Comments (headings, prose and inline comments) are Jinja templates. Put a variable name in {{ }} to insert its value, and use Jinja filters to format it:

F = 12.3456
# The applied force is {{ F|round(1) }} kN.
check = F < 15  # F = {{ "%.2f"|format(F) }} kN, below the 15 kN limit
Output (ASCIIRenderer)
    F = 12.346
The applied force is 12.3 kN.
check = F<15 = 12.346<15 = True (F = 12.35 kN, below the 15 kN limit)

Note

Values in {{ }} are inserted as they are, without the number format. Use a Jinja filter such as round or format to control them.

Parameter grids#

Long lists of inputs take up a lot of vertical space. Put several assignments on one line, separated by ;, and they are laid out as a grid, three per row:

b = 200; d = 450; c_c = 40; f_c = 30; f_y = 400
Output (MarkdownRenderer)
\[\begin{aligned} b &= 200 \; &d &= 450 \; &c_{c} &= 40 \; \\ f_{c} &= 30 \; &f_{y} &= 400 \; \end{aligned}\]
$$\begin{aligned} b &= 200 \; &d &= 450 \; &c_{c} &= 40 \; \\ f_{c} &= 30 \; &f_{y} &= 400 \; \end{aligned}$$

To keep inline comments, use a # hc: -h command instead. The assignments that follow go into the grid, each with its comment, until a blank line, a comment line or a statement that is not an assignment. Each cell shows only target = result:

# hc: -h
b = 200  # mm
d = 450  # mm
f_c = 30  # MPa
f_y = 400  # MPa
Output (MarkdownRenderer)
\[\begin{aligned} b &= 200 \; \text{(mm)} \qquad &d &= 450 \; \text{(mm)} \qquad &f_{c} &= 30 \; \text{(MPa)} \\ f_{y} &= 400 \; \text{(MPa)} \end{aligned}\]
$$\begin{aligned} b &= 200 \; \text{(mm)} \qquad &d &= 450 \; \text{(mm)} \qquad &f_{c} &= 30 \; \text{(MPa)} \\ f_{y} &= 400 \; \text{(MPa)} \end{aligned}$$

Change the number of columns with # hc: -c N:

# hc: -c 2
b = 200; d = 450; f_c = 30; f_y = 400
Output (MarkdownRenderer)
\[\begin{aligned} b &= 200 \; &d &= 450 \; \\ f_{c} &= 30 \; &f_{y} &= 400 \; \end{aligned}\]
$$\begin{aligned} b &= 200 \; &d &= 450 \; \\ f_{c} &= 30 \; &f_{y} &= 400 \; \end{aligned}$$

Note

A standalone command such as # hc: -c 2 stays in effect for the rest of the calculation. See scope.

Long equations#

A long expression is broken at the same places as your source code. Continue the line with a \, before or after an operator, and each continued piece starts a new row, indented under the expression above, in both the symbolic and the substituted step:

a = 1.5
b = 2.25
c = 3.125
total = a * b \
    + b * c \
    + a * c
Output (ASCIIRenderer)
    a = 1.5
    b = 2.25
    c = 3.125
total = a * b
          + b * c
          + a * c
      = 1.5 * 2.25
          + 2.25 * 3.125
          + 1.5 * 3.125
      = 15.094

Only breaks between the terms of the expression itself are kept. A break inside brackets or a fraction can’t be, so that calculation is shown on one line. Brackets around the whole expression show only the result, as for any bracketed expression. To break between the steps of a calculation (symbolic, substituted, result) instead, use # hc: -m.

Expressions#

A line that is only an expression, such as 2 * a, shows its result:

a = 3
2 * a + 1
Output (ASCIIRenderer)
        a = 3
2 * a + 1 = 2 * 3 + 1 = 7

An expression that calls a function or has a side effect isn’t evaluated again, so it is shown without a result.

Conditionals#

if / elif / else blocks show the condition that was true, first symbolically and then with values, followed by the lines in that branch. Branches that did not run are not shown, so the output reads as the reasoning that applies to these inputs:

b_t = 8.5
F_y = 350
if b_t <= 145 / sqrt(F_y):
    section_class = 1
elif b_t <= 170 / sqrt(F_y):
    section_class = 2
else:
    section_class = 3
Output (MarkdownRenderer)
\[\begin{aligned} b_{t} &= 8.5 \\ F_{y} &= 350 \end{aligned}\]

Since \(\bigl(b_{t} \le \frac{170}{\sqrt{F_{y}}}\bigr) \rightarrow \bigl(8.5 \le \frac{170}{\sqrt{350}}\bigr)\) is True:

\[\begin{aligned} \mathrm{section}_{\mathrm{class}} &= 2 \end{aligned}\]
$$\begin{aligned} b_{t} &= 8.5 \\ F_{y} &= 350 \end{aligned}$$

Since $\bigl(b_{t} \le \frac{170}{\sqrt{F_{y}}}\bigr) \rightarrow \bigl(8.5 \le \frac{170}{\sqrt{350}}\bigr)$ is True:

> $$\begin{aligned} \mathrm{section}_{\mathrm{class}} &= 2 \end{aligned}$$

Blocks can be nested; each level is indented.

Loops#

A for loop is shown as a line describing the iteration, followed by its body. The body is shown once, with the values of the last pass through the loop:

total = 0
for load in (1.2, 3.4, 5.6):
    factored = 1.25 * load
Output (ASCIIRenderer)
total = 0
Iterating over each load in (1.2, 3.4, 5.6):
    factored = 1.25 * load = 1.25 * 5.6 = 7

To show a calculation for every case in a table of inputs, render one representative case, or call a renderer once per case from a script (see the object-oriented interface).

Spacing#

# hc: -b inserts a blank line (vertical space) at that point:

a = 2
# hc: -b
b = a * 3
Output (ASCIIRenderer)
a = 2

b = a * 3 = 2 * 3 = 6

Hiding a line#

Some lines are needed for the calculation but are not interesting to the reader, such as setting up a unit system. Add # hc: -i to run the line but leave it out of the output:

a = 2
scale = 1000  # hc: -i
b = a * scale
Output (ASCIIRenderer)
a = 2
b = a * scale = 2 * 1000 = 2000

Next: Comment commands lists every command.