@theerapat-s28/ts-structural-eng-tools 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +89 -10
  3. package/dist/esm/fea/frame/elements/end-releases.js +20 -0
  4. package/dist/esm/fea/frame/elements/end-releases.js.map +1 -0
  5. package/dist/esm/fea/frame/elements/static-condensation.js +77 -26
  6. package/dist/esm/fea/frame/elements/static-condensation.js.map +1 -1
  7. package/dist/esm/fea/frame/post/member-diagram.js +13 -35
  8. package/dist/esm/fea/frame/post/member-diagram.js.map +1 -1
  9. package/dist/esm/fea/frame/post/present-state.js +38 -4
  10. package/dist/esm/fea/frame/post/present-state.js.map +1 -1
  11. package/dist/esm/fea/frame/solver/analyze-frame.js +13 -0
  12. package/dist/esm/fea/frame/solver/analyze-frame.js.map +1 -1
  13. package/dist/esm/fea/frame/solver/auto-restraint.js +104 -0
  14. package/dist/esm/fea/frame/solver/auto-restraint.js.map +1 -0
  15. package/dist/esm/fea/frame/solver/boundary-conditions.js +2 -3
  16. package/dist/esm/fea/frame/solver/boundary-conditions.js.map +1 -1
  17. package/dist/esm/fea/frame/solver/frame-system.js +30 -13
  18. package/dist/esm/fea/frame/solver/frame-system.js.map +1 -1
  19. package/dist/esm/fea/frame/solver/load-case-solver.js +23 -1
  20. package/dist/esm/fea/frame/solver/load-case-solver.js.map +1 -1
  21. package/dist/esm/fea/frame/validation/validate-model.js +18 -8
  22. package/dist/esm/fea/frame/validation/validate-model.js.map +1 -1
  23. package/dist/esm/index.js +2 -2
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/fea/frame/elements/end-releases.d.ts +19 -0
  26. package/dist/fea/frame/elements/end-releases.js +24 -0
  27. package/dist/fea/frame/elements/end-releases.js.map +1 -0
  28. package/dist/fea/frame/elements/static-condensation.d.ts +29 -10
  29. package/dist/fea/frame/elements/static-condensation.js +77 -26
  30. package/dist/fea/frame/elements/static-condensation.js.map +1 -1
  31. package/dist/fea/frame/index.d.ts +2 -2
  32. package/dist/fea/frame/post/member-diagram.d.ts +3 -3
  33. package/dist/fea/frame/post/member-diagram.js +13 -35
  34. package/dist/fea/frame/post/member-diagram.js.map +1 -1
  35. package/dist/fea/frame/post/present-state.js +38 -4
  36. package/dist/fea/frame/post/present-state.js.map +1 -1
  37. package/dist/fea/frame/solver/analyze-frame.js +13 -0
  38. package/dist/fea/frame/solver/analyze-frame.js.map +1 -1
  39. package/dist/fea/frame/solver/auto-restraint.d.ts +28 -0
  40. package/dist/fea/frame/solver/auto-restraint.js +108 -0
  41. package/dist/fea/frame/solver/auto-restraint.js.map +1 -0
  42. package/dist/fea/frame/solver/boundary-conditions.js +2 -3
  43. package/dist/fea/frame/solver/boundary-conditions.js.map +1 -1
  44. package/dist/fea/frame/solver/frame-system.d.ts +11 -3
  45. package/dist/fea/frame/solver/frame-system.js +30 -13
  46. package/dist/fea/frame/solver/frame-system.js.map +1 -1
  47. package/dist/fea/frame/solver/load-case-solver.d.ts +2 -1
  48. package/dist/fea/frame/solver/load-case-solver.js +23 -1
  49. package/dist/fea/frame/solver/load-case-solver.js.map +1 -1
  50. package/dist/fea/frame/types/model.type.d.ts +13 -7
  51. package/dist/fea/frame/types/results.type.d.ts +33 -3
  52. package/dist/fea/frame/validation/validate-model.js +18 -8
  53. package/dist/fea/frame/validation/validate-model.js.map +1 -1
  54. package/dist/index.d.ts +3 -3
  55. package/dist/index.js +4 -2
  56. package/dist/index.js.map +1 -1
  57. package/package.json +2 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,145 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@theerapat-s28/ts-structural-eng-tools` are recorded here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the package uses [semantic versioning](https://semver.org/): a **major** release can break existing code, a **minor** release adds features, a **patch** release fixes bugs.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [2.2.0] — 2026-10-24
10
+
11
+ ### Added
12
+
13
+ - **Partial-fixity end releases**: any member end DOF in `releases.start` / `releases.end` (`u1`, `u2`, `u3`, `r1`, `r2`, `r3`) can be a spring stiffness in kN/mm or kN·m/rad instead of `true`, modelling semi-rigid connections. The spring acts in series with the member end; `0` is the same as `true` (fully released). Springs never make a member a mechanism (307).
14
+ - **Release deformations**: every load case and combination result has `releaseDeformations`, the node-minus-member displacement across each released or spring end DOF of the members that have any — the rotation across a hinge, or the rotation of a semi-rigid connection.
15
+ - **Automatic joint restraint**: joint rotations that no member, support or spring resists (for example every joint of a pin-jointed truss) are held automatically instead of throwing 304, with no effect on the results. They are listed in `calculationDetails.autoRestrainedDofs`. A load with a moment about such a rotation still throws 304, now naming the load case.
16
+ - Types `DofRelease`, `ReleaseDeformation`, `MemberReleaseDeformations` and `AutoRestrainedDof`, exported from `/fea` and the package root.
17
+
18
+ ### Changed
19
+
20
+ - `MemberEndRelease` fields are typed `DofRelease` (`boolean | number`) instead of `boolean`. Release values that are not `true`, `false` or a finite number ≥ 0 are now rejected with 314 (`details.field` such as `"releases.end.r3"`); before, any truthy value counted as a release.
21
+
22
+ ## [2.1.1] — 2026-10-23
23
+
24
+ ### Fixed
25
+
26
+ - The package root now re-exports `distributeAreaLoads`, the `TrapezoidalMemberLoad`, `TorqueMemberLoad`, `NullArea`, `AreaLoad`, `AreaDistribution` and `AreaLoadDirection` types, and the `AREA_GEOMETRY_TOLERANCE` constant. Since 2.1.0 they were reachable only through the `/fea` and `/core` subpaths.
27
+ - The quick-start example on the docs landing page used `fc` instead of `fc_`.
28
+
29
+ ### Documentation
30
+
31
+ - README: added a quick-start example, the `core` and `utils` exports, error codes, release steps and the license.
32
+
33
+ ## [2.1.0] — 2026-10-23
34
+
35
+ ### Added
36
+
37
+ - **Trapezoidal member load** (`type: "trapezoidal"`): a load varying linearly from `w1` at `start` to `w2` at `end`. Member diagrams stay exact for it.
38
+ - **Torque member load** (`type: "torque"`): a uniform torque per unit length about the member's local 1 axis.
39
+ - **Null areas** (`model.areas`) and **area loads** (`model.areaLoads`, kN/m²): 3- or 4-node areas that add no stiffness and pass their loads to the edge members, distributed `"twoWay"`, `"oneWay"` or `"cantilever"`.
40
+ - `distributeAreaLoads(model)` returns the member loads generated from the areas, for checking or previewing (from `/fea`).
41
+ - `FrameAnalysisError` codes 315 (area geometry), 316 (an area edge without members) and 317 (supports that do not fit the distribution).
42
+
43
+ ### Changed
44
+
45
+ - Package description and README now present the library as a headless analysis engine for front-end applications.
46
+
47
+ ## [2.0.1] — 2026-10-11
48
+
49
+ Maintenance release with no changes to the library code.
50
+
51
+ ## [2.0.0] — 2026-10-11
52
+
53
+ ### Breaking changes
54
+
55
+ - `FrameModel` now requires `loadCases`. Loads move to the top-level lists `nodalLoads` and `memberLoads`, and every load names its `loadCase`.
56
+ - `analyzeFrame` returns `{ cases, combinations, envelope, … }` keyed by id, instead of a single result.
57
+
58
+ ### Added
59
+
60
+ - **3D frame analysis** (`/fea` subpath) by the direct stiffness method: uniform and point member loads in local or global directions, end releases, fixed and spring supports, ETABS-style local axes.
61
+ - **Load cases and combinations**: a self-weight multiplier per load case, combinations by superposition (including nested combinations), and an `envelope` reporting the governing combination.
62
+ - Member diagrams of forces (`N`, `V2`, `V3`, `T`, `M2`, `M3`) and deflections (`u1`–`u3` local, `ux`–`uz` global) along every member.
63
+ - `createFrameAnalysis` to compute member diagrams on demand, and `options.diagrams: "extremes"` for smaller results.
64
+ - `validateFrameModel` reports every problem in a model, with `details` locating it. `FrameAnalysisError` codes 301–314.
65
+ - `memberDemandEnvelope` turns analysis results into `MuPositive`, `MuNegative`, `Vu` and `Nu` for the RC capacity functions.
66
+ - `rectBeamToSectionProperties` (in `/rc`) converts an RC section into frame section properties, with the ACI 318-19 6.6.3.1.1 inertia modifiers and a unit weight for self-weight.
67
+ - `beam3dLocalStiffness`, `beam3dLocalStiffnessRightHanded` and `memberAxes`.
68
+ - Matrix and skyline solver helpers in `/utils`. Large models solve much faster (an 847-node model in about 1.5 s instead of 16.6 s).
69
+ - ES-module build in `dist/esm/` alongside CommonJS, with `"sideEffects": false` so bundlers can drop unused code.
70
+
71
+ ## [1.5.0] — 2026-08-17
72
+
73
+ ### Added
74
+
75
+ - **Side plate shear strengthening**, with two models because ACI 318-19 has no provisions for steel side plates:
76
+ - `sidePlateShearCapacityByWebYielding` (plate as a supplementary web, AISC 360 G2.1)
77
+ - `sidePlateShearCapacityByTensionTie` (plate as a tie across the crack, ACI 440.2R-17 §11.4 with steel in place of FRP)
78
+ - `compareSidePlateShearCapacity` runs both and reports the lower as governing.
79
+ - `hasSidePlate` type guard.
80
+ - `RCDesignError` codes 205 (inconsistent side plate geometry) and 206 (no shear headroom left for strengthening).
81
+
82
+ ## [1.4.0] — 2026-08-14
83
+
84
+ ### Changed
85
+
86
+ - Doubly reinforced sections are solved by strain compatibility. Compression steel below the neutral axis no longer throws: it is treated as tension steel and reported with a warning citing ACI 318-19 22.2.1.2.
87
+ - Steel plate jacketing measures every depth from the extreme compression fibre. A top plate now turns a singly reinforced section into a doubly reinforced one.
88
+ - `calculationDetails` reports `c`, `a`, `beta1`, `d`, `As` and `ro` for every section. `ro_` uses the same `b·d` basis as `ro`.
89
+ - `rectBeamBarLayout` takes `topBars` as optional.
90
+
91
+ ### Deprecated
92
+
93
+ - `RCDesignError` code 101 (`COMP_STEEL_DISTANCE_OVER_AC`) is no longer thrown. It will be removed in the next major release.
94
+
95
+ ## [1.3.0] — 2026-08-14
96
+
97
+ ### Added
98
+
99
+ - API reference site, published on GitHub Pages: <https://theerapat-s28.github.io/ts-structural-eng/>.
100
+ - `homepage`, `repository` and `bugs` links in the package metadata.
101
+
102
+ ## [1.2.0] — 2026-08-13
103
+
104
+ ### Added
105
+
106
+ - **Plate-to-concrete interface bolts** for steel plate jacketing:
107
+ - `plateInterfaceShearFlow` (shear flow `q = V·Q/I`, cracked or uncracked section)
108
+ - `boltShearCapacity` (ACI 318-19 17.7.1.2b)
109
+ - `plateInterfaceBoltRequirement` (required bolt pitch and count).
110
+ - Top and bottom jacketing plates are each optional, with the `hasTopPlate` and `hasBottomPlate` type guards.
111
+ - `RCDesignError` codes 201–204 for strengthening.
112
+
113
+ ### Fixed
114
+
115
+ - The RC shear functions are now exported from the package root, not only from `/rc`.
116
+
117
+ ## [1.1.0] — 2026-07-19
118
+
119
+ ### Added
120
+
121
+ - RC shear design per ACI 318-19: `concreteShearCapacity`, `stirrupShearCapacity` and `checkStirrupRequirement`, with `RCDesignError` code 105.
122
+ - `rectBeamBarLayout`: arranges bar groups into layers that meet the ACI 318-19 spacing rules and returns `d` and `d_`.
123
+
124
+ ## [1.0.1] — 2026-07-19
125
+
126
+ ### Fixed
127
+
128
+ - npm packaging: scoped package name `@theerapat-s28/ts-structural-eng-tools`, a build output that matches the `exports` map, and path aliases rewritten in the published JavaScript.
129
+
130
+ ## 1.0.0
131
+
132
+ First version, under the unscoped name `ts-structural-eng-tools`: `rectBeamMomentCapacity` (singly and doubly reinforced rectangular beams per ACI 318-19), steel plate jacketing, and concrete property helpers.
133
+
134
+ [Unreleased]: https://github.com/theerapat-s28/ts-structural-eng/compare/v2.2.0...HEAD
135
+ [2.2.0]: https://github.com/theerapat-s28/ts-structural-eng/compare/v2.1.1...v2.2.0
136
+ [2.1.1]: https://github.com/theerapat-s28/ts-structural-eng/compare/v2.1.0...v2.1.1
137
+ [2.1.0]: https://github.com/theerapat-s28/ts-structural-eng/compare/v2.0.1...v2.1.0
138
+ [2.0.1]: https://github.com/theerapat-s28/ts-structural-eng/compare/v2.0.0...v2.0.1
139
+ [2.0.0]: https://github.com/theerapat-s28/ts-structural-eng/compare/v1.5.0...v2.0.0
140
+ [1.5.0]: https://github.com/theerapat-s28/ts-structural-eng/compare/v1.4.0...v1.5.0
141
+ [1.4.0]: https://github.com/theerapat-s28/ts-structural-eng/compare/v1.3.0...v1.4.0
142
+ [1.3.0]: https://github.com/theerapat-s28/ts-structural-eng/compare/v1.2.0...v1.3.0
143
+ [1.2.0]: https://github.com/theerapat-s28/ts-structural-eng/compare/v1.1.0...v1.2.0
144
+ [1.1.0]: https://github.com/theerapat-s28/ts-structural-eng/compare/v1.0.1...v1.1.0
145
+ [1.0.1]: https://github.com/theerapat-s28/ts-structural-eng/releases/tag/v1.0.1
package/README.md CHANGED
@@ -59,6 +59,58 @@ Or install this repository's dependencies to work on the library itself:
59
59
  pnpm install
60
60
  ```
