Skip to content

Extend pem to multivariate independent distributions#6

Draft
Copilot wants to merge 4 commits intomainfrom
copilot/extend-model-to-multivariate-distributions
Draft

Extend pem to multivariate independent distributions#6
Copilot wants to merge 4 commits intomainfrom
copilot/extend-model-to-multivariate-distributions

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 20, 2026

pem only supported single univariate distributions. This extends it to handle K independent univariate distributions using the Hong (1998) concentration point scheme.

Changes

  • New pem overload (src/pem.jl): accepts Vector{<:UnivariateDistribution} and N::Union{Integer, Vector{<:Integer}} (scalar applies same N to all; vector allows per-distribution N). Returns (x::Matrix{Float64}, p::Vector{Float64}) where x is K×M with M = K×N (each column is one concentration point) and p[j] is the weight p_{k,n} / K.
  • Concentration point scheme: For K distributions with N concentrations each, produces K×N total points. Each point (μ_1, ..., x_{k,n}, ..., μ_K) keeps all variables at their means except variable k at concentration n. Weight is p_{k,n} / K, ensuring all weights sum to 1.
  • Ambiguity fix: narrowed existing pem(d::Vector, N::Integer) to pem(d::Vector{<:Real}, N::Integer) to avoid dispatch collision.
  • Tests: added MultivariateExample struct, 4 new test cases, test_multivariate_example helper with lexicographic column sorting (Tuple(col)), and YAML fixtures.

Usage

using Distributions, PointEstimateMethod

# Same N for all distributions — produces K*N = 2*3 = 6 concentration points
result = pem([Normal(), Normal(1.0, 0.5)], 3)
# result.x  → 2×6 Matrix (each column: only one variable deviates from its mean)
# result.p  → 6-element Vector (weights p_{k,n}/K)

# Per-distribution N
result = pem([Normal(), truncated(Normal(1.0, 0.4), 0.0, Inf)], [2, 3])
# result.x  → 2×5 Matrix, result.p → 5-element Vector

Implements the multivariate extension from Hong (1998).

Copilot AI linked an issue Apr 20, 2026 that may be closed by this pull request
Copilot AI changed the title [WIP] Extend PointEstimateModel to support multivariate distributions Extend pem to multivariate independent distributions Apr 20, 2026
Copilot AI requested a review from davide-f April 20, 2026 22:28
Copy link
Copy Markdown
Owner

@davide-f davide-f left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot No: the result shall not be the product of the combos.
Let K be the number of univariate distributions and N the number of concentrations, then the total number of points is K*N.

In particular, each concentration (or point) is in the form of:
(\mu_1, \mu_2, ..., x_{k,n}, ..., \mu_{N-1}, \mu_N) with n=1, ...N and k=1,...,K

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends pem beyond univariate usage by adding a multivariate overload for K independent univariate distributions, producing a Cartesian-product grid of point estimates and joint probabilities. It also updates the test suite to validate multivariate outputs via YAML fixtures.

Changes:

  • Added pem(::Vector{<:UnivariateDistribution}, ::Union{Integer, Vector{<:Integer}}) to build x::K×M and p::M via Cartesian products.
  • Narrowed the experimental-sample overload from pem(d::Vector, N::Integer) to pem(d::Vector{<:Real}, N::Integer) to avoid dispatch ambiguity.
  • Added multivariate examples/tests plus golden YAML fixtures under test/testcases/pem_multivariate/.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/pem.jl Adds multivariate pem overload; narrows vector-sample overload signature.
test/runtests.jl Adds test_multivariate_example and runs multivariate examples.
test/Examples.jl Introduces MultivariateExample and a list of multivariate test cases.
test/testcases/pem_multivariate/Normal2_N2.yml Golden fixture for 2D Normal with N=2.
test/testcases/pem_multivariate/Normal2_N3.yml Golden fixture for 2D Normal with N=3.
test/testcases/pem_multivariate/Normal_TruncNormal_N2.yml Golden fixture for Normal × TruncNormal with N=2.
test/testcases/pem_multivariate/Normal_TruncNormal_mixed_N.yml Golden fixture for Normal × TruncNormal with mixed N=[2,3].

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/runtests.jl Outdated
path_solution = joinpath(BASE_FOLDER, "test", "testcases", string(testing_function) * "_multivariate", example_name * ".yml")

# Sort columns lexicographically for deterministic comparison
I_calc = sortperm(eachcol(calc_solution.x), by=col -> collect(col))
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sortperm(eachcol(calc_solution.x), by=col -> collect(col)) will attempt to order Vectors using isless, which is not defined for arrays in Julia and typically throws a MethodError. Convert columns to an orderable key (e.g., Tuple(col) or NTuple) or provide an explicit lt that compares tuples lexicographically so the multivariate tests are deterministic and runnable.

Suggested change
I_calc = sortperm(eachcol(calc_solution.x), by=col -> collect(col))
I_calc = sortperm(eachcol(calc_solution.x), by=col -> Tuple(col))

