Skip to content

fix beauti clone bug and revive CloneTest #130 - #135

Merged
jordandouglas merged 1 commit into
masterfrom
clone
Aug 12, 2026
Merged

fix beauti clone bug and revive CloneTest #130#135
jordandouglas merged 1 commit into
masterfrom
clone

Conversation

@walterxie

Copy link
Copy Markdown
Member

Fixes #130

Summary

Cloning a site model (e.g. HKY, GTR) across partitions in BEAUti inflated the cloned
freqParameter's dimension from N to N² (4 → 16 for nucleotide frequencies), producing an
XML file that BEAST could not run.

Bug location

BeautiDoc.deepCopyPlugin() in beast-fx/src/main/java/beastfx/app/inputeditor/BeautiDoc.java
(~line 2364-2395). When copying a List-valued input, the code iterated once per element
of the source list, but on each iteration called copy.setInputValue(input.getName(), input.get())
— passing the entire source list, not the current element. Input.setValue() appends all
elements of a given list to the destination (Input.java:452-461), so this would over-fill the
copy on every pass.

This was previously masked by a special case: if copy instanceof Parameter.Base, the destination
list was clear()-ed before each setInputValue call, so each pass reset-then-refilled to the
correct size. But Parameter.Base (beast.base.inference.parameter.Parameter.Base) is BEAST3's
now-@Deprecated legacy parameter class. The new spec-based parameters used by BEAUti's
site-model templates — SimplexParamRealVectorParamKeyVectorParamStateNode — no
longer extend it. So copy instanceof Parameter.Base is false for a SimplexParam (e.g.
freqParameter), the clear() never runs, and the whole source list gets appended once per
source element: a dimension-4 list appends 4 times → 16 elements.

The bug was already documented by a disabled regression test:
beast-fx/src/test/java/test/beastfx/app/beauti/CloneTest.java
@Disabled("SimplexParam validation fails when freqParameter is cloned across partitions (dimension mismatch)").

Fix

Stop iterating per-element for primitive-valued lists (the loop never used the per-element
value o anyway — it always re-set the whole list). Instead, detect once whether the source
list holds primitives (vs. BEASTInterface sub-objects, which still need per-element dedup
handling), clear the destination list a single time, and set it once:

if (input.get() instanceof List) {
    List<?> sourceList = (List<?>) input.get();
    boolean isPrimitiveList = sourceList.isEmpty() || !(sourceList.get(0) instanceof BEASTInterface);
    if (isPrimitiveList) {
        // replace the copy's list wholesale instead of appending per source element
        Object dest = copy.getInput(input.getName()).get();
        if (dest instanceof List) {
            ((List<?>) dest).clear();
        }
        copy.setInputValue(input.getName(), input.get());
    } else {
        for (Object o : sourceList) {
            // ... unchanged BEASTInterface element handling
        }
    }
}

This removes the stale Parameter.Base cast, so it works uniformly for legacy and new-style
parameters, and does the clear-then-set exactly once instead of once per element (also
avoiding the redundant O(n²) work of the old code path).

Changes

  • BeautiDoc.deepCopyPlugin(): replace the per-element primitive-value copy (gated on the
    stale Parameter.Base check) with a single wholesale list replace.
  • CloneTest.simpleSiteModelCloneTest: re-enable (was @Disabled pending this fix).

@walterxie

walterxie commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

Look OK now:

<stateNode id="birthRate.t:29" spec="beast.base.spec.inference.parameter.RealScalarParam" domain="PositiveReal" value="1.0"/>
<stateNode id="kappa.s:26" spec="beast.base.spec.inference.parameter.RealScalarParam" domain="PositiveReal" value="2.0"/>
<stateNode id="freqParameter.s:26" spec="beast.base.spec.inference.parameter.SimplexParam" dimension="4" domain="UnitInterval">0.25 0.25 0.25 0.25</stateNode>
<stateNode id="freqParameter.s:29" spec="beast.base.spec.inference.parameter.SimplexParam" dimension="4" domain="UnitInterval">0.25 0.25 0.25 0.25</stateNode>
<stateNode id="kappa.s:29" spec="beast.base.spec.inference.parameter.RealScalarParam" domain="PositiveReal" value="2.0"/>
<stateNode id="freqParameter.s:47" spec="beast.base.spec.inference.parameter.SimplexParam" dimension="4" domain="UnitInterval">0.25 0.25 0.25 0.25</stateNode>
<stateNode id="kappa.s:47" spec="beast.base.spec.inference.parameter.RealScalarParam" domain="PositiveReal" value="2.0"/>

@jordandouglas
jordandouglas merged commit 7e516ce into master Aug 12, 2026
1 check passed
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.

Beauti bug when cloning site models

2 participants