61
61
 
62
+ ## Quick start
63
+
64
+ Analyse a simply supported RC beam under self-weight and live load, envelope the design demands, and check them against the section's flexural capacity:
65
+
66
+ ```ts
67
+ import { analyzeFrame, memberDemandEnvelope } from "@theerapat-s28/ts-structural-eng-tools/fea";
68
+ import type { FrameModel } from "@theerapat-s28/ts-structural-eng-tools/fea";
69
+ import {
70
+ rectBeamMomentCapacity,
71
+ rectBeamToSectionProperties,
72
+ } from "@theerapat-s28/ts-structural-eng-tools/rc";
73
+
74
+ // 300×600 beam, f'c 28 MPa, fy 420 MPa, As 1800 mm²
75
+ const beam = { Es: 200_000, fc_: 28, fy: 420, As: 1800, b: 300, h: 600, d: 540 };
76
+ const { sectionProperties } = rectBeamToSectionProperties(beam, { member: "beam" });
77
+
78
+ const model: FrameModel = {
79
+ nodes: [
80
+ { id: "A", x: 0, y: 0, z: 0 },
81
+ { id: "B", x: 6000, y: 0, z: 0 }, // mm
82
+ ],
83
+ members: [{ id: "AB", startNodeId: "A", endNodeId: "B", section: sectionProperties }],
84
+ restraints: [
85
+ { nodeId: "A", ux: true, uy: true, uz: true, rx: true },
86
+ { nodeId: "B", uy: true, uz: true },
87
+ ],
88
+ loadCases: [
89
+ { id: "D", type: "dead", selfWeight: 1 },
90
+ { id: "L", type: "live" },
91
+ ],
92
+ memberLoads: [{ loadCase: "L", type: "udl", memberId: "AB", direction: "Z", w: -15 }], // kN/m
93
+ combinations: [
94
+ {
95
+ id: "1.2D+1.6L",
96
+ terms: [
97
+ { case: "D", factor: 1.2 },
98
+ { case: "L", factor: 1.6 },
99
+ ],
100
+ },
101
+ ],
102
+ };
103
+
104
+ const result = analyzeFrame(model);
105
+ const demand = memberDemandEnvelope(result, "AB");
106
+ // demand.MuPositive → 131.328 (kN·m), demand.Vu → 87.552 (kN), governed by "1.2D+1.6L"
107
+
108
+ const { phiMn, warnings } = rectBeamMomentCapacity(beam);
109
+ // phiMn → 331.39 (kN·m), warnings → []
110
+ ```
111
+
112
+ Each module is also its own subpath export (`/fea`, `/rc`, `/strengthening`, `/core`, `/utils`), so a front end can import only what it uses. The package root re-exports the most-used functions and types from every module.
113
+
62
114
  ## Scripts
