Skip to content

Commit 5ec7815

Browse files
authored
Merge pull request #116 from CompEvol/beautipanel
add VectorInputEditor, and handle vector params in TensorDistribution…
2 parents 2773abc + e110d38 commit 5ec7815

4 files changed

Lines changed: 278 additions & 30 deletions

File tree

beast-fx/src/main/java/beastfx/app/inputeditor/TensorDistributionInputEditor.java

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,15 @@
11
package beastfx.app.inputeditor;
22

33

4-
5-
6-
7-
8-
9-
import java.util.ArrayList;
10-
import java.util.Arrays;
11-
import java.util.List;
12-
134
import beast.base.core.BEASTInterface;
145
import beast.base.core.Input;
156
import beast.base.inference.Distribution;
167
import beast.base.parser.PartitionContext;
17-
import beast.base.spec.domain.Int;
18-
import beast.base.spec.domain.NonNegativeInt;
19-
import beast.base.spec.domain.NonNegativeReal;
20-
import beast.base.spec.domain.PositiveInt;
21-
import beast.base.spec.domain.PositiveReal;
22-
import beast.base.spec.domain.Real;
8+
import beast.base.spec.domain.*;
239
import beast.base.spec.inference.distribution.ScalarDistribution;
2410
import beast.base.spec.inference.distribution.TensorDistribution;
25-
import beast.base.spec.inference.parameter.BoolScalarParam;
26-
import beast.base.spec.inference.parameter.IntScalarParam;
27-
import beast.base.spec.inference.parameter.IntVectorParam;
28-
import beast.base.spec.inference.parameter.RealScalarParam;
29-
import beast.base.spec.inference.parameter.RealVectorParam;
30-
import beast.base.spec.type.IntScalar;
31-
import beast.base.spec.type.IntVector;
32-
import beast.base.spec.type.RealScalar;
33-
import beast.base.spec.type.RealVector;
34-
import beast.base.spec.type.Scalar;
11+
import beast.base.spec.inference.parameter.*;
12+
import beast.base.spec.type.*;
3513
import beastfx.app.util.FXUtils;
3614
import javafx.scene.Node;
3715
import javafx.scene.control.Button;
@@ -42,6 +20,10 @@
4220
import javafx.scene.layout.Pane;
4321
import javafx.scene.layout.VBox;
4422

