diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5855ba3..5853db8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1 +1 @@ -FROM mcr.microsoft.com/devcontainers/go:1.22-bookworm \ No newline at end of file +FROM mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b3e92f3..f62c2d8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,22 +2,25 @@ "name": "Gazelle", "runArgs": ["--name", "gazelle"], "build": { "dockerfile": "Dockerfile" }, + "postCreateCommand": "pwsh Setup.ps1", + "privileged": true, "features": { "ghcr.io/devcontainers/features/git:1": {}, - "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers/features/powershell:1": {} + "ghcr.io/devcontainers/features/github-cli:1": {} }, "customizations": { "vscode": { "extensions": [ - "golang.go", "github.copilot", "github.vscode-github-actions", - "ms-azuretools.vscode-docker", - "ms-vscode.powershell", - "ritwickdey.liveserver", "grapecity.gc-excelviewer", - "bierner.markdown-preview-github-styles" + "ms-dotnettools.csdevkit", + "ionide.ionide-fsharp", + "ms-vscode.powershell", + "ms-azuretools.vscode-docker", + "ms-dotnettools.dotnet-interactive-vscode", + "bierner.markdown-preview-github-styles", + "tamasfe.even-better-toml" ] } } diff --git a/.github/CITATION.cff b/.github/CITATION.cff new file mode 100644 index 0000000..fa21c9e --- /dev/null +++ b/.github/CITATION.cff @@ -0,0 +1,17 @@ +cff-version: 1.2.0 +title: Gazelle +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software +authors: + - given-names: James + family-names: Bayley +repository-code: 'https://github.com/gazellekit/gazelle' +abstract: >- + A fast, cross-platform engine for structural analysis & + design. +keywords: + - Civil Engineering + - Structural Engineering +license: AGPL-3.0-or-later \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..4db073b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [jamesbayley] \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a20c546..04183ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,15 +11,24 @@ defaults: shell: pwsh jobs: - build: - name: Build + setup: + name: CI runs-on: Ubuntu-Latest - + steps: - name: Checkout Repository uses: actions/checkout@v3 - - name: Setup Go - uses: actions/setup-go@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v3 with: - go-version-file: ./go.mod \ No newline at end of file + dotnet-version: "8.0.x" + + - name: Restore Project + run: ./Setup.ps1 + + - name: Check Code Formatting + run: dotnet fantomas . --check + + - name: Run Test Suite + run: dotnet test diff --git a/.gitignore b/.gitignore index a40388d..cffb04d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -# Go +# .NET bin/ -build/ +obj/ +.fake # JavaScript node_modules/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index a20ae51..5bd305c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,12 +1,14 @@ { "recommendations": [ - "golang.go", "github.copilot", "github.vscode-github-actions", - "ms-azuretools.vscode-docker", - "ms-vscode.powershell", - "ritwickdey.liveserver", "grapecity.gc-excelviewer", - "bierner.markdown-preview-github-styles" + "ms-dotnettools.csdevkit", + "ionide.ionide-fsharp", + "ms-vscode.powershell", + "ms-azuretools.vscode-docker", + "ms-dotnettools.dotnet-interactive-vscode", + "bierner.markdown-preview-github-styles", + "tamasfe.even-better-toml" ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 79a9bd7..8af98d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,52 +1,46 @@ { - "editor.bracketPairColorization.enabled": true, - "editor.guides.bracketPairs": "active", - "editor.inlineSuggest.enabled": true, - "editor.rulers": [100], - "editor.tabSize": 2, - - "debug.internalConsoleOptions": "neverOpen", - - "diffEditor.ignoreTrimWhitespace": false, - - "files.exclude": { - "**/.git": false, - }, - - "git.autofetch": true, - "git.confirmSync": false, - "git.enableSmartCommit": true, - - "github.copilot.enable": { - "*": true, - "yaml": true, - "plaintext": false, - "markdown": false - }, - - "go.gopath": "/go", - "go.useLanguageServer": true, - "go.languageServerFlags": ["-rpc.trace"], - "gopls": { - "ui.semanticTokens": true - }, - - "[go]": { - "editor.rulers": [80] - }, - - "powershell.powerShellDefaultVersion": "7", - "powershell.powerShellAdditionalExePaths": { - "pwsh": "/usr/local/bin/pwsh" - }, - - "liveServer.settings.root": "/docs", - "liveServer.settings.file": "index.html", - "liveServer.settings.host": "localhost", - "liveServer.settings.port": 3000, - - "terminal.integrated.fontFamily": "'FiraCode Nerd Font Mono', 'Fira Code', Menlo, Monaco", - - "workbench.editor.highlightModifiedTabs": true, - "workbench.editor.wrapTabs": true, + "editor.rulers": [80], + "editor.bracketPairColorization.enabled": true, + "editor.guides.bracketPairs": "active", + "editor.inlineSuggest.enabled": true, + "editor.detectIndentation": false, + "editor.insertSpaces": true, + "editor.tabSize": 2, + + "debug.internalConsoleOptions": "neverOpen", + + "diffEditor.ignoreTrimWhitespace": false, + + "files.exclude": { + "**/.git": false, + "**/obj": true, + "**/bin": true + }, + + "git.autofetch": true, + "git.confirmSync": false, + "git.enableSmartCommit": true, + + "github.copilot.enable": { + "*": true, + "yaml": true, + "plaintext": false, + "markdown": false + }, + + "[fsharp]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "Ionide.Ionide-fsharp" + }, + + "powershell.powerShellDefaultVersion": "7", + "powershell.integratedConsole.showOnStartup": false, + "powershell.powerShellAdditionalExePaths": { + "pwsh": "/usr/bin/pwsh" + }, + + "terminal.integrated.fontFamily": "'FiraCode Nerd Font Mono', 'Fira Code', Menlo, Monaco", + + "workbench.editor.highlightModifiedTabs": true, + "workbench.editor.wrapTabs": true, } \ No newline at end of file diff --git a/Build.ps1 b/Build.ps1 deleted file mode 100644 index 38d523b..0000000 --- a/Build.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -Set-Location "$PSScriptRoot/.." - -$Platforms = @( - @{ GOOS = "windows"; GOARCH = "amd64" } - @{ GOOS = "linux"; GOARCH = "amd64" } - @{ GOOS = "darwin"; GOARCH = "amd64" } -) - -$InputFile = "./cmd/gazelle/main.go" - -$Platforms | ForEach-Object { - $Env:GOOS = $_.GOOS - $Env:GOARCH = $_.GOARCH - - $Extension = if ($_.GOOS -eq "windows") { ".exe" } else { "" } - $Executable = "gazelle-$($_.GOOS)-$($_.GOARCH)$Extension" - - go build -o "./build/$Executable" -} diff --git a/Gazelle.sln b/Gazelle.sln new file mode 100644 index 0000000..85b7dbb --- /dev/null +++ b/Gazelle.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Gazelle", "src\Gazelle.fsproj", "{5428DD04-09FD-41FD-B89B-E58C64DCBDCF}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Gazelle.Tests", "tests\Gazelle.Tests.fsproj", "{D539F49E-5374-4272-BFBB-F198068F1665}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5428DD04-09FD-41FD-B89B-E58C64DCBDCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5428DD04-09FD-41FD-B89B-E58C64DCBDCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5428DD04-09FD-41FD-B89B-E58C64DCBDCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5428DD04-09FD-41FD-B89B-E58C64DCBDCF}.Release|Any CPU.Build.0 = Release|Any CPU + {D539F49E-5374-4272-BFBB-F198068F1665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D539F49E-5374-4272-BFBB-F198068F1665}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D539F49E-5374-4272-BFBB-F198068F1665}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D539F49E-5374-4272-BFBB-F198068F1665}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index 97c7d61..a5d2acc 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@

A fast, cross-platform engine for structural analysis & design.