63
115
 
64
116
  | Command | Description |
@@ -80,6 +132,7 @@ src/
80
132
  ├── rc/ # Reinforced concrete design
81
133
  │ ├── index.ts # Module exports
82
134
  │ ├── general.ts # Concrete properties (beta1, Ec, unit conversion)
135
+ │ ├── rc-bar-layout.ts # Bar groups → layers satisfying ACI spacing, giving d and d'
83
136
  │ ├── rc-beam-design.ts # Beam moment capacity (singly/doubly reinforced) and shear capacity (Vc, Vs, stirrup requirement)
84
137
  │ └── rc-section-properties.ts # Section properties (E, G, A, I22, I33, J) for frame analysis
85
138
  ├── strengthening/ # Strengthening methods
@@ -92,17 +145,17 @@ src/
92
145
  │ └── frame/ # 3D frame analysis (direct stiffness method)
93
146
  │ ├── index.ts
94
147
  │ ├── area/ # Null areas: geometry, edge-member chains, area-load distribution
95
- │ ├── elements/ # Reference beam stiffness matrix, fixed-end forces, end-release condensation
148
+ │ ├── elements/ # Reference beam stiffness matrix, fixed-end forces, end releases and partial-fixity condensation
96
149
  │ ├── transformation/ # ETABS-style local axes and the 12×12 transformation