23+
import java.util.ArrayList;
24+
import java.util.Arrays;
25+
import java.util.List;
26+
4527
public class TensorDistributionInputEditor extends BEASTObjectInputEditor implements HasExpandBox {
4628

4729
public TensorDistributionInputEditor() {
@@ -420,6 +402,16 @@ private String getParameters() {
420402
b.append(',');
421403
b.append(p.getInput("value").get().toString().trim());
422404
}
405+
} else if (o != null && o instanceof RealVectorParam<?> || o instanceof IntVectorParam<?> || o instanceof BoolVectorParam) {
406+
// vector hyperparameters (e.g. alpha of Dirichlet)
407+
BEASTInterface p = (BEASTInterface) o;
408+
if (b == null) {
409+
b = new StringBuilder();
410+
b.append(p.getInput("value").get().toString().trim());
411+
} else {
412+
b.append(',');
413+
b.append(p.getInput("value").get().toString().trim());
414+
}
423415
} else if (o != null && o instanceof Double && !input.getName().equals("offset")) {
424416
Double p = (Double) o;
425417
if (b == null) {
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
package beastfx.app.inputeditor.spec;
2+
3+
import beast.base.core.BEASTInterface;
4+
import beast.base.core.Input;
5+
import beast.base.inference.Operator;
6+
import beast.base.inference.StateNode;
7+
import beast.base.spec.inference.distribution.TensorDistribution;
8+
import beast.base.spec.inference.parameter.BoolVectorParam;
9+
import beast.base.spec.inference.parameter.IntVectorParam;
10+
import beast.base.spec.inference.parameter.RealVectorParam;
11+
import beast.base.spec.type.Vector;
12+
import beastfx.app.inputeditor.BEASTObjectInputEditor;
13+
import beastfx.app.inputeditor.BEASTObjectPanel;
14+
import beastfx.app.inputeditor.BeautiDoc;
15+
import beastfx.app.util.FXUtils;
16+
import javafx.geometry.Insets;
17+
import javafx.scene.control.CheckBox;
18+
import javafx.scene.control.Tooltip;
19+
import javafx.scene.layout.HBox;
20+
import javafx.scene.layout.Pane;
21+
22+
import java.util.List;
23+
24+
/**
25+
* InputEditor for spec vector parameters: RealVectorParam, IntVectorParam,
26+
* BoolVectorParam (and their subclasses, e.g. SimplexParam).
27+
*
28+
* Displays all element values in a single space-separated text field, an
29+
* isEstimated checkbox, and an edit button for accessing dimension / domain /
30+
* key settings via BEASTObjectDialog.
31+
*
32+
* Handles IntVector and BoolVector via the same text-field pattern, which is
33+
* consistent with how ParameterInputEditor handles the legacy Parameter.Base.
34+
*/
35+
public class VectorInputEditor extends BEASTObjectInputEditor {
36+
37+
public CheckBox m_isEstimatedBox;
38+
39+
public VectorInputEditor() {
40+
super();
41+
}
42+
43+
public VectorInputEditor(BeautiDoc doc) {
44+
super(doc);
45+
}
46+
47+
@Override
48+
public Class<?> type() {
49+
return Vector.class;
50+
}
51+
52+
/**
53+
* Register all three concrete vector-param base classes.
54+
* Subclasses (e.g. SimplexParam extends RealVectorParam) are found
55+
* automatically through InputEditorFactory's superclass walk.
56+
*/
57+
@Override
58+
public Class<?>[] types() {
59+
return new Class<?>[] {
60+
RealVectorParam.class,
61+
IntVectorParam.class,
62+
BoolVectorParam.class,
63+
};
64+
}
65+
66+
@Override
67+
public void init(Input<?> input, BEASTInterface beastObject, int itemNr,
68+
ExpandOption isExpandOption, boolean addButtons) {
69+
if ("param".equals(input.getName()) && beastObject instanceof TensorDistribution) {
70+
// TensorDistributionInputEditor shows paramInput via the range button; suppress here
71+
pane = FXUtils.newHBox();
72+
setVisible(false);
73+
setManaged(false);
74+
return;
75+
}
76+
super.init(input, beastObject, itemNr, isExpandOption, addButtons);
77+
m_beastObject = beastObject;
78+
pane.setPadding(new Insets(5));
79+
}
80+
81+
// --- value helpers ---------------------------------------------------
82+
83+
private Object resolveParam() {
84+
if (itemNr < 0) {
85+
return m_input.get();
86+
}
87+
return ((List<?>) m_input.get()).get(itemNr);
88+
}
89+
90+
/** Space-separated string of all element values. */
91+
private String valuesToString(Object param) {
92+
StringBuilder sb = new StringBuilder();
93+
if (param instanceof RealVectorParam<?> rvp) {
94+
for (double v : rvp.getValues()) {
95+
if (sb.length() > 0) sb.append(' ');
96+
sb.append(v);
97+
}
98+
} else if (param instanceof IntVectorParam<?> ivp) {
99+
for (int v : ivp.getValues()) {
100+
if (sb.length() > 0) sb.append(' ');
101+
sb.append(v);
102+
}
103+
} else if (param instanceof BoolVectorParam bvp) {
104+
for (boolean v : bvp.getValues()) {
105+
if (sb.length() > 0) sb.append(' ');
106+
sb.append(v);
107+
}
108+
}
109+
return sb.toString();
110+
}
111+
112+
// --- InputEditor.Base overrides -------------------------------------
113+
114+
@Override
115+
protected void setUpEntry() {
116+
super.setUpEntry();
117+
// wider field: vectors can have many elements
118+
m_entry.setPrefWidth(300);
119+
m_entry.setMaxWidth(500);
120+
}
121+
122+
@Override
123+
protected void initEntry() {
124+
Object param = resolveParam();
125+
if (param == null) return;
126+
m_entry.setText(valuesToString(param));
127+
}
128+
129+
@Override
130+
protected void processEntry() {
131+
try {
132+
Object param = resolveParam();
133+
String text = m_entry.getText().trim();
134+
135+
if (param instanceof RealVectorParam<?> rvp) {
136+
String oldValue = valuesToString(rvp);
137+
int oldDim = rvp.size();
138+
rvp.valuesInput.setValue(text, rvp);
139+
rvp.initAndValidate();
140+
if (rvp.size() != oldDim) {
141+
rvp.setDimension(oldDim);
142+
rvp.valuesInput.setValue(oldValue, rvp);
143+
rvp.initAndValidate();
144+
throw new IllegalArgumentException("Entry caused change in dimension");
145+
}
146+
} else if (param instanceof IntVectorParam<?> ivp) {
147+
String oldValue = valuesToString(ivp);
148+
int oldDim = ivp.size();
149+
ivp.valuesInput.setValue(text, ivp);
150+
ivp.initAndValidate();
151+
if (ivp.size() != oldDim) {
152+
ivp.setDimension(oldDim);
153+
ivp.valuesInput.setValue(oldValue, ivp);
154+
ivp.initAndValidate();
155+
throw new IllegalArgumentException("Entry caused change in dimension");
156+
}
157+
} else if (param instanceof BoolVectorParam bvp) {
158+
bvp.valuesInput.setValue(text, bvp);
159+
bvp.initAndValidate();
160+
}
161+
162+
validateInput();
163+
} catch (Exception ex) {
164+
if (m_validateLabel != null) {
165+
m_validateLabel.setVisible(true);
166+
m_validateLabel.setTooltip(new Tooltip(
167+
"Parsing error: " + ex.getMessage() +
168+
". Value was left at " + resolveParam() + "."));
169+
m_validateLabel.setColor("orange");
170+
}
171+
repaint();
172+
}
173+
}
174+
175+
/**
176+
* Replaces the generic BEASTObject combobox with a text field showing
177+
* all element values, an isEstimated checkbox, and an edit button.
178+
*/
179+
@Override
180+
protected void addComboBox(Pane box, Input<?> input, BEASTInterface beastObject) {
181+
Object parameter = (itemNr >= 0)
182+
? ((List<?>) input.get()).get(itemNr)
183+
: input.get();
184+
185+
if (parameter == null) {
186+
super.addComboBox(box, input, beastObject);
187+
return;
188+
}
189+
190+
HBox paramBox = FXUtils.newHBox();
191+
192+
setUpEntry();
193+
paramBox.getChildren().add(m_entry);
194+
FXUtils.createHMCButton(paramBox, m_beastObject, m_input);
195+
196+
if (m_bAddButtons && BEASTObjectPanel.countInputs(parameter, doc) > 0) {
197+
paramBox.getChildren().add(createEditButton(input));
198+
}
199+
200+
if (parameter instanceof StateNode sn) {
201+
m_isEstimatedBox = new CheckBox(
202+
doc.beautiConfig.getInputLabel(
203+
(BEASTInterface) parameter, sn.isEstimatedInput.getName()));
204+
m_isEstimatedBox.setId(input.getName() + ".isEstimated");
205+
m_isEstimatedBox.setMaxWidth(Double.POSITIVE_INFINITY);
206+
box.setMaxWidth(Double.POSITIVE_INFINITY);
207+
m_isEstimatedBox.setSelected(sn.isEstimatedInput.get());
208+
m_isEstimatedBox.setTooltip(
209+
new Tooltip("Estimate value of this parameter in the MCMC chain"));
210+
m_isEstimatedBox.setVisible(doc.isExpertMode());
211+
212+
for (Object output : ((BEASTInterface) parameter).getOutputs()) {
213+
if (output instanceof Operator) {
214+
m_isEstimatedBox.setVisible(true);
215+
break;
216+
}
217+
}
218+
219+
m_isEstimatedBox.setOnAction(e -> {
220+
try {
221+
sn.isEstimatedInput.setValue(m_isEstimatedBox.isSelected(), sn);
222+
hardSync();
223+
refreshPanel();
224+
} catch (Exception ex) {
225+
// ignore
226+
}
227+
});
228+
229+
paramBox.getChildren().add(m_isEstimatedBox);
230+
}
231+
232+
box.getChildren().add(paramBox);
233+
}
234+
235+
@Override
236+
protected void addValidationLabel() {
237+
super.addValidationLabel();
238+
// hide the edit button when the isEstimated checkbox is hidden
239+
if (m_editBEASTObjectButton != null && m_isEstimatedBox != null) {
240+
m_editBEASTObjectButton.setVisible(m_isEstimatedBox.isVisible());
241+
}
242+
}
243+
244+
@Override
245+
protected void refresh() {
246+
Object param = resolveParam();
247+
if (param != null && m_entry != null) {
248+
m_entry.setText(valuesToString(param));
249+
}
250+
if (m_isEstimatedBox != null && param instanceof StateNode sn) {
251+
m_isEstimatedBox.setSelected(sn.isEstimatedInput.get());
252+
}
253+
repaint();
254+
}
255+
}

beast-fx/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
beastfx.app.beauti.TreeDistributionInputEditor,
105105
beastfx.app.inputeditor.spec.SiteModelInputEditor,
106106
beastfx.app.inputeditor.spec.ScalarInputEditor,
107+
beastfx.app.inputeditor.spec.VectorInputEditor,
107108
beastfx.app.inputeditor.ScalarDistributionInputEditor,
108109
beastfx.app.inputeditor.TensorDistributionInputEditor,
109110
beastfx.app.inputeditor.IIDInputEditor;

beast-fx/src/main/resources/beast.fx/fxtemplates/Standard.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,8 @@ ClusterTree/clusterType/,
290290
<taxonset id='TaxonSet.$(n)' spec='beast.base.evolution.alignment.TaxonSet' alignment='@$(n)' />
291291
<plugin spec='beast.base.evolution.tree.Tree' id='Tree.t:$(n)' taxonset='@TaxonSet.$(n)'/>
292292
293-
<!--plugin spec='beast.base.evolution.tree.coalescent.RandomTree' id='RandomTree.t:$(n)' estimate='false' trait='@datetrait.$(n)' initial='@Tree.t:$(n)'-->
294-
<plugin spec='beast.base.evolution.tree.coalescent.RandomTree' id='RandomTree.t:$(n)' estimate='false' initial='@Tree.t:$(n)'>
293+
<!--plugin spec='beast.base.spec.evolution.tree.coalescent.RandomTree' id='RandomTree.t:$(n)' estimate='false' trait='@datetrait.$(n)' initial='@Tree.t:$(n)'-->
294+
<plugin spec='beast.base.spec.evolution.tree.coalescent.RandomTree' id='RandomTree.t:$(n)' estimate='false' initial='@Tree.t:$(n)'>
295295
<taxa idref='data'/>
296296
<populationModel id='ConstantPopulation0.t:$(n)' spec='ConstantPopulation'>
297297
<popSize id='randomPopSize.t:$(n)' spec='parameter.RealParameter' value='1'/>
@@ -473,7 +473,7 @@ ClusterTree/clusterType/,
473473
<!-- Tree initialisation -->
474474
<!-- Random tree -->
475475

476-
<subtemplate id='RandomTree' class='beast.base.evolution.tree.coalescent.RandomTree' mainid='RandomTree.t:$(n)'>
476+
<subtemplate id='RandomTree' class='beast.base.spec.evolution.tree.coalescent.RandomTree' mainid='RandomTree.t:$(n)'>
477477
<![CDATA[
478478
<tree spec='beast.base.evolution.tree.coalescent.RandomTree' id='RandomTree.t:$(n)' estimate='false' initial="@Tree.t:$(n)">
479479
<taxa idref='data'/>
@@ -486,9 +486,9 @@ ClusterTree/clusterType/,
486486

487487
<!-- Cluster tree (defaults to UPGMA) -->
488488

489-
<subtemplate id='ClusterTree' class='beast.base.evolution.tree.ClusterTree' mainid='ClusterTree.t:$(n)'>
489+
<subtemplate id='ClusterTree' class='beast.base.spec.evolution.tree.ClusterTree' mainid='ClusterTree.t:$(n)'>
490490
<![CDATA[
491-
<tree spec='beast.base.evolution.tree.ClusterTree' id='ClusterTree.t:$(n)' initial="@Tree.t:$(n)" clusterType='upgma' estimate='false' taxa='@$(n)'/>
491+
<tree spec='beast.base.spec.evolution.tree.ClusterTree' id='ClusterTree.t:$(n)' initial="@Tree.t:$(n)" clusterType='upgma' estimate='false' taxa='@$(n)'/>
492492
]]>
493493
</subtemplate>
494494

0 commit comments

Comments
 (0)