[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/noblesource/gazelle) - [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](https://github.com/noblesource/gazelle/blob/main/CODE_OF_CONDUCT.md) + [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](https://github.com/gazellekit/gazelle/blob/main/CODE_OF_CONDUCT.md) - [![CI](https://github.com/noblesource/gazelle/actions/workflows/ci.yml/badge.svg)](https://github.com/noblesource/gazelle/actions/workflows/build.yml) - [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blueviolet)](https://choosealicense.com/licenses/agpl-3.0/) + [![CI](https://github.com/gazellekit/gazelle/actions/workflows/ci.yml/badge.svg)](https://github.com/gazellekit/gazelle/actions/workflows/ci.yml) + [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-00add8)](https://choosealicense.com/licenses/agpl-3.0/) - [![Go](https://img.shields.io/badge/Go-1.22-00add8)](https://go.dev/) + [![.NET](https://img.shields.io/badge/.NET-8.0-8a2be2)](https://dotnet.microsoft.com) ## Table of Contents @@ -77,7 +77,7 @@ - Develop bespoke plugins so that other software can benefit from Gazelle, - Organise local meetups to discuss how Gazelle could enhance your existing workflows, - Plan conferences and community engagement events to broaden Gazelle's adoption, -- For Go developers, consider importing Gazelle as a package into your own projects. +- For .NET developers, consider importing Gazelle as a library into your own projects. ## Errata diff --git a/Setup.ps1 b/Setup.ps1 new file mode 100644 index 0000000..0709640 --- /dev/null +++ b/Setup.ps1 @@ -0,0 +1,13 @@ +Set-Location $PSScriptRoot + +$DirectoryCommands = @( + @{ RelativePath = "."; Command = "sudo dotnet workload update" } + @{ RelativePath = "."; Command = "dotnet tool restore" } + @{ RelativePath = "."; Command = "dotnet restore" } +) + +$DirectoryCommands | ForEach-Object { + Push-Location $_.RelativePath + Invoke-Expression $_.Command + Pop-Location +} \ No newline at end of file diff --git a/dotnet-tools.json b/dotnet-tools.json new file mode 100644 index 0000000..56449ee --- /dev/null +++ b/dotnet-tools.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "fantomas": { + "version": "6.3.4", + "commands": [ + "fantomas" + ] + }, + "powershell": { + "version": "7.4.2", + "commands": [ + "pwsh" + ] + } + } +} \ No newline at end of file diff --git a/examples/bending-moment.ipynb b/examples/bending-moment.ipynb new file mode 100644 index 0000000..c38d63e --- /dev/null +++ b/examples/bending-moment.ipynb @@ -0,0 +1,93 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "dotnet_interactive": { + "language": "fsharp" + }, + "polyglot_notebook": { + "kernelName": "fsharp" + }, + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "z_bal: 123.00 mm\n" + ] + } + ], + "source": [ + "open FSharp.Data.UnitSystems.SI.UnitSymbols\n", + "\n", + "[]\n", + "type mm\n", + "\n", + "// 1. Define partial safety factors.\n", + "let γ_c = 1.5\n", + "let γ_s = 1.15\n", + "\n", + "// 2. Factors to account for long-term effects on concrete.\n", + "let α_cc = 0.85\n", + "let α_ct = 1.0\n", + "\n", + "// 3. Selected concrete strength.\n", + "let fck = 25.0\n", + "\n", + "// 4. Ultimate concrete design stress.\n", + "let fcd = α_cc * fck / 1.5\n", + "\n", + "// 5. Concrete strain limits.\n", + "let εc2 = 0.002\n", + "let εcu2 = 0.0035\n", + "\n", + "// 6. Rebar steel strength.\n", + "let fyk = 500.0\n", + "\n", + "// 7. Rebar steel modulus.\n", + "let Es = 200_000.0\n", + "\n", + "// 8. Rebar design yield strain.\n", + "let εy = fyk / (γ_s * Es)\n", + "\n", + "// 9. Equation to calculate neutral axis, x.\n", + "let x εst εcu2 (d: float) = d / (1.0 + (εst / εcu2))\n", + "let x_partial = x 0.00217 0.0035\n", + "\n", + "// 10. Stress block reduced to 0.8x depth.\n", + "let s d = (x_partial d) * 0.8\n", + "\n", + "// 11. Calculate concrete compressive force.\n", + "let Fcc d (b: float) = fcd * (s d) * b\n", + "\n", + "// 12. Calculate moment.\n", + "let z d = d - (s d / 2.0)\n", + "let M d b = (Fcc d b) * (z d)\n", + "\n", + "// 13. Calculate area of steel.\n", + "let As d b = (M d b) / (0.87 * fyk * (z d))\n", + "\n", + "M 440.0 260.0 / 10E6\n", + "As 440.0 260.0\n", + "\n", + "let x_limit = 0.45\n", + "let z_bal (d: float) = d - (0.8 * x_limit * d) / 2.0\n", + "\n", + "printfn $\"z_bal: %.2f{z_bal 150.0} mm\"" + ] + } + ], + "metadata": { + "language_info": { + "name": "csharp" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/global.json b/global.json new file mode 100644 index 0000000..ccbaf06 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.203", + "rollForward": "latestMinor" + } +} \ No newline at end of file diff --git a/go.mod b/go.mod deleted file mode 100644 index e6646d2..0000000 --- a/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/GazelleKit/gazelle - -go 1.22.0 diff --git a/go.sum b/go.sum deleted file mode 100644 index e69de29..0000000 diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..0e585fb --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/pkg/bluebook/doc.go b/pkg/bluebook/doc.go deleted file mode 100644 index 60b14f7..0000000 --- a/pkg/bluebook/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -// As Structural Engineers adopt digital workflows, the availability -// of data in consumable formats becomes increasingly important. -// Structural design data is often captured within Excel spreadsheets, -// books, tables and figures, which is inconvenient from a software -// perspective. Moreover, mapping data from one format to another can -// be time-consuming and a frequent source of error. The bluebook -// package encapsulates the Steel for Life steel section data as a -// library with a stable and convenient API. The original .xlsx data -// files were sourced from: https://www.steelforlifebluebook.co.uk. -package bluebook diff --git a/pkg/bluebook/universalsection.go b/pkg/bluebook/universalsection.go deleted file mode 100644 index 8273e9f..0000000 --- a/pkg/bluebook/universalsection.go +++ /dev/null @@ -1,24 +0,0 @@ -package bluebook - -import ( - units "github.com/GazelleKit/gazelle/pkg/units" -) - -type DimensionsAndProperties struct { - Depth units.Millimetre - Width units.Millimetre - Length units.Millimetre - TotalMass units.Kilogram - MassPerMetreLength float64 - CrossSectionalArea float64 - SurfaceAreaPerMetre float64 - SurfaceAreaPerTonne float64 - WebThickness float64 - FlangeThickness float64 - RootRadius float64 - DepthBetweenFillets float64 - EndClearanceForDetailing float64 - LongitudinalNotchDimension float64 - VerticalNotchDimension float64 - RadiusOfGyration float64 -} diff --git a/pkg/geometry/properties.go b/pkg/geometry/properties.go deleted file mode 100644 index f8a98f3..0000000 --- a/pkg/geometry/properties.go +++ /dev/null @@ -1,15 +0,0 @@ -package geometry - -type FlexuralAxis int - -const ( - XX FlexuralAxis = iota - YY -) - -type LinearAxis int - -const ( - X LinearAxis = iota - Y -) diff --git a/pkg/steel/sections.go b/pkg/steel/sections.go deleted file mode 100644 index caa4a2a..0000000 --- a/pkg/steel/sections.go +++ /dev/null @@ -1,35 +0,0 @@ -package steel - -import ( - bluebook "github.com/GazelleKit/gazelle/pkg/bluebook" -) - -type UniversalBeam struct { - SectionClassification string - IsNonStandard bool - DimensionsAndProperties bluebook.DimensionsAndProperties -} - -type UniversalColumn struct { - SectionClassification string - IsNonStandard bool - DimensionsAndProperties bluebook.DimensionsAndProperties -} - -type UniversalBearingPile struct { - SectionClassification string - IsNonStandard bool - DimensionsAndProperties bluebook.DimensionsAndProperties -} - -func (UniversalBeam) String() string { - return "Universal Beam" -} - -func (UniversalColumn) String() string { - return "Universal Column" -} - -func (UniversalBearingPile) String() string { - return "Universal Bearing Pile" -} diff --git a/pkg/units/units.go b/pkg/units/units.go deleted file mode 100644 index 029f231..0000000 --- a/pkg/units/units.go +++ /dev/null @@ -1,93 +0,0 @@ -package units - -import ( - "fmt" -) - -type Gram float64 -type Kilogram float64 -type Tonne float64 -type Metre float64 -type Millimetre float64 -type Newton float64 -type Kilonewton float64 -type Area float64 -type Perimeter float64 -type SurfaceArea float64 -type Volume float64 - -type SecondMomentOfArea struct { - XX, YY float64 -} - -type RadiusOfGyration struct { - XX, YY float64 -} - -func (g Gram) ToKilogram() Kilogram { - return Kilogram(g / 1000.0) -} - -func (g Gram) ToTonne() Tonne { - return Tonne(g / 1_000_000.0) -} - -func (g Gram) String() string { - return fmt.Sprintf("%f Gram(s)", g) -} - -func (kg Kilogram) ToGram() Gram { - return Gram(kg * 1000.0) -} - -func (kg Kilogram) ToTonne() Tonne { - return Tonne(kg / 1000.0) -} - -func (kg Kilogram) String() string { - return fmt.Sprintf("%f Kilogram(s)", kg) -} - -func (t Tonne) ToGram() Gram { - return Gram(t * 1_000_000.0) -} - -func (t Tonne) ToKilogram() Kilogram { - return Kilogram(t * 1000.0) -} - -func (t Tonne) String() string { - return fmt.Sprintf("%f Tonne(s)", t) -} - -func (m Metre) ToMillimetre() Millimetre { - return Millimetre(m * 1000.0) -} - -func (m Metre) String() string { - return fmt.Sprintf("%f Metre(s)", m) -} - -func (mm Millimetre) ToMetre() Metre { - return Metre(mm / 1000.0) -} - -func (m Millimetre) String() string { - return fmt.Sprintf("%f Millimetre(s)", m) -} - -func (N Newton) ToKilonewton() Kilonewton { - return Kilonewton(N / 1000.0) -} - -func (N Newton) String() string { - return fmt.Sprintf("%f Newton(s)", N) -} - -func (kN Kilonewton) ToNewton() Newton { - return Newton(kN * 1000.0) -} - -func (kN Kilonewton) String() string { - return fmt.Sprintf("%f Kilonewton(s)", kN) -} diff --git a/pkg/bluebook/.d/properties/cf-chs.json b/src/.d/bluebook/properties/cf-chs.json similarity index 100% rename from pkg/bluebook/.d/properties/cf-chs.json rename to src/.d/bluebook/properties/cf-chs.json diff --git a/pkg/bluebook/.d/properties/cf-rhs.json b/src/.d/bluebook/properties/cf-rhs.json similarity index 100% rename from pkg/bluebook/.d/properties/cf-rhs.json rename to src/.d/bluebook/properties/cf-rhs.json diff --git a/pkg/bluebook/.d/properties/cf-shs.json b/src/.d/bluebook/properties/cf-shs.json similarity index 100% rename from pkg/bluebook/.d/properties/cf-shs.json rename to src/.d/bluebook/properties/cf-shs.json diff --git a/pkg/bluebook/.d/properties/equal-l.json b/src/.d/bluebook/properties/equal-l.json similarity index 100% rename from pkg/bluebook/.d/properties/equal-l.json rename to src/.d/bluebook/properties/equal-l.json diff --git a/pkg/bluebook/.d/properties/hf-chs.json b/src/.d/bluebook/properties/hf-chs.json similarity index 100% rename from pkg/bluebook/.d/properties/hf-chs.json rename to src/.d/bluebook/properties/hf-chs.json diff --git a/pkg/bluebook/.d/properties/hf-ehs.json b/src/.d/bluebook/properties/hf-ehs.json similarity index 100% rename from pkg/bluebook/.d/properties/hf-ehs.json rename to src/.d/bluebook/properties/hf-ehs.json diff --git a/pkg/bluebook/.d/properties/hf-rhs.json b/src/.d/bluebook/properties/hf-rhs.json similarity index 100% rename from pkg/bluebook/.d/properties/hf-rhs.json rename to src/.d/bluebook/properties/hf-rhs.json diff --git a/pkg/bluebook/.d/properties/hf-shs.json b/src/.d/bluebook/properties/hf-shs.json similarity index 100% rename from pkg/bluebook/.d/properties/hf-shs.json rename to src/.d/bluebook/properties/hf-shs.json diff --git a/pkg/bluebook/.d/properties/pfc.json b/src/.d/bluebook/properties/pfc.json similarity index 100% rename from pkg/bluebook/.d/properties/pfc.json rename to src/.d/bluebook/properties/pfc.json diff --git a/pkg/bluebook/.d/properties/t-split-from-ub.json b/src/.d/bluebook/properties/t-split-from-ub.json similarity index 100% rename from pkg/bluebook/.d/properties/t-split-from-ub.json rename to src/.d/bluebook/properties/t-split-from-ub.json diff --git a/pkg/bluebook/.d/properties/t-split-from-uc.json b/src/.d/bluebook/properties/t-split-from-uc.json similarity index 100% rename from pkg/bluebook/.d/properties/t-split-from-uc.json rename to src/.d/bluebook/properties/t-split-from-uc.json diff --git a/pkg/bluebook/.d/properties/ub.json b/src/.d/bluebook/properties/ub.json similarity index 100% rename from pkg/bluebook/.d/properties/ub.json rename to src/.d/bluebook/properties/ub.json diff --git a/pkg/bluebook/.d/properties/ubp.json b/src/.d/bluebook/properties/ubp.json similarity index 100% rename from pkg/bluebook/.d/properties/ubp.json rename to src/.d/bluebook/properties/ubp.json diff --git a/pkg/bluebook/.d/properties/uc.json b/src/.d/bluebook/properties/uc.json similarity index 100% rename from pkg/bluebook/.d/properties/uc.json rename to src/.d/bluebook/properties/uc.json diff --git a/pkg/bluebook/.d/properties/unequal-l.json b/src/.d/bluebook/properties/unequal-l.json similarity index 100% rename from pkg/bluebook/.d/properties/unequal-l.json rename to src/.d/bluebook/properties/unequal-l.json diff --git a/src/Gazelle.fsproj b/src/Gazelle.fsproj new file mode 100644 index 0000000..5b5fc27 --- /dev/null +++ b/src/Gazelle.fsproj @@ -0,0 +1,30 @@ + + + + James S. Bayley + GazelleKit + net8.0 + https://github.com/gazellekit/gazelle + true + true + AGPL-3.0-or-later + A fast, cross-platform engine for structural analysis and design. + README.md + Gazelle + true + 0.0.1 + + + + + + + + + + + + + + + diff --git a/src/Geometry.fs b/src/Geometry.fs new file mode 100644 index 0000000..4622b90 --- /dev/null +++ b/src/Geometry.fs @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Geometry + +/// +/// Orthogonal geometric axis. +/// +type Axis = + | X + | Y + +/// +/// Orthogonal axis about which rotation occurs. +/// +type RotationalAxis = + | XX + | YY + +/// +/// Length dimension annotated with unit-of-measure. +/// +type Length<[] 'TUnit> = Length of float<'TUnit> + +/// +/// Width dimension annotated with unit-of-measure. +/// +type Width<[] 'TUnit> = Width of float<'TUnit> + +/// +/// Depth dimension annotated with unit-of-measure. +/// +type Depth<[] 'TUnit> = Depth of float<'TUnit> + +/// +/// Diameter dimension annotated with unit-of-measure. +/// +type Diameter<[] 'TUnit> = Diameter of float<'TUnit> + +/// +/// Perimeter dimension annotated with unit-of-measure. +/// +type Perimeter<[] 'TUnit> = Perimeter of float<'TUnit> + +/// +/// Area dimension annotated with unit-of-measure. +/// Union type represents possible area semantics. +/// +type Area<[] 'TUnit> = + | CrossSectionalArea of float<'TUnit^2> + | SurfaceArea of float<'TUnit^2> + +/// +/// Volume dimension annotated with unit-of-measure. +/// +type Volume<[] 'TUnit> = Volume of float<'TUnit^3> + +/// +/// Second moment of area dimension annotated with unit-of-measure. +/// +type SecondMomentOfArea<[] 'TUnit> = SecondMomentOfArea of float<'TUnit^4> diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..73a4204 --- /dev/null +++ b/src/README.md @@ -0,0 +1,3 @@ +# Gazelle + +A fast, cross-platform engine for structural analysis & design. \ No newline at end of file diff --git a/src/concrete/Cages.fs b/src/concrete/Cages.fs new file mode 100644 index 0000000..65efee3 --- /dev/null +++ b/src/concrete/Cages.fs @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Concrete + +//open System + +///// 3D rebar cage dimensions measured between centrelines of outermost reinforcement. +//type CageDimensions<[] 'T> = +// | RectangularCage of Width<'T> * Depth<'T> * Length<'T> + +// member this.Centreline = +// match this with +// | RectangularCage (_, _, Length l) -> +// (Point3D (0.0, 0.0, 0.0), +// Point3D (0.0, 0.0, stripUnitsFromFloat l)) +// ||> createCentreline + +// member this.Length = +// match this with +// | RectangularCage (_, _, l) -> l + + +//[] +//module ColumnCage = + +// /// Rectangular column cage face. +// type Face = Top | Left | Right | Bottom + +// /// Rectangular column cage corners. +// type Corner = TopRight | TopLeft | BottomRight | BottomLeft + +// /// Straight reinforcing bars positioned at cage corners. +// type CornerBars = +// { TopRight: StraightBar +// TopLeft: StraightBar +// BottomRight: StraightBar +// BottomLeft: StraightBar } + +// /// Collection of straight reinforcing bars for respective cage faces. +// type SideBars = +// { TopFace: StraightBar list +// LeftFace: StraightBar list +// RightFace: StraightBar list +// BottomFace: StraightBar list } + +// /// Defines rectangular column cage rebar configuration. +// type RectangularCageSpecification = +// { CornerBarSize: BarSize +// TopFaceBars: NumberOfBarsAndBarSize +// BottomFaceBars: NumberOfBarsAndBarSize +// LeftFaceBars: NumberOfBarsAndBarSize +// RightFaceBars: NumberOfBarsAndBarSize +// Links: LinkSpacingAndLinkSize } + +// /// Rebar specifications for alternative column cage geometries. +// type Specification = +// | RectangularCageSpec of RectangularCageSpecification + +// /// Fully configured column cage. +// type Cage = +// { Dimensions: CageDimensions +// CornerBars: CornerBars +// SideBars: SideBars +// Links: Link list } + + +// [] +// module Links = + +// /// Calculates no. of links within given length at the prescribed spacing. +// let getNumberOfLinks (BarSpacing sp) (Length l) = +// Math.Ceiling (l / sp) +// |> int +// |> (+) 1 +// |> AddUnitsToInteger32 +// |> NumberOfBars + +// /// Calculates link spacing for a discrete no. of links over a given length. +// let getLinkSpacing (NumberOfBars n) (Length l) = +// l / (float n - 1.0) +// |> BarSpacing + +// /// For a given spacing, returns all link centroids along a centreline. +// let getLinkCentroidsAlongCentreline (Centreline l) (BarSpacing spacing) = +// let spacing = stripUnitsFromFloat spacing +// let p1, p2 = l.StartPoint, l.EndPoint +// [ for z in p1.Z .. spacing .. p2.Z -> +// Point3D (p1.X, p1.Y, z) +// |> Centroid ] + +// /// Returns shear links for full column cage length. +// let create steel linkSize spacing (cageDims: CageDimensions) = +// let cl = cageDims.Centreline +// let length = cageDims.Length +// let quantity = getNumberOfLinks spacing length +// let updatedSpacing = getLinkSpacing quantity length +// let centroids = getLinkCentroidsAlongCentreline cl updatedSpacing +// [for ct in centroids -> +// match cageDims with +// | RectangularCage (Width w, Depth d, _) -> +// RectangularLink.create +// ct +// (Width w) +// (Depth d) +// linkSize +// steel] + + +// [] +// module CornerBars = + +// /// Locates the geometric bar centroid, for a selected corner and given bar size. +// let findCorner link (barSize: BarSize) corner = +// let b = link.Geometry.InnerWidth / 2.0 +// let d = link.Geometry.InnerDepth / 2.0 +// let ctX, ctY = link.Geometry.CentroidCoords +// let Φ = barSize.diameter / 2.0 + +// match corner with +// | TopRight -> +// let x = ctX + b - Φ +// let y = ctY + d - Φ +// x, y +// | TopLeft -> +// let x = ctX - b + Φ +// let y = ctY + d - Φ +// x, y +// | BottomRight -> +// let x = ctX + b - Φ +// let y = ctY - d + Φ +// x, y +// | BottomLeft -> +// let x = ctX - b + Φ +// let y = ctY - d + Φ +// x, y + +// /// Returns collection of straight steel bars for rectangular cage corners. +// let create barSize steel link (Length len) = +// let createAt corner = +// corner +// |> findCorner link barSize +// |> createSymmetricEndPoints len +// ||> StraightBar.create barSize steel + +// { TopRight = createAt TopRight +// TopLeft = createAt TopLeft +// BottomRight = createAt BottomRight +// BottomLeft = createAt BottomLeft } + + +// [] +// module SideBars = + +// /// Returns the spacing interval for intermediate side bars. +// let getSideBarSpacing cornerBars side numberOfBars = +// match side with +// | Top | Bottom -> +// distanceBetweenPoints +// cornerBars.TopRight.Geometry.StartPoint +// cornerBars.TopLeft.Geometry.StartPoint +// | Left | Right -> +// distanceBetweenPoints +// cornerBars.TopLeft.Geometry.StartPoint +// cornerBars.BottomLeft.Geometry.StartPoint +// |> AddUnitsToFloat +// |> Length +// |> getIntermediateBarSpacing numberOfBars + +// /// Returns the 'constant' x or y coordinate for a given side. +// let getSideBarConstantCoord (barSize: BarSize) side (link: Link) = +// let Φ = barSize.diameter |> stripUnitsFromFloat +// let ctX, ctY, w, d, sd = +// match link.Geometry with +// | RectangularHoop (Centroid ct, Width w, Depth d, SectionDiameter sd) -> +// ct.X, ct.Y, w, d, sd +// | CircularHoop (Centroid ct, Diameter d, SectionDiameter sd) -> +// ct.X, ct.Y, d, d, sd +// match side with +// | Top -> ctY + (d / 2.0<_>) - (sd / 2.0<_>) - (Φ / 2.0) +// | Bottom -> ctY - (d / 2.0<_>) + (sd / 2.0<_>) + (Φ / 2.0) +// | Left -> ctX - (w / 2.0<_>) + (sd / 2.0<_>) + (Φ / 2.0) +// | Right -> ctX + (w / 2.0<_>) - (sd / 2.0<_>) - (Φ / 2.0) + +// /// Locates the side bar centroids. +// let getSideBar2DCoords side numberOfBars cornerBars link barSize = +// let spacing = +// getSideBarSpacing cornerBars side numberOfBars +// |> unwrapBarSpacing +// |> stripUnitsFromFloat +// let constantCoord = getSideBarConstantCoord barSize side link +// match side with +// | Top | Bottom -> +// let xStart = cornerBars.TopLeft.Geometry.StartPoint.X + spacing +// let xEnd = cornerBars.TopRight.Geometry.StartPoint.X - spacing +// [for x in xStart .. spacing .. xEnd -> (x, constantCoord)] +// | Left | Right -> +// let yStart = cornerBars.BottomLeft.Geometry.StartPoint.Y + spacing +// let yEnd = cornerBars.TopLeft.Geometry.StartPoint.Y - spacing +// [for y in yStart .. spacing .. yEnd -> (constantCoord, y)] + +// /// Returns list of straight steel bars for a single cage side. +// let create side cornerBars link barSize numberOfBars steel = +// let xyCoords = getSideBar2DCoords side numberOfBars cornerBars link barSize +// let zStart = cornerBars.TopRight.Geometry.StartPoint.Z +// let zEnd = cornerBars.TopRight.Geometry.EndPoint.Z +// [ for x, y in xyCoords -> +// StraightBar.create +// barSize +// steel +// (Point3D (x, y, zStart)) +// (Point3D (x, y, zEnd)) ] + + +// /// Returns a fully configured column rebar cage. +// let create (rebarSpec: Specification) steel (dimensions: CageDimensions) = +// let length = match dimensions with RectangularCage (_, _, l) -> l + +// match rebarSpec with +// | RectangularCageSpec spec -> + +// let links = +// let linkSize = spec.Links.LinkSize +// let linkSpacing = spec.Links.LinkSpacing +// Links.create steel linkSize linkSpacing dimensions + +// let cornerBars = +// let barSize = spec.CornerBarSize +// CornerBars.create barSize steel links.[0] length + +// let sideBars = + +// let topFaceBars = +// let barSize = spec.TopFaceBars.BarSize +// let numOfBars = spec.TopFaceBars.NumberOfBars +// SideBars.create Top cornerBars links.[0] barSize numOfBars steel + +// let leftFaceBars = +// let barSize = spec.LeftFaceBars.BarSize +// let numOfBars = spec.LeftFaceBars.NumberOfBars +// SideBars.create Left cornerBars links.[0] barSize numOfBars steel + +// let rightFaceBars = +// let barSize = spec.RightFaceBars.BarSize +// let numOfBars = spec.RightFaceBars.NumberOfBars +// SideBars.create Right cornerBars links.[0] barSize numOfBars steel + +// let bottomFaceBars = +// let barSize = spec.BottomFaceBars.BarSize +// let numOfBars = spec.BottomFaceBars.NumberOfBars +// SideBars.create Bottom cornerBars links.[0] barSize numOfBars steel + +// { TopFace = topFaceBars +// LeftFace = leftFaceBars +// RightFace = rightFaceBars +// BottomFace = bottomFaceBars } + +// { Dimensions = dimensions +// CornerBars = cornerBars +// SideBars = sideBars +// Links = links } + + +//[] +//module WallCage = + +// /// Respective wall face. +// type Face = LeftFace | RightFace + +// /// Respective wall end zone. +// type EndZone = Top | Bottom + +// /// Number of pairwise bar layers in each end zone. +// type NumberOfBarLayersInEndZone = NumberOfBarLayersInEndZone of int + +// /// Defines bar configuration for wall end zones. +// type EndZoneBarSpecification = +// { NumberOfLayers: NumberOfBarLayersInEndZone +// BarSize: BarSize } + +// /// Defines bar configuration for wall central zone. +// type CentralZoneBarSpecification = { BarSize: BarSize; Spacing: BarSpacing } + +// /// Defines bar configuration for horizontal wall bars. +// type HorizontalBarSpecification = { BarSize: BarSize; Spacing: BarSpacing } + +// /// Defines wall cage rebar configurations for all zones. +// type Specification = +// { HorizontalBarSpec: HorizontalBarSpecification +// EndZoneBarSpec: EndZoneBarSpecification +// CentralZoneBarSpec: CentralZoneBarSpecification } + +// /// Fully configured wall cage. +// type Cage = +// { Dimensions: CageDimensions +// HorizontalBars: StraightBar list +// EndZoneBars: StraightBar list +// CentralZoneBars: StraightBar list } + + +// [] +// module HorizontalBars = + +// /// Returns x- and y-coordinates for horizontal wall bars. +// let findHorizontalBarXYCoords cageDims = +// let (Width w), (Depth d), _ = cageDims +// let xLeft = -w / 2.0 +// let xRight = w / 2.0 +// let yTop = d / 2.0 +// let yBottom = -d / 2.0 +// xLeft, xRight, yTop, yBottom + +// /// Returns z-coordinates for horizontal wall bars. +// let findHorizontalBarZCoords cageDims horizBarSpacing = +// let _, _, l = cageDims +// let numOfBars = getNumberOfIntermediateBars horizBarSpacing l +// let updatedSpacing = +// getIntermediateBarSpacing numOfBars l +// |> unwrapBarSpacing +// |> stripUnitsFromFloat +// let length = +// let unwrapLength (Length l) = l +// l |> unwrapLength |> stripUnitsFromFloat +// let zStart, zEnd = 0.0, length +// [for z in zStart .. updatedSpacing .. zEnd -> z] + +// /// Groups horizontal wall bar x-, y- and z-coordinates for a given face. +// let combineHorizontalXYZBarCoords xyCoords zCoords face = +// let xLeft, xRight, yTop, yBottom = xyCoords +// zCoords +// |> match face with +// | LeftFace -> +// List.map (fun z -> +// Point3D (stripUnitsFromFloat xLeft, stripUnitsFromFloat yBottom, z), +// Point3D (stripUnitsFromFloat xLeft, stripUnitsFromFloat yTop, z)) +// | RightFace -> +// List.map (fun z -> +// Point3D (stripUnitsFromFloat xRight, stripUnitsFromFloat yBottom, z), +// Point3D (stripUnitsFromFloat xRight, stripUnitsFromFloat yTop, z)) + +// /// Returns all steel horizontal wall bars. +// let create (cageDims: CageDimensions) horizBarSpacing barSize steel = +// let cageDims = +// match cageDims with +// | RectangularCage (w, d, l) -> (w, d, l) + +// let xyCoords = findHorizontalBarXYCoords cageDims +// let zCoords = findHorizontalBarZCoords cageDims horizBarSpacing +// let barCoordsLeft = combineHorizontalXYZBarCoords xyCoords zCoords LeftFace +// let barCoordsRight = combineHorizontalXYZBarCoords xyCoords zCoords RightFace +// barCoordsLeft @ barCoordsRight +// |> List.map (fun (p1, p2) -> +// StraightBar.create barSize steel p1 p2) + +// /// Returns clear space between horizontal wall bars. +// let getClearSpaceBetweenHorizontalBars cageDims (horizontalBar: BarSize) = +// let (Width cageWidth), _, _ = cageDims +// cageWidth - horizontalBar.diameter +// |> ClearSpace + + +// [] +// module VerticalBars = + +// /// Returns x-coordinate of vertical wall bar, measured from the centreline of the cage. +// let getVerticalBarXCoordinate face (verticalBarSize: BarSize) spaceBetweenHorizontalBars = +// let halfWidth = unwrapClearSpace spaceBetweenHorizontalBars / 2.0 +// match face with +// | LeftFace -> (- halfWidth) + (verticalBarSize.diameter / 2.0) +// | RightFace -> halfWidth - (verticalBarSize.diameter / 2.0) + + +// [] +// module EndZone = + +// /// Returns x- and y-coordinates for end zone bars, for a single end zone and wall face. +// let findEndZoneBarCoords zone face barSize (OuterDepth endZoneDepth) cageDims horizBarSize = +// let _, (Depth cageDepth), _ = cageDims +// let ezDepth = endZoneDepth +// let ezWidth = HorizontalBars.getClearSpaceBetweenHorizontalBars cageDims horizBarSize +// let xCoord = getVerticalBarXCoordinate face barSize ezWidth +// let Φ = barSize.diameter +// let spacingBetweenBarCentres = +// 2.0 * unwrapClearSpace (getMinimumClearBarSpacing barSize) +// match zone with +// | Top -> +// let yStart = (cageDepth - Φ) / 2.0 +// let yEnd = yStart - ezDepth + (Φ / 2.0) +// [for y in yStart .. -spacingBetweenBarCentres .. yEnd -> (xCoord, y)] +// | Bottom -> +// let yStart = (Φ - cageDepth) / 2.0 +// let yEnd = yStart + ezDepth - (Φ / 2.0) +// [for y in yStart .. spacingBetweenBarCentres .. yEnd -> (xCoord, y)] + +// /// Returns depth of single end zone, including clear space between bars. +// /// Measured to the outermost bar dimensions. +// let getEndZoneDepth (NumberOfBarLayersInEndZone layers) endZoneBarSize = +// let numOfLayers = layers |> float |> AddUnitsToFloat +// let clearBarSpace = +// getMinimumClearBarSpacing endZoneBarSize +// |> unwrapClearSpace +// |> (*) (numOfLayers - 1.0) +// endZoneBarSize.diameter +// |> (*) numOfLayers +// |> (+) clearBarSpace +// |> OuterDepth + +// /// Returns list of straight steel bars for both end zones and wall faces. +// let create barSize endZoneDepth cageDims horizBarSize steel = +// let _, _, (Length cageLength) = cageDims +// [ Top, LeftFace +// Top, RightFace +// Bottom, LeftFace +// Bottom, RightFace ] +// |> List.collect (fun (zone, face) -> +// findEndZoneBarCoords zone face barSize endZoneDepth cageDims horizBarSize) +// |> List.map (fun (x, y) -> stripUnitsFromFloat x, stripUnitsFromFloat y) +// |> List.map (fun (x, y) -> +// StraightBar.create +// barSize +// steel +// (Point3D (x, y, 0.0)) +// (Point3D (x, y, stripUnitsFromFloat cageLength))) + + +// [] +// module CentralZone = + +// /// Returns x- and y-coordinates for central zone bars, for a single end zone and wall face. +// let findCentralZoneBarCoords face barSize spacing (OuterDepth centralZoneDepth) cageDims horizBarSize = +// let czWidth = HorizontalBars.getClearSpaceBetweenHorizontalBars cageDims horizBarSize +// let czDepth = centralZoneDepth +// let xCoord = getVerticalBarXCoordinate face barSize czWidth +// let Φ = barSize.diameter +// let yStart = (-czDepth / 2.0) + (Φ / 2.0) +// let yEnd = (czDepth / 2.0) - (Φ / 2.0) +// let length = (yEnd - yStart) |> Length +// let numberOfBars = getNumberOfIntermediateBars spacing length +// let updatedSpacing = getIntermediateBarSpacing numberOfBars length |> unwrapBarSpacing +// [ for y in yStart .. updatedSpacing .. yEnd -> xCoord, y ] + +// /// Returns depth of central wall zone. Calculated using the outermost depth +// /// of both end zones; adds additional clear spacing between the end zone edges +// /// and the outermost bars in the central zone. +// let getCentralZoneDepth cageDims endZoneOuterDepth endZoneClearBarSpacing = +// let _, (Depth cageDepth), _ = cageDims +// let depthOfBothEndZones = +// match endZoneOuterDepth with OuterDepth d -> d +// |> (*) 2.0 +// let clearSpaceForBothEndZones = +// endZoneClearBarSpacing +// |> unwrapClearSpace +// |> (*) 2.0 +// (cageDepth - depthOfBothEndZones - clearSpaceForBothEndZones) +// |> OuterDepth + +// /// Returns list of straight steel bars for both faces of central zone. +// let create barSize spacing centralZoneDepth horizBarSize cageDims steel = +// let _, _, (Length cageLength) = cageDims +// [ LeftFace; RightFace ] +// |> List.collect (fun face -> +// findCentralZoneBarCoords face barSize spacing centralZoneDepth cageDims horizBarSize) +// |> List.map (fun (x, y) -> stripUnitsFromFloat x, stripUnitsFromFloat y) +// |> List.map (fun (x, y) -> +// StraightBar.create +// barSize +// steel +// (Point3D (x, y, 0.0)) +// (Point3D (x, y, stripUnitsFromFloat cageLength))) + + +// /// Returns fully configured wall cage. +// let create (spec: Specification) steel (dimensions: CageDimensions) = +// let dimsAsTuple = +// match dimensions with +// | RectangularCage (w, d, l) -> (w, d, l) + +// let horizBarSize = spec.HorizontalBarSpec.BarSize +// let ezLayers = spec.EndZoneBarSpec.NumberOfLayers +// let ezBarSize = spec.EndZoneBarSpec.BarSize +// let ezClearBarSpacing = getMinimumClearBarSpacing ezBarSize +// let ezDepth = VerticalBars.EndZone.getEndZoneDepth ezLayers ezBarSize +// let czBarSize = spec.CentralZoneBarSpec.BarSize +// let czDepth = VerticalBars.CentralZone.getCentralZoneDepth dimsAsTuple ezDepth ezClearBarSpacing +// let czSpacing = spec.CentralZoneBarSpec.Spacing +// let horizBarSpacing = spec.HorizontalBarSpec.Spacing + +// let ezBars = VerticalBars.EndZone.create ezBarSize ezDepth dimsAsTuple horizBarSize steel +// let czBars = VerticalBars.CentralZone.create czBarSize czSpacing czDepth horizBarSize dimsAsTuple steel +// let horizBars = HorizontalBars.create dimensions horizBarSpacing horizBarSize steel + +// { Dimensions = dimensions +// HorizontalBars = horizBars +// EndZoneBars = ezBars +// CentralZoneBars = czBars } diff --git a/src/concrete/Concrete.fs b/src/concrete/Concrete.fs new file mode 100644 index 0000000..43052ee --- /dev/null +++ b/src/concrete/Concrete.fs @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Structures + +open Gazelle.Units +open FSharp.Data.UnitSystems.SI.UnitSymbols + +type UKConcreteGrade = + | Fck12 + | Fck16 + | Fck20 + | Fck25 + | Fck30 + | Fck35 + | Fck40 + | Fck45 + | Fck50 + | Fck55 + | Fck60 + | Fck70 + | Fck80 + | Fck90 + +type CylinderStrength = UK of UKConcreteGrade + +type Aggregate = + | Basalt + | Limestone + | Sandstone + | Quartzite + +type Cement = + | ClassR + | ClassN + | ClassS + +type WeightClass = NormalWeight +type Age = Age of int + +type Concrete = + { Age: Age + Aggregate: Aggregate + Cement: Cement + Grade: CylinderStrength + WeightClass: WeightClass } + +[] +module Concrete = + + let private validateAge (age: int) = + match age with + | age when age <= 0 -> invalidArg $"{nameof (age)}" "Concrete age <= 0 days." + | age when age <= 3 -> invalidArg $"{nameof (age)}" "In-situ strength tests required for age <= 3 days." + | _ -> Age age + + let tryCreate (fck: CylinderStrength) (agg: Aggregate) (cem: Cement) (w: WeightClass) (age: int) = + let validAge = validateAge age + + { Age = validAge + Aggregate = agg + Cement = cem + Grade = fck + WeightClass = w } + + [] + module BasicProperties = + + [] + module Density = + + let unreinforced (w: WeightClass) = + match w with + | NormalWeight -> Density 2400.0 + + let reinforced (w: WeightClass) = + match w with + | NormalWeight -> Density 2500.0 + + [] + module Strength = + + let fck (fck: CylinderStrength) = + match fck with + | UK Fck12 -> 12.0 + | UK Fck16 -> 16.0 + | UK Fck20 -> 20.0 + | UK Fck25 -> 25.0 + | UK Fck30 -> 30.0 + | UK Fck35 -> 35.0 + | UK Fck40 -> 40.0 + | UK Fck45 -> 45.0 + | UK Fck50 -> 50.0 + | UK Fck55 -> 55.0 + | UK Fck60 -> 60.0 + | UK Fck70 -> 70.0 + | UK Fck80 -> 80.0 + | UK Fck90 -> 90.0 + + let fcm (fck: CylinderStrength) = + match fck with + | UK Fck12 -> 20.0 + | UK Fck16 -> 24.0 + | UK Fck20 -> 28.0 + | UK Fck25 -> 33.0 + | UK Fck30 -> 38.0 + | UK Fck35 -> 43.0 + | UK Fck40 -> 48.0 + | UK Fck45 -> 53.0 + | UK Fck50 -> 58.0 + | UK Fck55 -> 63.0 + | UK Fck60 -> 68.0 + | UK Fck70 -> 78.0 + | UK Fck80 -> 88.0 + | UK Fck90 -> 98.0 + + let fctm (fck: CylinderStrength) = + match fck with + | UK Fck12 -> 1.6 + | UK Fck16 -> 1.9 + | UK Fck20 -> 2.2 + | UK Fck25 -> 2.6 + | UK Fck30 -> 2.9 + | UK Fck35 -> 3.2 + | UK Fck40 -> 3.5 + | UK Fck45 -> 3.8 + | UK Fck50 -> 4.1 + | UK Fck55 -> 4.2 + | UK Fck60 -> 4.4 + | UK Fck70 -> 4.6 + | UK Fck80 -> 4.8 + | UK Fck90 -> 5.0 + + [] + module ElasticModulus = + + let Ecm (fck: CylinderStrength) (agg: Aggregate) = + let Ecm = + match fck with + | UK Fck12 -> 27_000.0 + | UK Fck16 -> 29_000.0 + | UK Fck20 -> 30_000.0 + | UK Fck25 -> 31_000.0 + | UK Fck30 -> 33_000.0 + | UK Fck35 -> 34_000.0 + | UK Fck40 -> 35_000.0 + | UK Fck45 -> 36_000.0 + | UK Fck50 -> 37_000.0 + | UK Fck55 -> 38_000.0 + | UK Fck60 -> 39_000.0 + | UK Fck70 -> 41_000.0 + | UK Fck80 -> 42_000.0 + | UK Fck90 -> 44_000.0 + + match agg with + | Quartzite -> Ecm * 1.0 + | Limestone -> Ecm * 0.9 + | Sandstone -> Ecm * 0.7 + | Basalt -> Ecm * 1.2 + + [] + module Strain = + + let c1 (fck: CylinderStrength) = + match fck with + | UK Fck12 -> 0.0018 + | UK Fck16 -> 0.0019 + | UK Fck20 -> 0.0020 + | UK Fck25 -> 0.0021 + | UK Fck30 -> 0.0022 + | UK Fck35 -> 0.00225 + | UK Fck40 -> 0.0023 + | UK Fck45 -> 0.0024 + | UK Fck50 -> 0.00245 + | UK Fck55 -> 0.0025 + | UK Fck60 -> 0.0026 + | UK Fck70 -> 0.0027 + | UK Fck80 -> 0.0028 + | UK Fck90 -> 0.0028 + + let cu1 (fck: CylinderStrength) = + match fck with + | UK Fck12 -> 0.0035 + | UK Fck16 -> 0.0035 + | UK Fck20 -> 0.0035 + | UK Fck25 -> 0.0035 + | UK Fck30 -> 0.0035 + | UK Fck35 -> 0.0035 + | UK Fck40 -> 0.0035 + | UK Fck45 -> 0.0035 + | UK Fck50 -> 0.0035 + | UK Fck55 -> 0.0032 + | UK Fck60 -> 0.0030 + | UK Fck70 -> 0.0028 + | UK Fck80 -> 0.0028 + | UK Fck90 -> 0.0028 + + let c2 (fck: CylinderStrength) = + match fck with + | UK Fck12 -> 0.0020 + | UK Fck16 -> 0.0020 + | UK Fck20 -> 0.0020 + | UK Fck25 -> 0.0020 + | UK Fck30 -> 0.0020 + | UK Fck35 -> 0.0020 + | UK Fck40 -> 0.0020 + | UK Fck45 -> 0.0020 + | UK Fck50 -> 0.0020 + | UK Fck55 -> 0.0022 + | UK Fck60 -> 0.0023 + | UK Fck70 -> 0.0024 + | UK Fck80 -> 0.0025 + | UK Fck90 -> 0.0026 + + let cu2 (fck: CylinderStrength) = + match fck with + | UK Fck12 -> 0.0035 + | UK Fck16 -> 0.0035 + | UK Fck20 -> 0.0035 + | UK Fck25 -> 0.0035 + | UK Fck30 -> 0.0035 + | UK Fck35 -> 0.0035 + | UK Fck40 -> 0.0035 + | UK Fck45 -> 0.0035 + | UK Fck50 -> 0.0035 + | UK Fck55 -> 0.0031 + | UK Fck60 -> 0.0029 + | UK Fck70 -> 0.0027 + | UK Fck80 -> 0.0026 + | UK Fck90 -> 0.0026 + + let c3 (fck: CylinderStrength) = + match fck with + | UK Fck12 -> 0.00175 + | UK Fck16 -> 0.00175 + | UK Fck20 -> 0.00175 + | UK Fck25 -> 0.00175 + | UK Fck30 -> 0.00175 + | UK Fck35 -> 0.00175 + | UK Fck40 -> 0.00175 + | UK Fck45 -> 0.00175 + | UK Fck50 -> 0.00175 + | UK Fck55 -> 0.0018 + | UK Fck60 -> 0.0019 + | UK Fck70 -> 0.0020 + | UK Fck80 -> 0.0022 + | UK Fck90 -> 0.0023 + + let cu3 (fck: CylinderStrength) = + match fck with + | UK Fck12 -> 0.0035 + | UK Fck16 -> 0.0035 + | UK Fck20 -> 0.0035 + | UK Fck25 -> 0.0035 + | UK Fck30 -> 0.0035 + | UK Fck35 -> 0.0035 + | UK Fck40 -> 0.0035 + | UK Fck45 -> 0.0035 + | UK Fck50 -> 0.0035 + | UK Fck55 -> 0.0031 + | UK Fck60 -> 0.0029 + | UK Fck70 -> 0.0027 + | UK Fck80 -> 0.0026 + | UK Fck90 -> 0.0026 + + let n (fck: CylinderStrength) = + match fck with + | UK Fck12 -> 2.0 + | UK Fck16 -> 2.0 + | UK Fck20 -> 2.0 + | UK Fck25 -> 2.0 + | UK Fck30 -> 2.0 + | UK Fck35 -> 2.0 + | UK Fck40 -> 2.0 + | UK Fck45 -> 2.0 + | UK Fck50 -> 2.0 + | UK Fck55 -> 1.75 + | UK Fck60 -> 1.6 + | UK Fck70 -> 1.45 + | UK Fck80 -> 1.4 + | UK Fck90 -> 1.4 + + [] + module TimeDependentProperties = + + let private s cement = + match cement with + | ClassN -> 0.25 + | ClassR -> 0.20 + | ClassS -> 0.38 + + let private α age = + match age with + | (Age age) when age < 28 -> 1.0 + | _ -> (2.0 / 3.0) + + let private βcc_t cement age = + let (Age age) = age + s cement |> exp |> (*) (1.0 - sqrt (28.0 / float age)) + + [] + module Strength = + + let fcm_t (fck: CylinderStrength) (cem: Cement) (age: Age) = + let βcc_t = βcc_t cem age + let fcm_basic = BasicProperties.Strength.fcm fck + βcc_t * fcm_basic + + let fctm_t (fck: CylinderStrength) (cem: Cement) (age: Age) = + let βcc_t = βcc_t cem age + let fctm_basic = BasicProperties.Strength.fctm fck + (βcc_t ** (α age)) * fctm_basic + + [] + module ElasticModulus = + + let Ecm_t (fck: CylinderStrength) (cem: Cement) (agg: Aggregate) (age: Age) = + let fcm = BasicProperties.Strength.fcm fck + let fcm_timeDep = Strength.fcm_t fck cem age + let Ecm = BasicProperties.ElasticModulus.Ecm fck agg + ((fcm_timeDep / fcm) ** 0.3) * Ecm diff --git a/src/concrete/ConcreteSerializer.fs b/src/concrete/ConcreteSerializer.fs new file mode 100644 index 0000000..336ca68 --- /dev/null +++ b/src/concrete/ConcreteSerializer.fs @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Concrete + +// module ConcreteSerializer = + +// module CylinderStrength = + +// let deserialize (region: string) (fck: string) = +// match region.ToUpperInvariant() with +// | "UK" -> +// match fck.ToUpperInvariant() with +// | "C12/15" -> UKConcrete Fck12 +// | "C16/20" -> UKConcrete Fck16 +// | "C20/25" -> UKConcrete Fck20 +// | "C25/30" -> UKConcrete Fck25 +// | "C30/37" -> UKConcrete Fck30 +// | "C35/45" -> UKConcrete Fck35 +// | "C40/50" -> UKConcrete Fck40 +// | "C45/55" -> UKConcrete Fck45 +// | "C50/60" -> UKConcrete Fck50 +// | "C55/67" -> UKConcrete Fck55 +// | "C60/75" -> UKConcrete Fck60 +// | "C70/85" -> UKConcrete Fck70 +// | "C80/95" -> UKConcrete Fck80 +// | "C90/105" -> UKConcrete Fck90 +// | _ -> invalidArg $"{nameof(fck)}" "No matching cylinder strength." +// | _ -> invalidArg $"{nameof(region)}" "No matching Eurocode region." + +// let serialize (fck: CylinderStrength) = +// match fck with +// | UKConcrete Fck12 -> "C12/15" +// | UKConcrete Fck16 -> "C16/20" +// | UKConcrete Fck20 -> "C20/25" +// | UKConcrete Fck25 -> "C25/30" +// | UKConcrete Fck30 -> "C30/37" +// | UKConcrete Fck35 -> "C35/45" +// | UKConcrete Fck40 -> "C40/50" +// | UKConcrete Fck45 -> "C45/55" +// | UKConcrete Fck50 -> "C50/60" +// | UKConcrete Fck55 -> "C55/67" +// | UKConcrete Fck60 -> "C60/75" +// | UKConcrete Fck70 -> "C70/85" +// | UKConcrete Fck80 -> "C80/95" +// | UKConcrete Fck90 -> "C90/105" + +// module Aggregate = + +// let deserialize (agg: string) = +// match agg.ToLowerInvariant() with +// | "basalt" -> Basalt +// | "limestone" -> Limestone +// | "quartzite" -> Quartzite +// | "sandstone" -> Sandstone +// | _ -> invalidArg $"{nameof(agg)}" "No matching aggregate type." + +// let serialize (aggregate: Aggregate) = +// match aggregate with +// | Basalt -> "Basalt" +// | Limestone -> "Limestone" +// | Quartzite -> "Quartzite" +// | Sandstone -> "Sandstone" + +// module Cement = + +// let deserialize (cem: string) = +// match cem.ToLowerInvariant() with +// | "classn" -> ClassN +// | "classs" -> ClassS +// | "classr" -> ClassR +// | _ -> invalidArg $"{nameof(cem)}" "No matching cement type." + +// let serialize (cem: Cement) = +// match cem with +// | ClassN -> "ClassN" +// | ClassR -> "ClassR" +// | ClassS -> "ClassS" + +// module MixProperties = + +// type MixPropertiesDTO = +// { Aggregate: string +// Cement: string +// Grade: string +// Region: string } + +// let deserialize (concreteMix: MixPropertiesDTO) : MixProperties = +// match concreteMix with +// | { Aggregate = agg +// Cement = cem +// Grade = fck +// Region = region } -> +// { Aggregate = Aggregate.deserialize agg +// Cement = Cement.deserialize cem +// Grade = CylinderStrength.deserialize region fck } + +// let serialize (mix: MixProperties) : MixPropertiesDTO = +// let region = +// match mix.Grade with +// | UKConcrete _ -> "UK" +// { Aggregate = Aggregate.serialize mix.Aggregate +// Cement = Cement.serialize mix.Cement +// Grade = CylinderStrength.serialize mix.Grade +// Region = region } + +// module Strengths = + +// type StrengthsDTO = +// { fck: float +// fcm: float +// fctm: float +// fcm_t: float +// fctm_t: float } + +// let deserialize (strengths: StrengthsDTO) : Strengths = +// match strengths with +// | { fck = _fck +// fcm = _fcm +// fctm = _fctm +// fcm_t = _fcm_t +// fctm_t = _fctm_t } -> +// { fck = AddUnitsToFloat _fck +// fcm = AddUnitsToFloat _fcm +// fctm = AddUnitsToFloat _fctm +// fcm_t = AddUnitsToFloat _fcm_t +// fctm_t = AddUnitsToFloat _fctm_t } + +// let serialize (strengths: Strengths) : StrengthsDTO = +// { fck = stripUnitsFromFloat strengths.fck +// fcm = stripUnitsFromFloat strengths.fcm +// fctm = stripUnitsFromFloat strengths.fctm +// fcm_t = stripUnitsFromFloat strengths.fcm_t +// fctm_t = stripUnitsFromFloat strengths.fctm_t } + +// module ElasticModuli = + +// type ElasticModuliDTO = +// { Ecm: float +// Ecm_t: float } + +// let deserialize (moduli: ElasticModuliDTO) : ElasticModuli = +// { Ecm = AddUnitsToFloat moduli.Ecm +// Ecm_t = AddUnitsToFloat moduli.Ecm_t } + +// let serialize (moduli: ElasticModuli) : ElasticModuliDTO = +// { Ecm = stripUnitsFromFloat moduli.Ecm +// Ecm_t = stripUnitsFromFloat moduli.Ecm_t } + +// module MechanicalProperties = + +// open Strengths +// open ElasticModuli + +// type MechanicalPropertiesDTO = +// { Strengths: StrengthsDTO +// Strains: Strains +// ElasticModuli: ElasticModuliDTO } + +// let deserialize (props: MechanicalPropertiesDTO) : MechanicalProperties = +// match props with +// | { Strengths = strengths +// Strains = strains +// ElasticModuli = moduli } -> +// { Strengths = Strengths.deserialize strengths +// Strains = strains +// ElasticModuli = ElasticModuli.deserialize moduli } + +// let serialize (props: MechanicalProperties) : MechanicalPropertiesDTO = +// { Strengths = Strengths.serialize props.Strengths +// Strains = props.Strains +// ElasticModuli = ElasticModuli.serialize props.ElasticModuli } + +// module Concrete = + +// open MixProperties +// open MechanicalProperties + +// type ConcreteDTO = +// { Age: int +// Density: float +// MixProperties: MixPropertiesDTO +// MechanicalProperties: MechanicalPropertiesDTO } + +// let deserialize (concrete: ConcreteDTO) : Concrete = +// match concrete with +// | { Age = age +// Density = density +// MixProperties = mix +// MechanicalProperties = props } -> +// { Age = CurrentAge (AddUnitsToInteger32 age) +// Density = AddUnitsToFloat density +// MixProperties = MixProperties.deserialize mix +// MechanicalProperties = MechanicalProperties.deserialize props } + +// let serialize (concrete: Concrete) : ConcreteDTO = +// let unwrapAge (CurrentAge age) = age +// { Age = stripUnitsFromInteger (unwrapAge concrete.Age) +// Density = stripUnitsFromFloat concrete.Density +// MixProperties = MixProperties.serialize concrete.MixProperties +// MechanicalProperties = MechanicalProperties.serialize concrete.MechanicalProperties } diff --git a/src/concrete/Creep.fs b/src/concrete/Creep.fs new file mode 100644 index 0000000..dff7a29 --- /dev/null +++ b/src/concrete/Creep.fs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Structures + +// open type System.Math +// open FSharp.Data.UnitSystems.SI.UnitSymbols + +// module Creep = + +//type RelativeHumidity = RelativeHumidity of float +//type LoadingAge = LoadingAge of int +//type NotionalSize = NotionalSize of h0:float + +/// Convenient collection of Creep and Shrinkage related parameters. +//type CreepShrinkage = +// { RelativeHumidity: RelativeHumidity +// LoadingAge: LoadingAge +// NotionalSize: NotionalSize +// CreepCoefficient: float +// ShrinkageStrain: float } + +// // Partially applied functions to calculate Creep α factors. +// let private α (power: float) (fcm: float) = (35.0 / fcm) ** power + +// let α1 = α 0.7 +// let α2 = α 0.2 +// let α3 = α 0.5 + +// /// β_fcm is a factor to allow for the effect of concrete strength on the +// /// notional creep coefficient. +// let βfcm (fcm: float) = +// let fcm = float fcm +// 16.8 / sqrt fcm + +// /// β_t0 is a factor to allow for the effect of concrete age at loading +// /// on the notional creep coefficient. +// let βt0 (LoadingAge loadingAge) = +// 1.0 / (0.1 + (float loadingAge ** 0.2)) + +// /// βH is a coefficient depending on the relative humidity (RH in %). +// let βH (RelativeHumidity rh) (NotionalSize h0) (fcm: float) = +// let h0 = float h0 +// if fcm <= 35.0 then +// let βh = +// 0.012 * (rh ** 18.0) +// |> (+) 1.0 +// |> (*) 1.5 +// |> (*) h0 +// |> (+) 250.0 +// if βh <= 1500.0 then βh else 1500.0 +// else +// let βh = +// 0.012 * (rh ** 18.0) +// |> (+) 1.0 +// |> (*) 1.5 +// |> (*) h0 +// |> (+) (250.0 * α3 fcm) +// if βh <= (1500.0 * α3 fcm) then βh else (1500.0 * α3 fcm) + +// /// β_ctt0 is a coefficient to describe the development of creep with time after loading. +// let βctt0 (LoadingAge loadingAge) (CurrentAge currentAge) (βH: float) = +// let t = (float)currentAge +// let t0 = (float)loadingAge +// let numerator = t - t0 +// let denominator = βH + t - t0 +// let quotient = numerator / denominator +// quotient ** 0.3 + +// /// Returns the Notional Creep Coefficient (RH) to EC2. +// let ψRH (RelativeHumidity rh) (NotionalSize h0) (fcm: float) = +// let h0 = (float)h0 +// if fcm <= 35.0 then +// let numerator = 1.0 - rh +// let denominator = 0.1 * Cbrt(h0) +// let quotient = numerator / denominator +// 1.0 + quotient +// else +// let numerator = 1.0 - rh +// let denominator = 0.1 * Cbrt(h0) * (α1 fcm) +// let quotient = numerator / denominator +// (1.0 + quotient) * (α2 fcm) + +// /// Returns the Creep Coefficient to EC2. +// let ψtt0 (βctt0: float) (βfcm: float) (βt0: float) (ψRH: float) = +// ψRH * βfcm * βt0 * βctt0 + +// /// Calculates notional geometric size of member. +// let private getNotionalSize (section: Shape3D) = +// let area = getSectionalArea section +// let perimeter = getPerimeter section +// 2.0 * area / perimeter |> NotionalSize + +// let private validateLoadingAge (loadingAge: int) (CurrentAge concreteAge) = +// if loadingAge <= 0 || loadingAge > concreteAge then +// invalidArg $"nameof{loadingAge}" "Illegal concrete loading age." +// else LoadingAge loadingAge + +// let private validateRelativeHumidity (rh: float) = +// if (rh <= 0.0 || rh >= 1.0) then +// invalidArg $"{nameof(rh)}" "Relative Humidity outside allowable range." +// else RelativeHumidity rh + +// /// Calculates the Creep Coefficient, ψtt0, to EC2. +// let getCreepCoefficient (section: Shape3D) (loadingAge: int) (relativeHumidity: float) concrete = +// let h0 = getNotionalSize section +// let rh = validateRelativeHumidity relativeHumidity +// let loadingAge = validateLoadingAge loadingAge concrete.Age +// let fcm = concrete.MechanicalProperties.Strengths.fcm +// let currentAge = concrete.Age +// let βfcm = βfcm fcm +// let βt0 = βt0 loadingAge +// let βH = βH rh h0 fcm +// let βctt0 = βctt0 loadingAge currentAge βH +// let ψRH = ψRH rh h0 fcm +// ψtt0 βctt0 βfcm βt0 ψRH diff --git a/src/concrete/DesignCodes.fs b/src/concrete/DesignCodes.fs new file mode 100644 index 0000000..d3f7098 --- /dev/null +++ b/src/concrete/DesignCodes.fs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Concrete + +/// Supported eurocodes (EC). +type Eurocode = | UK + +/// Supported global design codes. +type DesignCode = Eurocode of Eurocode diff --git a/src/concrete/Detailing.fs b/src/concrete/Detailing.fs new file mode 100644 index 0000000..fd5ec35 --- /dev/null +++ b/src/concrete/Detailing.fs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Structures + +//open System + +///// Centre-to-centre spacing between adjacent bars/links. +//type BarSpacing = BarSpacing of float + +///// Space between bars, measured between the outermost bar dimensions. +//type ClearSpace = ClearSpace of float + +///// Total number of reinforcing bars. +//type NumberOfBars = NumberOfBars of int + +//// Lightweight records specifying rebar configuration. +//type NumberOfBarsAndBarSize = { NumberOfBars: NumberOfBars; BarSize: BarSize } +//type LinkSpacingAndLinkSize = { LinkSpacing: BarSpacing; LinkSize: BarSize } + +//module Detailing = + +// /// Returns no. of intermediate bars along a given length at the prescribed spacing. +// /// This does NOT consider bars at the start or end (i.e. at x = 0 and x = L). +// /// For a 1000mm length at 250mm spacing, the equations returns 3 intermediate bars. +// let getNumberOfIntermediateBars (BarSpacing sp) (Length l) = +// int (Math.Ceiling (l / sp)) - 1 +// |> AddUnitsToInteger32 +// |> NumberOfBars + +// /// Calculates bar spacing for intermediate bars along a given length. +// /// This does NOT consider bars at the start or end (i.e. at x = 0 and x = L). +// /// For a 1000mm length with 3 inner bars and 2 end bars (5 bars in total), +// /// the equation ignores the end bars and returns a spacing of 250mm. +// let getIntermediateBarSpacing (NumberOfBars n) (Length l) = BarSpacing (l / (float n + 1.0)) + +// /// Returns the minimum permissible clear spacing between adjacent bars. +// let getMinimumClearBarSpacing (barSize: BarSize) = ClearSpace barSize.diameter + +// /// Calculates clear spacing between two straight bars by comparing the pythagorean +// /// distance between their respective cross-section centroids in 3D space. +// let clearSpaceBetweenBars (bar1: StraightBar) (bar2: StraightBar) = +// let p1 = bar1.Geometry.StartPoint +// let p2 = bar2.Geometry.StartPoint +// let halfΦ1 = (bar1.BarSize.diameter / 2.0) |> UnitAnnotation.stripUnitsFromFloat +// let halfΦ2 = (bar2.BarSize.diameter / 2.0) |> UnitAnnotation.stripUnitsFromFloat +// (distanceBetweenPoints p1 p2) - (halfΦ1 + halfΦ2) +// |> AddUnitsToFloat + +// // Union type helper methods. +// let internal unwrapBarSpacing (BarSpacing sp) = sp +// let internal unwrapClearSpace (ClearSpace sp) = sp diff --git a/src/concrete/DomainError.fs b/src/concrete/DomainError.fs new file mode 100644 index 0000000..8c0badf --- /dev/null +++ b/src/concrete/DomainError.fs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Concrete + +type ConcreteError = InvalidAge of message: string + +type ReinforcedConcreteError = ConcreteError of ConcreteError diff --git a/src/concrete/Durability.fs b/src/concrete/Durability.fs new file mode 100644 index 0000000..0d4678e --- /dev/null +++ b/src/concrete/Durability.fs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Concrete + +//module Durability = + +// /// Concrete protective cover, measured from the outside face +// /// of the concrete to the outermost rebar surface. +// type Cover = Cover of float + +// type ConcreteCover = +// | RectangularSectionCover of Top:Cover * Left:Cover * Right:Cover * Bottom:Cover * End:Cover +// | CircularSectionCover of Perimeter:Cover * End:CoverearSpace sp) = sp diff --git a/src/concrete/Geometry.fs b/src/concrete/Geometry.fs new file mode 100644 index 0000000..ca471bc --- /dev/null +++ b/src/concrete/Geometry.fs @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Structures + +open System +open Gazelle.Units + +/// Orthogonal geometric cross-sectional axes. +type Axis = + | X + | Y + +/// Orthogonal axes about which rotation occurs. +type RotationalAxis = + | XX + | YY + +// Basic geometric properties. +type Length<[] 'T> = Length of float<'T> +type Width<[] 'T> = Width of float<'T> +type Depth<[] 'T> = Depth of float<'T> +type Diameter<[] 'T> = Diameter of float<'T> +type Perimeter<[] 'T> = Perimeter of float<'T> +type CrossSectionDiameter<[] 'T> = CrossSectionDiameter of float<'T> +type CrossSectionalArea<[] 'T> = CrossSectionalArea of float<'T^2> +type SurfaceArea<[] 'T> = SurfaceArea of float<'T^2> +type Volume<[] 'T> = Volume of float<'T^3> +type SecondMomentOfArea<[] 'T> = SecondMomentOfArea of float<'T^4> + +// Inner/outer specific dimensions. +type InnerWidth<[] 'T> = InnerWidth of float<'T> +type OuterWidth<[] 'T> = OuterWidth of float<'T> +type InnerDepth<[] 'T> = InnerDepth of float<'T> +type OuterDepth<[] 'T> = OuterDepth of float<'T> +type InnerDiameter<[] 'T> = InnerDiameter of float<'T> +type OuterDiameter<[] 'T> = OuterDiameter of float<'T> + +/// Defines whether 3D shape end-faces are to be included in surface area calculations. +type EndFacesFlag = + | IncludeEndFaces + | ExcludeEndFaces + +/// 2D coordinate set. +type Point2D<[] 'T> = { X: float<'T>; Y: float<'T> } + +/// 3D coordinate set. +type Point3D<[] 'T> = + { X: float<'T> + Y: float<'T> + Z: float<'T> } + +/// Cuboidal geometry with centrepoints defined at either end. +type Cuboid<[] 'T> = + { StartPoint: Point3D<'T> + EndPoint: Point3D<'T> + Width: Width<'T> + Depth: Depth<'T> + Length: Length<'T> } + +/// Cylindrical geometry with centrepoints defined at either end. +type Cylinder<[] 'T> = + { StartPoint: Point3D<'T> + EndPoint: Point3D<'T> + Diameter: Diameter<'T> + Length: Length<'T> } + +/// Hoop geometry moulded into a rectangular shape with a circular cross-section. +type RectangularHoop<[] 'T> = + { Centroid: Point3D<'T> + Width: Width<'T> + Depth: Depth<'T> + LegDiameter: CrossSectionDiameter<'T> + InnerWidth: InnerWidth<'T> + InnerDepth: InnerDepth<'T> + OuterWidth: OuterWidth<'T> + OuterDepth: OuterDepth<'T> } + +/// Hoop geometry moulded into a circular shape with a circular cross-section. +type CircularHoop<[] 'T> = + { Centroid: Point3D<'T> + Diameter: Diameter<'T> + LegDiameter: CrossSectionDiameter<'T> + InnerDiameter: InnerDiameter<'T> + OuterDiameter: OuterDiameter<'T> } + +/// Unified type for all 3D solid geometries. +type Solid3D<[] 'T> = + | Cuboid of Cuboid<'T> + | Cylinder of Cylinder<'T> + +/// Unified type for all 3D hoop geometries. +type Hoop3D<[] 'T> = + | RectangularHoop of RectangularHoop<'T> + | CircularHoop of CircularHoop<'T> + +/// Unified type for all 3D geometries. +type Shape3D<[] 'T> = + | Solid3D of Solid3D<'T> + | Hoop3D of Hoop3D<'T> + + +/// Collection of modules/functions to calculate geometric properties for various shapes. +[] +module Geometry = + + /// Collection of utility functions to create various geometrical shapes. + module Create = + + /// Creates a validated Cuboid instance. + /// + let cuboid (xyCentroid: Point2D<'T>) width depth length = + match width, depth, length with + | width, _, _ when width <= 0.0<_> -> invalidArg $"{nameof (width)}" "Width <= 0." + | _, depth, _ when depth <= 0.0<_> -> invalidArg $"{nameof (depth)}" "Depth <= 0." + | _, _, length when length <= 0.0<_> -> invalidArg $"{nameof (length)}" "Length <= 0." + | _ -> + { StartPoint = + { X = xyCentroid.X + Y = xyCentroid.Y + Z = 0.0<_> } + EndPoint = + { X = xyCentroid.X + Y = xyCentroid.Y + Z = 0.0<_> } + Width = Width width + Depth = Depth depth + Length = Length length } + + /// Creates a validated Cylinder instance. + /// + let cylinder (xyCentroid: Point2D<'T>) (diameter: float<'T>) (length: float<'T>) = + match diameter, length with + | diameter, _ when diameter <= 0.0<_> -> invalidArg $"{nameof (diameter)}" "Diameter <= 0." + | _, length when length <= 0.0<_> -> invalidArg $"{nameof (length)}" "Length <= 0." + | _ -> + { StartPoint = + { X = xyCentroid.X + Y = xyCentroid.Y + Z = 0.0<_> } + EndPoint = + { X = xyCentroid.X + Y = xyCentroid.Y + Z = 0.0<_> } + Diameter = Diameter diameter + Length = Length length } + + + /// Creates a validated RectangularHoop instance. + /// + let rectangularHoop (centroid: Point3D<'T>) width depth legDiameter = + match width, depth, legDiameter with + | width, _, _ when width <= 0.0<_> -> invalidArg $"{nameof (width)}" "Width <= 0." + | _, depth, _ when depth <= 0.0<_> -> invalidArg $"{nameof (depth)}" "Depth <= 0." + | _, _, legDiameter when legDiameter <= 0.0<_> -> invalidArg $"{nameof (legDiameter)}" "Leg Diameter <= 0." + | _ -> + { Centroid = centroid + Width = Width width + Depth = Depth depth + LegDiameter = CrossSectionDiameter legDiameter + InnerWidth = InnerWidth(width - legDiameter) + InnerDepth = InnerDepth(depth - legDiameter) + OuterWidth = OuterWidth(width + legDiameter) + OuterDepth = OuterDepth(depth + legDiameter) } + + /// Creates a validated CircularHoop instance. + /// + let circularHoop (centroid: Point3D<'T>) diameter legDiameter = + match diameter, legDiameter with + | diameter, _ when diameter <= 0.0<_> -> invalidArg $"{nameof (diameter)}" "Diameter <= 0." + | _, legDiameter when legDiameter <= 0.0<_> -> invalidArg $"{nameof (legDiameter)}" "Leg Diameter <= 0." + | _ -> + { Centroid = centroid + Diameter = Diameter diameter + LegDiameter = CrossSectionDiameter legDiameter + InnerDiameter = InnerDiameter(diameter - legDiameter) + OuterDiameter = OuterDiameter(diameter + legDiameter) } + + + /// Collection of utility functions to query geometric properties of shapes. + [] + module Query = + + /// Unified type for all 3D solid geometries. + [] + [] + module Solid3D = + + /// Returns the start coordinate of a given 3D solid geometry. + let getStartPoint (s: Solid3D<'T>) = + match s with + | Cuboid c -> c.StartPoint + | Cylinder c -> c.StartPoint + + /// Returns the end coordinate of a given 3D solid geometry. + let getEndPoint (s: Solid3D<'T>) = + match s with + | Cuboid c -> c.EndPoint + | Cylinder c -> c.EndPoint + + /// Returns the length of a given 3D solid geometry. + let getLength (s: Solid3D<'T>) = + match s with + | Cuboid c -> c.Length + | Cylinder c -> c.Length + + /// Returns the cross-sectional perimeter of a given 3D solid geometry. + let getPerimeter (s: Solid3D<'T>) = + match s with + | Cuboid { Width = Width w; Depth = Depth d } -> Perimeter((2.0 * w) + (2.0 * d)) + | Cylinder { Diameter = Diameter d } -> Perimeter(Math.PI * d) + + /// Returns the cross-sectional area of a given 3D solid geometry. + let getCrossSectionalArea (s: Solid3D<'T>) = + match s with + | Cuboid { Width = Width w; Depth = Depth d } -> CrossSectionalArea(w * d) + | Cylinder { Diameter = Diameter d } -> CrossSectionalArea(Math.PI * (Math.pow2 d) / 4.0) + + /// Returns the volume of a given 3D solid geometry. + let getVolume (s: Solid3D<'T>) = + let (CrossSectionalArea a) = getCrossSectionalArea s + let (Length l) = getLength s + + match s with + | _ -> Volume(a * l) + + /// Returns the surface area of a given 3D solid geometry. Optionally include the end faces. + let getSurfaceArea (f: EndFacesFlag) (s: Solid3D<'T>) = + let (Perimeter p) = getPerimeter s + let (Length l) = getLength s + let (CrossSectionalArea a) = getCrossSectionalArea s + + match s with + | _ -> + match f with + | IncludeEndFaces -> SurfaceArea((2.0 * a) + (p * l)) + | ExcludeEndFaces -> SurfaceArea(p * l) + + /// Returns the second moment of area of a given 3D solid geometry about either the XX or YY axes. + let getSecondMomentOfArea (axis: RotationalAxis) (s: Solid3D<'T>) = + match s with + | Cuboid { Width = Width w; Depth = Depth d } -> + match axis with + | XX -> SecondMomentOfArea(w * Math.pow3 d / 12.0) + | YY -> SecondMomentOfArea(d * Math.pow3 w / 12.0) + | Cylinder { Diameter = Diameter d } -> + match axis with + | _ -> SecondMomentOfArea(Math.PI * Math.pow4 d / 64.0) + + /// Unified type for all 3D hoop geometries. + [] + [] + module Hoop3D = + + /// Returns the centroid coordinates of a given hoop geometry. + let getCentroid (h: Hoop3D<'T>) = + match h with + | RectangularHoop rh -> rh.Centroid + | CircularHoop ch -> ch.Centroid + + /// Returns the hoop length. + let getLength (h: Hoop3D<'T>) = + match h with + | RectangularHoop { Width = Width w; Depth = Depth d } -> Length((2.0 * w) + (2.0 * d)) + | CircularHoop { Diameter = Diameter d } -> Length(Math.PI * d) + + /// Returns the cross-sectional area of the hoop leg. + let getCrossSectionalAreaOfLeg (h: Hoop3D<'T>) = + match h with + | RectangularHoop { LegDiameter = CrossSectionDiameter sd } + | CircularHoop { LegDiameter = CrossSectionDiameter sd } -> + CrossSectionalArea(Math.PI * Math.pow2 sd / 4.0) + + /// Returns the hoop volume. + let getVolume (h: Hoop3D<'T>) = + let (Length l) = getLength h + let (CrossSectionalArea a) = getCrossSectionalAreaOfLeg h + + match h with + | _ -> Volume(l * a) + + /// Unified type for all 3D geometries. + [] + [] + module Shape3D = + + /// Returns the 3D shape volume. + let getVolume (shape: Shape3D<'T>) = + match shape with + | Solid3D s -> Solid3D.getVolume s + | Hoop3D h -> Hoop3D.getVolume h + + /// Returns the 3D shape length. + let getLength (shape: Shape3D<'T>) = + match shape with + | Solid3D s -> Solid3D.getLength s + | Hoop3D h -> Hoop3D.getLength h + + /// Collection of convenient utility functions to unwrap single-case union types. + [] + module Unwrap = + + let width (Width w) = w + let depth (Depth d) = d + let diameter (Diameter d) = d + let crossSectionDiameter (CrossSectionDiameter sd) = sd + let length (Length l) = l + let perimeter (Perimeter p) = p + let crossSectionalArea (CrossSectionalArea a) = a + let volume (Volume v) = v + let surfaceArea (SurfaceArea sa) = sa + let secondMomentOfArea (SecondMomentOfArea sm) = sm + let innerWidth (InnerWidth iw) = iw + let innerDepth (InnerDepth id) = id + let innerDiameter (InnerDiameter id) = id + let outerWidth (OuterWidth ow) = ow + let outerDepth (OuterDepth od) = od + let outerDiameter (OuterDiameter od) = od diff --git a/src/concrete/LimitStates.fs b/src/concrete/LimitStates.fs new file mode 100644 index 0000000..9138682 --- /dev/null +++ b/src/concrete/LimitStates.fs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Concrete + +type DesignSituation = + | Persistent + | Transient + | Accidental + +type LimitState = + | ULS of DesignSituation + | SLS diff --git a/src/concrete/RCColumn.fs b/src/concrete/RCColumn.fs new file mode 100644 index 0000000..243afb4 --- /dev/null +++ b/src/concrete/RCColumn.fs @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Concrete + +//open System + +//type RebarCageSpec = +// | WallCageSpec of WallCage.Specification +// | ColumnCageSpec of ColumnCage.Specification + +//type RebarCage = +// | WallCage of WallCage.Cage +// | ColumnCage of ColumnCage.Cage + +//type RCSection = +// { Geometry: Solid3D +// Concrete: Concrete +// Cover: ConcreteCover +// RebarCage: RebarCage } + +//[] +//module RCSection = + +// /// Returns a fully configured RC Section with concrete/steel properties and +// /// reinforcement cage. Method leverages complex union types to switch between +// /// different categories of section (e.g. column vs. wall) and to allow +// /// for alternative cross-sectional geometries. +// let create concrete steel sectionGeometry cover (cageSpecification: RebarCageSpec) = +// let cage = +// match cageSpecification with +// | WallCageSpec wallCageSpec -> +// match sectionGeometry, cover with +// | Cuboid (Centreline cl, Width w, Depth d), +// RectangularSectionCover (Cover top, Cover left, Cover right, Cover bottom, Cover ends) -> +// let horizBarSize = wallCageSpec.HorizontalBarSpec.BarSize.diameter +// let cageWidth = w - left - right - horizBarSize +// let cageDepth = d - top - bottom +// let cageLength = (AddUnitsToFloat cl.Length) - (2.0 * ends) +// RectangularCage (Width cageWidth, Depth cageDepth, Length cageLength) +// |> WallCage.create wallCageSpec steel |> WallCage +// | Cylinder _, _ | _, CircularSectionCover _ -> +// invalidArg $"{nameof(sectionGeometry)}" "Walls do not support cylindrical geometries." + +// | ColumnCageSpec columnCageSpec -> +// match sectionGeometry, cover with +// | Cuboid (Centreline cl, Width w, Depth d), +// RectangularSectionCover (Cover top, Cover left, Cover right, Cover bottom, Cover ends) -> +// match columnCageSpec with +// | ColumnCage.RectangularCageSpec rectColCageSpec -> +// let linkSize = rectColCageSpec.Links.LinkSize.diameter +// let cageWidth = w - left - right - linkSize +// let cageDepth = d - top - bottom - linkSize +// let cageLength = (AddUnitsToFloat cl.Length) - (2.0 * ends) +// RectangularCage (Width cageWidth, Depth cageDepth, Length cageLength) +// |> ColumnCage.create columnCageSpec steel |> ColumnCage +// | Cylinder _, _ | _, CircularSectionCover _ -> +// raise (NotImplementedException "Circular geometries not yet supported.") + +// { Geometry = sectionGeometry +// Concrete = concrete +// Cover = cover +// RebarCage = cage } diff --git a/src/concrete/RCPier.fs b/src/concrete/RCPier.fs new file mode 100644 index 0000000..5d5e588 --- /dev/null +++ b/src/concrete/RCPier.fs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Concrete + +//[] +//module RCPier = + +// type RCPier = +// { Section: RCSection +// PierDefinition: Pier } + +// let create pierDef rcSection = +// { Section = rcSection +// PierDefinition = pierDef } diff --git a/src/concrete/Rebar.fs b/src/concrete/Rebar.fs new file mode 100644 index 0000000..042c058 --- /dev/null +++ b/src/concrete/Rebar.fs @@ -0,0 +1,101 @@ +namespace Gazelle.Concrete + +open Gazelle.Units +open FSharp.Data.UnitSystems.SI.UnitSymbols + +type UKSteelGrade = + | B400 + | B500 + | B600 + | C400 + | C500 + | C600 + +type SteelGrade = UKSteelGrade of UKSteelGrade + +type UKBarSize = + | H6 + | H8 + | H10 + | H12 + | H16 + | H20 + | H25 + | H32 + | H40 + | H50 + +type BarSize = UKBarSize of UKBarSize + +type StraightBar = + { SteelGrade: SteelGrade + BarSize: BarSize + Geometry: Cylinder } + +type RectangularLink = + { SteelGrade: SteelGrade + BarSize: BarSize + Geometry: RectangularHoop } + +[] +module Rebar = + + let getDiameter (b: BarSize) = + match b with + | UKBarSize size -> + match size with + | H6 -> 6.0 + | H8 -> 8.0 + | H10 -> 10.0 + | H12 -> 12.0 + | H16 -> 16.0 + | H20 -> 20.0 + | H25 -> 25.0 + | H32 -> 32.0 + | H40 -> 40.0 + | H50 -> 50.0 + + [] + module Steel = + + let getYieldStrength (s: SteelGrade) = + match s with + | UKSteelGrade grade -> + match grade with + | B400 -> 400.0 + | B500 -> 500.0 + | B600 -> 600.0 + | C400 -> 400.0 + | C500 -> 500.0 + | C600 -> 600.0 + + let getDensity (s: SteelGrade) = + match s with + | UKSteelGrade _ -> Density 7850.0 + + let getElasticModulus (s: SteelGrade) = + match s with + | UKSteelGrade _ -> 200_000.0 + + [] + module StraightBar = + + let tryCreate (s: SteelGrade) (b: BarSize) (centroid: Point2D) (l: Length) : StraightBar = + let (Length length) = l + let diameter = getDiameter b + + { SteelGrade = s + BarSize = b + Geometry = Geometry.Create.cylinder centroid diameter length } + + [] + module RectangularLink = + + let tryCreate (s: SteelGrade) (b: BarSize) (centroid: Point3D) (w: Width) (d: Depth) = + let (Width width) = w + let (Depth depth) = d + let legDiameter = getDiameter b + + { SteelGrade = s + BarSize = b + Geometry = Geometry.Create.rectangularHoop centroid width depth legDiameter } diff --git a/src/units/Annotation.fs b/src/units/Annotation.fs new file mode 100644 index 0000000..f2ca505 --- /dev/null +++ b/src/units/Annotation.fs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Units + +/// +/// Remove unit-of-measure type annotations from various types. +/// +[] +module RemoveUnits = + + /// + /// Remove unit-of-measure type annotation from 32-bit integer. + /// + /// Annotated 32-bit integer. + /// Value without unit-of-measure. + let fromInt32 (value: int<'T>) : int = int value + + /// + /// Remove unit-of-measure type annotation from float. + /// + /// Annotated float. + /// Value without unit-of-measure. + let fromFloat (value: float<'T>) : float = float value + +/// +/// Add unit-of-measure type annotations to various types. +/// +[] +module AddUnits = + + /// + /// Add unit-of-measure type annotation to 32-bit integer. + /// + /// Primitive 32-bit integer. + /// Value annotated with unit-of-measure. + let toInt32 (value: int) : int<'T> = + LanguagePrimitives.Int32WithMeasure value + + /// + /// Add unit-of-measure type annotation to float. + /// + /// Primitive float. + /// Value annotated with unit-of-measure. + let toFloat (value: float) : float<'T> = + LanguagePrimitives.FloatWithMeasure value diff --git a/src/units/Conversion.fs b/src/units/Conversion.fs new file mode 100644 index 0000000..27a8786 --- /dev/null +++ b/src/units/Conversion.fs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Units + +open FSharp.Data.UnitSystems.SI.UnitSymbols + +/// +/// Functions to convert unit-of-measure type annotations. +/// +[] +module Convert = + + /// + /// Converts millimetres to metres. + /// + /// Value in millimetres. + /// Value in metres. + let millimetresToMetres (n: float) : float = n / 1000.0 + + /// + /// Converts metres to millimetres. + /// + /// Value in metres. + /// Value in millimetres. + let metresToMillimetres (n: float) : float = n * 1000.0 + + /// + /// Converts Newtons to Kilonewtons. + /// + /// Value in Newtons. + /// Value in Kilonewtons. + let newtonsToKilonewtons (x: float) : float = x / 1000.0 + + /// + /// Converts Kilonewtons to Newtons. + /// + /// Value in Kilonewtons. + /// Value in Newtons. + let kilonewtonsToNewtons (x: float) : float = x * 1000.0 diff --git a/src/units/Math.fs b/src/units/Math.fs new file mode 100644 index 0000000..129ad99 --- /dev/null +++ b/src/units/Math.fs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Units + +/// +/// Collection of mathematical modules that operate on +/// values with unit-of-measure type annotations. +/// +[] +module Math = + + /// + /// Unit safe exponentiation function raised to the power of 2. + /// + /// Original value. + /// Value raised to the power of 2. + let pow2 (n: float<'T>) : float<'T^2> = n * n + + /// + /// Unit safe exponentiation function raised to the power of 3. + /// + /// Original value. + /// Value raised to the power of 3. + let pow3 (n: float<'T>) : float<'T^3> = n * n * n + + /// + /// Unit safe exponentiation function raised to the power of 4. + /// + /// Original value. + /// Value raised to the power of 4. + let pow4 (n: float<'T>) : float<'T^4> = n * n * n * n diff --git a/src/units/Units.fs b/src/units/Units.fs new file mode 100644 index 0000000..1a8d205 --- /dev/null +++ b/src/units/Units.fs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Gazelle: a fast, cross-platform engine for structural analysis & design. +// Copyright (C) 2024 James S. Bayley + +namespace Gazelle.Units + +open FSharp.Data.UnitSystems.SI.UnitSymbols + +/// +/// Time in days. +/// +[] +type days + +/// +/// Length in millimetres. +/// +[] +type mm + +/// +/// Force in Kilonewtons. +/// +[] +type kN + +/// +/// Moment or Torque in Kilonewton-Metres. +/// +[] +type kNm = kN * m + +/// +/// Number of layers. +/// +[] +type layers + +/// +/// Number of bars. +/// +[] +type bars + +/// +/// Unit of density. +/// +type Density<[] 'TMass, [] 'TLength> = Density of float<'TMass / 'TLength^3> + +/// +/// Unit of mechanical stress. +/// +type Stress<[] 'TForce, [] 'TLength> = Stress of float<'TForce / 'TLength^2> + +/// +/// Unit of mechanical pressure. +/// +type Pressure<[] 'TForce, [] 'TLength> = Stress<'TForce, 'TLength> diff --git a/tests/Gazelle.Tests.fsproj b/tests/Gazelle.Tests.fsproj new file mode 100644 index 0000000..fd94ee8 --- /dev/null +++ b/tests/Gazelle.Tests.fsproj @@ -0,0 +1,31 @@ + + + + James S. Bayley + GazelleKit + net8.0 + https://github.com/gazellekit/gazelle + false + true + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/tests/Geometry.Tests.fs b/tests/Geometry.Tests.fs new file mode 100644 index 0000000..f2baeff --- /dev/null +++ b/tests/Geometry.Tests.fs @@ -0,0 +1,489 @@ +namespace Gazelle.Geometry.Tests + +open System +open Xunit + +module GeometryTests = + + [] + let ``Mock Test`` () = Assert.True(true) + +// module CuboidTests = + +// [] +// let ``Cuboid with width and depth of 200mm and length greater than 0mm has perimeter of 800mm`` () = +// let c = +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 200.0 200.0 1000.0 + +// let expected = 800.0 + +// let actual = +// Geometry.Query.Solid3D.getPerimeter (Cuboid c) |> Geometry.Unwrap.perimeter + +// Assert.Equal(expected, actual) + +// [] +// let ``Cuboid with width and depth of 200mm and length greater than 0mm has area of 40_000mm^2`` () = +// let c = +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 200.0 200.0 1000.0 + +// let expected = 40_000.0 + +// let actual = +// Geometry.Query.Solid3D.getCrossSectionalArea (Cuboid c) +// |> Geometry.Unwrap.crossSectionalArea + +// Assert.Equal(expected, actual) + +// [] +// let ``Cuboid with width and depth of 200mm and length of 1000mm has volume of 40_000_000mm^3`` () = +// let c = +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 200.0 200.0 1000.0 + +// let expected = 40_000_000.0 +// let actual = Geometry.Query.Solid3D.getVolume (Cuboid c) |> Geometry.Unwrap.volume +// Assert.Equal(expected, actual) + +// [] +// let ``Cuboid with width and depth of 200mm and length of 1000mm has 2nd-moment about XX of 133_333_333.3mm^4`` +// () +// = +// let c = +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 200.0 200.0 1000.0 + +// let expected = 133_333_333.3 + +// let actual = +// Geometry.Query.Solid3D.getSecondMomentOfArea XX (Cuboid c) +// |> Geometry.Unwrap.secondMomentOfArea +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Cuboid with width and depth of 200mm and length of 1000mm has 2nd-moment about YY of 133_333_333.3mm^4`` +// () +// = +// let c = +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 200.0 200.0 1000.0 + +// let expected = 133_333_333.3 + +// let actual = +// Geometry.Query.Solid3D.getSecondMomentOfArea YY (Cuboid c) +// |> Geometry.Unwrap.secondMomentOfArea +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Cuboid with width and depth of 200mm and length of 1000mm has surface area (excl. ends) of 800_000mm^2`` +// () +// = +// let c = +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 200.0 200.0 1000.0 + +// let expected = 800_000.0 + +// let actual = +// Geometry.Query.Solid3D.getSurfaceArea ExcludeEndFaces (Cuboid c) +// |> Geometry.Unwrap.surfaceArea + +// Assert.Equal(expected, actual) + +// [] +// let ``Cuboid with width and depth of 200mm and length of 1000mm has surface area (incl. ends) of 880_000mm^2`` +// () +// = +// let c = +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 200.0 200.0 1000.0 + +// let expected = 880_000.0 + +// let actual = +// Geometry.Query.Solid3D.getSurfaceArea IncludeEndFaces (Cuboid c) +// |> Geometry.Unwrap.surfaceArea + +// Assert.Equal(expected, actual) + +// [] +// let ``Cuboid with width less than 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } -200.0 200.0 1000.0 +// |> ignore) + +// [] +// let ``Cuboid with width equal to 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 0.0 200.0 1000.0 +// |> ignore) + +// [] +// let ``Cuboid with depth less than 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 200.0 -200.0 1000.0 +// |> ignore) + +// [] +// let ``Cuboid with depth equal to 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 200.0 0.0 1000.0 +// |> ignore) + +// [] +// let ``Cuboid with length less than 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 200.0 200.0 -1000.0 +// |> ignore) + +// [] +// let ``Cuboid with length equal to 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.cuboid { X = 0.0; Y = 0.0 } 200.0 200.0 0.0 +// |> ignore) + +// module CylinderTests = + +// [] +// let ``Cylinder with diameter of 200mm and length greater than 0mm has perimeter of 200π mm`` () = +// let c = Geometry.Create.cylinder { X = 0.0; Y = 0.0 } 200.0 1000.0 +// let expected = 200.0 * Math.PI + +// let actual = +// Geometry.Query.Solid3D.getPerimeter (Cylinder c) |> Geometry.Unwrap.perimeter + +// Assert.Equal(expected, actual) + +// [] +// let ``Cylinder with diameter of 200mm and length greater than 0mm has area of 10_000π mm^2`` () = +// let c = Geometry.Create.cylinder { X = 0.0; Y = 0.0 } 200.0 1000.0 +// let expected = 10_000.0 * Math.PI + +// let actual = +// Geometry.Query.Solid3D.getCrossSectionalArea (Cylinder c) +// |> Geometry.Unwrap.crossSectionalArea + +// Assert.Equal(expected, actual) + +// [] +// let ``Cylinder with diameter of 200mm and length of 1000mm has volume of 31_415_926.5 mm^3`` () = +// let c = Geometry.Create.cylinder { X = 0.0; Y = 0.0 } 200.0 1000.0 +// let expected = 31_415_926.5 + +// let actual = +// Geometry.Query.Solid3D.getVolume (Cylinder c) +// |> Geometry.Unwrap.volume +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Cylinder with diameter of 200mm and length of 1000mm has 2nd-moment about XX of 78_539_816.3 mm^4`` () = +// let c = Geometry.Create.cylinder { X = 0.0; Y = 0.0 } 200.0 1000.0 +// let expected = 78_539_816.3 + +// let actual = +// Geometry.Query.Solid3D.getSecondMomentOfArea XX (Cylinder c) +// |> Geometry.Unwrap.secondMomentOfArea +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Cylinder with diameter of 200mm and length of 1000mm has 2nd-moment about YY of 78_539_816.3 mm^4`` () = +// let c = Geometry.Create.cylinder { X = 0.0; Y = 0.0 } 200.0 1000.0 +// let expected = 78_539_816.3 + +// let actual = +// Geometry.Query.Solid3D.getSecondMomentOfArea YY (Cylinder c) +// |> Geometry.Unwrap.secondMomentOfArea +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Cylinder with diameter of 200mm and length of 1000mm has surface area (excl. ends) of 628_318.5 mm^2`` +// () +// = +// let c = Geometry.Create.cylinder { X = 0.0; Y = 0.0 } 200.0 1000.0 +// let expected = 628_318.5 + +// let actual = +// Geometry.Query.Solid3D.getSurfaceArea ExcludeEndFaces (Cylinder c) +// |> Geometry.Unwrap.surfaceArea +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Cylinder with diameter of 200mm and length of 1000mm has surface area (incl. ends) of 691_150.4 mm^2`` +// () +// = +// let c = Geometry.Create.cylinder { X = 0.0; Y = 0.0 } 200.0 1000.0 +// let expected = 691_150.4 + +// let actual = +// Geometry.Query.Solid3D.getSurfaceArea IncludeEndFaces (Cylinder c) +// |> Geometry.Unwrap.surfaceArea +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Cylinder with diameter less than 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.cylinder { X = 0.0; Y = 0.0 } -200.0 1000.0 +// |> ignore) + +// [] +// let ``Cylinder with diameter equal to 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.cylinder { X = 0.0; Y = 0.0 } 0.0 1000.0 +// |> ignore) + +// [] +// let ``Cylinder with length less than 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.cylinder { X = 0.0; Y = 0.0 } 200.0 -1000.0 +// |> ignore) + +// [] +// let ``Cylinder with length equal to 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.cylinder { X = 0.0; Y = 0.0 } 200.0 0.0 +// |> ignore) + +// module RectangularHoopTests = + +// [] +// let ``Rectangular Hoop with width and depth of 200mm has length of 800mm`` () = +// let rh = +// Geometry.Create.rectangularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// 200.0 +// 10.0 + +// let expected = 800.0 + +// let actual = +// Geometry.Query.Hoop3D.getLength (RectangularHoop rh) |> Geometry.Unwrap.length + +// Assert.Equal(expected, actual) + +// [] +// let ``Rectangular Hoop with leg diameter of 10mm has sectional area of 78.5mm^2`` () = +// let rh = +// Geometry.Create.rectangularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// 200.0 +// 10.0 + +// let expected = 78.5 + +// let actual = +// Geometry.Query.Hoop3D.getCrossSectionalAreaOfLeg (RectangularHoop rh) +// |> Geometry.Unwrap.crossSectionalArea +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Rectangular Hoop with width and depth of 200mm and leg diameter of 10mm has volume 62832mm^3`` () = +// let rh = +// Geometry.Create.rectangularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// 200.0 +// 10.0 + +// let expected = 62831.9 + +// let actual = +// Geometry.Query.Hoop3D.getVolume (RectangularHoop rh) +// |> Geometry.Unwrap.volume +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Rectangular Hoop with width less than 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.rectangularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// -200.0 +// 200.0 +// 10.0 +// |> ignore) + +// [] +// let ``Rectangular Hoop with width equal to 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.rectangularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 0.0 +// 200.0 +// 10.0 +// |> ignore) + +// [] +// let ``Rectangular Hoop with depth less than 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.rectangularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// -200.0 +// 10.0 +// |> ignore) + +// [] +// let ``Rectangular Hoop with depth equal to 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.rectangularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// 0.0 +// 10.0 +// |> ignore) + +// [] +// let ``Rectangular Hoop with leg diameter less than 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.rectangularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// 200.0 +// -10.0 +// |> ignore) + +// [] +// let ``Rectangular Hoop with leg diameter equal to 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.rectangularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// 200.0 +// 0.0 +// |> ignore) + +// module CircularHoopTests = + +// [] +// let ``Circular Hoop with diameter of 200mm has length of 628.3mm`` () = +// let ch = +// Geometry.Create.circularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// 10.0 + +// let expected = 628.3 + +// let actual = +// Geometry.Query.Hoop3D.getLength (CircularHoop ch) +// |> Geometry.Unwrap.length +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Circular Hoop with leg diameter of 10mm has sectional area of 78.5mm^2`` () = +// let ch = +// Geometry.Create.circularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// 10.0 + +// let expected = 78.5 + +// let actual = +// Geometry.Query.Hoop3D.getCrossSectionalAreaOfLeg (CircularHoop ch) +// |> Geometry.Unwrap.crossSectionalArea +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Circular Hoop with diameter of 200mm and leg diameter of 10mm has volume 49348.0mm^3`` () = +// let ch = +// Geometry.Create.circularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// 10.0 + +// let expected = 49348.0 + +// let actual = +// Geometry.Query.Hoop3D.getVolume (CircularHoop ch) +// |> Geometry.Unwrap.volume +// |> Units.Annotate.stripUnitsFromFloat + +// Assert.Equal(expected, actual, 1) + +// [] +// let ``Circular Hoop with diameter less than 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.circularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// -200.0 +// 10.0 +// |> ignore) + +// [] +// let ``Circular Hoop with diameter equal to 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.circularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 0.0 +// 10.0 +// |> ignore) + +// [] +// let ``Circular Hoop with leg diameter less than 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.circularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// -10.0 +// |> ignore) + +// [] +// let ``Circular Hoop with leg diameter equal to 0mm is invalid`` () = +// Assert.Throws(fun () -> +// Geometry.Create.circularHoop +// { X = 0.0 +// Y = 0.0 +// Z = 0.0 } +// 200.0 +// 0.0 +// |> ignore) diff --git a/tests/Program.fs b/tests/Program.fs new file mode 100644 index 0000000..80c6d84 --- /dev/null +++ b/tests/Program.fs @@ -0,0 +1,3 @@ +module Program = + [] + let main _ = 0