97
- │ ├── solver/ # System build + skyline factorisation, per-case solve, combinations, analyzeFrame
150
+ │ ├── solver/ # System build (with automatic joint restraint) + skyline factorisation, per-case solve, combinations, analyzeFrame
98
151
  │ ├── validation/ # validateFrameModel: every input problem, with its location
99
152
  │ ├── post/ # Result presentation, member force diagrams, envelopes, design demands
100
153
  │ └── types/ # Model (incl. null areas, load cases, combinations) and result types
101
154
  ├── core/ # Shared infrastructure
102
155
  │ ├── index.ts
103
156
  │ ├── constants/ # RC design constants (ACI 318) and frame-analysis tolerances
104
- │ ├── errors/ # Custom error classes
105
- │ └── types/ # Shared type definitions
157
+ │ ├── errors/ # RCDesignError and FrameAnalysisError with their coded entries
158
+ │ └── types/ # Shared types: RC sections, plate jacketing, section properties, warnings, units
106
159
  └── utils/ # Helper utilities
107
160
  ├── index.ts
108
161
  ├── math.ts # Quadratic solver, rounding
@@ -122,6 +175,9 @@ html-docs/ # HTML API reference, deployed to GitHub Pages
122
175
  ├── rc/index.html
123
176
  ├── strengthening/index.html
124
177
  └── fea/index.html
178
+ ROADMAP.md # FEA roadmap and implementation plan (not published)
179
+ .agents/skills/update-version/ # AI agent skill: bump the version and roll the changelog
180
+ .claude/skills/npm-publish/ # AI agent skill: release checklist for publishing to npm
125
181
  ```
126
182
 
127
183
  ## TypeScript path aliases
@@ -174,32 +230,55 @@ ACI 440.2R-17 11.4.2 (spacing, which defers to the ACI 318 limits) and 11.4.3 (`
174
230
 
175
231
  Linear-elastic 3D frame analysis by the direct stiffness method, imported from `@theerapat-s28/ts-structural-eng-tools/fea`. Global axes X, Y, Z (Z up) and ETABS-style local axes 1, 2, 3; inputs in mm, MPa, kN and kN·m, results in kN, kN·m, mm and rad.
176
232
 
177
- - **`analyzeFrame(model, options?)`** — Analyses every load case in `model.loadCases` (dead, superimposed dead, live, wind…, each with an optional `selfWeight` multiplier) and forms every combination in `model.combinations` by superposition. The loads are flat top-level lists, `model.nodalLoads` and `model.memberLoads` (uniform, trapezoidal and point loads in local or global directions, and torques about the member axis), each load tagged with its `loadCase`, so large models stay readable. Area loads (`model.areaLoads`, kN/m²) on null areas (`model.areas`) are first distributed to the members along each area's edges — see `distributeAreaLoads`. A combination is a list of factored terms `{ case, factor }` or `{ combination, factor }`; factors are entered by the user, and nested combinations are expanded. Supports end releases and fixed or spring supports. The stiffness matrix is factorised once for all cases. Returns `cases` and `combinations` keyed by id — each with displacements, reactions, member end forces, member diagrams along each member (forces `N`, `V2`, `V3`, `T`, `M2`, `M3` and deflections `u1`–`u3` local / `ux`–`uz` global, ready for plotting) and an equilibrium check — and an `envelope` of max/min over the combinations with the governing combination. Combination diagrams are rebuilt exactly, not summed from case extremes. The stiffness is stored in skyline (banded) form with nodes in reverse Cuthill–McKee order, so a building of ~850 nodes runs in about 1.5 s. Model and result are plain JSON, so it runs in a Web Worker. Invalid models, load cases or combinations and mechanisms throw `FrameAnalysisError` (codes 301–317) with `details` locating the problem.
233
+ - **`analyzeFrame(model, options?)`** — Analyses every load case in `model.loadCases` (dead, superimposed dead, live, wind…, each with an optional `selfWeight` multiplier) and forms every combination in `model.combinations` by superposition. The loads are flat top-level lists, `model.nodalLoads` and `model.memberLoads` (uniform, trapezoidal and point loads in local or global directions, and torques about the member axis), each load tagged with its `loadCase`, so large models stay readable. Area loads (`model.areaLoads`, kN/m²) on null areas (`model.areas`) are first distributed to the members along each area's edges — see `distributeAreaLoads`. A combination is a list of factored terms `{ case, factor }` or `{ combination, factor }`; factors are entered by the user, and nested combinations are expanded. Supports end releases — full (`true`) or partial fixity (a spring stiffness in series with the member end) on any of the six end DOFs, with the deformation across each release reported in `releaseDeformations` — and fixed or spring supports. Joint rotations that no member resists, as at pin-jointed truss joints, are held automatically and listed in `calculationDetails.autoRestrainedDofs`. The stiffness matrix is factorised once for all cases. Returns `cases` and `combinations` keyed by id — each with displacements, reactions, member end forces, member diagrams along each member (forces `N`, `V2`, `V3`, `T`, `M2`, `M3` and deflections `u1`–`u3` local / `ux`–`uz` global, ready for plotting) and an equilibrium check — and an `envelope` of max/min over the combinations with the governing combination. Combination diagrams are rebuilt exactly, not summed from case extremes. The stiffness is stored in skyline (banded) form with nodes in reverse Cuthill–McKee order, so a building of ~850 nodes runs in about 1.5 s. Model and result are plain JSON, so it runs in a Web Worker. Invalid models, load cases or combinations and mechanisms throw `FrameAnalysisError` (codes 301–317) with `details` locating the problem.
178
234
  - **`createFrameAnalysis(model, options?)`** — Same analysis, keeping the solution: returns `{ result, memberDiagram(resultId, memberId) }` so full member diagrams can be computed on demand. Combine with `options.diagrams: "extremes"`, which keeps diagram extremes (and the envelope) but drops the station arrays, for results about 4× smaller on large models.
