Skip to content

Fix hyper-prior support for spec-framework distribution parameters - #158

Closed
walterxie wants to merge 5 commits into
masterfrom
hyperprior
Closed

Fix hyper-prior support for spec-framework distribution parameters#158
walterxie wants to merge 5 commits into
masterfrom
hyperprior

Conversation

@walterxie

@walterxie walterxie commented Aug 24, 2026

Copy link
Copy Markdown
Member

Fix #157, #159 and #160

Restores the ability to add a hyper prior on a distribution's own parameter (e.g. LogNormal's M/S) from the Priors panel, for the new beast.base.spec distribution/parameter classes. Three independent bugs were compounding:

  1. The checkbox-visibility check (ScalarInputEditor) was mis-ported from the legacy editor.
  2. toggleEstimate()'s id/context logic assumed the legacy Prior-wrapper object model.
  3. The default hyper-prior template used deprecated classes incompatible with the new parameter types, and (once fixed) needed to pick a domain-appropriate distribution.

Changes

  • beastfx/app/inputeditor/spec/ScalarInputEditor.java
    • addComboBox(): fixed the mis-ported check from beastObject2 instanceof RealScalarParam to beastObject2 instanceof ScalarDistribution sd && sd.paramInput.get() != parameter — correctly identifies "this parameter is a distribution's own hyperparameter" (mean/sigma/alpha/theta/...) as opposed to "this parameter is the distribution's target," which is what actually makes the estimate checkbox appear outside Expert Mode.
    • toggleEstimate():
      • Generalized id.startsWith("RealParameter") to !id.startsWith("parameter."), so spec parameters (whose auto-ids look like "RealScalarParam.N") get renamed and picked up by BeautiDoc's general "parameter.*" sync scan.
      • Replaced the legacy "grandparent" lookup with using the owning distribution's own id directly (parent.getID()) — the flattened spec model no longer has a separate wrapping Prior object, so that id is already partition-specific.
      • Selects between two new hyper-prior templates based on the target parameter's domain.
  • beastfx/app/inputeditor/BeautiConfig.java
    • Added two new hyper-prior templates, registered as hyperPriorSpecTemplate and hyperPriorSpecPositiveTemplate, parsed with their own XMLParser instance each (note: reusing one XMLParser across multiple parseBareFragment() calls corrupts internal DOM state — hit this directly while developing the fix).
      • HYPER_PRIOR_SPEC_XML (Real-domain, e.g. LogNormal's M): Normal + RealRandomWalkOperator (additive; safe for a value that may be zero or negative).
      • HYPER_PRIOR_SPEC_POSITIVE_XML (PositiveReal/NonNegativeReal-domain, e.g. LogNormal's S, Gamma's alpha/theta): Gamma(alpha=0.001, theta=1000) + ScaleOperator, matching the existing weak-Gamma-prior convention already used for MutationRatePrior/ClockPrior/YuleBirthRatePrior.
    • Left hyperPriorTemplate/HYPER_PRIOR_XML (legacy Prior+OneOnX+ScaleOperator) untouched — it's still exercised by ParameterInputEditor when editing an old, pre-existing analysis file that has genuine legacy RealParameter objects, even though LegacyStandard.xml is no longer used to start new analyses.
  • beastfx/app/inputeditor/BeautiDoc.java
    • Registered both new templates in the general "parameter.*" id sync scan (applyBeautiRules), alongside the existing legacy one, so hyper-prior connects stay correct on future doc-wide syncs regardless of which template originally created them.
  • beast-fx/src/test/java/test/beastfx/app/beauti/HyperPriorTest.java (new)
    • addHyperPriorForRealDomainParameter: switches the birth rate's prior to LogNormal, checks "estimate" on M, confirms the dialog, asserts a Normal hyper prior is added to the Priors panel.
    • addHyperPriorForPositiveRealDomainParameter: same for S, asserts a domain-compatible Gamma (not Normal) is added.

Testing

  • mvn -pl beast-fx -am test -Pslow-tests -Dtest='test.beastfx.app.**' -Dsurefire.failIfNoSpecifiedTests=false → Tests run: 48, Failures: 0, Errors: 0, Skipped: 6
  • Verified both new tests fail against the pre-fix code (checkbox not found / wrong distribution class), confirming they're real regression coverage, not vacuous.

Notes for reviewers

  • RealScalarParam's domain (Real vs PositiveReal/NonNegativeReal) determines which hyper-prior template is used; other domains (e.g. UnitInterval) fall back to the Real-domain template as a safe default.
  • Hyper priors for IntScalarParam/BoolScalarParam distribution parameters are not supported by this change (rare in practice — e.g. IntUniform's bounds); toggleEstimate() shows a message and reverts the checkbox for those rather than silently breaking.

@walterxie
walterxie requested a review from alexeid August 24, 2026 01:43
@walterxie

walterxie commented Aug 24, 2026

Copy link
Copy Markdown
Member Author
Screenshot 2026-08-24 at 13 07 28 Screenshot 2026-08-24 at 13 46 43

@walterxie

Copy link
Copy Markdown
Member Author

Xml runs and log looks correct :

Screenshot 2026-08-25 at 11 01 50

@walterxie

Copy link
Copy Markdown
Member Author
Screenshot 2026-08-26 at 13 04 15

@alexeid

alexeid commented Aug 26, 2026

Copy link
Copy Markdown
Member

Nice catch on the three compounding bugs. The visibility fix and parent.getID() are right, and the tests are good. One problem with the proposed solution and a few correctness bugs before merge.

Blocking: concrete-class coupling must be resolved before this merges

BEAUti should not depend on concrete beast-base classes beyond the spec interfaces and the established mechanisms (InputEditors, templates). This PR adds two new such dependencies, and both need to go before merge:

  1. The two new template strings hardcode Normal, Gamma, RealScalarParam, RealRandomWalkOperator and ScaleOperator into beast-fx Java source. BeautiConfig is itself parsed from Standard.xml and already has Input<BeautiSubTemplate> partitionTemplate. Adding a hyperPriorTemplate input and moving these fragments into Standard.xml keeps the class names in the template layer, makes them package-overridable, and removes the XMLParser-reuse problem entirely.

  2. ScalarInputEditor now imports spec.domain.PositiveReal/NonNegativeReal to encode "positive ⇒ Gamma, else ⇒ Normal". ScalarDistributionInputEditor already derives domain compatibility declaratively from the templates (getDomain() L268 + isCompatible() L831). Extracting a shared firstCompatibleTemplate(domain) would pick the default from the same source as the Priors dropdown, with no domain imports, and it generalises to new domains and package-supplied distributions.

The legacy HYPER_PRIOR_XML set this precedent; this PR triples it, so it's the right moment to move it into the template layer rather than extend it.

Also: PositiveReal extends NonNegativeReal, so the instanceof PositiveReal clause is dead.

Correctness

Rename guard too broad. !id.startsWith("parameter.") matches any StateNode; the legacy RealParameter check only ever matched auto-generated ids (BEASTObjectPanel.getID()<SimpleName>.<n>). Reachable today via linking: link birthRate.t:anolis into a distribution's mean, tick estimate, and pluginmap.remove() + setID() breaks every connector on that id, and it will bite when ucldStdev.c:$(n)-style templates return. Suggest id == null || id.isEmpty() || id.startsWith(parameter2.getClass().getSimpleName() + ".").

Int params throw, don't revert. BoundedInt/OffsetInt lower/upper/offset are IntScalarParam → Real template → verified Input 102b: type mismatch for input 'scalar' of RealRandomWalkOperator. The catch swallows it after the parameter has already been renamed, re-registered and set estimate=true. The description says it "shows a message and reverts the checkbox", but it doesn't.

UnitInterval falls back to Normal and parses cleanly, i.e. fails silently in exactly the way S did before this fix. The compatibility-lookup approach above fixes this class of problem generically.

XMLParser comment. The failure is real (reproduced), but it isn't DOM state: XMLParser.beastObjectsWaitingToInit (L241) is never cleared, so objects from earlier parses get re-initAndValidated, and BeautiSubTemplate.initAndValidate() isn't idempotent: it re-wraps already-wrapped CDATA. Fresh-parser-per-fragment is a fine workaround; worth fixing the comment and filing the two underlying bugs. Related: the three parses share one try/catch, so a failure in the first leaves all three fields null and NPEs in setDoc().

Minor

  • suppressPlugins matches the runtime class name, so CompoundRealScalarParam/CompoundIntScalarParam aren't covered (cf. the existing CompoundRealParameter.keys).
  • LogUniform's domain is PositiveReal, so it is not an OneOnX replacement for Real-domain params, which is worth stating. And "OneOnX removed in beast3" is inaccurate: the class still exists, is a registered provider in version.xml, and is used by LegacyStandard.xml and HYPER_PRIOR_XML. "Removed from the BEAUti distribution list" is what's meant.
  • MethodsText.nameMap still maps OneOnX; add LogUniform.
  • import java.util.* / import beastfx.app.inputeditor.* and the import reshuffle add diff noise.

@walterxie walterxie closed this Aug 27, 2026
@walterxie walterxie added the invalid This doesn't seem right label Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

invalid This doesn't seem right

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Priors panel: "estimate" checkbox missing, and hyper-prior creation broken

2 participants