Repository navigation
Expand file tree
/
Copy pathcalc-concrete.js
More file actions
2711 lines (2595 loc) · 240 KB
/
Copy pathcalc-concrete.js
File metadata and controls
2711 lines (2595 loc) · 240 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Group E (cont.): ACI 318-19 reinforced-concrete member bench.
// spec-v257..v259 establish this new lazy-loaded renderer module -- the
// reinforced-concrete member companion to the steel-member bench
// (calc-steel.js, spec-v254..v256) one material over. The catalog already
// placed and cured the concrete (concrete, concrete-mix-design,
// concrete-evaporation-rate, concrete-strength-gain) and counted the rebar
// (rebar, rebar-schedule, rebar-lap-splice), but nothing checked the
// reinforced-concrete member itself: does the beam carry the moment, does it
// carry the shear, and does the bar develop its force. Every tile keeps
// group: "E" (a tile's group letter is independent of the module that holds
// it -- the spec-v70..v98 split precedent). Tiles:
// v257 rc-beam-flexure (ACI 318-19 §22.2, singly-reinforced, tension-controlled)
// v258 rc-beam-shear (ACI 318-19 §22.5, Vc + Vs and stirrup spacing)
// v259 rc-development-length (ACI 318-19 §25.4.2, straight-bar tension ld)
// All GOVERNANCE.general design aids; the psi factors and material strengths
// are user-supplied from the drawings and ACI 318-19 tables. See
// spec-v257.md..v259.md.
import {
DEBOUNCE_MS, debounce, makeNumber, makeSelect,
makeOutputLine, attachExampleButton, fmt,
makeStepsPanel,
} from "./ui-fields.js";
// v18 §7 contract guard: reject a non-finite numeric input (copied verbatim
// from the sibling calc-* modules; non-exported, so it adds no corpus row).
const _finiteGuard = (o) => {
if (o && typeof o === "object" && !Array.isArray(o)) {
for (const v of Object.values(o)) {
if (typeof v === "number" && !Number.isFinite(v)) {
return { error: "All numeric inputs must be finite numbers." };
}
}
}
return null;
};
// Compact renderer factory (same shape as the calc-steel / calc-firesprinkler
// _simpleRenderer factories) supporting number and select inputs.
function _simpleRenderer(spec) {
const _rlRender = function (inputRegion, outputRegion, citationEl) {
citationEl.textContent = spec.citation;
attachExampleButton(inputRegion, () => fillExample(spec.example));
const fields = {};
for (const f of spec.fields) {
let field;
if (f.kind === "select") field = makeSelect(f.label, f.id || f.key, f.options);
else field = makeNumber(f.label, f.id || f.key, f.attrs || { step: "any", min: "0" });
fields[f.key] = field;
if (f.default !== undefined) {
if (f.kind === "select") field.select.value = f.default;
else field.input.value = String(f.default);
}
inputRegion.appendChild(field.wrap);
}
const outs = {};
for (const o of spec.outputs) outs[o.key] = makeOutputLine(outputRegion, o.label, o.id);
const _steps = makeStepsPanel(outputRegion);
function fillExample(v) {
for (const f of spec.fields) {
if (v[f.key] === undefined) continue;
if (f.kind === "select") fields[f.key].select.value = v[f.key];
else fields[f.key].input.value = v[f.key];
}
update();
}
const update = debounce(() => {
const params = {};
for (const f of spec.fields) {
if (f.kind === "select") params[f.key] = fields[f.key].select.value;
// A field marked `required` passes null when blank, so the compute can
// ask for it. Blank must not become 0 where 0 is a real value (0 F).
else params[f.key] = f.required && fields[f.key].input.value === "" ? null : Number(fields[f.key].input.value) || 0;
}
const r = spec.compute(params); _steps.update(spec.fields, params, r);
if (r.error) { for (const k of Object.keys(outs)) outs[k].textContent = "-"; outs[spec.outputs[0].key].textContent = r.error; return; }
for (const o of spec.outputs) outs[o.key].textContent = o.value(r);
}, DEBOUNCE_MS);
for (const f of spec.fields) {
const el = f.kind === "select" ? fields[f.key].select : fields[f.key].input;
el.addEventListener(f.kind === "select" ? "change" : "input", update);
}
};
_rlRender.schema = {
inputs: (spec.fields || []).map((f) => ({ key: f.key, label: f.label, kind: f.kind ?? "number", options: f.options ?? null, default: f.default ?? null, attrs: f.attrs ?? (f.kind && f.kind !== "number" ? null : { step: "any", min: "0" }) })),
outputs: (spec.outputs || []).map((o) => ({ key: o.key, label: o.label, unit: o.unit ?? null, format: o.value })),
citation: spec.citation ?? null,
scope: spec.scope ?? null,
};
return _rlRender;
}
export const CONCRETE_RENDERERS = {};
// ===================== spec-v257: reinforced concrete beam flexural capacity =====================
// dims: in { fc: M L^-1 T^-2, fy: M L^-1 T^-2, as_in2: L^2, b: L, d: L, mu: M L^2 T^-2 } out: { a_in: L, mn_kipft: M L^2 T^-2, phi_mn: M L^2 T^-2, util: dimensionless, eps_t: dimensionless, tc_limit: dimensionless, tension_controlled: dimensionless }
export function computeRcBeamFlexure({ fc = 4000, fy = 60000, as_in2 = 0, b = 0, d = 0, mu = 0 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(fc > 0)) return { error: "Concrete strength f'c must be positive (psi)." };
if (!(fy > 0)) return { error: "Steel yield fy must be positive (psi)." };
if (!(as_in2 > 0)) return { error: "Tension steel area As must be positive (in^2)." };
if (!(b > 0)) return { error: "Beam width b must be positive (in)." };
if (!(d > 0)) return { error: "Effective depth d must be positive (in)." };
const a_in = (as_in2 * fy) / (0.85 * fc * b);
const mn_kipin = as_in2 * fy * (d - a_in / 2) / 1000;
const mn_kipft = mn_kipin / 12;
// phi = 0.90 holds only for a tension-controlled section, and ACI 318-19 9.3.3.1 requires a
// nonprestressed beam to be one (eps_t >= eps_ty + 0.003). Until 2026-10-03 this was never
// checked, so an over-reinforced beam read "OK" at phi 0.90; until 2026-10-10 the check only
// changed a label and phi Mn was still 0.90 Mn (21% high on a section the T-beam tile rates at
// phi 0.745). phi now follows Table 21.2.2, as the T-beam and doubly reinforced tiles do.
const c_in = a_in / _RC_BETA1(fc);
if (c_in >= d) return { error: "The neutral axis falls at or below the tension steel - this section is grossly over-reinforced and cannot be designed as a beam. Reduce As or enlarge the section." };
const eps_t = 0.003 * (d - c_in) / c_in;
const eps_ty = fy / 29e6;
const tc_limit = eps_ty + 0.003;
const tension_controlled = eps_t >= tc_limit - 1e-9 * tc_limit;
const phi = tension_controlled ? 0.90 : eps_t <= eps_ty ? 0.65 : 0.65 + 0.25 * (eps_t - eps_ty) / 0.003;
const phi_mn = phi * mn_kipft;
const util = mu > 0 ? mu / phi_mn : null;
return { a_in, mn_kipft, phi_mn, util, eps_t, tc_limit, tension_controlled, phi };
}
export const rcBeamFlexureExample = { inputs: { fc: 4000, fy: 60000, as_in2: 3.00, b: 12, d: 21.5, mu: 200 } };
CONCRETE_RENDERERS["rc-beam-flexure"] = _simpleRenderer({
citation: "Citation: ACI 318-19 §22.2.2.4.1 (equivalent rectangular stress block, a = As x fy / (0.85 x f'c x b)), §22.2 (Mn = As x fy x (d - a/2)), and §21.2.2 (phi = 0.90 for a tension-controlled section, net tensile strain at least fy/Es + 0.003, falling linearly to 0.65 at fy/Es). Covers only a singly-reinforced rectangular section; a beam that is not tension-controlled fails §9.3.3.1 and is reported at its reduced phi; compression reinforcement, T-beam flange action, minimum steel (§9.6.1.2), and shear are not checked here (see the RC beam shear tile). Take As and d from the reinforcement schedule for the actual section. A design aid, not a substitute for a licensed engineer's design -- the engineer of record's stamped design governs.",
example: rcBeamFlexureExample.inputs,
fields: [
{ key: "fc", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "fy", label: "Steel yield fy (psi)", kind: "number" },
{ key: "as_in2", label: "Tension steel area As (in²)", kind: "number" },
{ key: "b", label: "Beam width b (in)", kind: "number" },
{ key: "d", label: "Effective depth d (in)", kind: "number" },
{ key: "mu", label: "Required moment Mu (kip-ft, 0 = capacity only)", kind: "number", default: 0, attrs: { step: "any" } },
],
outputs: [
{ key: "a", id: "rbf-out-a", label: "Stress-block depth a", value: (r) => fmt(r.a_in, 2) + " in" },
{ key: "mn", id: "rbf-out-mn", label: "Nominal moment Mn", value: (r) => fmt(r.mn_kipft, 1) + " kip-ft" },
{ key: "pm", id: "rbf-out-pm", label: "Design moment phi Mn", value: (r) => fmt(r.phi_mn, 1) + " kip-ft (phi = " + fmt(r.phi, 2) + (r.tension_controlled ? ")" : "; NOT tension-controlled, eps_t " + fmt(r.eps_t, 4) + " under " + fmt(r.tc_limit, 4) + " -- fails ACI 318-19 9.3.3.1)") },
{ key: "ut", id: "rbf-out-ut", label: "Demand / capacity", value: (r) => r.util === null ? "- (no Mu entered)" : fmt(r.util, Math.abs(r.util - 1) < 0.005 && r.util !== 1 ? 4 : 2) + (r.util <= 1 + 1e-9 ? (r.tension_controlled ? " (OK)" : " (NOT tension-controlled - fails ACI 318-19 9.3.3.1)") : " (OVER)") },
],
compute: computeRcBeamFlexure,
});
// ===================== spec-v258: reinforced concrete beam shear and stirrup spacing =====================
// The simplified Vc (Table 22.5.5.1(a)) holds only with Av >= Av,min, and
// 9.6.3.1 requires Av,min wherever Vu > phi lambda sqrt(f'c) bw d (half of
// phi Vc here). s_max_in is the governing maximum spacing: 9.7.6.2.2's d/2 <=
// 24 in (d/4 <= 12 in when Vs > 4 sqrt(f'c) bw d) and the spacing at which Av
// still meets 9.6.3.4's Av,min = max(0.75 sqrt(f'c), 50) bw s / fyt. Until
// 2026-09-25 these were listed as separate checks and s_max was a bare d/2.
// dims: in { fc: M L^-1 T^-2, fyt: M L^-1 T^-2, bw: L, d: L, av_in2: L^2, vu: M L T^-2, lambda: dimensionless, stirrup_type: dimensionless } out: { vc_kip: M L T^-2, phi_vc: M L T^-2, vs_req_kip: M L T^-2, s_req_in: L, s_max_in: L, s_avmin_in: L }
export function computeRcBeamShear({ fc = 4000, fyt = 60000, bw = 0, d = 0, av_in2 = 0, vu = 0, lambda = 1.0, stirrup_type = "bar" } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(fc > 0)) return { error: "Concrete strength f'c must be positive (psi)." };
if (!(fyt > 0)) return { error: "Stirrup yield fyt must be positive (psi)." };
if (!(bw > 0)) return { error: "Web width bw must be positive (in)." };
if (!(d > 0)) return { error: "Effective depth d must be positive (in)." };
if (!(av_in2 > 0)) return { error: "Stirrup area Av must be positive (in^2)." };
if (!(lambda > 0)) return { error: "Lightweight factor lambda must be positive." };
if (lambda > 1) return { error: "Lightweight factor lambda cannot exceed 1.0 (ACI 318-19 19.2.4)." };
// ACI 318-19 Table 20.2.2.4(a) caps fyt for shear at 60,000 psi for deformed bars and 80,000 psi
// for welded deformed wire; the shear-friction and corbel tiles already apply the same table.
// Until 2026-10-07 a Grade 80 or 100 stirrup spread its spacing in proportion to the grade.
if (stirrup_type !== "bar" && stirrup_type !== "wwr") return { error: "Stirrup type must be bar (deformed bar) or wwr (welded deformed wire)." };
const fyt_used_psi = Math.min(fyt, stirrup_type === "wwr" ? 80000 : 60000);
const vc_kip = 2 * lambda * Math.sqrt(fc) * bw * d / 1000;
const phi_vc = 0.75 * vc_kip;
const vs_req_kip = vu > 0 ? Math.max(0, vu / 0.75 - vc_kip) : 0;
const s_req_in = vs_req_kip > 0 ? av_in2 * fyt_used_psi * d / (vs_req_kip * 1000) : null;
const sqfc_bwd_kip = Math.sqrt(fc) * bw * d / 1000;
// 22.5.3.1 caps sqrt(f'c) at 100 psi unless Av,min is provided (22.5.3.2), so the test for
// "no stirrups at all" uses the capped value. Until 2026-10-10 a 16,000 psi beam was told it
// needed none at a shear the capped expression does not allow.
const min_required = vu > 0.75 * lambda * Math.min(Math.sqrt(fc), 100) * bw * d / 1000;
const s_avmin_in = av_in2 * fyt_used_psi / (Math.max(0.75 * Math.sqrt(fc), 50) * bw);
const tight = vs_req_kip > 4 * sqfc_bwd_kip;
const s_geom_in = tight ? Math.min(d / 4, 12) : Math.min(d / 2, 24);
const s_max_in = Math.min(s_geom_in, s_avmin_in);
const section_ok = vs_req_kip <= (8 * sqfc_bwd_kip) + 1e-9 * Math.abs(8 * sqfc_bwd_kip);
const stirrups = vu > phi_vc;
return { vc_kip, phi_vc, vs_req_kip, s_req_in, s_max_in, s_avmin_in, fyt_used_psi, s_max_rule: s_avmin_in < s_geom_in ? "Av,min" : tight ? "d/4" : "d/2", min_required, section_ok, stirrups };
}
export const rcBeamShearExample = { inputs: { fc: 4000, fyt: 60000, bw: 12, d: 21.5, av_in2: 0.22, vu: 40, lambda: 1.0 } };
CONCRETE_RENDERERS["rc-beam-shear"] = _simpleRenderer({
citation: "Citation: ACI 318-19 §22.5.5.1 (Vc = 2 x lambda x sqrt(f'c) x bw x d, the simplified concrete shear for a non-prestressed member without axial load, psi units), §22.5.10.5.3 (Vs = Av x fyt x d / s for vertical stirrups), §21.2.1 (phi = 0.75 for shear), and §9.7.6.2.2 (the d/2 <= 24 in maximum stirrup spacing, halved to d/4 <= 12 in when Vs exceeds 4 x sqrt(f'c) x bw x d), §9.6.3.1 and §9.6.3.4 (Av,min = max(0.75 sqrt(f'c), 50) bw s / fyt wherever Vu > phi lambda sqrt(f'c) bw d, the condition for the simplified Vc), and §22.5.1.2 (Vs <= 8 sqrt(f'c) bw d). Uses the simplified Vc, not the detailed §22.5.5.1 expression with the reinforcement-ratio and size-effect terms; covers vertical stirrups on a member without significant axial load; does not check deep-beam action. Lambda is 1.0 normalweight, 0.75 lightweight (§19.2.4). A design aid, not a substitute for a licensed engineer's design -- the engineer of record's stamped design governs.",
example: rcBeamShearExample.inputs,
fields: [
{ key: "fc", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "fyt", label: "Stirrup yield fyt (psi)", kind: "number" },
{ key: "stirrup_type", label: "Stirrup type (sets the 20.2.2.4(a) fyt cap)", kind: "select", options: [{ value: "bar", label: "Deformed bar (fyt up to 60,000 psi)", selected: true }, { value: "wwr", label: "Welded deformed wire (up to 80,000 psi)" }] },
{ key: "bw", label: "Web width bw (in)", kind: "number" },
{ key: "d", label: "Effective depth d (in)", kind: "number" },
{ key: "av_in2", label: "Stirrup area Av, both legs (in²)", kind: "number" },
{ key: "vu", label: "Factored shear Vu (kip, 0 = capacity only)", kind: "number", default: 0, attrs: { step: "any" } },
{ key: "lambda", label: "Lightweight factor lambda (1.0 normalweight)", kind: "number" },
],
outputs: [
{ key: "vc", id: "rbs-out-vc", label: "Concrete shear Vc", value: (r) => fmt(r.vc_kip, 1) + " kip" },
{ key: "pv", id: "rbs-out-pv", label: "Design concrete shear phi Vc (phi = 0.75)", value: (r) => fmt(r.phi_vc, 1) + " kip" },
{ key: "st", id: "rbs-out-st", label: "Stirrups required", value: (r) => !r.section_ok ? "SECTION TOO SMALL (Vs > 8 sqrt(f'c) bw d; enlarge the beam)" : r.stirrups ? "YES by strength (Vu > phi Vc)" : r.min_required ? "YES, the Av,min minimum (Vu > phi lambda sqrt(f'c) bw d)" : "no (Vu <= phi lambda sqrt(f'c) bw d)" },
{ key: "vs", id: "rbs-out-vs", label: "Stirrup shear demand Vs,req", value: (r) => fmt(r.vs_req_kip, 1) + " kip" },
{ key: "sr", id: "rbs-out-sr", label: "Required stirrup spacing", value: (r) => r.s_req_in === null ? "- (none required by strength)" : fmt(r.s_req_in, 1) + " in" },
{ key: "sm", id: "rbs-out-sm", label: "Code max spacing", value: (r) => fmt(r.s_max_in, 2) + " in (" + r.s_max_rule + " governs; Av,min allows " + fmt(r.s_avmin_in, 1) + " in)" },
],
compute: computeRcBeamShear,
});
// ===================== spec-v259: rebar tension development length =====================
// dims: in { fc: M L^-1 T^-2, fy: M L^-1 T^-2, db: L, psi_t: dimensionless, psi_e: dimensionless, psi_s: dimensionless, psi_g: dimensionless, lambda: dimensionless, conf: dimensionless } out: { te: dimensionless, conf_eff: dimensionless, ld_in: L, ld_db: dimensionless }
export function computeRcDevelopmentLength({ fc = 4000, fy = 60000, db = 0, psi_t = 1.0, psi_e = 1.0, psi_s = 1.0, psi_g = 1.0, lambda = 1.0, conf = 2.5 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(fc > 0)) return { error: "Concrete strength f'c must be positive (psi)." };
if (!(fy > 0)) return { error: "Bar yield fy must be positive (psi)." };
if (!(db > 0)) return { error: "Bar diameter db must be positive (in)." };
if (!(conf > 0)) return { error: "Confinement term (cb + Ktr)/db must be positive." };
if (!(psi_t > 0) || !(psi_e > 0) || !(psi_s > 0) || !(psi_g > 0)) return { error: "The psi factors must be positive." };
// Table 25.4.2.5 has no value under 1.0 for casting position, coating or grade, and none under
// 0.8 for bar size. A smaller entry shortened ld in proportion with nothing said.
if (psi_t < 1 || psi_e < 1 || psi_g < 1) return { error: "psi_t, psi_e and psi_g are 1.0 or more in ACI 318-19 Table 25.4.2.5." };
if (psi_s < 0.8) return { error: "The bar-size factor psi_s is 0.8 (No. 6 and smaller) or 1.0 in ACI 318-19 Table 25.4.2.5." };
if (!(lambda > 0)) return { error: "Lightweight factor lambda must be positive." };
if (lambda > 1) return { error: "Lightweight factor lambda cannot exceed 1.0 (ACI 318-19 19.2.4)." };
const te = Math.min(psi_t * psi_e, 1.7);
const conf_eff = Math.min(conf, 2.5);
// 25.4.1.4 caps sqrt(f'c) at 100 psi in development-length calculations
// (no exception, unlike shear's 22.5.3.2). sqrt(f'c) is in the denominator,
// so the cap lengthens ld above f'c = 10,000 psi - the conservative direction.
const sqrt_fc = Math.min(Math.sqrt(fc), 100);
// Table 25.4.2.5 fixes psi_g by bar grade: 1.0 for Grade 40/60, 1.15 for
// Grade 80, 1.3 for Grade 100. An entered psi_g below the grade's value is
// raised to it; until 2026-09-25 a Grade 80 bar left at 1.0 read 13% short.
const psi_g_grade = fy <= 60000 ? 1.0 : fy <= 80000 ? 1.15 : 1.3;
const psi_g_used = Math.max(psi_g, psi_g_grade);
const ld_calc = (3 / 40) * fy * te * psi_s * psi_g_used / (lambda * sqrt_fc * conf_eff) * db;
const ld_in = Math.max(ld_calc, 12);
const ld_db = ld_in / db;
return { te, conf_eff, ld_in, ld_db, psi_g_used };
}
export const rcDevelopmentLengthExample = { inputs: { fc: 4000, fy: 60000, db: 1.00, psi_t: 1.0, psi_e: 1.0, psi_s: 1.0, psi_g: 1.0, lambda: 1.0, conf: 2.5 } };
CONCRETE_RENDERERS["rc-development-length"] = _simpleRenderer({
citation: "Citation: ACI 318-19 §25.4.2.3 (the general tension development-length equation ld = (3/40) x (fy x psi_t x psi_e x psi_s x psi_g) / (lambda x sqrt(f'c) x ((cb + Ktr)/db)) x db), §25.4.2.4 (the (cb + Ktr)/db confinement term, capped at 2.5), §25.4.2.5 / Table 25.4.2.5 (psi_t casting position 1.3 top bar / 1.0 other, psi_e coating 1.5 or 1.2 epoxy / 1.0 uncoated, psi_s size 0.8 for #6 and smaller / 1.0 for #7 and larger, psi_g grade 1.0 Gr 60 / 1.15 Gr 80 / 1.3 Gr 100, with psi_t x psi_e capped at 1.7), §25.4.2.1 (the 12 in minimum), and §25.4.1.4 (the value of sqrt(f'c) shall not exceed 100 psi, so f'c above 10,000 psi does not further shorten ld). The psi factors are user-selected inputs, not derived here -- the tool does not read cover, spacing, or transverse reinforcement to compute cb or Ktr. Straight-bar tension development only: not a standard hook (§25.4.3), a compression bar (§25.4.9), or a lap splice (§25.5, see the rebar lap splice tile). Lambda is 1.0 normalweight, 0.75 lightweight. A design aid, not a substitute for a licensed engineer's design -- the engineer of record's detailing governs.",
example: rcDevelopmentLengthExample.inputs,
fields: [
{ key: "fc", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "fy", label: "Bar yield fy (psi)", kind: "number" },
{ key: "db", label: "Bar diameter db (in, #8 = 1.00)", kind: "number" },
{ key: "psi_t", label: "Casting position psi_t (1.3 top bar, 1.0 other)", kind: "number" },
{ key: "psi_e", label: "Coating psi_e (1.5 / 1.2 epoxy, 1.0 uncoated)", kind: "number" },
{ key: "psi_s", label: "Bar size psi_s (0.8 for #6 and smaller, else 1.0)", kind: "number" },
{ key: "psi_g", label: "Grade psi_g (raised to 1.15 Gr 80 / 1.3 Gr 100 from fy)", kind: "number" },
{ key: "lambda", label: "Lightweight factor lambda (1.0 normalweight)", kind: "number" },
{ key: "conf", label: "Confinement (cb + Ktr)/db (max 2.5)", kind: "number" },
],
outputs: [
{ key: "te", id: "rdl-out-te", label: "psi_t x psi_e (capped 1.7)", value: (r) => fmt(r.te, 2) },
{ key: "ce", id: "rdl-out-ce", label: "Confinement term used (capped 2.5)", value: (r) => fmt(r.conf_eff, 2) },
{ key: "ld", id: "rdl-out-ld", label: "Development length ld (12 in floor)", value: (r) => fmt(r.ld_in, 1) + " in" },
{ key: "db", id: "rdl-out-db", label: "In bar diameters", value: (r) => fmt(r.ld_db, 1) + " db" },
],
compute: computeRcDevelopmentLength,
});
// ===================== spec-v284..v286: reinforced-concrete member depth batch =====================
// The ACI checks the beam flexure/shear/development bench left open: the tied
// column's concentric axial capacity (22.4), two-way punching shear at a
// column (22.6), and the standard-hook development length (25.4.3).
// dims: in { b_in: L, h_in: L, fc_psi: M L^-1 T^-2, fy_psi: M L^-1 T^-2, ast_in2: L^2 } out: { ag_in2: L^2, rho_g: dimensionless, po_kip: M L T^-2, phi_pn_kip: M L T^-2 }
export function computeRcColumnAxial({ b_in = 0, h_in = 0, fc_psi = 4000, fy_psi = 60000, ast_in2 = 0 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(b_in > 0) || !(h_in > 0)) return { error: "Column dimensions must be positive (in)." };
if (!(fc_psi > 0) || !(fy_psi > 0)) return { error: "Concrete and steel strengths must be positive (psi)." };
if (!(ast_in2 > 0)) return { error: "Longitudinal steel area must be positive (in^2)." };
const ag_in2 = b_in * h_in;
if (!(ast_in2 < ag_in2)) return { error: "The steel area must be less than the gross section." };
const rho_g = ast_in2 / ag_in2;
const rho_flag = rho_g < 0.01 ? "below the ACI 10.6.1 1% minimum" : (rho_g > 0.08 ? "above the ACI 10.6.1 8% maximum" : "within the ACI 10.6.1 1-8% range");
// ACI 318-19 22.4.2.1 limits fy to 80,000 psi in Po: high-strength bar cannot be credited past
// the strain concrete reaches in pure compression. Until 2026-09-25 a Grade 100 bar was taken
// at 100 ksi (StructurePoint's 18x18, 4 in^2 Grade 100 column: 1,488 kip here vs 1,408).
const fy_used = Math.min(fy_psi, 80000);
const po_lb = 0.85 * fc_psi * (ag_in2 - ast_in2) + fy_used * ast_in2;
const phi_pn_lb = 0.80 * 0.65 * po_lb;
const po_kip = po_lb / 1000;
const phi_pn_kip = phi_pn_lb / 1000;
return {
ag_in2, rho_g, rho_flag, po_kip, phi_pn_kip,
note: "ACI 318-19 22.4.2: nominal axial strength Po = 0.85 f'c (Ag - Ast) + fy Ast, with fy not more than 80,000 psi (22.4.2.1), with the 22.4.2.1 tied-column design cap phi Pn,max = 0.80 phi Po (phi = 0.65, compression-controlled tied) covering the accidental eccentricity a concentric load never truly avoids. Longitudinal steel belongs in the 10.6.1 1-8% ratio band. Concentric short tied column only - no P-M interaction diagram, no slenderness or second-order effects, and the spiral (0.85 / phi = 0.75) variant and tie detailing are separate. A design aid, not a substitute for the structural engineer of record's stamped design.",
};
}
export const rcColumnAxialExample = { inputs: { b_in: 16, h_in: 16, fc_psi: 4000, fy_psi: 60000, ast_in2: 6.32 } };
// rc-column-steel-for-load: inverse of rc-column-axial. The forward tile gives the design axial strength from the steel
// area; the inverse recovers the longitudinal steel a target factored axial load needs for a given column size. From
// phi Pn = 0.80 x 0.65 x [0.85 f'c (Ag - Ast) + fy Ast] = 0.52 x Po,
// Ast = (phi Pn / 0.52 - 0.85 f'c Ag) / (fy - 0.85 f'c). The 1% minimum governs the minimum and 8% the maximum (ACI 10.6.1).
// dims: in { target_load_kip: M L T^-2, b_in: L, h_in: L, fc_psi: M L^-1 T^-2, fy_psi: M L^-1 T^-2 } out: { ast_required_in2: L^2, ag_in2: L^2, rho_g: dimensionless }
export function computeRcColumnSteelForLoad({ target_load_kip = 0, b_in = 0, h_in = 0, fc_psi = 4000, fy_psi = 60000 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
const target = Number(target_load_kip) || 0;
const b = Number(b_in) || 0;
const h = Number(h_in) || 0;
const fc = Number(fc_psi) || 0;
const fy = Number(fy_psi) || 0;
if (!(target > 0)) return { error: "Target factored axial load must be positive (kip)." };
if (!(b > 0) || !(h > 0)) return { error: "Column dimensions must be positive (in)." };
if (!(fc > 0) || !(fy > 0)) return { error: "Concrete and steel strengths must be positive (psi)." };
if (!(Math.min(fy, 80000) > 0.85 * fc)) return { error: "Steel yield (capped at 80,000 psi in Po) must exceed 0.85 f'c." };
const ag_in2 = b * h;
const fyc = Math.min(fy, 80000); // ACI 318-19 22.4.2.1 caps fy at 80 ksi in Po, as the forward tile does
const ast_strength = (target * 1000 / 0.52 - 0.85 * fc * ag_in2) / (fyc - 0.85 * fc);
const ast_min = 0.01 * ag_in2;
const ast_max = 0.08 * ag_in2;
const ast_required_in2 = Math.max(ast_strength, ast_min);
const rho_g = ast_required_in2 / ag_in2;
const governs = ast_strength <= ast_min ? "the ACI 10.6.1 1% minimum" : "strength";
const over_max = ast_required_in2 > ast_max + 1e-9 * Math.abs(ast_max);
if (![ast_required_in2, ag_in2, rho_g].every(Number.isFinite)) return { error: "Column-steel math is not a finite value." };
return {
ast_required_in2, ag_in2, rho_g, ast_min_in2: ast_min, ast_max_in2: ast_max, governs, over_max,
note: "ACI 318-19 22.4.2 concentric tied column solved for the steel: Ast = (phi Pn / 0.52 - 0.85 f'c Ag) / (fy - 0.85 f'c), with phi Pn = 0.80 x 0.65 x Po the tied-column design cap. The reported Ast is the LARGER of the strength requirement and the ACI 10.6.1 1% minimum (Ast_min = 0.01 Ag); if the strength requirement exceeds the 8% maximum (0.08 Ag), the section is too small for the load - enlarge the column or raise f'c. Round UP to a whole-bar layout that also satisfies the tie and clear-cover detailing. Concentric short tied column only - no P-M interaction, slenderness, or second-order effects, and a spiral column uses different phi factors. A design aid, not a substitute for the structural engineer of record's stamped design.",
};
}
export const rcColumnSteelForLoadExample = { inputs: { target_load_kip: 639, b_in: 16, h_in: 16, fc_psi: 4000, fy_psi: 60000 } };
CONCRETE_RENDERERS["rc-column-steel-for-load"] = _simpleRenderer({
citation: "Citation: ACI 318-19 22.4.2 concentric tied column phi Pn = 0.80 x 0.65 x [0.85 f'c (Ag - Ast) + fy Ast] solved for the steel: Ast = (phi Pn / 0.52 - 0.85 f'c Ag) / (fy - 0.85 f'c), with the 10.6.1 1-8% band. Concentric short tied column; no P-M interaction or slenderness. A design aid, not a substitute for the engineer of record.",
example: rcColumnSteelForLoadExample.inputs,
fields: [
{ key: "target_load_kip", label: "Target factored axial load phi Pn (kip)", kind: "number" },
{ key: "b_in", label: "Column width b (in)", kind: "number" },
{ key: "h_in", label: "Column depth h (in)", kind: "number" },
{ key: "fc_psi", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "fy_psi", label: "Steel yield fy (psi)", kind: "number" },
],
outputs: [
{ key: "ast", id: "rcs-out-ast", label: "Required longitudinal steel Ast", value: (r) => fmt(r.ast_required_in2, 2) + " in^2 (" + fmt(r.rho_g * 100, 2) + "%, governed by " + r.governs + ")" + (r.over_max ? " -- OVER 8%, section too small" : "") },
{ key: "band", id: "rcs-out-band", label: "ACI 1-8% band", value: (r) => fmt(r.ast_min_in2, 2) + " to " + fmt(r.ast_max_in2, 2) + " in^2 (Ag " + fmt(r.ag_in2, 0) + " in^2)" },
{ key: "n", id: "rcs-out-n", label: "Note", value: (r) => r.note },
],
compute: computeRcColumnSteelForLoad,
});
CONCRETE_RENDERERS["rc-column-axial"] = _simpleRenderer({
citation: "Citation: ACI 318-19 22.4.2 nominal axial strength Po = 0.85 f'c (Ag - Ast) + fy Ast with the 22.4.2.1 tied-column cap phi Pn,max = 0.80 phi Po (phi = 0.65) and the 10.6.1 1-8% longitudinal ratio, by name. Concentric short tied column; no P-M interaction or slenderness. A design aid, not a substitute for the engineer of record.",
example: rcColumnAxialExample.inputs,
fields: [
{ key: "b_in", label: "Column width b (in)", kind: "number" },
{ key: "h_in", label: "Column depth h (in)", kind: "number" },
{ key: "fc_psi", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "fy_psi", label: "Steel yield fy (psi)", kind: "number" },
{ key: "ast_in2", label: "Total longitudinal steel Ast (in²)", kind: "number" },
],
outputs: [
{ key: "ag", id: "rca-out-ag", label: "Gross area Ag", value: (r) => fmt(r.ag_in2, 0) + " in^2" },
{ key: "rho", id: "rca-out-rho", label: "Steel ratio rho_g", value: (r) => fmt(r.rho_g * 100, 2) + "% (" + r.rho_flag + ")" },
{ key: "po", id: "rca-out-po", label: "Nominal Po", value: (r) => fmt(r.po_kip, 0) + " kip" },
{ key: "pn", id: "rca-out-pn", label: "Design phi Pn,max (0.80 x 0.65 x Po)", value: (r) => fmt(r.phi_pn_kip, 0) + " kip" },
{ key: "n", id: "rca-out-n", label: "Note", value: (r) => r.note },
],
compute: computeRcColumnAxial,
});
// dims: in { c1_in: L, c2_in: L, d_in: L, fc_psi: M L^-1 T^-2, position: dimensionless, lambda: dimensionless } out: { bo_in: L, beta: dimensionless, lambda_s: dimensionless, vc_psi: M L^-1 T^-2, phi_vc_kip: M L T^-2 }
export function computeRcPunchingShear({ c1_in = 0, c2_in = 0, d_in = 0, fc_psi = 4000, position = "interior", lambda = 1.0 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(c1_in > 0) || !(c2_in > 0)) return { error: "Column plan dimensions must be positive (in)." };
if (!(d_in > 0)) return { error: "Effective slab depth must be positive (in)." };
if (!(fc_psi > 0)) return { error: "Concrete strength must be positive (psi)." };
if (!(lambda > 0 && lambda <= 1)) return { error: "The lightweight factor lambda is over 0 and up to 1.0." };
if (position !== "interior" && position !== "edge" && position !== "corner") return { error: "Column position must be interior, edge, or corner." };
const alpha_s = position === "edge" ? 30 : (position === "corner" ? 20 : 40);
// The critical section sits d/2 from the column faces, and a slab edge cuts
// it off (ACI 318-19 22.6.4): interior four sides, edge three sides (c1
// perpendicular to the edge, slab flush with the column's outer face),
// corner two sides. Until 2026-09-18 every position took the four-sided
// perimeter, 35% (edge) to 78% (corner) unconservative.
const bo_in = position === "edge" ? 2 * (c1_in + d_in / 2) + (c2_in + d_in)
: position === "corner" ? (c1_in + d_in / 2) + (c2_in + d_in / 2)
: 2 * (c1_in + d_in) + 2 * (c2_in + d_in);
const beta = Math.max(c1_in, c2_in) / Math.min(c1_in, c2_in);
const t1 = 4;
const t2 = 2 + 4 / beta;
const t3 = 2 + (alpha_s * d_in) / bo_in;
const least = Math.min(t1, t2, t3);
const governs = least === t1 ? "the 4 sqrt(f'c) base term" : (least === t2 ? "the aspect-ratio (2 + 4/beta) term" : "the (2 + alpha_s d/bo) large-column term");
const lambda_s = Math.min(Math.sqrt(2 / (1 + d_in / 10)), 1.0);
// 22.5.3.1 caps sqrt(f'c) at 100 psi for two-way shear Vc; the 22.5.3.2
// exception that lifts the cap requires minimum shear reinforcement, which
// this no-shear-reinforcement punching case does not have. So the cap holds,
// and it only bites above f'c = 10,000 psi.
const sqrt_fc = Math.min(Math.sqrt(fc_psi), 100);
const vc_psi = least * lambda_s * lambda * sqrt_fc;
const phi_vc_kip = (0.75 * vc_psi * bo_in * d_in) / 1000;
return {
bo_in, beta, alpha_s, t2, t3, least, governs, lambda_s, vc_psi, phi_vc_kip,
note: "ACI 318-19 Table 22.6.5.2 two-way (punching) shear on the d/2 critical perimeter: vc is the least of 4 lambda sqrt(f'c), (2 + 4/beta) lambda sqrt(f'c), and (2 + alpha_s d/bo) lambda sqrt(f'c), with alpha_s = 40/30/20 for an interior/edge/corner column and phi = 0.75; phi Vc = phi vc bo d. All three terms also carry the 22.5.5.1.3 size-effect factor lambda_s = sqrt(2 / (1 + d/10)) capped at 1.0 (new in the 2019 edition), so lambda_s is 1.0 up to d = 10 in and drops below it for deeper slabs and footings, cutting the punching capacity of thick members. sqrt(f'c) is capped at 100 psi per 22.5.3.1 (the 22.5.3.2 exception needs minimum shear reinforcement, which this case lacks), so it only affects f'c above 10,000 psi. Shear without unbalanced-moment transfer (no gamma_v amplification), no shear reinforcement or drop panel, rectangular column; the edge (three-sided) and corner (two-sided) perimeters assume the slab edge flush with the column face, c1 measured perpendicular to the edge. A design aid, not a substitute for the structural engineer of record's stamped design.",
};
}
export const rcPunchingShearExample = { inputs: { c1_in: 20, c2_in: 20, d_in: 6, fc_psi: 4000, position: "interior", lambda: 1.0 } };
CONCRETE_RENDERERS["rc-punching-shear"] = _simpleRenderer({
citation: "Citation: ACI 318-19 Table 22.6.5.2 two-way shear (least of 4, 2 + 4/beta, 2 + alpha_s d/bo, each x the 22.5.5.1.3 size-effect factor lambda_s and lambda sqrt(f'c)) on the 22.6.4.1 d/2 critical perimeter, alpha_s = 40/30/20 interior/edge/corner, the 22.5.3.1 sqrt(f'c) <= 100 psi cap, and phi = 0.75, by name. No unbalanced-moment transfer or shear reinforcement. A design aid, not a substitute for the engineer of record.",
example: rcPunchingShearExample.inputs,
fields: [
{ key: "c1_in", label: "Column dimension c1 (in)", kind: "number" },
{ key: "c2_in", label: "Column dimension c2 (in)", kind: "number" },
{ key: "d_in", label: "Effective slab depth d (in)", kind: "number" },
{ key: "fc_psi", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "position", label: "Column position (alpha_s)", kind: "select", options: [
{ value: "interior", label: "Interior (alpha_s = 40)" },
{ value: "edge", label: "Edge (alpha_s = 30)" },
{ value: "corner", label: "Corner (alpha_s = 20)" },
], default: "interior" },
{ key: "lambda", label: "Lightweight factor lambda", kind: "number" },
],
outputs: [
{ key: "bo", id: "rps-out-bo", label: "Critical perimeter bo (at d/2)", value: (r) => fmt(r.bo_in, 0) + " in" },
{ key: "gov", id: "rps-out-gov", label: "Governing term", value: (r) => r.governs },
{ key: "ls", id: "rps-out-ls", label: "Size-effect factor lambda_s", value: (r) => fmt(r.lambda_s, 3) },
{ key: "vc", id: "rps-out-vc", label: "Concrete shear stress vc", value: (r) => fmt(r.vc_psi, 0) + " psi" },
{ key: "pvc", id: "rps-out-pvc", label: "Design capacity phi Vc", value: (r) => fmt(r.phi_vc_kip, 1) + " kip" },
{ key: "n", id: "rps-out-n", label: "Note", value: (r) => r.note },
],
compute: computeRcPunchingShear,
});
// Axial load added 2026-10-09: Table 22.5.5.1(c) carries + Nu/(6 Ag), Nu positive in compression, with
// Nu/(6 Ag) <= 0.05 f'c (22.5.5.1.2), Vc >= 0 and Vc <= 5 lambda sqrt(f'c) bw d (22.5.5.1.1).
// dims: in { fc_psi: M L^-1 T^-2, bw_in: L, d_in: L, as_in2: L^2, vu_kip: M L T^-2, lambda: dimensionless, nu_kip: M L T^-2, ag_in2: L^2 } out: { rho_w: dimensionless, lambda_s: dimensionless, vc_psi: M L^-1 T^-2, vc_kip: M L T^-2, phi_vc_kip: M L T^-2, vc_simplified_kip: M L T^-2, axial_term_psi: M L^-1 T^-2 }
export function computeRcOneWayShear({ fc_psi = 4000, bw_in = 0, d_in = 0, as_in2 = 0, vu_kip = 0, lambda = 1.0, nu_kip = 0, ag_in2 = 0 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(fc_psi > 0)) return { error: "Concrete strength f'c must be positive (psi)." };
if (!(bw_in > 0)) return { error: "Section width bw must be positive (in)." };
if (!(d_in > 0)) return { error: "Effective depth d must be positive (in)." };
if (!(as_in2 > 0)) return { error: "Tension reinforcement As must be positive (in^2)." };
if (!(lambda > 0 && lambda <= 1)) return { error: "The lightweight factor lambda is over 0 and up to 1.0." };
if (nu_kip !== 0 && !(ag_in2 > 0)) return { error: "With an axial load, enter the gross section area Ag (in^2)." };
if (ag_in2 < 0) return { error: "Gross area Ag cannot be negative (in^2)." };
const sqrt_fc = Math.min(Math.sqrt(fc_psi), 100); // 22.5.3.1 caps sqrt(f'c) at 100 psi
const rho_w = as_in2 / (bw_in * d_in);
const lambda_s = Math.min(Math.sqrt(2 / (1 + d_in / 10)), 1.0);
const axial_term_psi = nu_kip !== 0 ? Math.min((nu_kip * 1000) / (6 * ag_in2), 0.05 * fc_psi) : 0;
const vc_cap_psi = 5 * lambda * sqrt_fc;
const vc_raw_psi = 8 * lambda_s * lambda * Math.cbrt(rho_w) * sqrt_fc + axial_term_psi;
const vc_psi = Math.min(Math.max(vc_raw_psi, 0), vc_cap_psi);
const vc_kip = (vc_psi * bw_in * d_in) / 1000;
const phi_vc_kip = 0.75 * vc_kip;
const vc_simplified_kip = (2 * lambda * sqrt_fc * bw_in * d_in) / 1000;
const adequate = vu_kip > 0 ? vu_kip <= phi_vc_kip + 1e-9 * Math.abs(phi_vc_kip) : null;
return {
rho_w, lambda_s, vc_psi, vc_kip, phi_vc_kip, vc_simplified_kip, adequate, axial_term_psi, vc_capped: vc_raw_psi > vc_cap_psi, vc_floored: vc_raw_psi < 0,
note: "ACI 318-19 Table 22.5.5.1(c) one-way (beam-action) shear for a member WITHOUT at least minimum shear reinforcement: Vc = [8 lambda_s lambda (rho_w)^(1/3) sqrt(f'c) + Nu/(6 Ag)] bw d, with Nu positive in compression and Nu/(6 Ag) no more than 0.05 f'c; Vc is not less than zero (axial tension can erase it) or more than 5 lambda sqrt(f'c) bw d; where rho_w = As/(bw d) is the longitudinal tension-steel ratio and lambda_s = sqrt(2 / (1 + d/10)) capped at 1.0 is the 22.5.5.1.3 size-effect factor (new in the 2019 edition), phi = 0.75. This is the check that governs footings, one-way slabs, and shallow beams with no stirrups; the size-effect penalty and the (rho_w)^(1/3) term make a deep, lightly reinforced section carry noticeably less than the old 2 sqrt(f'c) rule of thumb (shown for comparison). sqrt(f'c) is capped at 100 psi per 22.5.3.1. If the member has at least Av,min stirrups, lambda_s = 1.0 and the simplified 2 lambda sqrt(f'c) (or the (a) expressions) applies instead - see rc-beam-shear. Normalweight unless lambda is set. A design aid, not a substitute for the structural engineer of record's stamped design.",
};
}
export const rcOneWayShearExample = { inputs: { fc_psi: 4000, bw_in: 12, d_in: 16, as_in2: 1.0, vu_kip: 0, lambda: 1.0 } };
CONCRETE_RENDERERS["rc-one-way-shear"] = _simpleRenderer({
citation: "Citation: ACI 318-19 Table 22.5.5.1(c) one-way shear Vc = 8 lambda_s lambda (rho_w)^(1/3) sqrt(f'c) bw d for a member without at least minimum shear reinforcement, the 22.5.5.1.3 size-effect factor lambda_s = sqrt(2/(1 + d/10)) <= 1.0, the 22.5.3.1 sqrt(f'c) <= 100 psi cap, and phi = 0.75, by name. Axial load enters as Nu/(6 Ag) when entered; the stirrup-reinforced case is rc-beam-shear. A design aid, not a substitute for the engineer of record.",
example: rcOneWayShearExample.inputs,
fields: [
{ key: "fc_psi", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "bw_in", label: "Section width bw (in)", kind: "number" },
{ key: "d_in", label: "Effective depth d (in)", kind: "number" },
{ key: "as_in2", label: "Tension steel As (in²)", kind: "number" },
{ key: "vu_kip", label: "Factored shear Vu (kip, 0 = capacity only)", kind: "number" },
{ key: "lambda", label: "Lightweight factor lambda (1.0 normalweight)", kind: "number" },
{ key: "nu_kip", label: "Factored axial load Nu (kip, + compression, - tension; 0 if none)", kind: "number", attrs: { step: "any" } },
{ key: "ag_in2", label: "Gross section area Ag (in², needed with Nu)", kind: "number" },
],
outputs: [
{ key: "rw", id: "rows-out-rw", label: "Reinforcement ratio rho_w = As/(bw d)", value: (r) => fmt(r.rho_w, 4) },
{ key: "ls", id: "rows-out-ls", label: "Size-effect factor lambda_s", value: (r) => fmt(r.lambda_s, 3) },
{ key: "vc", id: "rows-out-vc", label: "Concrete shear stress vc", value: (r) => fmt(r.vc_psi, 0) + " psi" + (r.axial_term_psi !== 0 ? " (axial term " + (r.axial_term_psi > 0 ? "+" : "") + fmt(r.axial_term_psi, 0) + " psi)" : "") + (r.vc_capped ? ", capped at 5 lambda sqrt(f'c)" : "") + (r.vc_floored ? ", floored at zero by the tension" : "") },
{ key: "pvc", id: "rows-out-pvc", label: "Design capacity phi Vc (phi = 0.75)", value: (r) => fmt(r.phi_vc_kip, 1) + " kip" },
{ key: "cmp", id: "rows-out-cmp", label: "vs. simplified 2 sqrt(f'c) bw d", value: (r) => fmt(r.vc_simplified_kip, 1) + " kip (before phi)" },
{ key: "ok", id: "rows-out-ok", label: "Adequate for Vu", value: (r) => r.adequate === null ? "- (enter Vu to check)" : (r.adequate ? "YES (Vu <= phi Vc)" : "NO (Vu > phi Vc; needs stirrups or a deeper section)") },
{ key: "n", id: "rows-out-n", label: "Note", value: (r) => r.note },
],
compute: computeRcOneWayShear,
});
// dims: in { fc_psi: M L^-1 T^-2, fyt_psi: M L^-1 T^-2, bw_in: L, d_in: L, av_in2: L^2, vu_kip: M L T^-2, lambda: dimensionless, stirrup_type: dimensionless } out: { av_min_per_s: L, s_max_av_min_in: L, trigger_kip: M L T^-2, vc_kip: M L T^-2, vs_req_kip: M L T^-2, vs_max_kip: M L T^-2, phi_vn_max_kip: M L T^-2, s_max_code_in: L, s_max_in: L }
export function computeRcMinShearReinforcement({ fc_psi = 4000, fyt_psi = 60000, bw_in = 0, d_in = 0, av_in2 = 0, vu_kip = 0, lambda = 1.0, stirrup_type = "bar" } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(fc_psi > 0)) return { error: "Concrete strength f'c must be positive (psi)." };
if (!(fyt_psi > 0)) return { error: "Stirrup yield fyt must be positive (psi)." };
if (!(bw_in > 0)) return { error: "Web width bw must be positive (in)." };
if (!(d_in > 0)) return { error: "Effective depth d must be positive (in)." };
if (!(av_in2 > 0)) return { error: "Stirrup area Av must be positive (in^2)." };
if (!(lambda > 0 && lambda <= 1)) return { error: "The lightweight factor lambda is over 0 and up to 1.0." };
// ACI 318-19 Table 20.2.2.4(a) caps fyt for shear at 60,000 psi for deformed bars and 80,000 psi
// for welded deformed wire; the shear-friction and corbel tiles already apply the same table.
// Until 2026-10-07 a Grade 80 or 100 stirrup spread its spacing in proportion to the grade.
if (stirrup_type !== "bar" && stirrup_type !== "wwr") return { error: "Stirrup type must be bar (deformed bar) or wwr (welded deformed wire)." };
const fyt_used_psi = Math.min(fyt_psi, stirrup_type === "wwr" ? 80000 : 60000);
const PHI = 0.75;
// 22.5.3.1 caps sqrt(f'c) at 100 psi for the shear-STRENGTH terms (Vc, the
// 9.6.3.1 trigger, and the Vs limits). The 9.6.3.4 Av,min detailing minimum
// sits in Chapter 9 and carries no such cap, so it uses the true sqrt(f'c).
// The two differ only above f'c = 10,000 psi.
const sqrt_fc_raw = Math.sqrt(fc_psi);
const sqrt_fc = Math.min(sqrt_fc_raw, 100);
// 9.6.3.4: Av,min/s = greater of 0.75 sqrt(f'c) bw/fyt and 50 bw/fyt.
const av_min_a = 0.75 * sqrt_fc_raw * bw_in / fyt_used_psi;
const av_min_b = 50 * bw_in / fyt_used_psi;
const av_min_per_s = Math.max(av_min_a, av_min_b);
const av_min_governs = av_min_a >= av_min_b ? "the 0.75 sqrt(f'c) bw/fyt term (f'c above ~4,444 psi)" : "the 50 bw/fyt floor (f'c below ~4,444 psi)";
const s_max_av_min_in = av_in2 / av_min_per_s;
// 9.6.3.1: Av,min is required where Vu > phi lambda sqrt(f'c) bw d (ACI
// 318-19 replaced the 318-14 "Vu > 0.5 phi Vc" trigger with this one).
const trigger_kip = PHI * lambda * sqrt_fc * bw_in * d_in / 1000;
const av_min_required = vu_kip > 0 ? vu_kip > trigger_kip : null;
// Simplified Vc for a member with at least Av,min (Table 22.5.5.1(a)).
const vc_kip = 2 * lambda * sqrt_fc * bw_in * d_in / 1000;
const vs_req_kip = vu_kip > 0 ? Math.max(0, vu_kip / PHI - vc_kip) : 0;
// 22.5.1.2 section-size ceiling and the 9.7.6.2.2 spacing tightening.
const vs_max_kip = 8 * sqrt_fc * bw_in * d_in / 1000;
const vs_half_kip = 4 * sqrt_fc * bw_in * d_in / 1000;
const phi_vn_max_kip = PHI * (vc_kip + vs_max_kip);
const tightened = vs_req_kip > vs_half_kip;
const s_max_code_in = tightened ? Math.min(d_in / 4, 12) : Math.min(d_in / 2, 24);
const s_max_in = Math.min(s_max_code_in, s_max_av_min_in);
const section_adequate = vu_kip > 0 ? vu_kip <= phi_vn_max_kip + 1e-9 * Math.abs(phi_vn_max_kip) : null;
return {
av_min_per_s, av_min_governs, s_max_av_min_in, trigger_kip, av_min_required, fyt_used_psi,
vc_kip, vs_req_kip, vs_max_kip, phi_vn_max_kip, tightened, s_max_code_in, s_max_in, section_adequate,
note: "The detailing and section-size checks that sit BESIDE the rc-beam-shear strength calculation, all ACI 318-19. (1) 9.6.3.4 minimum shear reinforcement Av,min/s = greater of 0.75 sqrt(f'c) bw/fyt and 50 bw/fyt; the 50 bw/fyt floor governs below about 4,444 psi and the sqrt(f'c) term above it. Dividing the stirrup area by Av,min/s gives the widest spacing that still satisfies the minimum. (2) 9.6.3.1 requires Av,min wherever Vu exceeds phi lambda sqrt(f'c) bw d -- the 2019 edition REPLACED the old 318-14 'Vu > 0.5 phi Vc' trigger with this one, and Table 9.6.3.1 exempts shallow beams (h <= 10 in), joists, and certain slab-integral members, which are not modeled here. (3) 22.5.1.2 caps the stirrup contribution at Vs <= 8 sqrt(f'c) bw d, so the section itself can never carry more than phi (Vc + 8 sqrt(f'c) bw d) no matter how much steel is added -- past that the beam must get deeper or wider. (4) 9.7.6.2.2 caps stirrup spacing at the lesser of d/2 and 24 in, halved to d/4 and 12 in once Vs exceeds 4 sqrt(f'c) bw d. Vc uses the simplified Table 22.5.5.1(a) expression appropriate to a member with at least Av,min. A design aid, not a substitute for the structural engineer of record's stamped design.",
};
}
export const rcMinShearReinforcementExample = { inputs: { fc_psi: 4000, fyt_psi: 60000, bw_in: 12, d_in: 21.5, av_in2: 0.22, vu_kip: 40, lambda: 1.0 } };
CONCRETE_RENDERERS["rc-min-shear-reinforcement"] = _simpleRenderer({
citation: "Citation: ACI 318-19 9.6.3.4 (Av,min/s = greater of 0.75 sqrt(f'c) bw/fyt and 50 bw/fyt), 9.6.3.1 (Av,min required where Vu > phi lambda sqrt(f'c) bw d, the 2019 replacement for the 318-14 0.5 phi Vc trigger), 22.5.1.2 (Vs <= 8 sqrt(f'c) bw d section-size ceiling), and 9.7.6.2.2 (spacing at the lesser of d/2 and 24 in, halved past Vs = 4 sqrt(f'c) bw d), with phi = 0.75, by name. The Table 9.6.3.1 exemptions are not modeled. A design aid, not a substitute for the engineer of record.",
example: rcMinShearReinforcementExample.inputs,
fields: [
{ key: "fc_psi", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "fyt_psi", label: "Stirrup yield fyt (psi)", kind: "number" },
{ key: "stirrup_type", label: "Stirrup type (sets the 20.2.2.4(a) fyt cap)", kind: "select", options: [{ value: "bar", label: "Deformed bar (fyt up to 60,000 psi)", selected: true }, { value: "wwr", label: "Welded deformed wire (up to 80,000 psi)" }] },
{ key: "bw_in", label: "Web width bw (in)", kind: "number" },
{ key: "d_in", label: "Effective depth d (in)", kind: "number" },
{ key: "av_in2", label: "Stirrup area Av, both legs (in²)", kind: "number" },
{ key: "vu_kip", label: "Factored shear Vu (kip, 0 = detailing only)", kind: "number", default: 0, attrs: { step: "any" } },
{ key: "lambda", label: "Lightweight factor lambda (1.0 normalweight)", kind: "number" },
],
outputs: [
{ key: "am", id: "rmsr-out-am", label: "Minimum Av/s required", value: (r) => fmt(r.av_min_per_s, 4) + " in^2/in" },
{ key: "ag", id: "rmsr-out-ag", label: "Which minimum governs", value: (r) => r.av_min_governs },
{ key: "sa", id: "rmsr-out-sa", label: "Widest spacing meeting Av,min", value: (r) => fmt(r.s_max_av_min_in, 1) + " in" },
{ key: "tr", id: "rmsr-out-tr", label: "Av,min triggered above Vu of", value: (r) => fmt(r.trigger_kip, 1) + " kip" },
{ key: "rq", id: "rmsr-out-rq", label: "Av,min required here", value: (r) => r.av_min_required === null ? "- (enter Vu to check)" : (r.av_min_required ? "YES (Vu > phi lambda sqrt(f'c) bw d)" : "no (below the trigger)") },
{ key: "sc", id: "rmsr-out-sc", label: "Code max spacing", value: (r) => fmt(r.s_max_code_in, 2) + " in" + (r.tightened ? " (halved: Vs > 4 sqrt(f'c) bw d)" : "") },
{ key: "sg", id: "rmsr-out-sg", label: "Governing max spacing", value: (r) => fmt(r.s_max_in, 2) + " in" },
{ key: "vm", id: "rmsr-out-vm", label: "Section ceiling phi (Vc + Vs,max)", value: (r) => fmt(r.phi_vn_max_kip, 1) + " kip" },
{ key: "ok", id: "rmsr-out-ok", label: "Section big enough for Vu", value: (r) => r.section_adequate === null ? "- (enter Vu to check)" : (r.section_adequate ? "YES" : "NO (deepen or widen; more stirrups cannot help)") },
{ key: "n", id: "rmsr-out-n", label: "Note", value: (r) => r.note },
],
compute: computeRcMinShearReinforcement,
});
// dims: in { db_in: L, fy_psi: M L^-1 T^-2, fc_psi: M L^-1 T^-2, psi_e: dimensionless, psi_r: dimensionless, psi_o: dimensionless, lambda: dimensionless, joint: dimensionless } out: { psi_c: dimensionless, ldh_eq_in: L, ldh_in: L, ld_straight_bottom_in: L, ld_straight_top_in: L }
export function computeRcHookDevelopment({ db_in = 0, fy_psi = 60000, fc_psi = 4000, psi_e = 1.0, psi_r = 1.0, psi_o = 1.0, lambda = 1.0, joint = "general" } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(db_in > 0)) return { error: "Bar diameter must be positive (in)." };
if (!(fy_psi > 0) || !(fc_psi > 0)) return { error: "Steel and concrete strengths must be positive (psi)." };
if (!(psi_e > 0) || !(psi_r > 0) || !(psi_o > 0)) return { error: "Modification factors must be positive (1.0 is the base)." };
if (!(lambda > 0 && lambda <= 1)) return { error: "The lightweight factor lambda is over 0 and up to 1.0." };
if (joint !== "general" && joint !== "smf") return { error: "Joint must be general (25.4.3) or a special moment frame joint (18.8.5)." };
// For hooked bars lambda is 0.75 for ANY lightweight concrete (Table 25.4.3.2 and
// 18.8.5.1), not the 19.2.4 value: a sand-lightweight 0.85 entered here used to
// pass through and gave a hook 12% short.
if (lambda < 1) lambda = 0.75;
// 25.4.1.4 caps sqrt(f'c) at 100 psi in development-length calculations; in
// the denominator, the cap lengthens ldh above f'c = 10,000 psi (conservative).
const sqrt_fc = Math.min(Math.sqrt(fc_psi), 100);
if (joint === "smf") {
// ACI 318-19 18.8.5.1: a No. 3-11 hooked bar in a special moment frame beam-column
// joint takes ldh = fy db / (65 lambda sqrt(f'c)), at least 8 db and 6 in (10 db and
// 7.5 in in lightweight concrete); the 25.4.3 psi factors do not apply, the hook sits
// in the confined core. 18.8.5.3: a straight bar needs 2.5 ldh, or 3.25 ldh with
// more than 12 in of fresh concrete cast below it. Added 2026-10-08.
if (db_in > 1.41 + 1e-9) return { error: "ACI 18.8.5.1 covers No. 3 to No. 11 bars (db up to 1.41 in)." };
const ldh_eq_in = fy_psi * db_in / (65 * lambda * sqrt_fc);
const floor_in = lambda < 1 ? Math.max(10 * db_in, 7.5) : Math.max(8 * db_in, 6);
const ldh_in = Math.max(ldh_eq_in, floor_in);
return {
psi_c: null, ldh_eq_in, floor_in, floor_governs: floor_in >= ldh_eq_in, ldh_in, joint,
ld_straight_bottom_in: 2.5 * ldh_in, ld_straight_top_in: 3.25 * ldh_in,
note: "ACI 318-19 18.8.5.1 hooked-bar development in a special moment frame beam-column joint: ldh = fy db / (65 lambda sqrt(f'c)), at least 8 db and 6 in (10 db and 7.5 in for lightweight concrete), with the hook inside the confined core of the column or boundary element and bent into the joint. The constant already folds in the cover and tie confinement the joint provides, so the 25.4.3 psi factors are not applied. 18.8.5.3: a straight bar through the joint needs 2.5 ldh, or 3.25 ldh when more than 12 in of fresh concrete is cast below it. Headed bars are the rc-headed-bar-development tile. A design aid, not a substitute for the structural engineer of record's stamped detailing.",
};
}
const psi_c = fc_psi < 6000 ? fc_psi / 15000 + 0.6 : 1.0;
const ldh_eq_in = (fy_psi * psi_e * psi_r * psi_o * psi_c / (55 * lambda * sqrt_fc)) * Math.pow(db_in, 1.5);
const floor_in = Math.max(8 * db_in, 6);
const ldh_in = Math.max(ldh_eq_in, floor_in);
const floor_governs = floor_in >= ldh_eq_in;
return {
psi_c, ldh_eq_in, floor_in, floor_governs, ldh_in, joint, ld_straight_bottom_in: null, ld_straight_top_in: null,
note: "ACI 318-19 Eq. 25.4.3.1a standard-hook tension development: ldh = (fy psi_e psi_r psi_o psi_c / (55 lambda sqrt(f'c))) db^1.5, not less than max(8 db, 6 in), with the 25.4.3.2 factors (psi_e 1.0 uncoated / 1.2-1.3 coated; psi_r confinement; psi_o location/cover; psi_c = f'c/15,000 + 0.6 below 6,000 psi, 1.0 at or above). The db^1.5 scaling is why a hook multiple-of-db rule of thumb misleads. Standard 90/180 degree hooks on deformed bars only - headed bars (25.4.4) are rc-headed-bar-development, compression development is rc-compression-dev-length, and the bend-diameter/geometry detailing is separate. A design aid, not a substitute for the structural engineer of record's stamped detailing.",
};
}
export const rcHookDevelopmentExample = { inputs: { db_in: 1.0, fy_psi: 60000, fc_psi: 4000, psi_e: 1.0, psi_r: 1.0, psi_o: 1.0, lambda: 1.0 } };
CONCRETE_RENDERERS["rc-hook-development"] = _simpleRenderer({
citation: "Citation: ACI 318-19 Eq. 25.4.3.1a hooked-bar development ldh = (fy psi_e psi_r psi_o psi_c / (55 lambda sqrt(f'c))) db^1.5 with the 25.4.3.2 modification factors (psi_c = f'c/15,000 + 0.6 under 6,000 psi), the 25.4.1.4 sqrt(f'c) <= 100 psi cap (f'c above 10,000 psi does not further shorten ldh), and the max(8 db, 6 in) floor, by name. Standard 90/180 hooks; headed bars are the rc-headed-bar-development tile. A design aid, not a substitute for the engineer of record.",
example: rcHookDevelopmentExample.inputs,
fields: [
{ key: "db_in", label: "Bar diameter db (in; #8 = 1.0)", kind: "number" },
{ key: "fy_psi", label: "Steel yield fy (psi)", kind: "number" },
{ key: "fc_psi", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "psi_e", label: "Epoxy factor psi_e", kind: "number" },
{ key: "psi_r", label: "Confinement factor psi_r", kind: "number" },
{ key: "psi_o", label: "Location/cover factor psi_o", kind: "number" },
{ key: "lambda", label: "Lightweight factor lambda", kind: "number" },
{ key: "joint", label: "Where the hook is", kind: "select", options: [{ value: "general", label: "General (ACI 25.4.3)", selected: true }, { value: "smf", label: "Special moment frame joint (ACI 18.8.5)" }] },
],
outputs: [
{ key: "pc", id: "rhd-out-pc", label: "Strength factor psi_c", value: (r) => r.psi_c === null ? "not used in a special moment frame joint (18.8.5.1)" : fmt(r.psi_c, 3) },
{ key: "eq", id: "rhd-out-eq", label: "Equation ldh (before floor)", value: (r) => fmt(r.ldh_eq_in, 1) + " in" },
{ key: "ldh", id: "rhd-out-ldh", label: "Hook development ldh", value: (r) => fmt(r.ldh_in, 1) + " in" + (r.floor_governs ? (r.joint === "smf" ? " (the 18.8.5.1 minimum governs)" : " (the max(8db, 6 in) floor governs)") : "") },
{ key: "st", id: "rhd-out-st", label: "Straight bar through the joint instead", value: (r) => r.ld_straight_bottom_in === null ? "(special moment frame joints only)" : fmt(r.ld_straight_bottom_in, 1) + " in, or " + fmt(r.ld_straight_top_in, 1) + " in with more than 12 in of concrete cast below (18.8.5.3)" },
{ key: "n", id: "rhd-out-n", label: "Note", value: (r) => r.note },
],
compute: computeRcHookDevelopment,
});
// ===================== spec-v1930: headed deformed bar development in tension (ACI 318-19 25.4.4) =====================
// The hook tile says "headed bars (25.4.4) ... are separate". ACI 318-19 Eq. 25.4.4.2(a):
// ldt = (fy psi_e psi_p psi_o psi_c / (75 sqrt(f'c))) db^1.5, not less than 8 db or 6 in, with
// psi_p 1.0 (Att >= 0.3 Ahs or s >= 6 db) / 1.6, psi_o 1.0 (inside a column core with 2.5 in side cover,
// or side cover >= 6 db) / 1.25, psi_e 1.0 / 1.2 epoxy, psi_c = f'c/15,000 + 0.6 below 6,000 psi.
// ACI 318-25 prints 90 in place of 75 (Dextra's 318-25 calculation: #8, fy 70,000, f'c 5,800, epoxy -> 12.09 in).
// dims: in { db_in: L, fy_psi: M L^-1 T^-2, fc_psi: M L^-1 T^-2, psi_e: dimensionless, psi_p: dimensionless, psi_o: dimensionless, edition: dimensionless } out: { psi_c: dimensionless, ldt_eq_in: L, floor_in: L, ldt_in: L, min_clear_cover_in: L, min_spacing_in: L }
export function computeRcHeadedBarDevelopment({ db_in = 0, fy_psi = 60000, fc_psi = 4000, psi_e = 1.0, psi_p = 1.0, psi_o = 1.0, edition = "318-19" } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(db_in > 0)) return { error: "Bar diameter must be positive (in)." };
if (db_in > 1.41 + 1e-9) return { error: "ACI 25.4.4.1(b) permits a head on No. 11 bars and smaller (db up to 1.41 in); use a hook or the anchor provisions." };
if (!(fy_psi > 0) || !(fc_psi > 0)) return { error: "Steel and concrete strengths must be positive (psi)." };
const pe = Number(psi_e), pp = Number(psi_p), po = Number(psi_o);
if (![1.0, 1.2].includes(pe)) return { error: "psi_e is 1.0 (uncoated or galvanized) or 1.2 (epoxy coated)." };
if (![1.0, 1.6].includes(pp)) return { error: "psi_p is 1.0 or 1.6." };
if (![1.0, 1.25].includes(po)) return { error: "psi_o is 1.0 or 1.25." };
if (edition !== "318-19" && edition !== "318-25") return { error: "Edition must be ACI 318-19 or 318-25." };
const psi_c = fc_psi < 6000 ? fc_psi / 15000 + 0.6 : 1.0;
const sqrt_fc = Math.min(Math.sqrt(fc_psi), 100);
const coef = edition === "318-25" ? 90 : 75;
const ldt_eq_in = (fy_psi * pe * pp * po * psi_c / (coef * sqrt_fc)) * Math.pow(db_in, 1.5);
const floor_in = Math.max(8 * db_in, 6);
const ldt_in = Math.max(ldt_eq_in, floor_in);
return {
psi_c, coef, ldt_eq_in, floor_in, floor_governs: floor_in >= ldt_eq_in, ldt_in,
min_clear_cover_in: 2 * db_in, min_spacing_in: 3 * db_in, min_bearing_area_ratio: edition === "318-25" && fy_psi > 60000 ? 6 : 4,
note: "A head lets a bar develop in a fraction of a straight bar's length and with less congestion than a hook: the head bears on the concrete, and the bond along ldt does the rest. ldt runs from the critical section to the bearing face of the head and grows with db^1.5, so a No. 11 needs about twice the length of a No. 7 at the same stress. psi_p rewards ties parallel to the bar (Att at least 0.3 Ahs) or wide bar spacing; psi_o rewards a head inside a column core or with generous side cover. A head is permitted only on No. 11 and smaller bars in normalweight concrete, with the bearing area, cover, and spacing shown; a bar into a beam-column joint should still run to the far face of the confined core. Special moment frame joints (18.8.5) and compression development are separate. A design aid; the engineer of record governs.",
};
}
export const rcHeadedBarDevelopmentExample = { inputs: { db_in: 1.0, fy_psi: 70000, fc_psi: 5800, psi_e: 1.2, psi_p: 1.0, psi_o: 1.0, edition: "318-25" } };
CONCRETE_RENDERERS["rc-headed-bar-development"] = _simpleRenderer({
citation: "Citation: ACI 318-19 Eq. 25.4.4.2(a) headed deformed bar development in tension ldt = (fy psi_e psi_p psi_o psi_c / (75 sqrt(f'c))) db^1.5, not less than 8 db or 6 in, with the Table 25.4.4.3 factors and the 25.4.1.4 sqrt(f'c) <= 100 psi cap, by name; 25.4.4.1 limits heads to No. 11 bars and smaller in normalweight concrete with Abrg >= 4 Ab, clear cover >= 2 db, and spacing >= 3 db. ACI 318-25 prints 90 in place of 75, as worked in Dextra's ACI 318-25 headed bar calculation. The engineer of record governs.",
example: rcHeadedBarDevelopmentExample.inputs,
fields: [
{ key: "db_in", label: "Bar diameter db (in; #8 = 1.0, No. 11 maximum)", kind: "number" },
{ key: "fy_psi", label: "Steel yield fy (psi)", kind: "number" },
{ key: "fc_psi", label: "Concrete strength f'c (psi, normalweight)", kind: "number" },
{ key: "psi_e", label: "Coating psi_e", kind: "select", options: [{ value: "1", label: "1.0 uncoated or galvanized", selected: true }, { value: "1.2", label: "1.2 epoxy or dual coated" }] },
{ key: "psi_p", label: "Parallel ties psi_p", kind: "select", options: [{ value: "1", label: "1.0 Att >= 0.3 Ahs, or wide bar spacing (6 db in 318-19)", selected: true }, { value: "1.6", label: "1.6 other" }] },
{ key: "psi_o", label: "Location psi_o", kind: "select", options: [{ value: "1", label: "1.0 inside a column core with 2.5 in side cover, or side cover >= 6 db", selected: true }, { value: "1.25", label: "1.25 other" }] },
{ key: "edition", label: "Code edition", kind: "select", options: [{ value: "318-19", label: "ACI 318-19 (75 sqrt(f'c))", selected: true }, { value: "318-25", label: "ACI 318-25 (90 sqrt(f'c))" }] },
],
outputs: [
{ key: "pc", id: "rhb-out-pc", label: "Strength factor psi_c", value: (r) => fmt(r.psi_c, 3) },
{ key: "eq", id: "rhb-out-eq", label: "Equation ldt (before floor)", value: (r) => fmt(r.ldt_eq_in, 1) + " in" },
{ key: "ldt", id: "rhb-out-ldt", label: "Headed bar development ldt", value: (r) => fmt(r.ldt_in, 1) + " in" + (r.floor_governs ? " (the max(8 db, 6 in) floor governs)" : "") },
{ key: "lim", id: "rhb-out-lim", label: "Head use requires", value: (r) => "clear cover >= " + fmt(r.min_clear_cover_in, 2) + " in, spacing >= " + fmt(r.min_spacing_in, 2) + " in, head bearing area >= " + r.min_bearing_area_ratio + " Ab" },
{ key: "n", id: "rhb-out-n", label: "Note", value: (r) => r.note },
],
compute: computeRcHeadedBarDevelopment,
});
// ===================== spec-v299..v301: reinforced-concrete depth-2 batch =====================
// The ACI checks the strength tiles never make: the deflection-control minimum
// thickness (Table 7.3.1.1 / 9.3.1.1), the doubly-reinforced beam with
// compression steel, and shear friction across an interface (22.9).
const _RC_HMIN_DENOMS = {
slab: { simply: 20, "one-end": 24, "both-ends": 28, cantilever: 10 },
beam: { simply: 16, "one-end": 18.5, "both-ends": 21, cantilever: 8 },
};
const _RC_BETA1 = (fc_psi) => fc_psi <= 4000 ? 0.85 : Math.max(0.85 - 0.05 * (fc_psi - 4000) / 1000, 0.65);
// dims: in { l_ft: L, support: dimensionless, member: dimensionless, fy_psi: M L^-1 T^-2, wc_pcf: M L^-2 T^-2 } out: { base_in: L, kfy: dimensionless, klw: dimensionless, hmin_in: L, denom: dimensionless }
export function computeRcSlabMinThickness({ l_ft = 0, support = "simply", member = "slab", fy_psi = 60000, wc_pcf = 145 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(l_ft > 0)) return { error: "Span must be positive (ft)." };
if (!(fy_psi > 0)) return { error: "Steel yield fy must be positive (psi)." };
if (!(wc_pcf >= 90)) return { error: "Concrete unit weight must be at least 90 pcf, the lightest concrete the table's lightweight factor covers." };
// Table 7.3.1.1 (one-way slabs) and Table 9.3.1.1 (beams) are different rows.
const denoms = _RC_HMIN_DENOMS[member];
if (!denoms) return { error: "Member must be slab or beam." };
const denom = denoms[support];
if (!denom) return { error: "Support condition must be simply, one-end, both-ends, or cantilever." };
const base_in = (l_ft * 12) / denom;
const kfy = fy_psi === 60000 ? 1.0 : 0.4 + fy_psi / 100000;
const klw = wc_pcf >= 145 ? 1.0 : Math.max(1.65 - 0.005 * wc_pcf, 1.09);
const hmin_in = base_in * kfy * klw;
return {
base_in, kfy, klw, hmin_in, denom,
note: "ACI 318-19 Table 7.3.1.1 (one-way slabs) / 9.3.1.1 (beams) deflection-control minimum thickness: for a one-way slab l/20 simply supported, l/24 one end continuous, l/28 both ends continuous, l/10 cantilever; for a beam l/16, l/18.5, l/21 and l/8; times (0.4 + fy/100,000) for fy other than 60,000 psi and the 1.65 - 0.005 wc lightweight factor (>= 1.09). This is the depth that WAIVES an explicit deflection calculation - it applies to normalweight (unless wc is set) members not supporting or attached to partitions or construction likely to be damaged by large deflections, uses the clear span, and is not the strength (flexure/shear) design or the actual deflection of a thinner member. A design aid, not a substitute for the structural engineer of record's stamped design.",
};
}
export const rcSlabMinThicknessExample = { inputs: { l_ft: 12, support: "simply", fy_psi: 60000, wc_pcf: 145 } };
CONCRETE_RENDERERS["rc-slab-min-thickness"] = _simpleRenderer({
citation: "Citation: ACI 318-19 Table 7.3.1.1 (one-way slabs) / 9.3.1.1 (beams) deflection-control minimum thickness (slab l/20, l/24, l/28, l/10; beam l/16, l/18.5, l/21, l/8), the (0.4 + fy/100,000) grade modifier, and the lightweight factor, by name. The depth that waives a deflection check, not a strength design. A design aid, not a substitute for the engineer of record.",
example: rcSlabMinThicknessExample.inputs,
fields: [
{ key: "l_ft", label: "Span l (ft, clear span)", kind: "number" },
{ key: "support", label: "Support condition", kind: "select", options: [
{ value: "simply", label: "Simply supported (slab l/20, beam l/16)" },
{ value: "one-end", label: "One end continuous (slab l/24, beam l/18.5)" },
{ value: "both-ends", label: "Both ends continuous (slab l/28, beam l/21)" },
{ value: "cantilever", label: "Cantilever (slab l/10, beam l/8)" },
], default: "simply" },
{ key: "member", label: "Member", kind: "select", options: [
{ value: "slab", label: "One-way slab (Table 7.3.1.1)" },
{ value: "beam", label: "Beam (Table 9.3.1.1)" },
], default: "slab" },
{ key: "fy_psi", label: "Steel yield fy (psi)", kind: "number" },
{ key: "wc_pcf", label: "Concrete unit weight (pcf)", kind: "number" },
],
outputs: [
{ key: "base", id: "rsmt-out-base", label: "Base thickness (l / denominator)", value: (r) => fmt(r.base_in, 2) + " in" },
{ key: "mods", id: "rsmt-out-mods", label: "Grade / lightweight modifiers", value: (r) => fmt(r.kfy, 3) + " / " + fmt(r.klw, 3) },
{ key: "hmin", id: "rsmt-out-hmin", label: "Minimum thickness hmin", value: (r) => fmt(r.hmin_in, 2) + " in" },
{ key: "n", id: "rsmt-out-n", label: "Note", value: (r) => r.note },
],
compute: computeRcSlabMinThickness,
});
// rc-slab-max-span-for-thickness: inverse of rc-slab-min-thickness. The forward
// tile gives hmin from a span; with the depth fixed, the max span that still
// waives a deflection check is the inverse: hmin = l x (12/denom) x kfy x klw is
// linear in l, so max_span = available_thickness / hmin(at l = 1 ft). Reuses the
// forward at l = 1 ft to carry the denom / kfy / klw geometry in one place.
// dims: in { available_thickness_in: L, support: dimensionless, member: dimensionless, fy_psi: M L^-1 T^-2, wc_pcf: M L^-2 T^-2 } out: { max_span_ft: L, kfy: dimensionless, klw: dimensionless, denom: dimensionless }
export function computeRcSlabMaxSpanForThickness({ available_thickness_in = 0, support = "simply", member = "slab", fy_psi = 60000, wc_pcf = 145 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
const h = Number(available_thickness_in) || 0;
if (!(h > 0)) return { error: "Available thickness must be positive (in)." };
const base = computeRcSlabMinThickness({ l_ft: 1, support, member, fy_psi, wc_pcf });
if (base.error) return { error: base.error };
const hmin_per_ft = base.hmin_in;
const max_span_ft = h / hmin_per_ft;
return {
max_span_ft, kfy: base.kfy, klw: base.klw, denom: base.denom,
note: "ACI 318-19 Table 7.3.1.1 / 9.3.1.1 deflection-control minimum thickness solved for the span: with the depth fixed, this is the longest span that still WAIVES an explicit deflection calculation (max_span = h x denom / (12 kfy klw), denom 20 / 24 / 28 / 10 for a slab and 16 / 18.5 / 21 / 8 for a beam). A longer span needs either a deflection calculation or a deeper member. It applies to normalweight (unless wc is set) members not supporting or attached to partitions or construction likely to be damaged by large deflections, uses the clear span, and is not the strength (flexure/shear) design. A design aid, not a substitute for the structural engineer of record's stamped design.",
};
}
export const rcSlabMaxSpanForThicknessExample = { inputs: { available_thickness_in: 10, support: "both-ends", fy_psi: 60000, wc_pcf: 145 } };
CONCRETE_RENDERERS["rc-slab-max-span-for-thickness"] = _simpleRenderer({
citation: "Citation: ACI 318-19 Table 7.3.1.1 (one-way slabs) / 9.3.1.1 (beams) deflection-control minimum thickness solved for the span, max_span = h x denom / (12 kfy klw), by name. The longest span the depth covers without a deflection check, not a strength design. A design aid, not a substitute for the engineer of record.",
example: rcSlabMaxSpanForThicknessExample.inputs,
fields: [
{ key: "available_thickness_in", label: "Available thickness h (in)", kind: "number" },
{ key: "support", label: "Support condition", kind: "select", options: [
{ value: "simply", label: "Simply supported (slab l/20, beam l/16)" },
{ value: "one-end", label: "One end continuous (slab l/24, beam l/18.5)" },
{ value: "both-ends", label: "Both ends continuous (slab l/28, beam l/21)" },
{ value: "cantilever", label: "Cantilever (slab l/10, beam l/8)" },
], default: "simply" },
{ key: "member", label: "Member", kind: "select", options: [
{ value: "slab", label: "One-way slab (Table 7.3.1.1)" },
{ value: "beam", label: "Beam (Table 9.3.1.1)" },
], default: "slab" },
{ key: "fy_psi", label: "Steel yield fy (psi)", kind: "number" },
{ key: "wc_pcf", label: "Concrete unit weight (pcf)", kind: "number" },
],
outputs: [
{ key: "span", id: "rms-out-span", label: "Max span (no deflection check)", value: (r) => fmt(r.max_span_ft, 2) + " ft" },
{ key: "mods", id: "rms-out-mods", label: "Grade / lightweight modifiers", value: (r) => fmt(r.kfy, 3) + " / " + fmt(r.klw, 3) },
{ key: "n", id: "rms-out-n", label: "Note", value: (r) => r.note },
],
compute: computeRcSlabMaxSpanForThickness,
});
// dims: in { b_in: L, d_in: L, dp_in: L, as_in2: L^2, asp_in2: L^2, fc_psi: M L^-1 T^-2, fy_psi: M L^-1 T^-2 } out: { a_in: L, c_in: L, eps_sp: dimensionless, eps_t: dimensionless, mn_kipft: M L^2 T^-2, phi_mn_kipft: M L^2 T^-2 }
export function computeRcDoublyReinforced({ b_in = 0, d_in = 0, dp_in = 0, as_in2 = 0, asp_in2 = 0, fc_psi = 4000, fy_psi = 60000 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(b_in > 0) || !(d_in > 0)) return { error: "Beam width and effective depth must be positive (in)." };
if (!(dp_in > 0)) return { error: "Compression-steel depth d' must be positive (in)." };
if (!(dp_in < d_in)) return { error: "The compression-steel depth d' must be less than the effective depth d." };
if (!(as_in2 > 0)) return { error: "Tension steel As must be positive (in^2)." };
if (asp_in2 < 0) return { error: "Compression steel A's cannot be negative (in^2)." };
if (!(as_in2 > asp_in2)) return { error: "The tension steel must exceed the compression steel (As > A's) for a net tension couple." };
if (!(fc_psi > 0) || !(fy_psi > 0)) return { error: "Concrete and steel strengths must be positive (psi)." };
const beta1 = _RC_BETA1(fc_psi);
const Es = 29e6;
const eps_y = fy_psi / Es; // eps_ty = fy/Es, the yield (compression-controlled) strain
// Strain compatibility, both steels at their own stress. The closed form
// a = (As - A's) fy / (0.85 f'c b) assumes BOTH yield, and until 2026-10-10 it was
// used whatever the strains said: a heavily reinforced section whose tension
// steel had not yielded still got fy (9% high at As 12, A's 1 on a 14 x 22 beam),
// and a neutral axis below the tension steel still returned a capacity. The
// force balance 0.85 f'c b beta1 c + A's fs' = As fs is solved for c by
// bisection; where both steels yield it returns the closed form exactly.
const fsAt = (c) => Math.min(fy_psi, Es * 0.003 * (d_in - c) / c);
const fspAt = (c) => Math.max(-fy_psi, Math.min(fy_psi, Es * 0.003 * (c - dp_in) / c));
const unbalanced = (c) => 0.85 * fc_psi * b_in * beta1 * c + asp_in2 * fspAt(c) - as_in2 * fsAt(c);
let lo = 1e-6, hi = d_in;
for (let i = 0; i < 200; i++) { const mid = (lo + hi) / 2; if (unbalanced(mid) > 0) hi = mid; else lo = mid; }
const c_in = (lo + hi) / 2;
const a_in = beta1 * c_in;
const fs_psi = fsAt(c_in), fsp_psi = fspAt(c_in);
const eps_sp = 0.003 * (c_in - dp_in) / c_in;
const eps_t = 0.003 * (d_in - c_in) / c_in;
const tension_yields = eps_t >= eps_y * (1 - 1e-9);
// With no compression steel there is nothing to yield; the check does not apply.
const comp_yields = asp_in2 > 0 ? eps_sp >= eps_y * (1 - 1e-9) : true;
// ACI 318-19 21.2.2 / Table 21.2.2: the tension-controlled limit is eps_ty +
// 0.003 and the transition denominator is 0.003 - NOT the fixed 0.005 of
// 318-14. They coincide only near Grade 60 (eps_ty ~ 0.00207); for Grade 80
// and 100 the old 0.005 wrongly classifies transition sections as
// tension-controlled and overstates phi.
const tc_limit = eps_y + 0.003;
const tension_controlled = eps_t >= tc_limit;
const phi = tension_controlled ? 0.90 : (eps_t <= eps_y ? 0.65 : 0.65 + 0.25 * (eps_t - eps_y) / 0.003);
// Moments of the concrete block and the compression steel about the tension steel.
const mn_lbin = 0.85 * fc_psi * b_in * a_in * (d_in - a_in / 2) + asp_in2 * fsp_psi * (d_in - dp_in);
const mn_kipft = mn_lbin / 12000;
const phi_mn_kipft = phi * mn_kipft;
return {
a_in, c_in, beta1, eps_sp, eps_t, eps_y, tc_limit, comp_yields, tension_yields, fs_psi, fsp_psi, tension_controlled, phi, mn_kipft, phi_mn_kipft,
note: "ACI 318-19 doubly-reinforced rectangular-beam flexure: a = (As - A's) fy / (0.85 f'c b), c = a/beta1, and Mn = (As - A's) fy (d - a/2) + A's fy (d - d') - the singly-reinforced couple plus the steel-to-steel couple. This ASSUMES both steel layers yield (the compression-steel yield check epsilon's = 0.003 (c - d')/c >= fy/Es is flagged when it fails, where a rigorous solution would iterate with f's = Es epsilon's <= fy). The strength-reduction factor phi follows ACI 318-19 21.2.2: tension-controlled (phi = 0.90) at epsilon_t >= epsilon_ty + 0.003, compression-controlled (phi = 0.65) at epsilon_t <= epsilon_ty = fy/Es, and phi = 0.65 + 0.25 (epsilon_t - epsilon_ty)/0.003 between - the 2019 edition replaced the fixed 0.005 tension-controlled limit with epsilon_ty + 0.003, which matters for Grade 80 and 100 steel. Rectangular section (no T-beam flange); minimum steel, bar spacing, and development are not checked. A design aid, not a substitute for the structural engineer of record's stamped design.",
};
}
export const rcDoublyReinforcedExample = { inputs: { b_in: 14, d_in: 22, dp_in: 2.0, as_in2: 8.0, asp_in2: 3.0, fc_psi: 4000, fy_psi: 60000 } };
CONCRETE_RENDERERS["rc-doubly-reinforced"] = _simpleRenderer({
citation: "Citation: ACI 318-19 doubly-reinforced flexure a = (As - A's) fy / (0.85 f'c b), Mn = (As - A's) fy (d - a/2) + A's fy (d - d'), the compression-steel yield check epsilon's >= fy/Es, and the 21.2.2 strength-reduction factor (tension-controlled phi = 0.90 at epsilon_t >= epsilon_ty + 0.003, the 2019 replacement for the fixed 0.005 limit), by name. Both layers assumed to yield; rectangular section. A design aid, not a substitute for the engineer of record.",
example: rcDoublyReinforcedExample.inputs,
fields: [
{ key: "b_in", label: "Beam width b (in)", kind: "number" },
{ key: "d_in", label: "Effective depth d (in)", kind: "number" },
{ key: "dp_in", label: "Compression-steel depth d' (in)", kind: "number" },
{ key: "as_in2", label: "Tension steel As (in²)", kind: "number" },
{ key: "asp_in2", label: "Compression steel A's (in²)", kind: "number" },
{ key: "fc_psi", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "fy_psi", label: "Steel yield fy (psi)", kind: "number" },
],
outputs: [
{ key: "ac", id: "rdr-out-ac", label: "Stress block a / neutral axis c", value: (r) => fmt(r.a_in, 2) + " / " + fmt(r.c_in, 2) + " in" },
{ key: "yld", id: "rdr-out-yld", label: "Compression steel yields?", value: (r) => (r.comp_yields ? "yes (epsilon's " + fmt(r.eps_sp, 5) + " >= " + fmt(r.eps_y, 5) + ")" : "no - it is at " + fmt(r.fsp_psi / 1000, 1) + " ksi, and the capacity is computed at that stress") + (r.tension_yields ? "" : "; THE TENSION STEEL HAS NOT YIELDED (" + fmt(r.fs_psi / 1000, 1) + " ksi): the section is compression-controlled and over-reinforced") },
{ key: "tc", id: "rdr-out-tc", label: "Section (phi)", value: (r) => (r.tension_controlled ? "tension-controlled (phi 0.90)" : "transition (phi " + fmt(r.phi, 3) + ")") + ", epsilon_t " + fmt(r.eps_t, 4) },
{ key: "mn", id: "rdr-out-mn", label: "Nominal Mn", value: (r) => fmt(r.mn_kipft, 1) + " kip-ft" },
{ key: "pm", id: "rdr-out-pm", label: "Design phi Mn", value: (r) => fmt(r.phi_mn_kipft, 1) + " kip-ft" },
{ key: "n", id: "rdr-out-n", label: "Note", value: (r) => r.note },
],
compute: computeRcDoublyReinforced,
});
// dims: in { avf_in2: L^2, fy_psi: M L^-1 T^-2, ac_in2: L^2, fc_psi: M L^-1 T^-2, iface: dimensionless, lambda: dimensionless } out: { mu_f: dimensionless, vn0_kip: M L T^-2, cap_kip: M L T^-2, vn_kip: M L T^-2, phi_vn_kip: M L T^-2 }
export function computeRcShearFriction({ avf_in2 = 0, fy_psi = 60000, ac_in2 = 0, fc_psi = 4000, iface = "roughened", lambda = 1.0 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(avf_in2 > 0)) return { error: "The friction reinforcement area Avf must be positive (in^2)." };
if (!(fy_psi > 0)) return { error: "Steel yield fy must be positive (psi)." };
if (!(ac_in2 > 0)) return { error: "The interface area Ac must be positive (in^2)." };
if (!(fc_psi > 0)) return { error: "Concrete strength f'c must be positive (psi)." };
if (!(lambda > 0 && lambda <= 1)) return { error: "The lightweight factor lambda is over 0 and up to 1.0." };
const mus = { monolithic: 1.4, roughened: 1.0, unroughened: 0.6, steel: 0.7 };
const base_mu = mus[iface];
if (!base_mu) return { error: "The interface must be monolithic, roughened, unroughened, or steel." };
const mu_f = base_mu * lambda;
// Table 20.2.2.4(a) caps fy at 60,000 psi for shear friction, as the corbel tile has since
// 2026-10-03. Until 2026-10-07 this tile let Grade 80 and 100 bars raise Vn in proportion.
const vn0_kip = (mu_f * avf_in2 * Math.min(fy_psi, 60000)) / 1000;
// ACI 318-19 Table 22.9.4.4 caps Vn for EVERY interface, not just the
// monolithic/roughened case: (a) monolithic or roughened to 1/4 in is the
// least of 0.2 f'c, 480 + 0.08 f'c, and 1600 (x Ac); (b) all other cases
// (unroughened concrete, or against as-rolled steel) is the least of 0.2 f'c
// and 800 (x Ac). Omitting the (b) cap overstates Vn for those interfaces.
const case_a = iface === "monolithic" || iface === "roughened";
const cap_kip = case_a
? (Math.min(0.2 * fc_psi, 480 + 0.08 * fc_psi, 1600) * ac_in2) / 1000
: (Math.min(0.2 * fc_psi, 800) * ac_in2) / 1000;
const vn_kip = Math.min(vn0_kip, cap_kip);
const capped = cap_kip < vn0_kip;
const phi_vn_kip = 0.75 * vn_kip;
return {
mu_f, vn0_kip, cap_kip, vn_kip, capped, phi_vn_kip,
note: "ACI 318-19 22.9 shear-friction strength Vn = mu Avf fy, with mu = 1.4 lambda (monolithic), 1.0 lambda (against roughened hardened concrete), 0.6 lambda (unroughened), 0.7 lambda (against as-rolled steel), and phi = 0.75. Table 22.9.4.4 caps Vn: a monolithic or 1/4-in-roughened interface (case a) by the least of 0.2 f'c, 480 + 0.08 f'c, and 1600 (x Ac); every OTHER interface - unroughened concrete or as-rolled steel (case b) - by the least of 0.2 f'c and 800 (x Ac), which is the lower ceiling and governs sooner. This returns the shear transferred across a single well-defined plane by friction in the perpendicular (no permanent net tension) case; a net tension across the plane needs added reinforcement Avf = Vu/(phi fy mu) + An. The reduced caps for lightweight concrete, the anchorage/development of the crossing bars, and the concrete bracket/corbel bearing are separate. A design aid, not a substitute for the structural engineer of record's stamped design.",
};
}
export const rcShearFrictionExample = { inputs: { avf_in2: 2.0, fy_psi: 60000, ac_in2: 192, fc_psi: 4000, iface: "roughened", lambda: 1.0 } };
CONCRETE_RENDERERS["rc-shear-friction"] = _simpleRenderer({
citation: "Citation: ACI 318-19 22.9 shear friction Vn = mu Avf fy (mu 1.4/1.0/0.6/0.7 x lambda by interface), with the Table 22.9.4.4 cap - min(0.2 f'c, 480 + 0.08 f'c, 1600) Ac for a monolithic/roughened interface (case a) and min(0.2 f'c, 800) Ac for every other interface (case b) - and phi = 0.75, by name. Perpendicular (no net tension) case, single plane. A design aid, not a substitute for the engineer of record.",
example: rcShearFrictionExample.inputs,
fields: [
{ key: "avf_in2", label: "Reinforcement crossing the plane Avf (in²)", kind: "number" },
{ key: "fy_psi", label: "Steel yield fy (psi)", kind: "number" },
{ key: "ac_in2", label: "Interface area Ac (in²)", kind: "number" },
{ key: "fc_psi", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "iface", label: "Interface condition (mu)", kind: "select", options: [
{ value: "monolithic", label: "Monolithic (1.4)" },
{ value: "roughened", label: "Against roughened concrete (1.0)" },
{ value: "unroughened", label: "Against unroughened concrete (0.6)" },
{ value: "steel", label: "Against as-rolled steel (0.7)" },
], default: "roughened" },
{ key: "lambda", label: "Lightweight factor lambda", kind: "number" },
],
outputs: [
{ key: "mu", id: "rsf-out-mu", label: "Friction coefficient mu", value: (r) => fmt(r.mu_f, 2) },
{ key: "vn0", id: "rsf-out-vn0", label: "Friction strength mu Avf fy", value: (r) => fmt(r.vn0_kip, 1) + " kip" },
{ key: "cap", id: "rsf-out-cap", label: "Interface cap", value: (r) => fmt(r.cap_kip, 1) + " kip" + (r.capped ? " (governs)" : "") },
{ key: "vn", id: "rsf-out-vn", label: "Nominal Vn", value: (r) => fmt(r.vn_kip, 1) + " kip" },
{ key: "pv", id: "rsf-out-pv", label: "Design phi Vn", value: (r) => fmt(r.phi_vn_kip, 1) + " kip" },
{ key: "n", id: "rsf-out-n", label: "Note", value: (r) => r.note },
],
compute: computeRcShearFriction,
});
// ===================== spec-v378..v380: concrete material-properties trio =====================
// dims: in { fc_psi: M L^-1 T^-2, wc_pcf: M L^-3 } out: { ec_psi: M L^-1 T^-2, ec_ksi: M L^-1 T^-2, ec_normal_psi: M L^-1 T^-2 }
export function computeConcreteElasticModulus({ fc_psi = 4000, wc_pcf = 145 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(fc_psi > 0)) return { error: "Concrete strength f'c must be positive (psi)." };
if (!(wc_pcf > 0)) return { error: "Unit weight wc must be positive (pcf)." };
const ec_psi = Math.pow(wc_pcf, 1.5) * 33 * Math.sqrt(fc_psi);
const ec_normal_psi = 57000 * Math.sqrt(fc_psi);
const out_of_band = wc_pcf < 90 || wc_pcf > 160;
return {
ec_psi, ec_ksi: ec_psi / 1000, ec_normal_psi, ec_normal_ksi: ec_normal_psi / 1000, out_of_band,
note: "ACI 318-19 §19.2.2.1(a): Ec = wc^1.5 x 33 x sqrt(f'c) psi, valid for 90 <= wc <= 160 pcf; the §19.2.2.1(b) normalweight shortcut is 57000 x sqrt(f'c). Ec sets the stiffness behind every deflection, drift, and short-column calculation. A lightweight deck (lower wc) is markedly less stiff at the same strength. A design aid; the engineer of record's stamped design governs.",
};
}
export const concreteElasticModulusExample = { inputs: { fc_psi: 4000, wc_pcf: 145 } };
CONCRETE_RENDERERS["concrete-elastic-modulus"] = _simpleRenderer({
citation: "Citation: ACI 318-19 §19.2.2.1(a): Ec = wc^1.5 x 33 x sqrt(f'c) (psi), applicable for wc between 90 and 160 pcf; §19.2.2.1(b) gives the normalweight shortcut Ec = 57000 x sqrt(f'c). Returns the secant modulus used for deflection, drift, and stiffness; it is not the dynamic or tangent modulus, and the actual in-place modulus varies with aggregate and mix. A design aid, not a substitute for a licensed engineer's design -- the engineer of record's stamped design governs.",
example: concreteElasticModulusExample.inputs,
fields: [
{ key: "fc_psi", label: "Concrete strength f'c (psi)", kind: "number" },
{ key: "wc_pcf", label: "Unit weight wc (pcf, 145 normalweight)", kind: "number" },
],
outputs: [
{ key: "ec", id: "cem-out-ec", label: "Modulus Ec = wc^1.5 x 33 x sqrt(f'c)", value: (r) => fmt(r.ec_psi, 0) + " psi (" + fmt(r.ec_ksi, 0) + " ksi)" },
{ key: "en", id: "cem-out-en", label: "Normalweight shortcut 57000 x sqrt(f'c)", value: (r) => fmt(r.ec_normal_psi, 0) + " psi (" + fmt(r.ec_normal_ksi, 0) + " ksi)" },
{ key: "ob", id: "cem-out-ob", label: "Unit-weight range", value: (r) => r.out_of_band ? "OUT OF BAND (wc outside 90-160 pcf; ACI eq. not applicable)" : "within ACI 90-160 pcf" },
{ key: "n", id: "cem-out-n", label: "Note", value: (r) => r.note },
],
compute: computeConcreteElasticModulus,
});
// concrete-strength-from-modulus: inverse of concrete-elastic-modulus. The forward
// tile gives Ec from f'c; backing out the in-place strength from a measured (or
// specified) stiffness is the inverse: Ec = wc^1.5 x 33 x sqrt(f'c), so
// f'c = (Ec / (wc^1.5 x 33))^2.
// dims: in { ec_psi: M L^-1 T^-2, wc_pcf: M L^-3 } out: { fc_psi: M L^-1 T^-2, fc_ksi: M L^-1 T^-2 }
export function computeConcreteStrengthFromModulus({ ec_psi = 0, wc_pcf = 145 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
const ec = Number(ec_psi) || 0;
const wc = Number(wc_pcf) || 0;
if (!(ec > 0)) return { error: "Elastic modulus Ec must be positive (psi)." };
if (!(wc > 0)) return { error: "Unit weight wc must be positive (pcf)." };
const root = ec / (Math.pow(wc, 1.5) * 33);
const fc_psi = root * root;
const out_of_band = wc < 90 || wc > 160;
return {
fc_psi, fc_ksi: fc_psi / 1000, out_of_band,
note: "ACI 318-19 §19.2.2.1(a) Ec = wc^1.5 x 33 x sqrt(f'c) solved for the strength: f'c = (Ec / (wc^1.5 x 33))^2. Useful for backing out the equivalent in-place f'c from a resonance / sonic modulus test or a specified stiffness. The relation is valid for 90 <= wc <= 160 pcf; the in-place modulus scatters with aggregate and mix, so this is an equivalent strength, not a cylinder-break value. A design aid; the engineer of record's stamped design governs.",
};
}
export const concreteStrengthFromModulusExample = { inputs: { ec_psi: 3644147, wc_pcf: 145 } };
CONCRETE_RENDERERS["concrete-strength-from-modulus"] = _simpleRenderer({
citation: "Citation: ACI 318-19 §19.2.2.1(a) Ec = wc^1.5 x 33 x sqrt(f'c) solved for f'c = (Ec / (wc^1.5 x 33))^2, by name. Backs out an equivalent in-place strength from a measured or specified modulus; the in-place modulus scatters with aggregate and mix, so this is not a cylinder-break value. A design aid, not a substitute for the engineer of record.",
example: concreteStrengthFromModulusExample.inputs,
fields: [
{ key: "ec_psi", label: "Elastic modulus Ec (psi)", kind: "number" },
{ key: "wc_pcf", label: "Unit weight wc (pcf, 145 normalweight)", kind: "number" },
],
outputs: [
{ key: "fc", id: "csm-out-fc", label: "Equivalent strength f'c", value: (r) => fmt(r.fc_psi, 0) + " psi (" + fmt(r.fc_ksi, 2) + " ksi)" },
{ key: "ob", id: "csm-out-ob", label: "Unit-weight range", value: (r) => r.out_of_band ? "OUT OF BAND (wc outside 90-160 pcf; ACI eq. not applicable)" : "within ACI 90-160 pcf" },
{ key: "n", id: "csm-out-n", label: "Note", value: (r) => r.note },
],
compute: computeConcreteStrengthFromModulus,
});
// dims: in { fc_psi: M L^-1 T^-2, lambda: dimensionless } out: { fr_psi: M L^-1 T^-2, fr_fraction: dimensionless }
export function computeConcreteModulusOfRupture({ fc_psi = 4000, lambda = 1.0 } = {}) {
const _g = _finiteGuard(arguments[0]); if (_g) return _g;
if (!(fc_psi > 0)) return { error: "Concrete strength f'c must be positive (psi)." };