179
- - **`validateFrameModel(model)`** — Checks a model without analysing it and returns every problem, each with `details` (`nodeId`, `memberId`, `areaId`, `edge`, `loadCaseId`, `combinationId`, `field`, `dof`) for highlighting in a UI. `analyzeFrame` runs it first and throws the first problem as a `FrameAnalysisError` whose `issues` lists all of them; non-finite numbers, negative springs and unknown option values are rejected (314).
235
+ - **`validateFrameModel(model)`** — Checks a model without analysing it and returns every problem, each with `details` (`nodeId`, `memberId`, `areaId`, `edge`, `loadCaseId`, `combinationId`, `field`, `dof`) for highlighting in a UI. `analyzeFrame` runs it first and throws the first problem as a `FrameAnalysisError` whose `issues` lists all of them; non-finite numbers, negative springs, invalid end-release values and unknown option values are rejected (314).
180
236
  - **`distributeAreaLoads(model)`** — Returns `{ memberLoads, warnings }`: the member loads that the model's null areas send to their edge members, exactly what `analyzeFrame` adds, for checking or previewing. A null area (`{ id, nodeIds, distribution, supportEdges? }`) has 3 or 4 nodes forming a planar convex polygon and adds no stiffness. `"twoWay"` sends each point to its nearest edge (the corner-bisector split, any convex shape); `"oneWay"` spans strips between the listed `supportEdges`, parallel to the unsupported edges, half to each end; `"cantilever"` (rectangles) puts `q·c` and a torque `q·c²/2` on its one support edge. Area loads (`{ loadCase, areaId, direction: "3" | "X" | "Y" | "Z", q }`) are per unit true area, `"3"` being the area's normal. Edges split by intermediate nodes are cut at them. Area problems throw 315 (geometry), 316 (an edge without members) or 317 (supports that do not fit the distribution).
181
237
  - **`memberDemandEnvelope(result, memberId, options?)`** — Reduces a member's diagrams to `MuPositive`, `MuNegative`, `Vu` (optionally at distance `d` from the supports) and `Nu`, enveloped over all combinations (or the `combinations` / `cases` you select), with the `governing` combination of each, for the RC capacity functions.
182
238
  - **`rectBeamToSectionProperties(section, options?)`** (from `rc`) — Converts a rectangular RC section into `SectionProperties` (`E`, `G`, `A`, `I22`, `I33`, `J`, `unitWeight`) with the ACI 318-19 6.6.3.1.1 inertia modifiers (0.35 beams, 0.70 columns) and a unit weight for self-weight (default 24 kN/m³, common practice rather than an ACI value).
183
239
  - **`beam3dLocalStiffness(E, G, A, I22, I33, J, L)`** — The 12×12 local stiffness matrix exactly as in the reference "Complete Beam Stiffness Matrix". Its bending rotations R2 and R3 follow the opposite sign to the right-hand rule; **`beam3dLocalStiffnessRightHanded`** returns the right-handed form that `analyzeFrame` uses.
184
240
  - **`memberAxes(start, end, angle?)`** — The ETABS-style local axes of a member as a direction-cosine matrix.
185
241
 
186
- ### General Utilities
242
+ ### Concrete properties (from `rc`)
187
243
 
188
244
  - **`concreteBeta(fc_)`** — ACI 318 β₁ factor
189
245
  - **`concreteElasticModulus(fc_)`** — Ec = 4700√f'c (MPa)
190
246
  - **`psiToMpa(psi)`** — Unit conversion
191
247
 
248
+ ### Errors and warnings (from `core`)
249
+
250
+ A design that is valid but breaks a code provision returns normally with an entry in `warnings`, citing the clause (e.g. `"ACI318-19, 9.6.1.2"`). A calculation whose assumptions break down throws instead, never returning a silently wrong result:
251
+
252
+ - **`RCDesignError`** — `code` and `message` from `Errors`: 1XX for RC beam design (e.g. 102, tensile steel not yielding; 105, `Vs` above the 22.5.1.2 limit), 2XX for strengthening and plate connections (201–206).
253
+ - **`FrameAnalysisError`** — `code` from `FrameErrors` (301–317), `details` locating the problem, and `issues` listing every problem `validateFrameModel` found.
254
+
255
+ `core` also exports the shared input types, the type guards `isSinglyReinforced`, `hasTopPlate`, `hasBottomPlate` and `hasSidePlate`, and the engineering constants (φ factors, ultimate concrete strain, inertia modifiers, frame-analysis tolerances).
256
+
257
+ ### Utilities (from `utils`)
258
+
259
+ Low-level helpers the engine is built on, exported for reuse: `solveQuadratic`, `roundToDecimalPlaces`, `mergeWarnings`, dense matrix helpers (`zeros`, `identity`, `transpose`, `matMul`, `matVec`), Cholesky and general linear solvers (`choleskyFactor`, `choleskySolveFactored`, `choleskySolve`, `solveLinearSystem`), and the skyline solver (`createSkyline`, `skylineAdd`, `skylineGet`, `skylineSize`, `skylineFactor`, `skylineSolve`, `reverseCuthillMcKee`).
260
+
192
261
  ## Build