Copilot uses AI. Check for mistakes.
Comment thread src/pem.jl Outdated
Comment on lines +273 to +280
# Form the Cartesian product of all point index combinations
combos = vec(collect(Iterators.product([1:Ns[k] for k in 1:K]...)))

M = length(combos)
x = Matrix{Float64}(undef, K, M)
p = Vector{Float64}(undef, M)

for (j, combo) in enumerate(combos)
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The multivariate implementation materializes the full Cartesian product twice (collect(Iterators.product(...)) into combos, then iterating it to fill x/p). This adds avoidable memory pressure proportional to M (which can be huge). Iterate the Iterators.product(...) directly inside the enumerate loop and drop combos to reduce allocations without changing behavior.

Suggested change
# Form the Cartesian product of all point index combinations
combos = vec(collect(Iterators.product([1:Ns[k] for k in 1:K]...)))
M = length(combos)
x = Matrix{Float64}(undef, K, M)
p = Vector{Float64}(undef, M)
for (j, combo) in enumerate(combos)
# Form the Cartesian product of all point index combinations lazily
M = prod(Ns)
x = Matrix{Float64}(undef, K, M)
p = Vector{Float64}(undef, M)
for (j, combo) in enumerate(Iterators.product([1:Ns[k] for k in 1:K]...))

