Use Pint and unit-free variable names in concrete-beam

Convert input parsing to Pint (matching the other calculations), carry
units in the YAML values, and drop unit suffixes from input keys, local
variables, results keys, the Typst sheet, and tests.
This commit is contained in:
smillmorel 2026-09-21 12:44:28 -04:00
commit 8f91baee41
6 changed files with 176 additions and 165 deletions

View file

@ -10,54 +10,68 @@ try:
except ImportError:
raise SystemExit("Install PyYAML: python -m pip install pyyaml")
try:
from pint import DimensionalityError, UndefinedUnitError, UnitRegistry
except ImportError:
raise SystemExit("Install dependencies: python -m pip install -r requirements.txt")
HERE = Path(__file__).resolve().parent
ureg = UnitRegistry()
ureg.define("kip = 1000 * force_pound")
ureg.define("ksi = kip / inch ** 2")
ureg.define("psf = force_pound / foot ** 2")
ureg.define("pcf = force_pound / foot ** 3")
def _require_positive(name: str, value: float) -> float:
if value <= 0:
def quantity(value, unit: str, name: str) -> float:
try:
q = ureg.Quantity(value).to(unit)
except (DimensionalityError, UndefinedUnitError, TypeError, ValueError) as exc:
raise ValueError(f"{name}: expected {unit}, got {value!r}") from exc
magnitude = float(q.magnitude)
if magnitude <= 0:
raise ValueError(f"{name} must be positive")
return value
return magnitude
def compute(inp: dict) -> dict:
span_ft = _require_positive("span_ft", float(inp["span_ft"]))
tributary_ft = _require_positive("tributary_ft", float(inp["tributary_ft"]))
D_psf = _require_positive("D_psf", float(inp["D_psf"]))
L_psf = _require_positive("L_psf", float(inp["L_psf"]))
bw_in = _require_positive("bw_in", float(inp["bw_in"]))
h_in = _require_positive("h_in", float(inp["h_in"]))
d_in = _require_positive("d_in", float(inp["d_in"]))
fc_ksi = _require_positive("fc_ksi", float(inp["fc_ksi"]))
fy_ksi = _require_positive("fy_ksi", float(inp["fy_ksi"]))
As_in2 = _require_positive("As_in2", float(inp["As_in2"]))
concrete_pcf = _require_positive("concrete_pcf", float(inp["concrete_pcf"]))
span = quantity(inp["span"], "ft", "span")
tributary = quantity(inp["tributary"], "ft", "tributary")
D = quantity(inp["D"], "psf", "D")
L = quantity(inp["L"], "psf", "L")
bw = quantity(inp["bw"], "in", "bw")
h = quantity(inp["h"], "in", "h")
d = quantity(inp["d"], "in", "d")
fc = quantity(inp["fc"], "ksi", "fc")
fy = quantity(inp["fy"], "ksi", "fy")
As = quantity(inp["As"], "in**2", "As")
concrete_density = quantity(inp["concrete_density"], "pcf", "concrete_density")
self_weight_klf = (bw_in * h_in / 144.0) * concrete_pcf / 1000.0
wD_klf = D_psf * tributary_ft / 1000.0 + self_weight_klf
wL_klf = L_psf * tributary_ft / 1000.0
wu_klf = 1.2 * wD_klf + 1.6 * wL_klf
Mu_kipft = wu_klf * span_ft**2 / 8.0
Vu_kip = wu_klf * span_ft / 2.0
self_weight = (bw * h / 144.0) * concrete_density / 1000.0
wD = D * tributary / 1000.0 + self_weight
wL = L * tributary / 1000.0
wu = 1.2 * wD + 1.6 * wL
Mu = wu * span**2 / 8.0
Vu = wu * span / 2.0
fc_psi = fc_ksi * 1000.0
fy_psi = fy_ksi * 1000.0
a_in = As_in2 * fy_ksi / (0.85 * fc_ksi * bw_in)
beta1 = max(0.65, min(0.85, 0.85 - 0.05 * max(0.0, (fc_psi - 4000.0) / 1000.0)))
c_in = a_in / beta1
et = 0.003 * (d_in - c_in) / c_in if c_in > 0 else 0.0
a = As * fy / (0.85 * fc * bw)
beta1 = max(0.65, min(0.85, 0.85 - 0.05 * max(0.0, (fc * 1000.0 - 4000.0) / 1000.0)))
c = a / beta1
et = 0.003 * (d - c) / c if c > 0 else 0.0
if et >= 0.005:
phi = 0.90
else:
phi = max(0.65, min(0.90, 0.65 + (et - 0.002) * 250.0 / 3.0))
Mn_kipft = As_in2 * fy_ksi * (d_in - a_in / 2.0) / 12.0
phiMn_kipft = phi * Mn_kipft
Mn = As * fy * (d - a / 2.0) / 12.0
phiMn = phi * Mn
rho_min = max(3.0 * math.sqrt(fc_psi) / fy_psi, 200.0 / fy_psi)
As_min_in2 = rho_min * bw_in * d_in
rho_min = max(3.0 * math.sqrt(fc * 1000.0) / (fy * 1000.0), 200.0 / (fy * 1000.0))
As_min = rho_min * bw * d
Vc_kip = 2.0 * math.sqrt(fc_psi) * bw_in * d_in / 1000.0
phiVc_kip = 0.75 * Vc_kip
Vc = 2.0 * math.sqrt(fc * 1000.0) * bw * d / 1000.0
phiVc = 0.75 * Vc
def q(value: float) -> float:
return round(value, 6)
@ -68,46 +82,46 @@ def compute(inp: dict) -> dict:
"project": inp.get("project", ""),
"prepared_by": inp.get("prepared_by", ""),
"values": {
"span_ft": q(span_ft),
"tributary_ft": q(tributary_ft),
"D_psf": q(D_psf),
"L_psf": q(L_psf),
"self_weight_klf": q(self_weight_klf),
"wD_klf": q(wD_klf),
"wL_klf": q(wL_klf),
"wu_klf": q(wu_klf),
"Mu_kipft": q(Mu_kipft),
"Vu_kip": q(Vu_kip),
"bw_in": q(bw_in),
"h_in": q(h_in),
"d_in": q(d_in),
"fc_ksi": q(fc_ksi),
"fy_ksi": q(fy_ksi),
"As_in2": q(As_in2),
"a_in": q(a_in),
"span": q(span),
"tributary": q(tributary),
"D": q(D),
"L": q(L),
"self_weight": q(self_weight),
"wD": q(wD),
"wL": q(wL),
"wu": q(wu),
"Mu": q(Mu),
"Vu": q(Vu),
"bw": q(bw),
"h": q(h),
"d": q(d),
"fc": q(fc),
"fy": q(fy),
"As": q(As),
"a": q(a),
"et": q(et),
"phi": q(phi),
"Mn_kipft": q(Mn_kipft),
"phiMn_kipft": q(phiMn_kipft),
"As_min_in2": q(As_min_in2),
"Vc_kip": q(Vc_kip),
"phiVc_kip": q(phiVc_kip),
"Mn": q(Mn),
"phiMn": q(phiMn),
"As_min": q(As_min),
"Vc": q(Vc),
"phiVc": q(phiVc),
},
"checks": {
"flexure": {
"demand": q(Mu_kipft),
"capacity": q(phiMn_kipft),
"ok": Mu_kipft <= phiMn_kipft,
"demand": q(Mu),
"capacity": q(phiMn),
"ok": Mu <= phiMn,
},
"minimum_steel": {
"demand": q(As_min_in2),
"capacity": q(As_in2),
"ok": As_in2 >= As_min_in2,
"demand": q(As_min),
"capacity": q(As),
"ok": As >= As_min,
},
"shear": {
"demand": q(Vu_kip),
"capacity": q(phiVc_kip),
"ok": Vu_kip <= phiVc_kip,
"demand": q(Vu),
"capacity": q(phiVc),
"ok": Vu <= phiVc,
},
},
}