193
262
 
194
263
  ```bash
195
264
  pnpm run build
196
- node dist/index.js
197
265
  ```
198
266
 
199
- The build emits a CommonJS build in `dist/` (for `require`, with the `.d.ts` types) and an ES-module build in `dist/esm/` (for `import` and bundlers such as Vite); `package.json` `exports` routes each subpath to the right one, and `"sideEffects": false` lets bundlers drop unused code.
267
+ The build emits a CommonJS build in `dist/` (for `require`, with the `.d.ts` types) and an ES-module build in `dist/esm/` (for `import` and bundlers such as Vite); `package.json` `exports` routes each subpath to the right one, and `"sideEffects": false` lets bundlers drop unused code. `pnpm run dev` runs [`examples/basic-usage.ts`](examples/basic-usage.ts) against the sources without building.
200
268
 
201
269
  ## Tests
202
270
 
203
271
  ```bash
204
- pnpm run test
272
+ pnpm run test # all tests
273
+ pnpm vitest run tests/rc/rc-beam-design.test.ts # one file
205
274
  ```
275
+
276
+ Tests live in `tests/`, mirroring `src/`, and check results against hand-calculated or textbook values.
277
+
278
+ ## Releasing
279
+
280
+ Releases follow semantic versioning and are published to npm with tags `vX.Y.Z`. What changed in each version is in [CHANGELOG.md](CHANGELOG.md). Preparing a version (changelog, version number, checks) is described in [`.agents/skills/update-version/SKILL.md`](.agents/skills/update-version/SKILL.md), and publishing (release commit and tag, dry run, publish, push tags) in [`.claude/skills/npm-publish/SKILL.md`](.claude/skills/npm-publish/SKILL.md).
281
+
282
+ ## License
283
+
284
+ [ISC](LICENSE)
@@ -0,0 +1,20 @@
1
+ /** Release keys in local DOF order at one member end. */
2
+ export const RELEASE_KEYS = ["u1", "u2", "u3", "r1", "r2", "r3"];
3
+ /**
4
+ * Spring stiffness unit conversion per DOF of one node or member end:
5
+ * kN/mm → N/mm for translations, kN·m/rad → N·mm/rad for rotations.
6
+ */
7
+ export const SPRING_UNIT_FACTOR = [1e3, 1e3, 1e3, 1e6, 1e6, 1e6];
8
+ /**
9
+ * The released local end DOFs of a member, in local DOF order. `true` and `0` are full releases,
10
+ * a positive number is a partial-fixity spring; `false` and omitted DOFs stay rigidly connected.
11
+ * Expects values that passed `validateFrameModel`.
12
+ */
13
+ export const memberEndReleases = (releases) => [releases?.start, releases?.end].flatMap((release, end) => RELEASE_KEYS.flatMap((key, k) => {
14
+ const value = release?.[key];
15
+ if (value === undefined || value === false)
16
+ return [];
17
+ const spring = value === true ? 0 : value * SPRING_UNIT_FACTOR[k];
18
+ return [{ index: 6 * end + k, spring }];
19
+ }));
20
+ //# sourceMappingURL=end-releases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"end-releases.js","sourceRoot":"","sources":["../../../../../src/fea/frame/elements/end-releases.ts"],"names":[],"mappings":"AAEA,yDAAyD;AACzD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC;AAE1E;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AAQ1E;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,QAA4B,EAAmB,EAAE,CAChF,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAW,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CACnE,YAAY,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAmB,EAAE;IAC/C,MAAM,KAAK,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,EAAE,CAAC;IACtD,MAAM,MAAM,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;IAClE,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;AAC1C,CAAC,CAAC,CACH,CAAC"}
@@ -1,55 +1,106 @@
1
1
  import { FrameAnalysisError, FrameErrors } from "../../../core/errors/frame-analysis.error.js";
2
2
  import { solveLinearSystem } from "../../../utils/matrix.js";
3
3
  /**
4
- * Statically condenses released local DOFs out of a member, so those DOFs carry no force.
4
+ * Condenses released local DOFs out of a member. Each released DOF r splits into a member-side
5
+ * value u_m and a node-side value u_n, tied by a spring s (s = 0 for a full release):
5
6
  *
6
- * With released set r and retained set c:
7
- * `k* = k_cc − k_cr·k_rr⁻¹·k_rc`, `f* = f_c − k_cr·k_rr⁻¹·f_r`.
8
- * The stiffness is condensed once; the returned function condenses the fixed-end forces of
9
- * each load case.
7
+ * ```
8
+ * u_c u_m u_n
9
+ * [ k_cc k_cr 0 ] A = k_rr + S, S = diag(s)
10
+ * [ k_rc A −S ]
11
+ * [ 0 −S S ]
12
+ * ```
10
13
  *
11
- * @param k - 12×12 local stiffness (right-hand)
12
- * @param released - Local DOF indices (0–11) to release
13
- * @throws {FrameAnalysisError} 307 if the released set makes the member a mechanism
14
+ * Eliminating u_m leaves a 12×12 matrix in the node DOFs:
15
+ * `k*_cc = k_cc − k_cr·A⁻¹·k_rc`, `k*_cn = k_cr·A⁻¹·S`, `k*_nn = S·A⁻¹·k_rr`
16
+ * (the stable form of `S − S·A⁻¹·S`), and `f*_c = f_c − k_cr·A⁻¹·f_r`, `f*_n = S·A⁻¹·f_r`.
17
+ * The member side follows from `u_m = A⁻¹·(S·u_n − k_rc·u_c − f_r)`.
18
+ * With every s = 0 this is the classical condensation with zero rows and columns at r.
19
+ *
20
+ * @param k - 12×12 local stiffness (right-hand), N and mm
21
+ * @param releases - Released local DOFs (0–11) with their springs, N/mm or N·mm/rad
22
+ * @throws {FrameAnalysisError} 307 if the full releases make the member a mechanism
14
23
  */