Copilot uses AI. Check for mistakes.
Comment thread src/pem.jl
Comment on lines 90 to 92
function pem(
d::Vector,
d::Vector{<:Real},
N::Integer;
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature was narrowed to d::Vector{<:Real}, but the docstring above still documents the parameter as d :: Vector (generic). Please update the docstring to reflect the new accepted type (and consider AbstractVector{<:Real} if you want to keep supporting views/subarrays while still avoiding the dispatch ambiguity).

Copilot uses AI. Check for mistakes.
@davide-f
Copy link
Copy Markdown
Owner

@copilot No: the result shall not be the product of the combos.
Let K be the number of univariate distributions and N the number of concentrations, then the total number of points is K*N.

In particular, each concentration (or point) is in the form of:
(\mu_1, \mu_2, ..., x_{k,n}, ..., \mu_{N-1}, \mu_N) with n=1, ...N and k=1,...,K

…ead of Cartesian product

Agent-Logs-Url: https://github.com/davide-f/PointEstimateMethod.jl/sessions/a0b94002-41ae-4fce-b2b3-3727252b4461

Co-authored-by: davide-f <67809479+davide-f@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 20, 2026

@copilot No: the result shall not be the product of the combos.
Let K be the number of univariate distributions and N the number of concentrations, then the total number of points is K*N.

...

Fixed in commit 8698bbd. The implementation now uses the Hong (1998) concentration point scheme: for K distributions with N concentrations each, there are K×N total points where each point (μ_1, ..., x_{k,n}, ..., μ_K) keeps all variables at their means except variable k. The weight of each point is p_{k,n} / K, ensuring all weights sum to 1.

@copilot No: the result shall not be the product of the combos.
Let K be the number of univariate distributions and N the number of concentrations, then the total number of points ...

Fixed in commit 8698bbd. The implementation now uses the Hong (1998) concentration point scheme: for K distributions with N concentrations each, there are K×N total points where each point (μ_1, ..., x_{k,n}, ..., μ_K) keeps all variables at their means except variable k. The weight of each point is p_{k,n} / K, ensuring all weights sum to 1.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/FluxML/MacroTools.jl/tarball/1e0228a030642014fe5cfe68c2c0a818f9e3f522
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaArrays/FillArrays.jl/tarball/2f979084d1e13948a3352cf64a25df6bd3b4dca3
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaArrays/StaticArraysCore.jl/tarball/6ab403037779dae8c514bad259f32a447262455a
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/Bzip2_jll.jl/tarball/1b96ea4a01afe0ea4090c5c8039690672dd13f2e
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/HiGHS_jll.jl/tarball/50ed12dc8c37ebb8d2b759f21755259d8512f2bd
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/Libiconv_jll.jl/tarball/be484f5c92fad0bd8acfef35fe017900b0b73809
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/OpenBLAS32_jll.jl/tarball/46cce8b42186882811da4ce1f4c7208b02deb716
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/OpenSpecFun_jll.jl/tarball/1346c9208249809840c91b26703912dff463d335
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaBinaryWrappers/Rmath_jll.jl/tarball/58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaCI/BenchmarkTools.jl/tarball/9670d3febc2b6da60a0ae57846ba74670290653f
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaCollections/DataStructures.jl/tarball/e86f4a2805f7f19bec5129bc9150c38208e5dc23
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaCollections/OrderedCollections.jl/tarball/05868e21324cede2207c6f0f466b4bfef6d5e7ee
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaCollections/SortingAlgorithms.jl/tarball/64d974c2e6fdf07f8155b5b2ca2ffa9069b608d9
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaData/DataAPI.jl/tarball/abe83f3a2f1b857aac70ef8b269080af17764bbe
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaData/Missings.jl/tarball/ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaData/Parsers.jl/tarball/7d2f8f21da5db6a806faf7b9b292296da42b2810
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaData/YAML.jl/tarball/a1c0c7585346251353cddede21f180b96388c403
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaDiff/DiffResults.jl/tarball/782dd5f4561f5d267313f23853baaaa4c52ea621
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaDiff/DiffRules.jl/tarball/23163d55f885173722d1e4cf0f6110cdbaf7e272
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaDiff/ForwardDiff.jl/tarball/cddeab6487248a39dae1a960fff0ac17b2a28888
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaDocs/DocStringExtensions.jl/tarball/7442a5dfe1ebb773c29cc2962a8980f47221d76c
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaIO/CodecBzip2.jl/tarball/84990fa864b7f2b4901901ca12736e45ee79068c
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaIO/CodecZlib.jl/tarball/962834c22b66e32aa10f7611c08c8ca4e20749a9
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaIO/JSON.jl/tarball/67c6f1f085cb2671c93fe34244c9cccde30f7a26
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaIO/TranscodingStreams.jl/tarball/0c45878dcfdcfa8480052b6ab162cdd138781742
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaLang/Compat.jl/tarball/9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaLang/PrecompileTools.jl/tarball/07a921781cab75691315adc645096ed5e370cb77
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaMath/Combinatorics.jl/tarball/c761b00e7755700f9cdf5b02039939d1359330e1
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaMath/HypergeometricFunctions.jl/tarball/68c173f4f449de5b438ee67ed0c9c748dc31a2ec
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaMath/IrrationalConstants.jl/tarball/b2d91fe939cae05960e760110b328288867b5758
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaMath/NaNMath.jl/tarball/9b8215b1ee9e78a293f99797cd31375471b2bcae
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaMath/Polynomials.jl/tarball/2d99b4c8a7845ab1342921733fa29366dae28b24
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaMath/QuadGK.jl/tarball/5e8e8b0ab68215d7a2b14b9921a946fee794749e
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaMath/SpecialFunctions.jl/tarball/2700b235561b0335d5bef7097a111dc513b8655e
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaObjects/ConstructionBase.jl/tarball/b4b092499347b18a015186eae3042f72267106cb
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaPackaging/JLLWrappers.jl/tarball/0533e564aae234aff59ab625543145446d8b6ec2
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaPackaging/Preferences.jl/tarball/8b770b60760d4451834fe79dd483e318eee709c4
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaServices/StructUtils.jl/tarball/aab80fbf866600f3299dd7f6656d80e7be177cfe
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaStats/Distributions.jl/tarball/e421c1938fafab0165b04dc1a9dbe2a26272952c
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaStats/LogExpFunctions.jl/tarball/13ca9e2586b89836fd20cccf56e57e2b9ae7f38f
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaStats/PDMats.jl/tarball/e4cff168707d441cd6bf3ff7e4832bdf34278e4a
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaStats/Rmath.jl/tarball/5b3d50eb374cea306873b371d3f8d3915a018f0b
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaStats/Statistics.jl/tarball/ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaStats/StatsAPI.jl/tarball/178ed29fd5b2a2cfc3bd31c13375ae925623ff36
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaStats/StatsBase.jl/tarball/aceda6f4e598d331548e04cc6b2124a6148138e3
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaStats/StatsFuns.jl/tarball/91f091a8716a6bb38417a6e6f274602a19aaa685
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/JuliaStrings/StringEncodings.jl/tarball/b765e46ba27ecf6b44faf70df40c57aa3a547dcb
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/LilithHafner/AliasTables.jl/tarball/9876e1e164b144ca45e9e3198d0b689cadfed9ff
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/LilithHafner/PtrArrays.jl/tarball/4fbbafbc6251b883f4d2705356f3641f3652a7fe
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/jump-dev/HiGHS.jl/tarball/bf5e946f72ebd1b4620249a6be7ff34832ba9ca0
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/jump-dev/JuMP.jl/tarball/4091a1338a0e32766b11b9bd3fac247d34200c77
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/jump-dev/MathOptIIS.jl/tarball/3b3d69130d8ab8c39d5fa4d30e20a8e6428c9d37
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/jump-dev/MathOptInterface.jl/tarball/ce739e3d8a21313ea418772edfc3b7b15a1dfc16
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/jump-dev/MutableArithmetics.jl/tarball/7c25249fc13a070f5ba433c50e21e22bb33c6fb0
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/jw3126/Setfield.jl/tarball/c5391c6ace3bc430ca630251d02ea9687169ca68
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/rdeits/CommonSubexpressions.jl/tarball/cda2cfaebb4be89c9084adaca7dd7333369715c5
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • https://api.github.com/repos/simonster/Reexport.jl/tarball/45e428421666073eab6f2da5c9d310d99bb12f9b
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (http block)
  • pkg.julialang.org
    • Triggering command: /usr/bin/julia julia --project=. -e using Pkg; Pkg.test() (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from davide-f April 20, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extend model to multivariate distributions

3 participants