Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 89 additions & 16 deletions beast-fx/src/main/java/beastfx/app/inputeditor/BeautiConfig.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
package beastfx.app.inputeditor;



import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import beastfx.app.util.Alert;
import beastfx.app.util.FXUtils;
import javafx.scene.Parent;
import beast.base.core.BEASTInterface;
import beast.base.core.BEASTObject;
import beast.base.core.Description;
import beast.base.core.Input;
import beast.base.core.Input.Validate;
import beast.base.evolution.alignment.Alignment;
import beast.base.spec.evolution.tree.MRCAPrior;
import beast.base.parser.XMLParser;
import beast.base.spec.evolution.tree.MRCAPrior;
import beastfx.app.util.Alert;
import beastfx.app.util.FXUtils;
import javafx.scene.Parent;

import java.io.File;
import java.util.*;

@Description("Beauti configuration object, used to find Beauti configuration " +
"information from Beauti template files.")
Expand Down Expand Up @@ -92,7 +87,16 @@ public class BeautiConfig extends BEASTObject {
public List<BeautiAlignmentProvider> alignmentProvider;

public BeautiSubTemplate hyperPriorTemplate = null;

/** hyper prior template for spec-framework (RealScalarParam-based) distribution parameters
* whose domain is unbounded Real, e.g. LogNormal's M -- kept separate from hyperPriorTemplate,
* which is still used by the legacy ParameterInputEditor for RealParameter-based priors
* (e.g. when loading an old analysis) */
public BeautiSubTemplate hyperPriorSpecTemplate = null;
/** as hyperPriorSpecTemplate, but for PositiveReal/NonNegativeReal-domain parameters, e.g.
* LogNormal's S -- a Normal prior would be domain-incompatible with such a parameter (it
* would show as an invalid/mismatched entry in the distribution dropdown) */
public BeautiSubTemplate hyperPriorSpecPositiveTemplate = null;

@Override
public void initAndValidate() {
parseSet(inlineInput.get(), null, inlineBEASTObject);
Expand Down Expand Up @@ -123,8 +127,12 @@ public void initAndValidate() {
alignmentProvider = alignmentProviderInput.get();

try {
XMLParser parser = new XMLParser();
hyperPriorTemplate = (BeautiSubTemplate) parser.parseBareFragment(HYPER_PRIOR_XML, true);
// a fresh XMLParser per fragment -- the parser keeps mutable DOM state
// internally, and reusing one instance across multiple parseBareFragment()
// calls corrupts the second parse (stray "]]>" CDATA-boundary errors)
hyperPriorTemplate = (BeautiSubTemplate) new XMLParser().parseBareFragment(HYPER_PRIOR_XML, true);
hyperPriorSpecTemplate = (BeautiSubTemplate) new XMLParser().parseBareFragment(HYPER_PRIOR_SPEC_XML, true);
hyperPriorSpecPositiveTemplate = (BeautiSubTemplate) new XMLParser().parseBareFragment(HYPER_PRIOR_SPEC_POSITIVE_XML, true);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
Expand Down Expand Up @@ -153,14 +161,79 @@ public void initAndValidate() {
" <connect spec='beastfx.app.inputeditor.BeautiConnector' srcID='HyperPrior.$(n)' targetID='prior' inputName='distribution' if='inposterior(parameter.$(n)) and parameter.$(n)/estimate=true'>Hyper prior for parameter $(n)</connect>\n" +
" </subtemplate>\n" +
" </beast>\n";


/**
* Hyper prior for a spec-framework distribution's own parameter whose domain is unbounded
* Real (e.g. LogNormal's M). Unlike the legacy template above, the distribution is connected
* to 'prior' directly (there is no wrapping Prior object any more). A Normal + random walk is
* used since the target's domain is not restricted to positive values, unlike the legacy
* OneOnX (which has no spec equivalent) + multiplicative ScaleOperator.
*/
final static String HYPER_PRIOR_SPEC_XML =
" <beast version='2.8'\n" +
" namespace='beastfx.app.beauti:beast.base.core'>\n" +
" <!-- Parameter Hyper Prior for spec-framework distribution parameters -->\n" +
" <subtemplate spec='beastfx.app.inputeditor.BeautiSubTemplate' id='HyperPriorSpec' class='beast.base.spec.inference.distribution.Normal' mainid='HyperPriorSpec.$(n)'>\n" +
" <![CDATA[\n" +
" <distribution id='HyperPriorSpec.$(n)' spec='beast.base.spec.inference.distribution.Normal' param='@parameter.$(n)'>\n" +
" <sigma spec='beast.base.spec.inference.parameter.RealScalarParam' domain='PositiveReal' value='10.0' estimate='false'/>\n" +
" </distribution>\n" +
"\n" +
" <operator id='hyperRandomWalk.$(n)' spec='beast.base.spec.inference.operator.RealRandomWalkOperator' scalar='@parameter.$(n)' windowSize='1' weight='0.1'/>\n" +
" ]]>\n" +
" <connect spec='beastfx.app.inputeditor.BeautiConnector' srcID='parameter.$(n)' targetID='state' inputName='stateNode' if='inposterior(parameter.$(n)) and parameter.$(n)/estimate=true'/>\n" +
"\n" +
" <connect spec='beastfx.app.inputeditor.BeautiConnector' srcID='hyperRandomWalk.$(n)' targetID='mcmc' inputName='operator' if='inposterior(parameter.$(n)) and parameter.$(n)/estimate=true'>Random walk on hyper parameter $(n)</connect>\n" +
"\n" +
" <connect spec='beastfx.app.inputeditor.BeautiConnector' srcID='parameter.$(n)' targetID='tracelog' inputName='log' if='inposterior(parameter.$(n)) and parameter.$(n)/estimate=true'/>\n" +
" <connect spec='beastfx.app.inputeditor.BeautiConnector' srcID='HyperPriorSpec.$(n)' targetID='tracelog' inputName='log' if='inposterior(parameter.$(n)) and parameter.$(n)/estimate=true'/>\n" +
"\n" +
" <connect spec='beastfx.app.inputeditor.BeautiConnector' srcID='HyperPriorSpec.$(n)' targetID='prior' inputName='distribution' if='inposterior(parameter.$(n)) and parameter.$(n)/estimate=true'>Hyper prior for parameter $(n)</connect>\n" +
" </subtemplate>\n" +
" </beast>\n";

/**
* As HYPER_PRIOR_SPEC_XML, but for a PositiveReal/NonNegativeReal-domain parameter (e.g.
* LogNormal's S, Gamma's alpha/theta). A Normal prior is domain-incompatible with such a
* parameter (it would show as a mismatched entry outside the compatible-domain dropdown list
* in the Priors panel -- see ScalarDistributionInputEditor.isCompatible), so a Gamma is used
* instead, matching the weak/vague-prior convention already used elsewhere in Standard.xml
* (e.g. MutationRatePrior, ClockPrior, YuleBirthRatePrior), together with the same
* multiplicative ScaleOperator used for other PositiveReal-domain parameters.
*/
final static String HYPER_PRIOR_SPEC_POSITIVE_XML =
" <beast version='2.8'\n" +
" namespace='beastfx.app.beauti:beast.base.core'>\n" +
" <!-- Parameter Hyper Prior for spec-framework PositiveReal/NonNegativeReal-domain distribution parameters -->\n" +
" <subtemplate spec='beastfx.app.inputeditor.BeautiSubTemplate' id='HyperPriorSpecPositive' class='beast.base.spec.inference.distribution.Gamma' mainid='HyperPriorSpecPositive.$(n)'>\n" +
" <![CDATA[\n" +
" <distribution id='HyperPriorSpecPositive.$(n)' spec='beast.base.spec.inference.distribution.Gamma' param='@parameter.$(n)'>\n" +
" <alpha spec='beast.base.spec.inference.parameter.RealScalarParam' domain='PositiveReal' value='0.001' estimate='false'/>\n" +
" <theta spec='beast.base.spec.inference.parameter.RealScalarParam' domain='PositiveReal' value='1000.0' estimate='false'/>\n" +
" </distribution>\n" +
"\n" +
" <operator id='hyperScaler.$(n)' spec='beast.base.spec.inference.operator.ScaleOperator' scaleFactor='0.5' weight='0.1' parameter='@parameter.$(n)'/>\n" +
" ]]>\n" +
" <connect spec='beastfx.app.inputeditor.BeautiConnector' srcID='parameter.$(n)' targetID='state' inputName='stateNode' if='inposterior(parameter.$(n)) and parameter.$(n)/estimate=true'/>\n" +
"\n" +
" <connect spec='beastfx.app.inputeditor.BeautiConnector' srcID='hyperScaler.$(n)' targetID='mcmc' inputName='operator' if='inposterior(parameter.$(n)) and parameter.$(n)/estimate=true'>Scale hyper parameter $(n)</connect>\n" +
"\n" +
" <connect spec='beastfx.app.inputeditor.BeautiConnector' srcID='parameter.$(n)' targetID='tracelog' inputName='log' if='inposterior(parameter.$(n)) and parameter.$(n)/estimate=true'/>\n" +
" <connect spec='beastfx.app.inputeditor.BeautiConnector' srcID='HyperPriorSpecPositive.$(n)' targetID='tracelog' inputName='log' if='inposterior(parameter.$(n)) and parameter.$(n)/estimate=true'/>\n" +
"\n" +
" <connect spec='beastfx.app.inputeditor.BeautiConnector' srcID='HyperPriorSpecPositive.$(n)' targetID='prior' inputName='distribution' if='inposterior(parameter.$(n)) and parameter.$(n)/estimate=true'>Hyper prior for parameter $(n)</connect>\n" +
" </subtemplate>\n" +
" </beast>\n";

public void setDoc(BeautiDoc doc) {
partitionTemplate.get().setDoc(doc);
for (BeautiSubTemplate sub : subTemplates) {
sub.setDoc(doc);
}
doc.setExpertMode(isExpertInput.get());
hyperPriorTemplate.doc = doc;
hyperPriorSpecTemplate.doc = doc;
hyperPriorSpecPositiveTemplate.doc = doc;
}

public void clear() {
Expand Down
2 changes: 2 additions & 0 deletions beast-fx/src/main/java/beastfx/app/inputeditor/BeautiDoc.java
Original file line number Diff line number Diff line change
Expand Up @@ -1632,6 +1632,8 @@ synchronized public void scrubAll(boolean useNotEstimatedStateNodes, boolean isI

List<BeautiSubTemplate> templates = new ArrayList<>();
templates.add(beautiConfig.hyperPriorTemplate);
templates.add(beautiConfig.hyperPriorSpecTemplate);
templates.add(beautiConfig.hyperPriorSpecPositiveTemplate);
for (BEASTInterface beastObject : pluginmap.values()) {
if (beastObject instanceof StateNode) {
if (beastObject.getID() != null && beastObject.getID().startsWith("parameter.")) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
package beastfx.app.inputeditor.spec;



import beast.base.core.BEASTInterface;
import beast.base.core.Input;
import beast.base.core.Log;
import beast.base.evolution.branchratemodel.BranchRateModel;
import beast.base.inference.Distribution;
import beast.base.inference.Operator;
import beast.base.inference.StateNode;
import beast.base.parser.PartitionContext;
import beast.base.spec.domain.NonNegativeReal;
import beast.base.spec.domain.PositiveReal;
import beast.base.spec.inference.distribution.ScalarDistribution;
import beast.base.spec.inference.parameter.BoolScalarParam;
import beast.base.spec.inference.parameter.IntScalarParam;
import beast.base.spec.inference.parameter.RealScalarParam;
import beast.base.spec.type.Scalar;
import beastfx.app.inputeditor.BEASTObjectDialog;
import beastfx.app.inputeditor.BEASTObjectInputEditor;
import beastfx.app.inputeditor.BEASTObjectPanel;
import beastfx.app.inputeditor.BeautiDoc;
import beastfx.app.inputeditor.*;
import beastfx.app.util.Alert;
import beastfx.app.util.FXUtils;
import javafx.geometry.Insets;
Expand All @@ -32,9 +29,6 @@
import java.util.List;





public class ScalarInputEditor extends BEASTObjectInputEditor {
boolean isParametricDistributionParameter = false;

Expand Down Expand Up @@ -212,7 +206,9 @@ protected void addComboBox(Pane box, Input<?> input, BEASTInterface beastObject)
//m_bAddButtons = false;
if (itemNr < 0) {
for (Object beastObject2 : ((BEASTInterface) m_input.get()).getOutputs()) {
if (beastObject2 instanceof RealScalarParam) {
// a distribution whose *own* parameter (mean, sigma, alpha, ...) this is,
// as opposed to a distribution for which this is the target (paramInput)
if (beastObject2 instanceof ScalarDistribution<?,?> sd && sd.paramInput.get() != parameter) {
m_isEstimatedBox.setVisible(doc.allowLinking);
m_isEstimatedBox.setVisible(true);
isParametricDistributionParameter = true;
Expand Down Expand Up @@ -259,39 +255,54 @@ public void toggleEstimate() {
}

String id = parameter2.getID();


if (id.startsWith("RealParameter")) {
ScalarDistribution<?,?> parent = null;
// generalises the legacy "RealParameter" prefix check: any parameter that
// hasn't already been given a "parameter.<context>" id (spec params default
// to e.g. "RealScalarParam.N", not "RealParameter.N") needs one, so that
// $(n) substitution below and BeautiDoc's general "parameter.*" sync scan
// (see BeautiDoc.applyBeautiRules) can find it
if (id == null || !id.startsWith("parameter.")) {
ScalarDistribution<?,?> parent = null;
for (Object beastObject2 : parameter2.getOutputs()) {
if (beastObject2 instanceof ScalarDistribution<?,?>) {
parent = (ScalarDistribution<?,?>) beastObject2;
break;
}
}
Distribution grandparent = null;
for (Object beastObject2 : parent.getOutputs()) {
if (beastObject2 instanceof Distribution) {
grandparent = (Distribution) beastObject2;
if (beastObject2 instanceof ScalarDistribution<?,?> sd && sd.paramInput.get() != parameter2) {
parent = sd;
break;
}
}
id = "parameter.hyper" + parent.getClass().getSimpleName() + "-" +
m_input.getName() + "-" + grandparent.getID();
if (parent == null) {
Log.err.println("Could not find the distribution owning " + parameter2.getID() + "; not adding a hyperprior");
m_isEstimatedBox.setSelected(false);
parameter2.isEstimatedInput.setValue(false, parameter2);
return;
}
// parent's own id is already partition-specific in the spec framework
// (e.g. "ClockPrior.c:1stpos", connected directly into 'prior') -- there
// is no separate wrapping Prior object to look up any more
id = "parameter.hyper" + parent.getClass().getSimpleName() + "-" +
m_input.getName() + "-" + parent.getID();
doc.pluginmap.remove(parameter2.getID());
parameter2.setID(id);
doc.addPlugin(parameter2);
}



PartitionContext context = new PartitionContext(id.substring("parameter.".length()));
Log.warning.println(context + " " + id);
doc.beautiConfig.hyperPriorTemplate.createSubNet(context, true);

// the default hyperprior distribution must be domain-compatible with the
// parameter it is applied to, or it shows up as a mismatched entry outside
// the compatible-domain dropdown list in the Priors panel (see
// ScalarDistributionInputEditor.isCompatible)
boolean isPositive = parameter2 instanceof RealScalarParam<?> realParam &&
(realParam.getDomain() instanceof PositiveReal || realParam.getDomain() instanceof NonNegativeReal);
BeautiSubTemplate hyperTemplate = isPositive ?
doc.beautiConfig.hyperPriorSpecPositiveTemplate :
doc.beautiConfig.hyperPriorSpecTemplate;
hyperTemplate.createSubNet(context, true);
}
hardSync();
refreshPanel();
} catch (Exception ex) {
Log.err.println("ParameterInputEditor " + ex.getMessage());
Log.err.println("ScalarInputEditor " + ex.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,6 @@
]]>
</subtemplate>

<!-- OneOnX -->
<subtemplate id='1/X' class='beast.base.inference.distribution.OneOnX' mainid='[top]'>
<![CDATA[
<distr spec="beast.base.inference.distribution.OneOnX"/>
]]>
</subtemplate>

<!-- lognormal -->
<subtemplate id='LogNormal' class='beast.base.spec.inference.distribution.LogNormal' mainid='[top]'
suppressInputs='beast.base.spec.inference.distribution.LogNormal.param'
Expand Down Expand Up @@ -188,6 +181,21 @@
]]>
</subtemplate>

<!-- OneOnX (improper 1/x prior) is removed from the BEAUti distribution list; -->
<!-- use LogUniform as a replacement to a PositiveReal-domain param bounded on [lower, upper]. -->
<subtemplate id='LogUniform' class='beast.base.spec.inference.distribution.LogUniform' mainid='[top]'
suppressInputs='beast.base.spec.inference.distribution.LogUniform.param'
hmc='
LogUniform/lower/=ParametricDistributions/LogUniform/lower/,
LogUniform/upper/=ParametricDistributions/LogUniform/upper/'>
<![CDATA[
<distr spec="beast.base.spec.inference.distribution.LogUniform">
<lower spec="beast.base.spec.inference.parameter.RealScalarParam" domain="PositiveReal" value="1.0E-8" estimate="false"/>
<upper spec="beast.base.spec.inference.parameter.RealScalarParam" domain="PositiveReal" value="1.0E8" estimate="false"/>
</distr>
]]>
</subtemplate>

<!-- Poisson -->
<subtemplate id='Poisson' class='beast.base.spec.inference.distribution.Poisson' mainid='[top]'
suppressInputs='beast.base.spec.inference.distribution.Poisson.param'
Expand All @@ -202,11 +210,13 @@

<!-- Dirichlet -->
<subtemplate id='Dirichlet' class='beast.base.spec.inference.distribution.Dirichlet' mainid='[top]'
suppressInputs='beast.base.spec.inference.distribution.Dirichlet.param'
hmc='
Dirichlet/alpha/=ParametricDistributions/Dirichlet/mean/,
Dirichlet/offset/=ParametricDistributions/Dirichlet/offset/'>
<![CDATA[
<distr spec="beast.base.spec.inference.distribution.Dirichlet">
<param spec="beast.base.spec.inference.parameter.SimplexParam" dimension="1" value="1.0" estimate="false"/>
<alpha spec="beast.base.spec.inference.parameter.RealVectorParam" domain="PositiveReal" estimate="false">1.0</alpha>
</distr>
]]>
Expand Down
12 changes: 12 additions & 0 deletions beast-fx/src/main/resources/beast.fx/fxtemplates/Standard.xml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@
beast.base.spec.inference.parameter.IntScalarParam.domain,
beast.base.spec.inference.parameter.RealVectorParam.domain,
beast.base.spec.inference.parameter.IntVectorParam.domain,
beast.base.spec.inference.parameter.RealVectorParam.shape,
beast.base.spec.inference.parameter.RealVectorParam.keys,
beast.base.spec.inference.parameter.IntVectorParam.shape,
beast.base.spec.inference.parameter.IntVectorParam.keys,
beast.base.spec.inference.parameter.BoolVectorParam.shape,
beast.base.spec.inference.parameter.BoolVectorParam.keys,
beast.base.spec.inference.parameter.SimplexParam.domain,
beast.base.spec.inference.parameter.SimplexParam.shape,
beast.base.spec.inference.parameter.SimplexParam.keys,
beast.base.spec.inference.parameter.IntSimplexParam.domain,
beast.base.spec.inference.parameter.IntSimplexParam.shape,
beast.base.spec.inference.parameter.IntSimplexParam.keys,
beast.base.spec.inference.distribution.IID.param,
beast.base.spec.inference.distribution.MarkovChainDistribution.param
'
Expand Down
Loading
Loading