Pre-JOSS code review: correctness, robustness, and test fixes#18
Conversation
Addresses ten severe findings from the pre-submission review:
- StaticDesigns: fix UndefVarError (`nrow(data)` → `nrow(X)`) in the no-target
branch.
- fronts: rewrite `front()` with a proper Pareto-dominance predicate over
joint minimization and a separate tolerance-thinning pass; previous
implementation kept dominated points and dropped non-dominated ones.
- Mahalanobis: canonicalize evidence-key ordering against the column order
used to precompute Λ, so distances no longer depend on evidence insertion
order.
- EfficientValueMDP: tighten `value` `hasmethod` check to
`Tuple{Evidence, NTuple{2, Float64}}` to match the documented signature.
- efficient_designs / conditional_efficient_designs: guard `thresholds < 2`
with an `ArgumentError` before calling `range(0.0, 1.0, thresholds)`.
- POMDPs.actions: drop a multi-feature experiment iff ANY of its features is
already observed (atomic semantics) across all three MDPs.
- Variance() / Entropy(): throw `ArgumentError` when the prior gives zero
initial variance/entropy instead of producing NaN ratios.
- DistanceBased weights: extract `apply_masks!`; the zero-similarity recovery
branch now re-applies hard-match and importance/filter masks, and throws if
no historical row satisfies the constraints.
- Public design entry-points (`efficient_design`, `efficient_designs`,
`efficient_value`, `conditional_*`, `perform_ensemble_designs`) now accept
an `rng::AbstractRNG` keyword threaded into `Sim` for reproducibility; the
`DistanceBased` sampler tightens its `rng` parameter to `AbstractRNG`.
Tests: add Pareto-front edge cases (ties, duplicates, chain, tolerance) and
a Mahalanobis insertion-order regression. Update sampler `applicable` checks
to pass an RNG instance.
Deferred minor findings are recorded in papers/joss/code_review_findings.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts: # src/GenerativeDesigns/EfficientValueMDP.jl
# Conflicts: # test/GenerativeDesigns/test.jl
# Conflicts: # src/GenerativeDesigns/distancebased.jl # test/GenerativeDesigns/test_mahalanobis.jl
Julia's `(; a, b) = nt` syntax does not support `(; a = newname)`. Replace with explicit field accesses for clarity. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
M2: Replace `::Function` fields on `UncertaintyReductionMDP`,
`EfficientValueMDP`, and `ConditionalUncertaintyReductionMDP` with type
parameters (e.g. `UncertaintyReductionMDP{S, U}` over the sampler and
uncertainty closures). This removes dynamic dispatch on hot MCTS rollout
paths.
M10: Remove `Reexport` and `Requires` from `Project.toml` deps and
`[compat]` — neither is `using`-imported anywhere in `src/`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
The PR fixes several useful robustness issues, but there are still confirmed correctness, reproducibility, and API-consistency problems that can materially affect results. The most important blockers are: categorical distance direction, Thanks for the work in this PR. I reviewed the current diff in depth, including the distance kernels, generative MDPs, conditional MDP logic, static design path, ensemble front utilities, tests, docs, and the paper-facing behavior. Below are the issues I think should be addressed. Blocking correctness / reproducibility issues1.
|
|
I have found above issues, can @thevolatilebit you check if those make sense, I double checked locally, they seems to be real issues. I could also open another branch and fix them and merge back here. Let me know. |
Correctness & crashes:
- S1: DiscreteDistance was inverted (match -> large distance). Now
`y == x ? 0.0 : λ`, consistent with QuadraticDistance, so rows matching
the evidence receive the highest similarity weight.
- C1: ConditionalUncertaintyReductionMDP normalizes target_condition keys to
String (Symbol keys constructed fine but crashed at runtime) and validates
ranges; conditional_likelihood is now robust to String/Symbol keys.
- C2: accept Union{Missing,<:Real} constraint columns (nonmissingtype check)
and exclude missing rows via coalesce(..., false).
- M1: max_experiments now counts completed experiments, not raw evidence
entries, so prior evidence / multi-feature experiments are not mis-counted.
- M4: QuadraticDistance raises a clear ArgumentError on a zero-variance
(constant) column and recomputes σ per call (drops cross-column memoization).
- N3: DistanceBased validates importance_weights vector length.
Reproducibility & concurrency:
- S2: default_solver(rng) is seedable; each Sim runoff gets an independent rng
(fixes a parallel data race); efficient_designs / conditional_efficient_designs
use a fresh solver+rng per threshold; perform_ensemble_designs uses an
independent rng per ensemble run.
- S3: optimal_arrangement takes an rng (no implicit GLOBAL_RNG); static
efficient_designs pre-seeds one rng per task before @threads.
Docs / robustness / naming:
- C3: public-input @Assert -> ArgumentError across MDP constructors,
conditional_likelihood, and Entropy.
- D1: EfficientValueMDP docstring value signature -> value(evidence, costs).
- D2: Exponential docstring (λ=1/2, exp(-λx)); typos; unified terminal_condition
default; tau_set alias (thred_set still accepted).
- D3: static evaluate_experiments normalizes Symbol features; plot_front empty
guard and get_labels -> make_labels docstring fix; ensemble column
Average_Utility -> Average_Cost.
- M2: ensemble_to_dataframe de-duplicates identical points within a run so
realized_uncertainty collapses no longer inflate MLASP frequencies.
- P1/P2: documented normalized uncertainty and hard terminal condition.
Also adds CHANGELOG.md summarizing these changes, regression tests
(test_review_fixes.jl, ensemble M2 dedupe, static S3 reproducibility), and
.gitignore entries for local workspace/tooling artifacts. Full suite green
with JULIA_NUM_THREADS=4.
Update code-review: pre-JOSS fixes (9daf77f)
|
@thevolatilebit I fixed the issues mentioned above. Please check |
|
@danielchen26 Thank you! We can merge. |
|
Thanks both, looks like this solves some serious issues. Just one comment, I noticed it adds a lot of code (+1,624 -255). We may want to try to keep PR LoC additions as small as possible to avoid saturating human or LLM context. But in this case it seems warranted. Also, looks like we can delete the stale branch. |
Summary
A pre-submission code review surfaced ~15 severe and ~12 minor issues across the package. This PR lands the fixes for all 15 severe findings plus the applicable minors, with new unit tests covering previously untested public APIs (
EfficientValueMDP,ConditionalUncertaintyReductionMDP,plot_ensemble_pareto,ensemble_to_dataframe,front).The JOSS manuscript work is intentionally excluded from this PR.
Severe — correctness / crashes
evaluate_experimentsUndefVarError(StaticDesigns.jl:105).nrow(data)referenced an arg namedX. Crashed on the defaultevaluate_empty_subset=truepath wheneverzero_cost_featureswas empty (also default). Tests never exercised it. Fixed; test added.SquaredMahalanobisDistancemixed axes (distancebased.jl).Λ_marginalwas indexed innon_targets(column) order whilevec_evidenceand therow[evidence_keys]slice were in evidence-insertion order. Identical inputs produced different distances depending on insertion order (verified: 4.62 vs 5.90 with a non-trivial Σ). Now canonicalized against the column order at every cache key and dot product.front()dropped Pareto-optimal points and used a non-strict-weak sort (fronts.jl). The dominance check required strict improvement on both axes against the last kept point (rather than the partial order against all incumbents); withatol1=0it silently lost points sharing an x-coordinate, and at non-zero atols it lost true front members below tolerance. The sort comparator used<=on ties →Base.sortundefined behavior. Replaced with a proper online Pareto-front algorithm; atols are now a separate post-hoc thinning step. Sort comparator fixed to<.EfficientValueMDPconstructor checked the wrongvaluesignature (EfficientValueMDP.jl:50). AssertedTuple{Evidence, Vector{Float64}}while every call site passedNTuple{2,Float64}. Users who matched the docstring failed the assert; users who matched the assert crashed at runtime. Now checksTuple{Evidence, NTuple{2, Float64}}.range(0.0, 1.0, thresholds)errored onthresholds=1(UncertaintyReductionMDP.jl,ConditionalUncertaintyReductionMDP.jl). Now validatesthresholds ≥ 2with a clearArgumentError.actions()filtered multi-feature experiments byfirst(features)only (all three MDPs). A multi-feature experiment was treated as "taken" iff only its first feature was observed; later observations could re-sample a feature already in evidence, or a fully observed experiment could remain available. Decided semantics: an experiment is treated as a single atomic action and excluded iff any of its features is already observed.Variance()/Entropy()divided by zero → silent NaN propagation (distancebased.jl). With a degenerate prior or constant target column,initial == 0made the closure return NaN; the MDP terminal checkuncertainty <= thresholdwas always false and the planner ran tomax_experimentsthen took EOX with-bigM. Now validated at construction.distancebased.jl). The fallback resetsimilarities .= 1and re-applied only the target hard-match, ignoringfilter_range/importance_weightsfor evidence columns. Recovery now re-applies all active masks (or fails loudly when no rows match).EfficientValueMDPnot exported (GenerativeDesigns.jl). The type had its own docstring and was the subject ofefficient_value, but reviewers copying type-level examples hitUndefVarError. Added toexport.zero_cost_featureselement-type assumption (StaticDesigns.jl). Splat intoString[]failed for the common DataFramesSymbolidiom. Now accepts both.catchinDistanceBasedswallowed real errors (distancebased.jl). User-supplied distance closures had their stacktraces masked behind a single "unsupported scitype" message. Thetryis now narrowed to the dispatch onelscitype; user closures throw normally.target_conditionnumeric comparison broke onMulticlass/ NaN (ConditionalUncertaintyReductionMDP.jl). Now dispatches on scitype and raises clearly on unsupported target types.samplerreproducibility hole (distancebased.jl). The defaultrng = default_rng()made repeated runs ofefficient_design(s)/efficient_valuenon-deterministic with no public seed kwarg. The default is removed;rngis required.distancebased.jl).Λwas eagerly computed for every non-empty subset (2^p − 1inversions), andinv(Σ)returned Inf/NaN on rank-deficient submatrices without warning. Now lazy + memoized, with explicit singular detection (and a clear error suggestingdiagonal > 0).Minor
test/GenerativeDesigns/test_efficient_value.jl,test/GenerativeDesigns/test_conditional.jl,test/test_ensemble_fronts.jl,test/StaticDesigns/test.jladditions, expandedtest/GenerativeDesigns/test_mahalanobis.jl,test/fronts.jl.costs,uncertainty,weights,sampler,terminal_conditionare now type parameters rather than abstractFunction/ untypedDict, removing dynamic dispatch on every MCTS step.arrangements.jlguard againstKeyErroron partialevalswhenoptimal_arrangementis run over a state outside the cached set.ensemble_to_dataframeaction-set stringification: stopped relying on Julia's vector printing (["A"]["B"]); producesA,Bdirectly.default_solvershared global: replaced with adefault_solver()factory so MCTS RNG state isn't shared across calls.test/GenerativeDesigns/test_active_sampling.jlfixed.Reexport,Requiresremoved fromProject.toml.arrangements.jlandensemble_fronts.jl.Test plan
Pkg.test()— 94 tests pass on Julia 1.12.5ConditionalUncertaintyReduction,GliomaGrading,ActiveSampling)