From 96eb547f26c8d979cedc18b129aa9e5d111674bb Mon Sep 17 00:00:00 2001 From: walterxie Date: Thu, 25 Jun 2026 16:17:50 +1200 Subject: [PATCH 1/3] add VectorInputEditor, and handle vector params in TensorDistributionInputEditor #115 --- .../TensorDistributionInputEditor.java | 42 ++- .../inputeditor/spec/VectorInputEditor.java | 255 ++++++++++++++++++ beast-fx/src/main/java/module-info.java | 1 + 3 files changed, 273 insertions(+), 25 deletions(-) create mode 100644 beast-fx/src/main/java/beastfx/app/inputeditor/spec/VectorInputEditor.java diff --git a/beast-fx/src/main/java/beastfx/app/inputeditor/TensorDistributionInputEditor.java b/beast-fx/src/main/java/beastfx/app/inputeditor/TensorDistributionInputEditor.java index c934f633..4acb3509 100644 --- a/beast-fx/src/main/java/beastfx/app/inputeditor/TensorDistributionInputEditor.java +++ b/beast-fx/src/main/java/beastfx/app/inputeditor/TensorDistributionInputEditor.java @@ -1,37 +1,15 @@ package beastfx.app.inputeditor; - - - - - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import beast.base.core.BEASTInterface; import beast.base.core.Input; import beast.base.inference.Distribution; import beast.base.parser.PartitionContext; -import beast.base.spec.domain.Int; -import beast.base.spec.domain.NonNegativeInt; -import beast.base.spec.domain.NonNegativeReal; -import beast.base.spec.domain.PositiveInt; -import beast.base.spec.domain.PositiveReal; -import beast.base.spec.domain.Real; +import beast.base.spec.domain.*; import beast.base.spec.inference.distribution.ScalarDistribution; import beast.base.spec.inference.distribution.TensorDistribution; -import beast.base.spec.inference.parameter.BoolScalarParam; -import beast.base.spec.inference.parameter.IntScalarParam; -import beast.base.spec.inference.parameter.IntVectorParam; -import beast.base.spec.inference.parameter.RealScalarParam; -import beast.base.spec.inference.parameter.RealVectorParam; -import beast.base.spec.type.IntScalar; -import beast.base.spec.type.IntVector; -import beast.base.spec.type.RealScalar; -import beast.base.spec.type.RealVector; -import beast.base.spec.type.Scalar; +import beast.base.spec.inference.parameter.*; +import beast.base.spec.type.*; import beastfx.app.util.FXUtils; import javafx.scene.Node; import javafx.scene.control.Button; @@ -42,6 +20,10 @@ import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + public class TensorDistributionInputEditor extends BEASTObjectInputEditor implements HasExpandBox { public TensorDistributionInputEditor() { @@ -420,6 +402,16 @@ private String getParameters() { b.append(','); b.append(p.getInput("value").get().toString().trim()); } + } else if (o != null && o instanceof RealVectorParam || o instanceof IntVectorParam || o instanceof BoolVectorParam) { + // vector hyperparameters (e.g. alpha of Dirichlet) + BEASTInterface p = (BEASTInterface) o; + if (b == null) { + b = new StringBuilder(); + b.append(p.getInput("value").get().toString().replaceAll("[\\[\\] ]", "").trim()); + } else { + b.append(','); + b.append(p.getInput("value").get().toString().replaceAll("[\\[\\] ]", "").trim()); + } } else if (o != null && o instanceof Double && !input.getName().equals("offset")) { Double p = (Double) o; if (b == null) { diff --git a/beast-fx/src/main/java/beastfx/app/inputeditor/spec/VectorInputEditor.java b/beast-fx/src/main/java/beastfx/app/inputeditor/spec/VectorInputEditor.java new file mode 100644 index 00000000..7ac3473a --- /dev/null +++ b/beast-fx/src/main/java/beastfx/app/inputeditor/spec/VectorInputEditor.java @@ -0,0 +1,255 @@ +package beastfx.app.inputeditor.spec; + +import beast.base.core.BEASTInterface; +import beast.base.core.Input; +import beast.base.inference.Operator; +import beast.base.inference.StateNode; +import beast.base.spec.inference.distribution.TensorDistribution; +import beast.base.spec.inference.parameter.BoolVectorParam; +import beast.base.spec.inference.parameter.IntVectorParam; +import beast.base.spec.inference.parameter.RealVectorParam; +import beast.base.spec.type.Vector; +import beastfx.app.inputeditor.BEASTObjectInputEditor; +import beastfx.app.inputeditor.BEASTObjectPanel; +import beastfx.app.inputeditor.BeautiDoc; +import beastfx.app.util.FXUtils; +import javafx.geometry.Insets; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; + +import java.util.List; + +/** + * InputEditor for spec vector parameters: RealVectorParam, IntVectorParam, + * BoolVectorParam (and their subclasses, e.g. SimplexParam). + * + * Displays all element values in a single space-separated text field, an + * isEstimated checkbox, and an edit button for accessing dimension / domain / + * key settings via BEASTObjectDialog. + * + * Handles IntVector and BoolVector via the same text-field pattern, which is + * consistent with how ParameterInputEditor handles the legacy Parameter.Base. + */ +public class VectorInputEditor extends BEASTObjectInputEditor { + + public CheckBox m_isEstimatedBox; + + public VectorInputEditor() { + super(); + } + + public VectorInputEditor(BeautiDoc doc) { + super(doc); + } + + @Override + public Class type() { + return Vector.class; + } + + /** + * Register all three concrete vector-param base classes. + * Subclasses (e.g. SimplexParam extends RealVectorParam) are found + * automatically through InputEditorFactory's superclass walk. + */ + @Override + public Class[] types() { + return new Class[] { + RealVectorParam.class, + IntVectorParam.class, + BoolVectorParam.class, + }; + } + + @Override + public void init(Input input, BEASTInterface beastObject, int itemNr, + ExpandOption isExpandOption, boolean addButtons) { + if ("param".equals(input.getName()) && beastObject instanceof TensorDistribution) { + // TensorDistributionInputEditor shows paramInput via the range button; suppress here + pane = FXUtils.newHBox(); + setVisible(false); + setManaged(false); + return; + } + super.init(input, beastObject, itemNr, isExpandOption, addButtons); + m_beastObject = beastObject; + pane.setPadding(new Insets(5)); + } + + // --- value helpers --------------------------------------------------- + + private Object resolveParam() { + if (itemNr < 0) { + return m_input.get(); + } + return ((List) m_input.get()).get(itemNr); + } + + /** Space-separated string of all element values. */ + private String valuesToString(Object param) { + StringBuilder sb = new StringBuilder(); + if (param instanceof RealVectorParam rvp) { + for (double v : rvp.getValues()) { + if (sb.length() > 0) sb.append(' '); + sb.append(v); + } + } else if (param instanceof IntVectorParam ivp) { + for (int v : ivp.getValues()) { + if (sb.length() > 0) sb.append(' '); + sb.append(v); + } + } else if (param instanceof BoolVectorParam bvp) { + for (boolean v : bvp.getValues()) { + if (sb.length() > 0) sb.append(' '); + sb.append(v); + } + } + return sb.toString(); + } + + // --- InputEditor.Base overrides ------------------------------------- + + @Override + protected void setUpEntry() { + super.setUpEntry(); + // wider field: vectors can have many elements + m_entry.setPrefWidth(300); + m_entry.setMaxWidth(500); + } + + @Override + protected void initEntry() { + Object param = resolveParam(); + if (param == null) return; + m_entry.setText(valuesToString(param)); + } + + @Override + protected void processEntry() { + try { + Object param = resolveParam(); + String text = m_entry.getText().trim(); + + if (param instanceof RealVectorParam rvp) { + String oldValue = valuesToString(rvp); + int oldDim = rvp.size(); + rvp.valuesInput.setValue(text, rvp); + rvp.initAndValidate(); + if (rvp.size() != oldDim) { + rvp.setDimension(oldDim); + rvp.valuesInput.setValue(oldValue, rvp); + rvp.initAndValidate(); + throw new IllegalArgumentException("Entry caused change in dimension"); + } + } else if (param instanceof IntVectorParam ivp) { + String oldValue = valuesToString(ivp); + int oldDim = ivp.size(); + ivp.valuesInput.setValue(text, ivp); + ivp.initAndValidate(); + if (ivp.size() != oldDim) { + ivp.setDimension(oldDim); + ivp.valuesInput.setValue(oldValue, ivp); + ivp.initAndValidate(); + throw new IllegalArgumentException("Entry caused change in dimension"); + } + } else if (param instanceof BoolVectorParam bvp) { + bvp.valuesInput.setValue(text, bvp); + bvp.initAndValidate(); + } + + validateInput(); + } catch (Exception ex) { + if (m_validateLabel != null) { + m_validateLabel.setVisible(true); + m_validateLabel.setTooltip(new Tooltip( + "Parsing error: " + ex.getMessage() + + ". Value was left at " + resolveParam() + ".")); + m_validateLabel.setColor("orange"); + } + repaint(); + } + } + + /** + * Replaces the generic BEASTObject combobox with a text field showing + * all element values, an isEstimated checkbox, and an edit button. + */ + @Override + protected void addComboBox(Pane box, Input input, BEASTInterface beastObject) { + Object parameter = (itemNr >= 0) + ? ((List) input.get()).get(itemNr) + : input.get(); + + if (parameter == null) { + super.addComboBox(box, input, beastObject); + return; + } + + HBox paramBox = FXUtils.newHBox(); + + setUpEntry(); + paramBox.getChildren().add(m_entry); + FXUtils.createHMCButton(paramBox, m_beastObject, m_input); + + if (m_bAddButtons && BEASTObjectPanel.countInputs(parameter, doc) > 0) { + paramBox.getChildren().add(createEditButton(input)); + } + + if (parameter instanceof StateNode sn) { + m_isEstimatedBox = new CheckBox( + doc.beautiConfig.getInputLabel( + (BEASTInterface) parameter, sn.isEstimatedInput.getName())); + m_isEstimatedBox.setId(input.getName() + ".isEstimated"); + m_isEstimatedBox.setMaxWidth(Double.POSITIVE_INFINITY); + box.setMaxWidth(Double.POSITIVE_INFINITY); + m_isEstimatedBox.setSelected(sn.isEstimatedInput.get()); + m_isEstimatedBox.setTooltip( + new Tooltip("Estimate value of this parameter in the MCMC chain")); + m_isEstimatedBox.setVisible(doc.isExpertMode()); + + for (Object output : ((BEASTInterface) parameter).getOutputs()) { + if (output instanceof Operator) { + m_isEstimatedBox.setVisible(true); + break; + } + } + + m_isEstimatedBox.setOnAction(e -> { + try { + sn.isEstimatedInput.setValue(m_isEstimatedBox.isSelected(), sn); + hardSync(); + refreshPanel(); + } catch (Exception ex) { + // ignore + } + }); + + paramBox.getChildren().add(m_isEstimatedBox); + } + + box.getChildren().add(paramBox); + } + + @Override + protected void addValidationLabel() { + super.addValidationLabel(); + // hide the edit button when the isEstimated checkbox is hidden + if (m_editBEASTObjectButton != null && m_isEstimatedBox != null) { + m_editBEASTObjectButton.setVisible(m_isEstimatedBox.isVisible()); + } + } + + @Override + protected void refresh() { + Object param = resolveParam(); + if (param != null && m_entry != null) { + m_entry.setText(valuesToString(param)); + } + if (m_isEstimatedBox != null && param instanceof StateNode sn) { + m_isEstimatedBox.setSelected(sn.isEstimatedInput.get()); + } + repaint(); + } +} diff --git a/beast-fx/src/main/java/module-info.java b/beast-fx/src/main/java/module-info.java index 4943921c..3f93d324 100644 --- a/beast-fx/src/main/java/module-info.java +++ b/beast-fx/src/main/java/module-info.java @@ -104,6 +104,7 @@ beastfx.app.beauti.TreeDistributionInputEditor, beastfx.app.inputeditor.spec.SiteModelInputEditor, beastfx.app.inputeditor.spec.ScalarInputEditor, + beastfx.app.inputeditor.spec.VectorInputEditor, beastfx.app.inputeditor.ScalarDistributionInputEditor, beastfx.app.inputeditor.TensorDistributionInputEditor, beastfx.app.inputeditor.IIDInputEditor; From a09edbd2d151420f47a3ccb402b2bcd37a7ac340 Mon Sep 17 00:00:00 2001 From: walterxie Date: Fri, 26 Jun 2026 10:11:55 +1200 Subject: [PATCH 2/3] [] are already removed in the end #115 --- .../app/inputeditor/TensorDistributionInputEditor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beast-fx/src/main/java/beastfx/app/inputeditor/TensorDistributionInputEditor.java b/beast-fx/src/main/java/beastfx/app/inputeditor/TensorDistributionInputEditor.java index 4acb3509..e48fcd12 100644 --- a/beast-fx/src/main/java/beastfx/app/inputeditor/TensorDistributionInputEditor.java +++ b/beast-fx/src/main/java/beastfx/app/inputeditor/TensorDistributionInputEditor.java @@ -407,10 +407,10 @@ private String getParameters() { BEASTInterface p = (BEASTInterface) o; if (b == null) { b = new StringBuilder(); - b.append(p.getInput("value").get().toString().replaceAll("[\\[\\] ]", "").trim()); + b.append(p.getInput("value").get().toString().trim()); } else { b.append(','); - b.append(p.getInput("value").get().toString().replaceAll("[\\[\\] ]", "").trim()); + b.append(p.getInput("value").get().toString().trim()); } } else if (o != null && o instanceof Double && !input.getName().equals("offset")) { Double p = (Double) o; From e110d38dee64f36090bd236a4fba9e79ee7f2ab1 Mon Sep 17 00:00:00 2001 From: walterxie Date: Fri, 26 Jun 2026 11:49:25 +1200 Subject: [PATCH 3/3] fix the 3rd bug, and review any missing updates in Standard.xml #115 --- .../main/resources/beast.fx/fxtemplates/Standard.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/beast-fx/src/main/resources/beast.fx/fxtemplates/Standard.xml b/beast-fx/src/main/resources/beast.fx/fxtemplates/Standard.xml index a1e06ace..1ec479ae 100644 --- a/beast-fx/src/main/resources/beast.fx/fxtemplates/Standard.xml +++ b/beast-fx/src/main/resources/beast.fx/fxtemplates/Standard.xml @@ -290,8 +290,8 @@ ClusterTree/clusterType/, - - + + @@ -473,7 +473,7 @@ ClusterTree/clusterType/, - + @@ -486,9 +486,9 @@ ClusterTree/clusterType/, - + + ]]>