diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..69cb760 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1 @@ +comment: false diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 86c258b..01b4be8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,11 +11,9 @@ updates: - "/lib/CanonicalMoments" - "/lib/CanonicalMoments/docs" - "/lib/CanonicalMoments/examples" - - "/lib/CanonicalMoments/test" - "/lib/DiscreteMeasures" - "/lib/DiscreteMeasures/docs" - "/lib/OUQBase" - - "/lib/OUQBase/test" schedule: interval: "daily" groups: diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..13f23c0 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,107 @@ +name: CI +on: + pull_request: + branches: + - main + paths-ignore: + - 'docs/**' + push: + branches: + - main + paths-ignore: + - 'docs/**' +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} +jobs: + test: + permissions: + actions: write + contents: read + runs-on: ubuntu-latest + timeout-minutes: 120 + env: + # Pin the depot path so the registry-refresh step and the subsequent + # julia-actions/julia-{buildpkg,runtest} steps all read and write the + # same depot on self-hosted runners (where the runner may otherwise + # inject ~/github-runners//.julia for some steps but not others, + # causing "expected package X to be registered" failures when General is + # refreshed into ~/.julia but resolve happens against the per-runner + # depot). + JULIA_DEPOT_PATH: ~/.julia + strategy: + fail-fast: false + matrix: + group: + - Core + version: + - 'lts' + - '1' + - 'pre' + steps: + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v3 + with: + version: ${{ matrix.version }} + # Split cache restore/save by event to work around recurring GitHub + # Actions cache upload auth failures (Azure SAS "Server failed to + # authenticate the request"). PRs only restore from a recent main push + # cache; only main push runs write the cache. continue-on-error keeps a + # transient cache backend hiccup from flunking an otherwise green job. + - name: Cache Julia depot (push only — read+write) + if: github.event_name == 'push' + uses: julia-actions/cache@v3 + continue-on-error: true + with: + cache-compiled: 'false' + - name: Cache Julia depot (PR — restore only) + if: github.event_name != 'push' + uses: actions/cache/restore@v5 + continue-on-error: true + with: + path: | + ~/.julia/artifacts + ~/.julia/packages + ~/.julia/registries + ~/.julia/scratchspaces + ~/.julia/logs + # Primary key intentionally won't match (run_id is unique); the + # restore-keys prefix matches julia-actions/cache's key scheme + # `${cache-name};os=${runner.os};${matrix...}` so we pull the most + # recent cache from a main push for this same matrix cell. + key: julia-cache;workflow=${{ github.workflow }};job=${{ github.job }};os=${{ runner.os }};group=${{ matrix.group }};version=${{ matrix.version }};run_id=${{ github.run_id }};run_attempt=${{ github.run_attempt }} + restore-keys: | + julia-cache;workflow=${{ github.workflow }};job=${{ github.job }};os=${{ runner.os }};group=${{ matrix.group }};version=${{ matrix.version }}; + - name: Develop local path deps (Julia < 1.11) + shell: julia --color=yes --project=. {0} + run: | + using Pkg + if VERSION < v"1.11.0-DEV.0" + toml = Pkg.TOML.parsefile("Project.toml") + if haskey(toml, "sources") + specs = Pkg.PackageSpec[] + for (dep, spec) in toml["sources"] + if spec isa Dict && haskey(spec, "path") && isdir(spec["path"]) + @info "Developing" dep spec["path"] + push!(specs, Pkg.PackageSpec(path=spec["path"])) + end + end + isempty(specs) || Pkg.develop(specs) + end + end + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + with: + coverage: false + check_bounds: auto + env: + GROUP: ${{ matrix.group }} + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v6 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + fail_ci_if_error: false + disable_safe_directory: true diff --git a/.github/workflows/DocPreviewCleanup.yml b/.github/workflows/DocPreviewCleanup.yml new file mode 100644 index 0000000..7b69480 --- /dev/null +++ b/.github/workflows/DocPreviewCleanup.yml @@ -0,0 +1,17 @@ +name: Doc Preview Cleanup + +on: + pull_request: + types: [closed] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + +jobs: + doc-preview-cleanup: + uses: "SciML/.github/.github/workflows/docs-preview-cleanup.yml@v1" + secrets: "inherit" diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml new file mode 100644 index 0000000..aba0b1d --- /dev/null +++ b/.github/workflows/Documentation.yml @@ -0,0 +1,12 @@ +name: Documentation +on: + push: + branches: + - main + tags: '*' + pull_request: +jobs: + build-and-deploy-docs: + name: "Documentation" + uses: "SciML/.github/.github/workflows/documentation.yml@v1" + secrets: "inherit" diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml new file mode 100644 index 0000000..5feb9a6 --- /dev/null +++ b/.github/workflows/Downgrade.yml @@ -0,0 +1,24 @@ +name: Downgrade +on: + pull_request: + branches: + - main + paths-ignore: + - 'docs/**' + push: + branches: + - main + paths-ignore: + - 'docs/**' +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} +jobs: + test: + name: "Downgrade" + uses: "SciML/.github/.github/workflows/downgrade.yml@v1" + with: + group: "Core" + secrets: "inherit" diff --git a/.github/workflows/DowngradeSublibraries.yml b/.github/workflows/DowngradeSublibraries.yml index 2a6f7bd..60d6d78 100644 --- a/.github/workflows/DowngradeSublibraries.yml +++ b/.github/workflows/DowngradeSublibraries.yml @@ -22,5 +22,6 @@ jobs: uses: "SciML/.github/.github/workflows/sublibrary-downgrade.yml@v1" secrets: "inherit" with: - julia-version: "1.10" - skip: "Pkg,TOML" + group-env-name: "OPTIMALUNCERTAINTYQUANTIFICATION_TEST_GROUP" + group-env-value: "Core" + # Every lib/* sublibrary is downgrade-tested (projects auto-discovered, no exclusions). diff --git a/.github/workflows/Downstream.yml b/.github/workflows/Downstream.yml new file mode 100644 index 0000000..923a053 --- /dev/null +++ b/.github/workflows/Downstream.yml @@ -0,0 +1,62 @@ +name: IntegrationTest +on: + push: + branches: [main] + tags: [v*] + pull_request: +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} +jobs: + test: + name: ${{ matrix.package.repo }}/${{ matrix.package.group }}/${{ matrix.julia-version }} + runs-on: ${{ matrix.os }} + env: + GROUP: ${{ matrix.package.group }} + strategy: + fail-fast: false + matrix: + julia-version: ['1.10'] + os: [ubuntu-latest] + # OptimalUncertaintyQuantification.jl currently has no registered + # downstream consumers. The matrix is intentionally empty so this job + # is a no-op; add `{user: ..., repo: ..., group: ...}` entries here as + # downstream packages adopt it. An empty matrix skips the job cleanly + # rather than failing. + package: ${{ fromJSON('[]') }} + + steps: + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v3 + with: + version: ${{ matrix.julia-version }} + arch: x64 + - name: Clone Downstream + uses: actions/checkout@v6 + with: + repository: ${{ matrix.package.user }}/${{ matrix.package.repo }} + path: downstream + - name: Load this and run the downstream tests + shell: julia --color=yes --project=downstream {0} + run: | + using Pkg + try + Pkg.develop(map(path ->Pkg.PackageSpec.(;path="lib/$(path)"), readdir("./lib"))); + Pkg.test(coverage=true) # resolver may fail with test time deps + catch err + err isa Pkg.Resolve.ResolverError || rethrow() + # If we can't resolve that means this is incompatible by SemVer and this is fine + # It means we marked this as a breaking change, so we don't need to worry about + # mistakenly introducing a breaking change, as we have intentionally made one + @info "Not compatible with this release. No problem." exception=err + exit(0) # Exit immediately, as a success + end + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v6 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + fail_ci_if_error: false + disable_safe_directory: true diff --git a/.github/workflows/SublibraryCI.yml b/.github/workflows/SublibraryCI.yml index a44aae5..45c8cff 100644 --- a/.github/workflows/SublibraryCI.yml +++ b/.github/workflows/SublibraryCI.yml @@ -19,4 +19,7 @@ concurrency: jobs: sublibraries: uses: "SciML/.github/.github/workflows/sublibrary-project-tests.yml@v1" + with: + group-env-name: OPTIMALUNCERTAINTYQUANTIFICATION_TEST_GROUP + check-bounds: auto secrets: "inherit" diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index 2cadafa..9ed2d3e 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -1,42 +1,9 @@ -name: TagBot +name: "TagBot" on: issue_comment: - types: - - created + types: [created] workflow_dispatch: - inputs: - lookback: - default: "3" - -permissions: - actions: read - checks: read - contents: write - deployments: read - issues: read - discussions: read - packages: read - pages: read - pull-requests: read - repository-projects: read - security-events: read - statuses: read - jobs: - TagBot-Subpackages: - if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - package: - - CanonicalMoments - - DiscreteMeasures - - OUQBase - steps: - - name: Tag ${{ matrix.package }} - uses: JuliaRegistries/TagBot@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - ssh: ${{ secrets.DOCUMENTER_KEY }} - subdir: "lib/${{ matrix.package }}" + tagbot: + uses: "SciML/.github/.github/workflows/tagbot.yml@v1" + secrets: "inherit" diff --git a/.gitignore b/.gitignore index c64c6f3..036bd31 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,14 @@ +*.jl.cov +*.jl.*.cov +*.jl.mem +*.jl.*.mem Manifest.toml -.vscode +*/Manifest.toml *.DS_Store -*/Manifest.toml \ No newline at end of file +.vscode +profile.pb.gz +.*.swp +LocalPreferences.toml + +docs/build +.claude/ diff --git a/_typos.toml b/.typos.toml similarity index 62% rename from _typos.toml rename to .typos.toml index 31f6b47..5f9f145 100644 --- a/_typos.toml +++ b/.typos.toml @@ -1,3 +1,7 @@ +[default.extend-words] +# Abbreviation for "Theorem" used in citation references (e.g. "Dette Thm 1.3.2"). +Thm = "Thm" + [files] # Machine-generated Pluto.jl HTML exports; the only hits are author surnames # (e.g. "Fons van der Plas") in embedded boilerplate, not editable prose. diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/Project.toml b/Project.toml index e9ea91c..c2e3281 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,31 @@ -[workspace] -projects = [ - "lib/CanonicalMoments", - "lib/DiscreteMeasures", - "lib/OUQBase" -] \ No newline at end of file +name = "OptimalUncertaintyQuantification" +uuid = "91ab1271-1799-4997-981e-07ad84422b0d" +authors = ["Avinash Subramanian (JuliaHub), Benjamin Chung (JuliaHub), Adam Gerlach (AFRL)"] +version = "0.1.0" + +[deps] +CanonicalMoments = "58d2c334-1a3c-4862-bb37-9012b9e58a38" +DiscreteMeasures = "7766d772-2108-41ee-a4bd-11c51440a39b" +OUQBase = "01930cae-99d2-7439-8f4f-ace2ece9f1b9" +Reexport = "189a3867-3050-52da-a836-e630ba90ab69" + +[sources] +CanonicalMoments = { path = "lib/CanonicalMoments" } +DiscreteMeasures = { path = "lib/DiscreteMeasures" } +OUQBase = { path = "lib/OUQBase" } + +[compat] +julia = "1.10" +CanonicalMoments = "0.1" +DiscreteMeasures = "0.1" +OUQBase = "0.1" +Reexport = "1.2.2" +SafeTestsets = "0.1" + +[extras] +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Pkg", "SafeTestsets", "Test"] diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 0000000..7b80851 --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,19 @@ +[deps] +CanonicalMoments = "58d2c334-1a3c-4862-bb37-9012b9e58a38" +DiscreteMeasures = "7766d772-2108-41ee-a4bd-11c51440a39b" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +OUQBase = "01930cae-99d2-7439-8f4f-ace2ece9f1b9" +OptimalUncertaintyQuantification = "91ab1271-1799-4997-981e-07ad84422b0d" + +[sources] +CanonicalMoments = {path = "../lib/CanonicalMoments"} +DiscreteMeasures = {path = "../lib/DiscreteMeasures"} +OUQBase = {path = "../lib/OUQBase"} +OptimalUncertaintyQuantification = {path = ".."} + +[compat] +CanonicalMoments = "0.1" +DiscreteMeasures = "0.1" +Documenter = "1" +OUQBase = "0.1" +OptimalUncertaintyQuantification = "0.1" diff --git a/docs/make.jl b/docs/make.jl new file mode 100644 index 0000000..b08e616 --- /dev/null +++ b/docs/make.jl @@ -0,0 +1,33 @@ +using Documenter, OptimalUncertaintyQuantification +using OUQBase +using CanonicalMoments +using DiscreteMeasures + +mkpath(joinpath(@__DIR__, "src", "assets")) +cp(joinpath(@__DIR__, "Project.toml"), joinpath(@__DIR__, "src", "assets", "Project.toml"), force = true) + +# Keep pages.jl separate for the DiffEqDocs.jl build +include("pages.jl") + +makedocs( + sitename = "OptimalUncertaintyQuantification.jl", + authors = "Avinash Subramanian, Benjamin Chung, Adam Gerlach et al.", + clean = true, + doctest = false, + modules = [ + OptimalUncertaintyQuantification, + OUQBase, + CanonicalMoments, + DiscreteMeasures, + ], + warnonly = [:docs_block, :missing_docs, :eval_block], + format = Documenter.HTML( + canonical = "https://docs.sciml.ai/OptimalUncertaintyQuantification/stable/", + ), + pages = pages +) + +deploydocs( + repo = "github.com/SciML/OptimalUncertaintyQuantification.jl"; + push_preview = true +) diff --git a/docs/pages.jl b/docs/pages.jl new file mode 100644 index 0000000..1260bf6 --- /dev/null +++ b/docs/pages.jl @@ -0,0 +1,3 @@ +pages = [ + "OptimalUncertaintyQuantification.jl: Optimal Uncertainty Quantification" => "index.md", +] diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 0000000..2a343aa --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,65 @@ +# OptimalUncertaintyQuantification.jl: Optimal Uncertainty Quantification + +**DISTRIBUTION STATEMENT A. Approved for public release: distribution unlimited. Case Number: AFRL-2024-5455. Cleared 10/2/2024.** + +The Optimal Uncertainty Quantification (OUQ) algorithm provides a means of computing +the bounds of the expectations of quantities of interest despite not having complete +knowledge of the probability distribution of the uncertain variables. This is achieved +by finding the worst/best case distributions in some set ``\mathcal{A}`` of possible +distributions given the knowledge available. + +OptimalUncertaintyQuantification.jl implements the OUQ algorithm and its convex and +"moment class" forms in the Julia programming language, using techniques based on +*complete* and *rigorous* global methods in order to bound the effects of finite +computation on the OUQ bounds. + +## Installation + +To install OptimalUncertaintyQuantification.jl, use the Julia package manager: + +```julia +using Pkg +Pkg.add("OptimalUncertaintyQuantification") +``` + +## Contributing + +- Please refer to the + [SciML ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://github.com/SciML/ColPrac/blob/master/README.md) + for guidance on PRs, issues, and other matters relating to contributing to SciML. +- See the [SciML Style Guide](https://github.com/SciML/SciMLStyle) for common coding practices and other style decisions. +- There are a few community forums: + - The #diffeq-bridged and #sciml-bridged channels in the + [Julia Slack](https://julialang.org/slack/) + - The #diffeq-bridged and #sciml-bridged channels in the + [Julia Zulip](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) + - On the [Julia Discourse forums](https://discourse.julialang.org) + - See also [SciML Community page](https://sciml.ai/community/) + +## Reproducibility + +```@raw html +
The documentation of this SciML package was built using these direct dependencies, +``` + +```@example +using Pkg # hide +Pkg.status() # hide +``` + +```@raw html +
+``` + +```@raw html +
and using this machine and Julia version. +``` + +```@example +using InteractiveUtils # hide +versioninfo() # hide +``` + +```@raw html +
+``` diff --git a/lib/CanonicalMoments/LICENSE.md b/lib/CanonicalMoments/LICENSE.md new file mode 100644 index 0000000..dd2d5f4 --- /dev/null +++ b/lib/CanonicalMoments/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 SciML Open Source Scientific Machine Learning + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/CanonicalMoments/Project.toml b/lib/CanonicalMoments/Project.toml index 69b53b0..0ebc55f 100644 --- a/lib/CanonicalMoments/Project.toml +++ b/lib/CanonicalMoments/Project.toml @@ -25,14 +25,36 @@ SymbolicsExt = "Symbolics" [compat] julia = "1.10" +ChainRulesCore = "1" DiscreteMeasures = "0.1" +ForwardDiff = "0.10, 1" +InteractiveUtils = "1.10" +IntervalArithmetic = "0.18 - 0.20, =0.20.9" LinearAlgebra = "1.10" +PolynomialRoots = "1.0" Polynomials = "4" +Random = "1.10" RecurrenceRelationships = "0.2" -Statistics = "1.10" -IntervalArithmetic = "0.18 - 0.20, =0.20.9" Reexport = "1.2.2" +SafeTestsets = "0.1" +SpecialFunctions = "2" +StaticArrays = "1" +Statistics = "1.10" Symbolics = "6.29.2" +Test = "1.10" [sources] DiscreteMeasures = { path = "../DiscreteMeasures" } + +[extras] +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" +PolynomialRoots = "3a141323-8675-5d76-9d11-e1df1406c778" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" +SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["InteractiveUtils", "IntervalArithmetic", "PolynomialRoots", "Random", "SafeTestsets", "SpecialFunctions", "StaticArrays", "Test"] diff --git a/lib/CanonicalMoments/README.md b/lib/CanonicalMoments/README.md new file mode 100644 index 0000000..dd95fab --- /dev/null +++ b/lib/CanonicalMoments/README.md @@ -0,0 +1,10 @@ +# CanonicalMoments.jl + +[![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) +[![Global Docs](https://img.shields.io/badge/docs-SciML-blue.svg)](https://docs.sciml.ai/OptimalUncertaintyQuantification/stable/) + +[![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor%27s%20Guide-blueviolet)](https://github.com/SciML/ColPrac) +[![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle) + +CanonicalMoments.jl is a component of the [OptimalUncertaintyQuantification.jl](https://github.com/SciML/OptimalUncertaintyQuantification.jl) monorepo. It provides canonical moment sequences, orthogonal polynomial root solvers, and the Stieltjes-transform machinery used to map moment information to discrete measures. +While completely independent and usable on its own, users wanting the full Optimal Uncertainty Quantification suite should use [OptimalUncertaintyQuantification.jl](https://github.com/SciML/OptimalUncertaintyQuantification.jl). diff --git a/lib/CanonicalMoments/examples/1_Stenger_flood.jl b/lib/CanonicalMoments/examples/1_Stenger_flood.jl index 65da7b9..410387e 100644 --- a/lib/CanonicalMoments/examples/1_Stenger_flood.jl +++ b/lib/CanonicalMoments/examples/1_Stenger_flood.jl @@ -45,7 +45,7 @@ pof_ = pof(ql, qu, c1, g_h(2), p1_free) pof2_ = pof(ql, qu, c2, g_h(2), p2_free) ## Compare to Stenger -### 1 Moment Contstraint +### 1 Moment Constraint threshold = 4 p1_free = [fill(0.5, 2) for i in 1:4] pof_ = pof(ql, qu, c1, g_h(threshold), p1_free) diff --git a/lib/CanonicalMoments/examples/multivariate_example.jl b/lib/CanonicalMoments/examples/multivariate_example.jl index 6a80627..a9d6f5f 100644 --- a/lib/CanonicalMoments/examples/multivariate_example.jl +++ b/lib/CanonicalMoments/examples/multivariate_example.jl @@ -21,8 +21,8 @@ raws = [ [54.5, 8911 / 3.0, 647569 / 4.0], #Zm ] -raw_seqences = RawMomentSequence.(raws, lb, ub) -transforms = DiscreteMeasureTransform1.(raw_seqences) +raw_sequences = RawMomentSequence.(raws, lb, ub) +transforms = DiscreteMeasureTransform1.(raw_sequences) p_frees = fill.([0.1, 0.2, 0.3, 0.4], length.(raws) .+ 1) diff --git a/lib/CanonicalMoments/ext/ForwardDiffExt.jl b/lib/CanonicalMoments/ext/ForwardDiffExt.jl index 3af7203..520f96a 100644 --- a/lib/CanonicalMoments/ext/ForwardDiffExt.jl +++ b/lib/CanonicalMoments/ext/ForwardDiffExt.jl @@ -14,11 +14,11 @@ function simple_real_roots( P = Polynomial(C) numerator = -Polynomial(∂C) - denomenator = Polynomials.derivative(P) + denominator = Polynomials.derivative(P) X = root_solver(P) return map(X) do xi - 𝕋(xi, numerator(xi) / denomenator(xi)) + 𝕋(xi, numerator(xi) / denominator(xi)) end end diff --git a/lib/CanonicalMoments/src/measure_transform_algs.jl b/lib/CanonicalMoments/src/measure_transform_algs.jl index 24b899c..db5fc24 100644 --- a/lib/CanonicalMoments/src/measure_transform_algs.jl +++ b/lib/CanonicalMoments/src/measure_transform_algs.jl @@ -12,7 +12,7 @@ Base.show(io::IO, alg::AbstractSupportAlg) = print(io, "support_alg") """ PolyRootsSupportAlg <: AbstractSupportAlg -An algorithm for computing the support of a measure by finding the roots of the denominator of teh Stieltjes transform. See Eq. 3.6.3 [1]. The default solver is set to `DEFAULT_ROOT_SOLVER`. +An algorithm for computing the support of a measure by finding the roots of the denominator of the Stieltjes transform. See Eq. 3.6.3 [1]. The default solver is set to `DEFAULT_ROOT_SOLVER`. Note, that the polynomial is guaranteed to have real, unique roots. diff --git a/lib/CanonicalMoments/src/moment_sequences.jl b/lib/CanonicalMoments/src/moment_sequences.jl index 26654e7..34c35da 100644 --- a/lib/CanonicalMoments/src/moment_sequences.jl +++ b/lib/CanonicalMoments/src/moment_sequences.jl @@ -137,7 +137,7 @@ end Checks if the distribution represented by the canonical moment sequence is symmetric around its mean. -This uses the property that odd-numbered canonical moments are ``1/2`` for symmetric distributions. See Corrollary 1.3.4 of [1]. +This uses the property that odd-numbered canonical moments are ``1/2`` for symmetric distributions. See Corollary 1.3.4 of [1]. [1] Dette, H., and Studden, W. J., “The Theory of Canonical Moments with Applications in Statistics, Probability, and Analysis,” Wiley-Interscience, New York, 1997. diff --git a/lib/CanonicalMoments/test/moment_sequence.jl b/lib/CanonicalMoments/test/Core/moment_sequence.jl similarity index 100% rename from lib/CanonicalMoments/test/moment_sequence.jl rename to lib/CanonicalMoments/test/Core/moment_sequence.jl diff --git a/lib/CanonicalMoments/test/orthopoly_roots.jl b/lib/CanonicalMoments/test/Core/orthopoly_roots.jl similarity index 100% rename from lib/CanonicalMoments/test/orthopoly_roots.jl rename to lib/CanonicalMoments/test/Core/orthopoly_roots.jl diff --git a/lib/CanonicalMoments/test/stenger_setup.jl b/lib/CanonicalMoments/test/Core/stenger_setup.jl similarity index 100% rename from lib/CanonicalMoments/test/stenger_setup.jl rename to lib/CanonicalMoments/test/Core/stenger_setup.jl diff --git a/lib/CanonicalMoments/test/Project.toml b/lib/CanonicalMoments/test/Project.toml deleted file mode 100644 index fa5721e..0000000 --- a/lib/CanonicalMoments/test/Project.toml +++ /dev/null @@ -1,13 +0,0 @@ -[deps] -InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" -IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" -LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" -PolynomialRoots = "3a141323-8675-5d76-9d11-e1df1406c778" -Polynomials = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" -SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" -StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" -Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" diff --git a/lib/CanonicalMoments/test/runtests.jl b/lib/CanonicalMoments/test/runtests.jl index 287b6dc..ef6b2d7 100644 --- a/lib/CanonicalMoments/test/runtests.jl +++ b/lib/CanonicalMoments/test/runtests.jl @@ -1,12 +1,12 @@ -# using OptimalUncertaintyQuantification -using CanonicalMoments using SafeTestsets using Test -# @run_package_tests verbose = true +const TEST_GROUP = get(ENV, "OPTIMALUNCERTAINTYQUANTIFICATION_TEST_GROUP", "All") -@safetestset "Orthogonal Polynomial Roots" include("orthopoly_roots.jl") -@safetestset "Moment Sequence" include("moment_sequence.jl") +if TEST_GROUP == "Core" || TEST_GROUP == "All" + @safetestset "Orthogonal Polynomial Roots" include("Core/orthopoly_roots.jl") + @safetestset "Moment Sequence" include("Core/moment_sequence.jl") +end # @safetestset "Root Domain Restriction" begin # using IntervalArithmetic @@ -52,12 +52,12 @@ using Test # using IntervalArithmetic # import OptimalUncertaintyQuantification.CanonicalMoments: moments_to_canonical, canonical_to_position -# include("stenger_setup.jl") +# include("Core/stenger_setup.jl") # for (c, setup) in zip((c1, c2, c3), (setup1, setup2, setup3)) # for _setup in setup # p = _setup.free -# for (lb, ub, _c, _p) in zip(ql, qu, c, p) +# for (lb, ub, _c, _p) in zip(ql, qu, c, p) # can = moments_to_canonical(lb, ub, _c, _p) # @test all(@. 0 ≤ can ≤ 1) diff --git a/lib/CanonicalMoments/test/test_groups.toml b/lib/CanonicalMoments/test/test_groups.toml new file mode 100644 index 0000000..dda1469 --- /dev/null +++ b/lib/CanonicalMoments/test/test_groups.toml @@ -0,0 +1,2 @@ +[Core] +versions = ["lts", "1", "pre"] diff --git a/lib/DiscreteMeasures/LICENSE.md b/lib/DiscreteMeasures/LICENSE.md new file mode 100644 index 0000000..dd2d5f4 --- /dev/null +++ b/lib/DiscreteMeasures/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 SciML Open Source Scientific Machine Learning + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/DiscreteMeasures/Project.toml b/lib/DiscreteMeasures/Project.toml index c2546c5..4944557 100644 --- a/lib/DiscreteMeasures/Project.toml +++ b/lib/DiscreteMeasures/Project.toml @@ -12,10 +12,13 @@ IntervalArithmeticExt = "IntervalArithmetic" [compat] julia = "1.10" IntervalArithmetic = "0.16 - 0.20, =0.20.9" +SafeTestsets = "0.1" +Test = "1.10" [extras] -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" +SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test", "IntervalArithmetic"] +test = ["IntervalArithmetic", "SafeTestsets", "Test"] diff --git a/lib/DiscreteMeasures/README.md b/lib/DiscreteMeasures/README.md new file mode 100644 index 0000000..64c46b5 --- /dev/null +++ b/lib/DiscreteMeasures/README.md @@ -0,0 +1,10 @@ +# DiscreteMeasures.jl + +[![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) +[![Global Docs](https://img.shields.io/badge/docs-SciML-blue.svg)](https://docs.sciml.ai/OptimalUncertaintyQuantification/stable/) + +[![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor%27s%20Guide-blueviolet)](https://github.com/SciML/ColPrac) +[![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle) + +DiscreteMeasures.jl is a component of the [OptimalUncertaintyQuantification.jl](https://github.com/SciML/OptimalUncertaintyQuantification.jl) monorepo. It provides the discrete and product discrete measure types, expectation operators, and support/weight utilities that the rest of the suite builds on. +While completely independent and usable on its own, users wanting the full Optimal Uncertainty Quantification suite should use [OptimalUncertaintyQuantification.jl](https://github.com/SciML/OptimalUncertaintyQuantification.jl). diff --git a/lib/DiscreteMeasures/test/Core/discrete_measures_tests.jl b/lib/DiscreteMeasures/test/Core/discrete_measures_tests.jl new file mode 100644 index 0000000..1f5d4b6 --- /dev/null +++ b/lib/DiscreteMeasures/test/Core/discrete_measures_tests.jl @@ -0,0 +1,101 @@ +using DiscreteMeasures, IntervalArithmetic +using Test + +x = [[1.0, 2.0, 2.5], [3.0, 4.0, 5.0]] + +w = [[10.0, 20.0, 25], [30.0, 40.0, 6.0]] + +w3d = [1.0, 2.0] + +@testset "DiscreteMeasure" begin + dm = DiscreteMeasure(first(x), first(w)) + @test support(dm) == first(x) + @test weights(dm) == first(w) + @test order(dm) == 3 + @test ndims(dm) == 1 + + + dm3d = DiscreteMeasure(x, w3d) + @test support(dm3d) == x + @test weights(dm3d) == w3d + @test order(dm3d) == 2 + @test ndims(dm3d) == 3 +end + +@testset "ProductDiscreteMeasure" begin + dms = DiscreteMeasure.(x, w) + pdms = ProductDiscreteMeasure(dms) + @test marginals(pdms) == dms + @test support(pdms) == vec([[a, b] for a in first(x), b in last(x)]) + @test weights(pdms) == vec([a * b for a in first(w), b in last(w)]) + + x2 = [-1.0, -2.0] + w2 = [100.0, 200.0] + + dm2 = DiscreteMeasure(x2, w2) + pdms2 = ProductDiscreteMeasure(pdms, dm2) + @test marginals(pdms2) == [dms; dm2] + @test support(pdms2) == vec([[a, b, c] for a in first(x), b in last(x), c in x2]) + @test weights(pdms2) == vec([a * b * c for a in first(w), b in last(w), c in w2]) + + pdms3 = ProductDiscreteMeasure(pdms, dms) + @test marginals(pdms3) == [dms; dms] + @test support(pdms3) == + vec([[a, b, c, d] for a in first(x), b in last(x), c in first(x), d in last(x)]) + @test weights(pdms3) == + vec([a * b * c * d for a in first(w), b in last(w), c in first(w), d in last(w)]) + + dm1d = DiscreteMeasure(first(x), first(w)) + dm3d = DiscreteMeasure(x, w3d) + pdms = ProductDiscreteMeasure([dm3d, dm1d]) + @test marginals(pdms) == [dm3d; dm1d] + @test support(pdms) == vec([vcat(a, b) for a in support(dm3d), b in support(dm1d)]) + @test weights(pdms) == vec([a * b for a in weights(dm3d), b in weights(dm1d)]) + + @testset "Product of single DiscreteMeasure" begin + dm = DiscreteMeasure(x, w3d) + pdm = ProductDiscreteMeasure([dm]) + @test only(marginals(pdm)) == dm + @test support(pdm) == x + @test weights(pdm) == w3d + + end +end + +@testset "Expectation" begin + dm = DiscreteMeasure(first(x), first(w)) + dms = DiscreteMeasure.(x, w) + pdms = ProductDiscreteMeasure(dms) + dm1d = DiscreteMeasure(first(x), first(w)) + dm3d = DiscreteMeasure(x, w3d) + pdms4d = ProductDiscreteMeasure([dm3d, dm1d]) + + + f(x) = sin(first(x)) + + for μ in (dm, pdms, pdms4d) + @test expectation(f, μ) ≈ weights(μ)' * f.(support(μ)) + end +end + +@testset "Measure Restrictions" begin + for T in (Int64, Float64) + @test clamp_domain(T(-1), 0, 2) == T(0) + @test clamp_domain(T(1), 0, 2) == T(1) + @test clamp_domain(T(3), 0, 2) == T(2) + + @test clamp_weight(T(-1)) == T(0) + @test clamp_weight(T(1), T(2)) == T(1) + @test clamp_weight(T(3)) == T(1) + @test clamp_weight(T(3), T(2)) == T(2) + end + + @testset "Interval Ext" begin + @test clamp_domain(Interval(-2, -1), 0, 2) == ∅ + @test clamp_domain(Interval(-2, 1), 0, 2) == Interval(0, 1) + @test clamp_domain(Interval(0.1, 0.5), 0, 2) == Interval(0.1, 0.5) + @test clamp_domain(Interval(0.5, 5), 0, 2) == Interval(0.5, 2) + @test clamp_domain(Interval(3, 4), 0, 2) == ∅ + end + +end diff --git a/lib/DiscreteMeasures/test/runtests.jl b/lib/DiscreteMeasures/test/runtests.jl index 1f5d4b6..bcdf257 100644 --- a/lib/DiscreteMeasures/test/runtests.jl +++ b/lib/DiscreteMeasures/test/runtests.jl @@ -1,101 +1,8 @@ -using DiscreteMeasures, IntervalArithmetic +using SafeTestsets using Test -x = [[1.0, 2.0, 2.5], [3.0, 4.0, 5.0]] - -w = [[10.0, 20.0, 25], [30.0, 40.0, 6.0]] - -w3d = [1.0, 2.0] - -@testset "DiscreteMeasure" begin - dm = DiscreteMeasure(first(x), first(w)) - @test support(dm) == first(x) - @test weights(dm) == first(w) - @test order(dm) == 3 - @test ndims(dm) == 1 - - - dm3d = DiscreteMeasure(x, w3d) - @test support(dm3d) == x - @test weights(dm3d) == w3d - @test order(dm3d) == 2 - @test ndims(dm3d) == 3 -end - -@testset "ProductDiscreteMeasure" begin - dms = DiscreteMeasure.(x, w) - pdms = ProductDiscreteMeasure(dms) - @test marginals(pdms) == dms - @test support(pdms) == vec([[a, b] for a in first(x), b in last(x)]) - @test weights(pdms) == vec([a * b for a in first(w), b in last(w)]) - - x2 = [-1.0, -2.0] - w2 = [100.0, 200.0] - - dm2 = DiscreteMeasure(x2, w2) - pdms2 = ProductDiscreteMeasure(pdms, dm2) - @test marginals(pdms2) == [dms; dm2] - @test support(pdms2) == vec([[a, b, c] for a in first(x), b in last(x), c in x2]) - @test weights(pdms2) == vec([a * b * c for a in first(w), b in last(w), c in w2]) - - pdms3 = ProductDiscreteMeasure(pdms, dms) - @test marginals(pdms3) == [dms; dms] - @test support(pdms3) == - vec([[a, b, c, d] for a in first(x), b in last(x), c in first(x), d in last(x)]) - @test weights(pdms3) == - vec([a * b * c * d for a in first(w), b in last(w), c in first(w), d in last(w)]) - - dm1d = DiscreteMeasure(first(x), first(w)) - dm3d = DiscreteMeasure(x, w3d) - pdms = ProductDiscreteMeasure([dm3d, dm1d]) - @test marginals(pdms) == [dm3d; dm1d] - @test support(pdms) == vec([vcat(a, b) for a in support(dm3d), b in support(dm1d)]) - @test weights(pdms) == vec([a * b for a in weights(dm3d), b in weights(dm1d)]) - - @testset "Product of single DiscreteMeasure" begin - dm = DiscreteMeasure(x, w3d) - pdm = ProductDiscreteMeasure([dm]) - @test only(marginals(pdm)) == dm - @test support(pdm) == x - @test weights(pdm) == w3d - - end -end - -@testset "Expectation" begin - dm = DiscreteMeasure(first(x), first(w)) - dms = DiscreteMeasure.(x, w) - pdms = ProductDiscreteMeasure(dms) - dm1d = DiscreteMeasure(first(x), first(w)) - dm3d = DiscreteMeasure(x, w3d) - pdms4d = ProductDiscreteMeasure([dm3d, dm1d]) - - - f(x) = sin(first(x)) - - for μ in (dm, pdms, pdms4d) - @test expectation(f, μ) ≈ weights(μ)' * f.(support(μ)) - end -end - -@testset "Measure Restrictions" begin - for T in (Int64, Float64) - @test clamp_domain(T(-1), 0, 2) == T(0) - @test clamp_domain(T(1), 0, 2) == T(1) - @test clamp_domain(T(3), 0, 2) == T(2) - - @test clamp_weight(T(-1)) == T(0) - @test clamp_weight(T(1), T(2)) == T(1) - @test clamp_weight(T(3)) == T(1) - @test clamp_weight(T(3), T(2)) == T(2) - end - - @testset "Interval Ext" begin - @test clamp_domain(Interval(-2, -1), 0, 2) == ∅ - @test clamp_domain(Interval(-2, 1), 0, 2) == Interval(0, 1) - @test clamp_domain(Interval(0.1, 0.5), 0, 2) == Interval(0.1, 0.5) - @test clamp_domain(Interval(0.5, 5), 0, 2) == Interval(0.5, 2) - @test clamp_domain(Interval(3, 4), 0, 2) == ∅ - end +const TEST_GROUP = get(ENV, "OPTIMALUNCERTAINTYQUANTIFICATION_TEST_GROUP", "All") +if TEST_GROUP == "Core" || TEST_GROUP == "All" + @safetestset "Discrete Measures" include("Core/discrete_measures_tests.jl") end diff --git a/lib/DiscreteMeasures/test/test_groups.toml b/lib/DiscreteMeasures/test/test_groups.toml new file mode 100644 index 0000000..dda1469 --- /dev/null +++ b/lib/DiscreteMeasures/test/test_groups.toml @@ -0,0 +1,2 @@ +[Core] +versions = ["lts", "1", "pre"] diff --git a/lib/OUQBase/LICENSE.md b/lib/OUQBase/LICENSE.md new file mode 100644 index 0000000..dd2d5f4 --- /dev/null +++ b/lib/OUQBase/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 SciML Open Source Scientific Machine Learning + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/OUQBase/Project.toml b/lib/OUQBase/Project.toml index 43ba8ab..e37e2e4 100644 --- a/lib/OUQBase/Project.toml +++ b/lib/OUQBase/Project.toml @@ -20,19 +20,33 @@ SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [compat] +julia = "1.10" +CanonicalMoments = "0.1" +DiscreteMeasures = "0.1" DomainSets = "0.7.15" JuMP = "1.23.6" ModelingToolkit = "9.69" NaNMath = "1.1.3" +Optimization = "5" +OptimizationBBO = "0.4" OrderedCollections = "1.7.0" +PolynomialRoots = "1" Polynomials = "4.0.13" Reexport = "1.2.2" SciMLBase = "2.153" +SafeTestsets = "0.1" +SymbolicUtils = "3" Symbolics = "6.39.1" +Test = "1.10" [sources] CanonicalMoments = { path = "../CanonicalMoments" } DiscreteMeasures = { path = "../DiscreteMeasures" } -[workspace] -projects = ["test"] +[extras] +OptimizationBBO = "3e6eede4-6085-4f62-9a71-46d9bc1eb92b" +SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["OptimizationBBO", "SafeTestsets", "Test"] diff --git a/lib/OUQBase/README.md b/lib/OUQBase/README.md new file mode 100644 index 0000000..dabd893 --- /dev/null +++ b/lib/OUQBase/README.md @@ -0,0 +1,10 @@ +# OUQBase.jl + +[![Join the chat at https://julialang.zulipchat.com #sciml-bridged](https://img.shields.io/static/v1?label=Zulip&message=chat&color=9558b2&labelColor=389826)](https://julialang.zulipchat.com/#narrow/stream/279055-sciml-bridged) +[![Global Docs](https://img.shields.io/badge/docs-SciML-blue.svg)](https://docs.sciml.ai/OptimalUncertaintyQuantification/stable/) + +[![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor%27s%20Guide-blueviolet)](https://github.com/SciML/ColPrac) +[![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle) + +OUQBase.jl is a component of the [OptimalUncertaintyQuantification.jl](https://github.com/SciML/OptimalUncertaintyQuantification.jl) monorepo. It holds the core Optimal Uncertainty Quantification interface: admissible sets, OUQ systems and problems, and the reduction transformations (Winkler extremal measures and Stenger canonical moments) that turn an OUQ problem into a solvable optimization. +While completely independent and usable on its own, users wanting the full Optimal Uncertainty Quantification suite should use [OptimalUncertaintyQuantification.jl](https://github.com/SciML/OptimalUncertaintyQuantification.jl). diff --git a/lib/OUQBase/src/reduction_transformations/winkler_extremal_measures.jl b/lib/OUQBase/src/reduction_transformations/winkler_extremal_measures.jl index 12c3fd9..ee9673a 100644 --- a/lib/OUQBase/src/reduction_transformations/winkler_extremal_measures.jl +++ b/lib/OUQBase/src/reduction_transformations/winkler_extremal_measures.jl @@ -51,7 +51,7 @@ end # We need to provide: # 1) right DiscreteMeasure/ProductDiscreteMeasure and the right set of full random variable group the random variable of the constraint belongs to. # 2) right optim_vars_sub -# Possible other things like dispatch on reduction_algorithm or backen d, but for now it is JuMP and WinklerExtremalMeasures. +# Possible other things like dispatch on reduction_algorithm or backend, but for now it is JuMP and WinklerExtremalMeasures. # We can rewrite it using map_vars_to_group. # 1D case: @@ -186,7 +186,7 @@ function convert_objective!( jump_on_var = @variable(optim_model, binary = true) #var_name = Symbol(:x, group_name, i, :_on) var_name = Symbol(:on, i) - set_name(jump_on_var, String(var_name)) # Binary indicator variable: 1 if support point i satisified probability condition + set_name(jump_on_var, String(var_name)) # Binary indicator variable: 1 if support point i satisfied probability condition optim_model[var_name] = jump_on_var #support_to_jump_binary_dict[substitute(support(discrete_measure)[i], optim_to_jump_dict)] = jump_on_var @@ -298,7 +298,7 @@ function construct_optimization_problem( _discrete_measure_map = discrete_measure_map(ouq_sys, ouq_sys.reduction_data, Symbolic()) # We want flat vectors even in multidimensional DM cases: - # This may fail if you have a product measure of discrete measures of multiple random varaibles. + # This may fail if you have a product measure of discrete measures of multiple random variables. weight_vars, support_vars = collect(flatten(flatten(weights.(values(_discrete_measure_map))))), collect(flatten(flatten(support.(values(_discrete_measure_map))))) # TODO: Modify to use `extract_decision_vars` diff --git a/lib/OUQBase/test/FloodProblem/2D_independent.jl b/lib/OUQBase/test/Core/FloodProblem/2D_independent.jl similarity index 100% rename from lib/OUQBase/test/FloodProblem/2D_independent.jl rename to lib/OUQBase/test/Core/FloodProblem/2D_independent.jl diff --git a/lib/OUQBase/test/FloodProblem/Q_only.jl b/lib/OUQBase/test/Core/FloodProblem/Q_only.jl similarity index 84% rename from lib/OUQBase/test/FloodProblem/Q_only.jl rename to lib/OUQBase/test/Core/FloodProblem/Q_only.jl index 1f37c32..1a47e5e 100644 --- a/lib/OUQBase/test/FloodProblem/Q_only.jl +++ b/lib/OUQBase/test/Core/FloodProblem/Q_only.jl @@ -99,12 +99,16 @@ isdefined(Main, :probability_canonical_moments_problems_analytic) && push!( isdefined(Main, :probability_solutions) && push!(probability_solutions, "Flood Q only" => 0.17) -# Hacky test for now: -ouq_prob = OUQProblem(ouq_sys_probability_canonical_moments, OptimizationModel(), Oracle()) -sol = solve( - ouq_prob.optim_model, - BBO_adaptive_de_rand_1_bin_radiuslimited(); - maxiters = 100, - maxtime = 60.0, - verbose = true, -) +# Smoke-solve the probability problem. Skipped when this file is loaded only as +# a problem fixture (the all_problems.jl aggregator sets this flag) so the +# expensive solve runs once, from its own test group entry. +if !(isdefined(Main, :LOAD_OUQ_PROBLEMS_FIXTURE) && Main.LOAD_OUQ_PROBLEMS_FIXTURE) + ouq_prob = OUQProblem(ouq_sys_probability_canonical_moments, OptimizationModel(), Oracle()) + sol = solve( + ouq_prob.optim_model, + BBO_adaptive_de_rand_1_bin_radiuslimited(); + maxiters = 100, + maxtime = 60.0, + verbose = true, + ) +end diff --git a/lib/OUQBase/test/FloodProblem/full_dependent.jl b/lib/OUQBase/test/Core/FloodProblem/full_dependent.jl similarity index 100% rename from lib/OUQBase/test/FloodProblem/full_dependent.jl rename to lib/OUQBase/test/Core/FloodProblem/full_dependent.jl diff --git a/lib/OUQBase/test/FloodProblem/full_independent.jl b/lib/OUQBase/test/Core/FloodProblem/full_independent.jl similarity index 100% rename from lib/OUQBase/test/FloodProblem/full_independent.jl rename to lib/OUQBase/test/Core/FloodProblem/full_independent.jl diff --git a/lib/OUQBase/test/FloodProblem/test_canonical_moments.jl b/lib/OUQBase/test/Core/FloodProblem/test_canonical_moments.jl similarity index 87% rename from lib/OUQBase/test/FloodProblem/test_canonical_moments.jl rename to lib/OUQBase/test/Core/FloodProblem/test_canonical_moments.jl index cdc7c1e..52bc925 100644 --- a/lib/OUQBase/test/FloodProblem/test_canonical_moments.jl +++ b/lib/OUQBase/test/Core/FloodProblem/test_canonical_moments.jl @@ -1,7 +1,7 @@ using CanonicalMoments, ModelingToolkit using OUQBase -include(joinpath(@__DIR__, "../../../Problems/all_problems.jl")) +include(joinpath(@__DIR__, "..", "..", "Problems", "all_problems.jl")) name = "Flood full independent" ouq_sys = probability_canonical_moments_problems[name] diff --git a/lib/OUQBase/test/Problems/all_problems.jl b/lib/OUQBase/test/Problems/all_problems.jl new file mode 100644 index 0000000..a70b559 --- /dev/null +++ b/lib/OUQBase/test/Problems/all_problems.jl @@ -0,0 +1,27 @@ +# Shared OUQ problem fixtures for the OUQBase test suite. +# +# Each FloodProblem/.jl setup file builds a family of OUQSystems and +# pushes them into the dictionaries below (guarded by `isdefined(Main, ...)`), +# so a test only needs to `include` this file to get every flood problem keyed +# by name. The setup files redefine their own top-level scratch variables +# (`rand_vars`, `constraints`, `H`, ...), so they must be included into the +# same (Main) scope sequentially. + +expectation_winkler_problems = Dict{String, Any}() +expectation_canonical_moments_problems = Dict{String, Any}() +expectation_canonical_moments_problems_analytic = Dict{String, Any}() +expectation_solutions = Dict{String, Any}() + +probability_winkler_problems = Dict{String, Any}() +probability_canonical_moments_problems = Dict{String, Any}() +probability_canonical_moments_problems_analytic = Dict{String, Any}() +probability_solutions = Dict{String, Any}() + +LOAD_OUQ_PROBLEMS_FIXTURE = true + +const _FLOOD_PROBLEM_DIR = joinpath(@__DIR__, "..", "Core", "FloodProblem") + +include(joinpath(_FLOOD_PROBLEM_DIR, "Q_only.jl")) +include(joinpath(_FLOOD_PROBLEM_DIR, "2D_independent.jl")) +include(joinpath(_FLOOD_PROBLEM_DIR, "full_independent.jl")) +include(joinpath(_FLOOD_PROBLEM_DIR, "full_dependent.jl")) diff --git a/lib/OUQBase/test/Project.toml b/lib/OUQBase/test/Project.toml deleted file mode 100644 index 5b835d0..0000000 --- a/lib/OUQBase/test/Project.toml +++ /dev/null @@ -1,6 +0,0 @@ -[deps] -ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" -OUQBase = "01930cae-99d2-7439-8f4f-ace2ece9f1b9" -OptimizationBBO = "3e6eede4-6085-4f62-9a71-46d9bc1eb92b" -Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/lib/OUQBase/test/runtests.jl b/lib/OUQBase/test/runtests.jl index 1d35e73..bdc2e37 100644 --- a/lib/OUQBase/test/runtests.jl +++ b/lib/OUQBase/test/runtests.jl @@ -1 +1,16 @@ -include("FloodProblem/Q_only.jl") +using SafeTestsets +using Test + +const TEST_GROUP = get(ENV, "OPTIMALUNCERTAINTYQUANTIFICATION_TEST_GROUP", "All") + +if TEST_GROUP == "Core" || TEST_GROUP == "All" + @safetestset "Flood Problem (Q only)" include("Core/FloodProblem/Q_only.jl") + + # The flood-problem fixtures push every OUQSystem into Main-scoped problem + # dictionaries (guarded by `isdefined(Main, ...)`), and the canonical-moments + # objective test reads them back, so this group is built and run at top level + # in Main rather than inside an isolated @safetestset module. + @testset "Flood Problem (canonical moments objective)" begin + include("Core/FloodProblem/test_canonical_moments.jl") + end +end diff --git a/lib/OUQBase/test/test_groups.toml b/lib/OUQBase/test/test_groups.toml new file mode 100644 index 0000000..dda1469 --- /dev/null +++ b/lib/OUQBase/test/test_groups.toml @@ -0,0 +1,2 @@ +[Core] +versions = ["lts", "1", "pre"] diff --git a/src/OptimalUncertaintyQuantification.jl b/src/OptimalUncertaintyQuantification.jl index 301133f..6468157 100644 --- a/src/OptimalUncertaintyQuantification.jl +++ b/src/OptimalUncertaintyQuantification.jl @@ -1,3 +1,6 @@ module OptimalUncertaintyQuantification +using Reexport +@reexport using OUQBase + end # module diff --git a/test/Core/umbrella_load.jl b/test/Core/umbrella_load.jl new file mode 100644 index 0000000..6ec983c --- /dev/null +++ b/test/Core/umbrella_load.jl @@ -0,0 +1,12 @@ +using OptimalUncertaintyQuantification +using Test + +@testset "Umbrella module loads" begin + @test OptimalUncertaintyQuantification isa Module + # The umbrella re-exports OUQBase's public interface. + @test isdefined(OptimalUncertaintyQuantification, :OUQSystem) + @test isdefined(OptimalUncertaintyQuantification, :AdmissibleSet) + # Re-exported names are available unqualified in the test scope. + @test OUQSystem isa Type + @test AdmissibleSet isa Type +end diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100644 index 0000000..4f9fe49 --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,70 @@ +using Pkg +using SafeTestsets, Test + +const GROUP = get(ENV, "GROUP", "All") + +@time begin + # Detect sublibrary test groups. + # GROUP can be a bare sublibrary name (Core test group) or + # "{sublibrary}_{TEST_GROUP}" for any custom group (e.g., QA, GPU, etc.). + # Sublibraries declare their groups in test/test_groups.toml. + lib_dir = joinpath(dirname(@__DIR__), "lib") + + # Check if GROUP matches a sublibrary, possibly with a _SUFFIX for the test group. + # Scan underscores right-to-left to find the longest matching sublibrary prefix. + function _detect_sublibrary_group(group, lib_dir) + isdir(joinpath(lib_dir, group)) && return (group, "Core") + for i in length(group):-1:1 + if group[i] == '_' && isdir(joinpath(lib_dir, group[1:(i - 1)])) + return (group[1:(i - 1)], group[(i + 1):end]) + end + end + return (group, "Core") + end + base_group, test_group = _detect_sublibrary_group(GROUP, lib_dir) + + if isdir(joinpath(lib_dir, base_group)) + Pkg.activate(joinpath(lib_dir, base_group)) + # On Julia < 1.11, the [sources] section in Project.toml is not supported. + # Manually Pkg.develop local path dependencies so CI tests the PR branch code. + # We resolve transitively: each developed dependency's own [sources] are also + # developed, so that sibling packages reachable only through another + # sublibrary's sources are correctly found. + if VERSION < v"1.11.0-DEV.0" + developed = Set{String}() + # Never develop the active project: when sublibraries cyclically + # reference each other via [sources], the transitive walk below would + # otherwise try to `Pkg.develop` the active project itself, which Pkg + # refuses with "package has the same name or UUID as the active + # project". + push!(developed, normpath(joinpath(lib_dir, base_group))) + specs = Pkg.PackageSpec[] + queue = [joinpath(lib_dir, base_group)] + while !isempty(queue) + pkg_dir = popfirst!(queue) + toml_path = joinpath(pkg_dir, "Project.toml") + isfile(toml_path) || continue + toml = Pkg.TOML.parsefile(toml_path) + if haskey(toml, "sources") + for (dep_name, source_spec) in toml["sources"] + if source_spec isa Dict && haskey(source_spec, "path") + dep_path = normpath(joinpath(pkg_dir, source_spec["path"])) + if isdir(dep_path) && !(dep_path in developed) + push!(developed, dep_path) + @info "Queuing local source dependency" dep_name dep_path + push!(specs, Pkg.PackageSpec(path = dep_path)) + push!(queue, dep_path) + end + end + end + end + end + isempty(specs) || Pkg.develop(specs) + end + withenv("OPTIMALUNCERTAINTYQUANTIFICATION_TEST_GROUP" => test_group) do + Pkg.test(base_group, julia_args = ["--check-bounds=auto", "--compiled-modules=yes", "--depwarn=yes"], force_latest_compatible_version = false, allow_reresolve = true) + end + elseif GROUP == "All" || GROUP == "Core" + @time @safetestset "Umbrella Load" include("Core/umbrella_load.jl") + end +end # @time