Checks and decisions#

Design calculations end in a decision: does the member pass? Recipes for showing the check, the governing case, and a verdict in words.

Mark a check as pass or fail#

Asked in #251 and #195.

--check at the end of a comparison replaces its True/False result with a green ✓ or a red ✗:

M_Ed = 120        # kN m
M_Rd = 150        # kN m
eta = M_Ed / M_Rd
ok = eta <= 1.0   # hc: --check
Output (MarkdownRenderer)
\[\begin{aligned} M_{\mathrm{Ed}} &= 120 \quad\text{(kN m)} \\ M_{\mathrm{Rd}} &= 150 \quad\text{(kN m)} \\ \eta &= \frac{M_{\mathrm{Ed}}}{M_{\mathrm{Rd}}} = \frac{120}{150} = 0.8 \\ \mathrm{ok} &= \eta \le 1.0 = 0.8 \le 1.0\quad{\color{green}\checkmark} \end{aligned}\]
$$\begin{aligned} M_{\mathrm{Ed}} &= 120 \quad\text{(kN m)} \\ M_{\mathrm{Rd}} &= 150 \quad\text{(kN m)} \\ \eta &= \frac{M_{\mathrm{Ed}}}{M_{\mathrm{Rd}}} = \frac{120}{150} = 0.8 \\ \mathrm{ok} &= \eta \le 1.0 = 0.8 \le 1.0\quad{\color{green}\checkmark} \end{aligned}$$

A bare comparison works too, and a failing check is marked as clearly:

V_Ed = 210        # kN
V_Rd = 180        # kN
V_Ed <= V_Rd      # hc: --check
Output (MarkdownRenderer)
\[\begin{aligned} V_{\mathrm{Ed}} &= 210 \quad\text{(kN)} \\ V_{\mathrm{Rd}} &= 180 \quad\text{(kN)} \\ V_{\mathrm{Ed}} \le V_{\mathrm{Rd}} &= 210 \le 180\quad{\color{red}\boldsymbol{\times}} \end{aligned}\]
$$\begin{aligned} V_{\mathrm{Ed}} &= 210 \quad\text{(kN)} \\ V_{\mathrm{Rd}} &= 180 \quad\text{(kN)} \\ V_{\mathrm{Ed}} \le V_{\mathrm{Rd}} &= 210 \le 180\quad{\color{red}\boldsymbol{\times}} \end{aligned}$$

On its own line, # hc: --check marks every boolean result after it, until # hc: check = False. Lines that aren’t comparisons are unaffected:

# hc: --check
f_y = 355
f_u = 510
f_u / f_y >= 1.1
f_y <= 460
Output (HTMLRenderer)
fy = 355
fu = 510
fu / fy ≥ 1.1 = 510 / 355 ≥ 1.1 ✓
fy ≤ 460 = 355 ≤ 460 ✓
<div class="handcalcs"><style>.handcalcs{--hc-accent:#0B5F66;--hc-accent-dark:#91CFD4;--hc-guide-1:#85B8BD;--hc-guide-2:#D5B386;--hc-guide-3:#A8B0C7;}:is([data-jp-theme-light="false"],.vscode-dark,.vscode-high-contrast,[data-theme="dark"]) .handcalcs{--hc-accent-text:var(--hc-accent-dark);--hc-tint:color-mix(in srgb,var(--hc-accent) 30%,transparent);--hc-highlight:color-mix(in srgb,var(--hc-accent) 38%,transparent);}.handcalcs{--hc-gap:0.35em;--hc-indent:1.5em;--hc-param-gap:2em;--hc-eq-gap:0.3em;--hc-accent-text:var(--hc-accent);--hc-tint:color-mix(in srgb,var(--hc-accent) 9%,transparent);--hc-highlight:color-mix(in srgb,var(--hc-accent) 12%,transparent);--hc-muted:color-mix(in srgb,currentColor 60%,transparent);--hc-chip:color-mix(in srgb,currentColor 6%,transparent);display:flex;flex-direction:column;gap:var(--hc-gap);line-height:1.6;font-variant-numeric:tabular-nums;}.handcalcs :where(.hc-block,.hc-body){display:flex;flex-direction:column;gap:var(--hc-gap);}.handcalcs :where(.hc-body){margin-inline-start:0.55em;padding-inline-start:calc(var(--hc-indent) - 0.55em);border-inline-start:2px solid var(--hc-guide);--hc-guide:var(--hc-guide-1);}.handcalcs :where(.hc-body .hc-body){--hc-guide:var(--hc-guide-2);}.handcalcs :where(.hc-body .hc-body .hc-body){--hc-guide:var(--hc-guide-3);}.handcalcs :where(.hc-body .hc-body .hc-body .hc-body){--hc-guide:var(--hc-guide-1);}.handcalcs :where(.hc-body .hc-body .hc-body .hc-body .hc-body){--hc-guide:var(--hc-guide-2);}.handcalcs :where(.hc-line,.hc-header,.hc-comment){margin-block:0;}.handcalcs :where(.hc-header){background:var(--hc-tint);border-radius:4px;padding:0.1em 0.5em;}.handcalcs :where(.hc-header)::before{content:"\25B8";color:var(--hc-accent-text);margin-inline-end:0.4em;}.handcalcs :where(.hc-kw){color:var(--hc-accent-text);}.handcalcs :where(.hc-result){color:var(--hc-accent-text);background:var(--hc-highlight);font-weight:600;padding:0 0.25em;border-radius:3px;-webkit-box-decoration-break:clone;box-decoration-break:clone;}.handcalcs :where(.hc-note){color:var(--hc-muted);}.handcalcs :where(.hc-check){font-weight:700;}.handcalcs :where(.hc-pass){color:#1A7F37;}.handcalcs :where(.hc-fail){color:#CF222E;}.handcalcs :where(.hc-import){align-self:start;font-family:var(--jp-code-font-family,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);font-size:0.85em;color:var(--hc-muted);background:var(--hc-chip);padding:0.05em 0.5em;border-radius:3px;}.handcalcs :where(.hc-comment,.hc-note) :where(code){font-size:0.9em;background:var(--hc-chip);padding:0 0.25em;border-radius:3px;}.handcalcs table.hc-params{margin:0;border:0;border-collapse:separate;border-spacing:0;table-layout:auto;width:auto;align-self:center;font-size:inherit;color:inherit;font-variant-numeric:tabular-nums;background:var(--hc-tint);border-radius:4px;padding:0.1em 0.6em;}.handcalcs table.hc-params.hc-multiline{align-self:start;background:none;border-radius:0;padding:0 0 0 0.5em;border-inline-start:2px solid var(--hc-accent-text);}.handcalcs table.hc-params tr:nth-child(n){background:none;}.handcalcs table.hc-params td{padding:0;border:0;vertical-align:baseline;line-height:inherit;white-space:normal;max-width:none;}.handcalcs table.hc-params td.hc-param-id{text-align:right;padding-inline-end:var(--hc-eq-gap);}.handcalcs table.hc-params td.hc-param-eq{text-align:center;}.handcalcs table.hc-params td.hc-param-val{text-align:left;padding-inline-start:var(--hc-eq-gap);}.handcalcs table.hc-params td.hc-param-gap{width:var(--hc-param-gap);}.handcalcs table.hc-params td.hc-param-comment{text-align:left;padding-inline-start:var(--hc-eq-gap);}.handcalcs :where(.hc-pre){white-space:pre;}.handcalcs :where(.hc-matrix){display:inline-table;vertical-align:middle;border-inline:1px solid currentColor;border-radius:0.2em;padding:0 0.15em;}.handcalcs :where(.hc-matrix-row){display:table-row;}.handcalcs :where(.hc-matrix-cell){display:table-cell;text-align:right;padding:0 0.3em;line-height:1.2;}</style><div class="hc-line">f<sub>y</sub> = 355</div><div class="hc-line">f<sub>u</sub> = 510</div><div class="hc-line">f<sub>u</sub> / f<sub>y</sub> &ge; 1.1 = 510 / 355 &ge; 1.1 <span class="hc-check hc-pass">✓</span></div><div class="hc-line">f<sub>y</sub> &le; 460 = 355 &le; 460 <span class="hc-check hc-pass">✓</span></div></div>

The marks come from each renderer’s CHECK_MARKS class attribute, a (pass, fail) pair that includes the gap before the mark. For example, words instead of symbols:

class WordyMarkdownRenderer(MarkdownRenderer):
    CHECK_MARKS = ("\\quad\\text{OK}", "\\quad\\text{NOT OK}")

In a LaTeX document, the default marks need the amssymb and xcolor packages. The HTML renderer colors them with the hc-pass and hc-fail classes, and the Excel renderer keeps the live TRUE/FALSE formula with green or red formatting.

Write the verdict in a comment#

Asked in #195 and #251.

Comments can contain Jinja expressions in {{ }}. They are evaluated with the calculation’s values, so a sentence can state the outcome:

M_Ed = 120  # kN m
M_Rd = 150  # kN m
eta = M_Ed / M_Rd
# Utilisation is {{ "%.0f" % (eta * 100) }} %, so the section {{ "passes" if eta <= 1 else "fails" }}.
Output (MarkdownRenderer)
\[\begin{aligned} M_{\mathrm{Ed}} &= 120 \quad\text{(kN m)} \\ M_{\mathrm{Rd}} &= 150 \quad\text{(kN m)} \\ \eta &= \frac{M_{\mathrm{Ed}}}{M_{\mathrm{Rd}}} = \frac{120}{150} = 0.8 \end{aligned}\]

Utilisation is 80 %, so the section passes.

$$\begin{aligned} M_{\mathrm{Ed}} &= 120 \quad\text{(kN m)} \\ M_{\mathrm{Rd}} &= 150 \quad\text{(kN m)} \\ \eta &= \frac{M_{\mathrm{Ed}}}{M_{\mathrm{Rd}}} = \frac{120}{150} = 0.8 \end{aligned}$$

Utilisation is 80 %, so the section passes.

Inline comments work the same way:

eta = 0.83  # {{ "OK" if eta <= 1 else "NOT OK" }}
Output (MarkdownRenderer)
\[\begin{aligned} \eta &= 0.83 \quad\text{(OK)} \end{aligned}\]
$$\begin{aligned} \eta &= 0.83 \quad\text{(OK)} \end{aligned}$$

Pick a value with a conditional expression#

A one-line if/else keeps a simple choice on one line, and the substitution shows which branch the values pick:

t = 18           # mm
f_y = 355 if t <= 16 else 345
Output (MarkdownRenderer)
\[\begin{aligned} t &= 18 \quad\text{(mm)} \\ f_{y} &= 355\;\mathrm{if}\;t \le 16\;\mathrm{else}\;345 = 355\;\mathrm{if}\;18 \le 16\;\mathrm{else}\;345 = 345 \end{aligned}\]
$$\begin{aligned} t &= 18 \quad\text{(mm)} \\ f_{y} &= 355\;\mathrm{if}\;t \le 16\;\mathrm{else}\;345 = 355\;\mathrm{if}\;18 \le 16\;\mathrm{else}\;345 = 345 \end{aligned}$$

Show the comparison as True or False#

Without --check, a comparison on its own line is shown with its result. Assign it to keep the result for later:

M_Ed = 120
M_Rd = 150
M_Ed <= M_Rd
passes = M_Ed / M_Rd <= 1.0
Output (MarkdownRenderer)
\[\begin{aligned} M_{\mathrm{Ed}} &= 120 \\ M_{\mathrm{Rd}} &= 150 \\ M_{\mathrm{Ed}} \le M_{\mathrm{Rd}} &= 120 \le 150 = \mathrm{True} \\ \mathrm{passes} &= \frac{M_{\mathrm{Ed}}}{M_{\mathrm{Rd}}} \le 1.0 = \frac{120}{150} \le 1.0 = \mathrm{True} \end{aligned}\]
$$\begin{aligned} M_{\mathrm{Ed}} &= 120 \\ M_{\mathrm{Rd}} &= 150 \\ M_{\mathrm{Ed}} \le M_{\mathrm{Rd}} &= 120 \le 150 = \mathrm{True} \\ \mathrm{passes} &= \frac{M_{\mathrm{Ed}}}{M_{\mathrm{Rd}}} \le 1.0 = \frac{120}{150} \le 1.0 = \mathrm{True} \end{aligned}$$

Show only the governing branch#

Asked in #251 and #191.

An if/elif/else block shows the test that was true and the lines that ran under it. The other branches are left out, so the output reads as the governing case:

eta = 0.97
if eta <= 0.9:
    verdict = "Pass"
elif eta <= 1.0:
    verdict = "Pass, close to the limit"
else:
    verdict = "Fail"
Output (MarkdownRenderer)
\[\begin{aligned} \eta &= 0.97 \end{aligned}\]

Since \(\bigl(\eta \le 1.0\bigr) \rightarrow \bigl(0.97 \le 1.0\bigr)\) is True:

\[\begin{aligned} \mathrm{verdict} &= \text{"Pass, close to the limit"} \end{aligned}\]
$$\begin{aligned} \eta &= 0.97 \end{aligned}$$

Since $\bigl(\eta \le 1.0\bigr) \rightarrow \bigl(0.97 \le 1.0\bigr)$ is True:

> $$\begin{aligned} \mathrm{verdict} &= \text{"Pass, close to the limit"} \end{aligned}$$

Calculations inside the branch are rendered too, so a code clause with two cases can be written as it reads:

lamb = 0.6
if lamb <= 0.2:
    chi = 1.0
else:
    phi = 0.5 * (1 + 0.34 * (lamb - 0.2) + lamb**2)
    chi = 1 / (phi + sqrt(phi**2 - lamb**2))
Output (MarkdownRenderer)
\[\begin{aligned} \lambda &= 0.6 \end{aligned}\]

Otherwise:

\[\begin{aligned} \phi &= 0.5 \cdot \left(1 + 0.34 \cdot \left(\lambda - 0.2\right) + \lambda^{2}\right) = 0.5 \cdot \left(1 + 0.34 \cdot \left(0.6 - 0.2\right) + 0.6^{2}\right) = 0.748 \\ \chi &= \frac{1}{\phi + \sqrt{\phi^{2} - \lambda^{2}}} = \frac{1}{0.748 + \sqrt{0.748^{2} - 0.6^{2}}} = 0.83706 \end{aligned}\]
$$\begin{aligned} \lambda &= 0.6 \end{aligned}$$

Otherwise:

> $$\begin{aligned} \phi &= 0.5 \cdot \left(1 + 0.34 \cdot \left(\lambda - 0.2\right) + \lambda^{2}\right) = 0.5 \cdot \left(1 + 0.34 \cdot \left(0.6 - 0.2\right) + 0.6^{2}\right) = 0.748 \\ \chi &= \frac{1}{\phi + \sqrt{\phi^{2} - \lambda^{2}}} = \frac{1}{0.748 + \sqrt{0.748^{2} - 0.6^{2}}} = 0.83706 \end{aligned}$$

Check several load cases at once#

A for loop’s body shows the values of its last iteration only, so it can’t show a check per case. Put the cases in a list instead, and let max pick the governing one:

M_Rd = 150
M_Ed = [90, 120, 140]
eta = [M / M_Rd for M in M_Ed]  # hc: -f .2f
eta_max = max(eta)              # hc: -f .2f
Output (MarkdownRenderer)
\[\begin{aligned} M_{\mathrm{Rd}} &= 150 \\ M_{\mathrm{Ed}} &= [90, 120, 140] \\ \eta &= [\frac{M}{M_{\mathrm{Rd}}}\;\mathrm{for}\;M\;\mathrm{in}\;M_{\mathrm{Ed}}] = [0.60, 0.80, 0.93] \\ \eta_{\mathrm{max}} &= \operatorname{max}\left(\eta\right) = \operatorname{max}\left([0.60, 0.80, 0.93]\right) = 0.93 \end{aligned}\]
$$\begin{aligned} M_{\mathrm{Rd}} &= 150 \\ M_{\mathrm{Ed}} &= [90, 120, 140] \\ \eta &= [\frac{M}{M_{\mathrm{Rd}}}\;\mathrm{for}\;M\;\mathrm{in}\;M_{\mathrm{Ed}}] = [0.60, 0.80, 0.93] \\ \eta_{\mathrm{max}} &= \operatorname{max}\left(\eta\right) = \operatorname{max}\left([0.60, 0.80, 0.93]\right) = 0.93 \end{aligned}$$

Run the same check for many designs#

To render one calculation for each row of a table of inputs, see Rendering many input sets.

Next: Symbols and notation.