15
- export const condenseMember = (k, released) => {
16
- if (released.length === 0) {
17
- return { k, condenseFixedEndForces: (fixed) => [...fixed] };
24
+ export const condenseMember = (k, releases) => {
25
+ if (releases.length === 0) {
26
+ return {
27
+ k,
28
+ condenseFixedEndForces: (fixed) => [...fixed],
29
+ memberEndDisplacements: (nodeDisplacements) => [...nodeDisplacements],
30
+ };
18
31
  }
32
+ const released = releases.map((r) => r.index);
33
+ const s = releases.map((r) => r.spring);
19
34
  const retained = Array.from({ length: 12 }, (_, i) => i).filter((i) => !released.includes(i));
35
+ const nr = released.length;
36
+ const nc = retained.length;
20
37
  const sub = (rows, cols) => rows.map((r) => cols.map((c) => k[r][c]));
21
38
  const krr = sub(released, released);
22
39
  const krc = sub(released, retained);
23
- const kcr = sub(retained, released);
24
- const kcc = sub(retained, retained);
25
- // [k_rr⁻¹·k_rc | k_rr⁻¹]
26
- const rhs = released.map((_, i) => [...krc[i], ...released.map((__, j) => (i === j ? 1 : 0))]);
27
- const solved = solveLinearSystem(krr, rhs);
28
- if (!solved)
40
+ // A = k_rr + S, scaled to D·A·D with unit diagonal (D = diag(1/√A_ii)) so the singularity test
41
+ // does not depend on mixing N/mm and N·mm/rad; then A⁻¹·B = D·(D·A·D)⁻¹·(D·B).
42
+ const d = krr.map((row, i) => 1 / Math.sqrt(row[i] + s[i]));
43
+ const scaledA = krr.map((row, i) => row.map((v, j) => d[i] * (i === j ? v + s[i] : v) * d[j]));
44
+ // [A⁻¹·k_rc | A⁻¹·k_rr | A⁻¹]
45
+ const rhs = released.map((_, i) => [...krc[i], ...krr[i], ...released.map((__, j) => (i === j ? 1 : 0))].map((v) => d[i] * v));
46
+ const scaledSolution = solveLinearSystem(scaledA, rhs);
47
+ if (!scaledSolution)
29
48
  throw new FrameAnalysisError(FrameErrors.INVALID_RELEASE);
30
- const nc = retained.length;
49
+ const solved = scaledSolution.map((row, i) => row.map((v) => d[i] * v));
50
+ const AinvKrc = solved.map((row) => row.slice(0, nc));
51
+ const AinvKrr = solved.map((row) => row.slice(nc, nc + nr));
52
+ const Ainv = solved.map((row) => row.slice(nc + nr));
31
53
  const kStar = k.map((row) => row.map(() => 0));
32
- retained.forEach((r, ri) => {
54
+ retained.forEach((r) => {
33
55
  retained.forEach((c, ci) => {
34
- let value = kcc[ri][ci];
35
- released.forEach((_, j) => {
36
- value -= kcr[ri][j] * solved[j][ci];
37
- });
56
+ let value = k[r][c];
57
+ for (let j = 0; j < nr; j++)
58
+ value -= k[r][released[j]] * AinvKrc[j][ci];
38
59
  kStar[r][c] = value;
39
60
  });
61
+ released.forEach((c, j) => {
62
+ // k*_cn = k_cr·A⁻¹·S, mirrored into k*_nc
63
+ let value = 0;
64
+ for (let m = 0; m < nr; m++)
65
+ value += k[r][released[m]] * Ainv[m][j];
66
+ kStar[r][c] = value * s[j];
67
+ kStar[c][r] = value * s[j];
68
+ });
40
69
  });
41
- // k_cr·k_rr⁻¹, applied to f_r for every load case
42
- const kcrKrrInv = retained.map((_, ri) => released.map((__, m) => released.reduce((s, ___, j) => s + kcr[ri][j] * solved[j][nc + m], 0)));
70
+ released.forEach((r, i) => {
71
+ released.forEach((c, j) => {
72
+ // k*_nn = S·A⁻¹·k_rr, symmetrised against round-off
73
+ kStar[r][c] = (s[i] * AinvKrr[i][j] + s[j] * AinvKrr[j][i]) / 2;
74
+ });
75
+ });
76
+ // k_cr·A⁻¹, applied to f_r for every load case
77
+ const kcrAinv = retained.map((r) => released.map((_, m) => released.reduce((sum, rj, j) => sum + k[r][rj] * Ainv[j][m], 0)));
43
78
  return {
44
79
  k: kStar,
45
80
  condenseFixedEndForces: (fixed) => {
46
81
  const fStar = new Array(12).fill(0);
47
82
  const fr = released.map((r) => fixed[r]);
48
83
  retained.forEach((r, ri) => {
49
- fStar[r] = fixed[r] - kcrKrrInv[ri].reduce((s, v, m) => s + v * fr[m], 0);
84
+ fStar[r] = fixed[r] - kcrAinv[ri].reduce((sum, v, m) => sum + v * fr[m], 0);
85
+ });
86
+ released.forEach((r, i) => {
87
+ fStar[r] = s[i] * Ainv[i].reduce((sum, v, m) => sum + v * fr[m], 0);
50
88
  });
51
89
  return fStar;
52
90
  },
91
+ memberEndDisplacements: (nodeDisplacements, fixed) => {
92
+ const u = [...nodeDisplacements];
93
+ released.forEach((r, i) => {
94
+ let value = 0;
95
+ for (let m = 0; m < nr; m++) {
96
+ value += Ainv[i][m] * (s[m] * nodeDisplacements[released[m]] - fixed[released[m]]);
97
+ }
98
+ for (let ci = 0; ci < nc; ci++)
99
+ value -= AinvKrc[i][ci] * nodeDisplacements[retained[ci]];
100
+ u[r] = value;
101
+ });
102
+ return u;
103
+ },
53
104
  };
54
105
  };
55
106
  //# sourceMappingURL=static-condensation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"static-condensation.js","sourceRoot":"","sources":["../../../../../src/fea/frame/elements/static-condensation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"static-condensation.js","sourceRoot":"","sources":["../../../../../src/fea/frame/elements/static-condensation.ts"],"names":[],"mappings":"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"}
@@ -68,9 +68,9 @@ const internalForces = (startForce, loads, x, side) => {
68
68
  * smooth piece where N is at most quadratic and M at most cubic, and one 3-point Gauss panel per
69
69
  * interval integrates the (at most quartic) integrands exactly.
70
70
  *
71
- * The constants come from the member's own end displacements: end translations first, an end
72
- * rotation only where a translation is released. A released rotation (hinge) never enters, so
73
- * the curve is correct at hinged ends whose rotation differs from the node's.
71
+ * The constants a, b come from the member's own end translations (u at x = 0 and x = L), which
72
+ * differ from the node displacements across a released or spring end DOF; with them the curve
73
+ * is correct at hinged and semi-rigid ends whose rotation differs from the node's.
74
74
  */
75
75
  const displacementsOnGrid = (member, startForce, endDisplacements, loads, grid) => {
76
76
  const { E, A, I22, I33 } = member.section;
@@ -103,39 +103,16 @@ const displacementsOnGrid = (member, startForce, endDisplacements, loads, grid)
103
103
  }
104
104
  const [strain, k3, sk3, k2, sk2] = cumulative;
105
105
  const last = n - 1;
106
- /** a, b of `u = a + b·x + P(x)` from the first independent pair of end conditions. */
107
- const planeConstants = (translation, rotation, rotationSign, PL, // P(L)
108
- dPL) => {
109
- const { released } = member;
110
- // rows [ca, cb | rhs] of ca·a + cb·b = rhs
111
- const conditions = [];
112
- if (!released[translation])
113
- conditions.push([1, 0, endDisplacements[translation]]);
114
- if (!released[6 + translation]) {
115
- conditions.push([1, L, endDisplacements[6 + translation] - PL]);
116
- }
117
- if (!released[rotation])
118
- conditions.push([0, 1, rotationSign * endDisplacements[rotation]]);
119
- if (!released[6 + rotation]) {
120
- conditions.push([0, 1, rotationSign * endDisplacements[6 + rotation] - dPL]);
121
- }
122
- for (let i = 0; i < conditions.length; i++) {
123
- for (let j = i + 1; j < conditions.length; j++) {
124
- const [a1, b1, r1] = conditions[i];
125
- const [a2, b2, r2] = conditions[j];
126
- const det = a1 * b2 - a2 * b1;
127
- if (Math.abs(det) > 1e-12 * Math.max(1, L)) {
128
- return { a: (r1 * b2 - r2 * b1) / det, b: (a1 * r2 - a2 * r1) / det };
129
- }
130
- }
131
- }
132
- return { a: 0, b: 0 }; // unreachable: such releases are rejected as a mechanism (307)
106
+ /** a, b of `u = a + b·x + P(x)` from the end translations: u(0) = a, u(L) = a + b·L + P(L). */
107
+ const planeConstants = (translation, PL) => {
108
+ const a = endDisplacements[translation];
109
+ return { a, b: (endDisplacements[6 + translation] - a - PL) / L };
133
110
  };
134
111
  const P3 = (i) => grid[i] * k3[i] - sk3[i];
135
112
  const P2 = (i) => grid[i] * k2[i] - sk2[i];
136
- const plane2 = planeConstants(1, 5, 1, P3(last), k3[last]); // u2, with u2' = R3
137
- const plane3 = planeConstants(2, 4, -1, P2(last), k2[last]); // u3, with u3' = −R2
138
- const axialStart = member.released[0] ? endDisplacements[6] - strain[last] : endDisplacements[0];
113
+ const plane2 = planeConstants(1, P3(last)); // u2
114
+ const plane3 = planeConstants(2, P2(last)); // u3
115
+ const axialStart = endDisplacements[0];
139
116
  const onGrid = grid.map((x, i) => ({
140
117
  u1: axialStart + strain[i],
141
118
  u2: plane2.a + plane2.b * x + P3(i),
@@ -181,9 +158,10 @@ const present = (c, value) => roundToDecimalPlaces(value * SCALE[c], c === "u1"
181
158
  * Builds the force and displacement diagrams of one member from its end forces, its end
182
159
  * displacements and its loads.
183
160
  *
184
- * @param member - Member geometry, section, axes and end releases
161
+ * @param member - Member geometry, section and axes
185
162
  * @param startForce - Force the start node exerts on the member, right-hand local, N and N·mm
186
- * @param endDisplacements - The 12 local right-hand displacements of the member's end nodes, mm and rad
163
+ * @param endDisplacements - The member's own 12 local right-hand end displacements (equal to the
164
+ * node displacements except across released or spring end DOFs), mm and rad
187
165
  * @param loads - Member loads in local axes, N and mm
188
166
  * @param stationCount - Number of evenly spaced stations (at least 2); load breakpoints and
189
167
  * zero-shear points are added, with both sides reported at a point load