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
Beam design
Loads
The beam carries a uniformly distributed load from the floor above. Self-weight is included in w.
# 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 |
|---|---|---|
|
Iy |
|
|
Npl,Rd |
each further |
|
σmax, Γ |
a Greek letter’s name becomes the letter ( |
|
ΔH, σR |
Unicode Greek letters work too |
|
ΔH, γM0, α2 |
a Greek name followed by an uppercase letter or a digit |
|
x̂, x̄, V̇, ẍ |
accents over the part before them |
|
x̄, ŷ2, σ̂ |
Unicode letters with a combining accent (hat, bar, dot, double dot) |
|
f′c, M* |
a prime, a superscript |
|
Annual Rate |
|
|
a_b |
|
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
$$\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
$$\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
$$\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 |
|---|---|
|
subscripts in braces, separated by commas; |
|
superscripts, the same way |
|
a subscript with its own subscript |
|
spaces separate words; Greek names or Unicode letters |
|
an accent over the base |
|
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"]
$$\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
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
$$\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
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
$$\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
$$\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
$$\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
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
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
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
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
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
a = 2
b = a * scale = 2 * 1000 = 2000
Next: Comment commands lists every command.