From 5f1a4a0e106cf1f3b999fafd4ea7c83d886d2ce8 Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sat, 28 Feb 2026 15:20:07 +1300 Subject: [PATCH 01/15] migrate morph-models to BEAST 3 Maven multi-module with strongly-typed spec classes Two modules: beast-morph-models (core, depends on beast-base) and beast-morph-models-fx (BEAUti integration, depends on beast-fx). - LewisMK extends spec Base, adds getRateMatrix(), renames internal freqValues field to avoid shadowing parent Frequencies - NStatesNoRatesSubstitutionModel extends spec BasicGeneralSubstitutionModel, now abstract (setupRelativeRates inherited as abstract) - Ordinal/NestedOrdinal: clean unused imports - BEAUti template: update namespace, replace ScaleOperator/Uniform/SubtreeSlide with UpDownOperator/BactrianNodeOperator/BactrianSubtreeSlide - Add JPMS module-info.java for both modules - Add LewisMKTest with 8 unit tests --- .gitignore | 3 + beast-morph-models-fx/pom.xml | 41 ++ .../src/main/java/module-info.java | 12 + .../BeautiMorphModelAlignmentProvider.java | 0 .../resources/fxtemplates}/morph-models.xml | 13 +- beast-morph-models/pom.xml | 54 ++ .../src/main/java/module-info.java | 14 + ...nedForParsimonyUninformativeAlignment.java | 0 ...rsimonyUninformativeFilteredAlignment.java | 0 .../evolution/substitutionmodel/LewisMK.java | 79 +- .../NStatesNoRatesSubstitutionModel.java | 15 +- .../substitutionmodel/NestedOrdinal.java | 11 - .../evolution/substitutionmodel/Ordinal.java | 10 - .../substitutionmodel/LewisMKTest.java | 180 +++++ .../src/test/resources/examples/M3982.xml | 274 +++++++ .../test/resources/examples/nonEqualFreqs.xml | 116 +++ .../src/test/resources/examples/penguins.xml | 678 +++++++++++++++++ .../test/resources/examples/penguins_Mkv.xml | 679 ++++++++++++++++++ pom.xml | 100 +++ 19 files changed, 2206 insertions(+), 73 deletions(-) create mode 100644 beast-morph-models-fx/pom.xml create mode 100644 beast-morph-models-fx/src/main/java/module-info.java rename {src => beast-morph-models-fx/src/main/java}/morphmodels/app/beauti/BeautiMorphModelAlignmentProvider.java (100%) rename {fxtemplates => beast-morph-models-fx/src/main/resources/fxtemplates}/morph-models.xml (91%) create mode 100644 beast-morph-models/pom.xml create mode 100644 beast-morph-models/src/main/java/module-info.java rename {src => beast-morph-models/src/main/java}/morphmodels/evolution/alignment/AscertainedForParsimonyUninformativeAlignment.java (100%) rename {src => beast-morph-models/src/main/java}/morphmodels/evolution/alignment/AscertainedForParsimonyUninformativeFilteredAlignment.java (100%) rename {src => beast-morph-models/src/main/java}/morphmodels/evolution/substitutionmodel/LewisMK.java (68%) rename {src => beast-morph-models/src/main/java}/morphmodels/evolution/substitutionmodel/NStatesNoRatesSubstitutionModel.java (64%) rename {src => beast-morph-models/src/main/java}/morphmodels/evolution/substitutionmodel/NestedOrdinal.java (82%) rename {src => beast-morph-models/src/main/java}/morphmodels/evolution/substitutionmodel/Ordinal.java (77%) create mode 100644 beast-morph-models/src/test/java/morphmodels/evolution/substitutionmodel/LewisMKTest.java create mode 100644 beast-morph-models/src/test/resources/examples/M3982.xml create mode 100644 beast-morph-models/src/test/resources/examples/nonEqualFreqs.xml create mode 100644 beast-morph-models/src/test/resources/examples/penguins.xml create mode 100644 beast-morph-models/src/test/resources/examples/penguins_Mkv.xml create mode 100644 pom.xml diff --git a/.gitignore b/.gitignore index c55bec8..19229a9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ /.idea /morph-models.iml /out + +# Maven build output +target/ diff --git a/beast-morph-models-fx/pom.xml b/beast-morph-models-fx/pom.xml new file mode 100644 index 0000000..535fd24 --- /dev/null +++ b/beast-morph-models-fx/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + + beast + morph-models-parent + 1.3.0-SNAPSHOT + + + beast-morph-models-fx + BEAST Morph Models FX + + + + + beast + beast-morph-models + + + beast + beast-base + + + beast + beast-fx + + + beast + beast-pkgmgmt + + + + + org.openjfx + javafx-controls + + + diff --git a/beast-morph-models-fx/src/main/java/module-info.java b/beast-morph-models-fx/src/main/java/module-info.java new file mode 100644 index 0000000..55d4a35 --- /dev/null +++ b/beast-morph-models-fx/src/main/java/module-info.java @@ -0,0 +1,12 @@ +open module beast.morph.models.fx { + requires beast.morph.models; + requires beast.base; + requires beast.pkgmgmt; + requires beast.fx; + requires javafx.controls; + + exports morphmodels.app.beauti; + + provides beast.base.core.BEASTInterface with + morphmodels.app.beauti.BeautiMorphModelAlignmentProvider; +} diff --git a/src/morphmodels/app/beauti/BeautiMorphModelAlignmentProvider.java b/beast-morph-models-fx/src/main/java/morphmodels/app/beauti/BeautiMorphModelAlignmentProvider.java similarity index 100% rename from src/morphmodels/app/beauti/BeautiMorphModelAlignmentProvider.java rename to beast-morph-models-fx/src/main/java/morphmodels/app/beauti/BeautiMorphModelAlignmentProvider.java diff --git a/fxtemplates/morph-models.xml b/beast-morph-models-fx/src/main/resources/fxtemplates/morph-models.xml similarity index 91% rename from fxtemplates/morph-models.xml rename to beast-morph-models-fx/src/main/resources/fxtemplates/morph-models.xml index c5eb999..eff6b9f 100644 --- a/fxtemplates/morph-models.xml +++ b/beast-morph-models-fx/src/main/resources/fxtemplates/morph-models.xml @@ -1,5 +1,5 @@ + namespace='morphmodels.evolution.substitutionmodel:morphmodels.evolution.alignment:morphmodels.app.beauti:beastfx.app.beauti:beastfx.app.inputeditor:beast.pkgmgmt:beast.base.core:beast.base.inference:beast.base.evolution.branchratemodel:beast.base.evolution.speciation:beast.evolution.Tree.t:coalescent:beast.base.util:beast.base.math:beast.evolution.nuc:beast.base.evolution.operator:beast.base.inference.operator:beast.base.spec.evolution.sitemodel:beast.base.evolution.substitutionmodel:beast.base.evolution.likelihood:beast.evolution:beast.base.inference.distribution'> @@ -16,7 +16,6 @@ - @@ -69,10 +68,10 @@ - - - - + + + + @@ -116,7 +115,7 @@ Estimates tip dates for tree t:$(n) - Scales all internal nodes for tree t:$(n) + Scale internal nodes for tree t:$(n) Scales root node for tree t:$(n) Draws new internal node heights uniformally for tree t:$(n) Performs subtree slide rearrangement of tree t:$(n) diff --git a/beast-morph-models/pom.xml b/beast-morph-models/pom.xml new file mode 100644 index 0000000..76690a3 --- /dev/null +++ b/beast-morph-models/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + + beast + morph-models-parent + 1.3.0-SNAPSHOT + + + beast-morph-models + BEAST Morph Models + + + + + beast + beast-base + + + beast + beast-pkgmgmt + + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${project.build.testOutputDirectory} + + --add-reads beast.morph.models=ALL-UNNAMED + --add-reads beast.base=ALL-UNNAMED + --add-reads beast.pkgmgmt=ALL-UNNAMED + + + ${project.build.outputDirectory} + + + + + + diff --git a/beast-morph-models/src/main/java/module-info.java b/beast-morph-models/src/main/java/module-info.java new file mode 100644 index 0000000..91fb9ed --- /dev/null +++ b/beast-morph-models/src/main/java/module-info.java @@ -0,0 +1,14 @@ +open module beast.morph.models { + requires beast.base; + requires beast.pkgmgmt; + + exports morphmodels.evolution.substitutionmodel; + exports morphmodels.evolution.alignment; + + provides beast.base.core.BEASTInterface with + morphmodels.evolution.alignment.AscertainedForParsimonyUninformativeAlignment, + morphmodels.evolution.alignment.AscertainedForParsimonyUninformativeFilteredAlignment, + morphmodels.evolution.substitutionmodel.LewisMK, + morphmodels.evolution.substitutionmodel.Ordinal, + morphmodels.evolution.substitutionmodel.NestedOrdinal; +} diff --git a/src/morphmodels/evolution/alignment/AscertainedForParsimonyUninformativeAlignment.java b/beast-morph-models/src/main/java/morphmodels/evolution/alignment/AscertainedForParsimonyUninformativeAlignment.java similarity index 100% rename from src/morphmodels/evolution/alignment/AscertainedForParsimonyUninformativeAlignment.java rename to beast-morph-models/src/main/java/morphmodels/evolution/alignment/AscertainedForParsimonyUninformativeAlignment.java diff --git a/src/morphmodels/evolution/alignment/AscertainedForParsimonyUninformativeFilteredAlignment.java b/beast-morph-models/src/main/java/morphmodels/evolution/alignment/AscertainedForParsimonyUninformativeFilteredAlignment.java similarity index 100% rename from src/morphmodels/evolution/alignment/AscertainedForParsimonyUninformativeFilteredAlignment.java rename to beast-morph-models/src/main/java/morphmodels/evolution/alignment/AscertainedForParsimonyUninformativeFilteredAlignment.java diff --git a/src/morphmodels/evolution/substitutionmodel/LewisMK.java b/beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/LewisMK.java similarity index 68% rename from src/morphmodels/evolution/substitutionmodel/LewisMK.java rename to beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/LewisMK.java index d0cd909..cf3edb3 100644 --- a/src/morphmodels/evolution/substitutionmodel/LewisMK.java +++ b/beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/LewisMK.java @@ -8,8 +8,10 @@ import beast.base.evolution.datatype.Binary; import beast.base.evolution.datatype.DataType; import beast.base.evolution.datatype.StandardData; +import beast.base.evolution.substitutionmodel.EigenDecomposition; import beast.base.evolution.tree.Node; -import beast.base.evolution.substitutionmodel.*; +import beast.base.spec.evolution.substitutionmodel.Base; +import beast.base.spec.evolution.substitutionmodel.Frequencies; import java.util.Arrays; @@ -22,33 +24,22 @@ "A likelihood approach to estimating phylogeny from discrete morphological character data. \n" + "Systematic biology, 50(6), 913-925.", year = 2001, firstAuthorSurname = "Lewis", DOI="10.1080/106351501753462876") -public class LewisMK extends SubstitutionModel.Base { +public class LewisMK extends Base { public Input nrOfStatesInput = new Input("stateNumber", "the number of character states"); public Input dataTypeInput = new Input("datatype", "datatype, used to determine the number of states", Validate.XOR, nrOfStatesInput); - //public Input isMKvInput = new Input<>("isMkv", "whether to use MKv model or just MK", false); boolean hasFreqs; private boolean updateFreqs; public LewisMK() { // this is added to avoid a parsing error inherited from superclass because frequencies are not provided. frequenciesInput.setRule(Validate.OPTIONAL); - try { - // this call will be made twice when constructed from XML - // but this ensures that the object is validly constructed for testing purposes. - // RRB: for testing you should call initAndValidate() independently - // At this stage, the inputs are not valid, hence initAndValidate fails. - // initAndValidate(); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("initAndValidate() call failed when constructing LewisMK()"); - } } double totalSubRate; - double[] frequencies; + double[] freqValues; EigenDecomposition eigenDecomposition; private void setFrequencies() { @@ -57,14 +48,14 @@ private void setFrequencies() { if( frequencies1.getFreqs().length != nrOfStates ) { throw new RuntimeException("number of stationary frequencies does not match number of states."); } - System.arraycopy(frequencies1.getFreqs(), 0, frequencies, 0, nrOfStates); + System.arraycopy(frequencies1.getFreqs(), 0, freqValues, 0, nrOfStates); totalSubRate = 1; for(int k = 0; k < nrOfStates; ++k) { - totalSubRate -= frequencies[k]*frequencies[k]; + totalSubRate -= freqValues[k]*freqValues[k]; } hasFreqs = true; } else { - Arrays.fill(frequencies, (1.0 / nrOfStates)); + Arrays.fill(freqValues, (1.0 / nrOfStates)); hasFreqs = false; } updateFreqs = false; @@ -78,26 +69,19 @@ public void initAndValidate() { nrOfStates = dataTypeInput.get().getStateCount(); } - if (nrOfStates <= 1) { - // this goes here so that nrOfStates/(nrOfStates-1) in getTransitionProbabilities() - // does not throw a division by zero exception + if (nrOfStates <= 1) { throw new IllegalArgumentException("The number of states should be at least 2 but is" + nrOfStates + ".\n" + "This may be due to a site in the alignment having only 1 state, which can be fixed by " + "removing the site from the alignment."); } -// double[] eval = new double[]{0.0, -1.3333333333333333, -1.3333333333333333, -1.3333333333333333}; -// double[] evec = new double[]{1.0, 2.0, 0.0, 0.5, 1.0, -2.0, 0.5, 0.0, 1.0, 2.0, 0.0, -0.5, 1.0, -2.0, -0.5, 0.0}; -// double[] ivec = new double[]{0.25, 0.25, 0.25, 0.25, 0.125, -0.125, 0.125, -0.125, 0.0, 1.0, 0.0, -1.0, 1.0, 0.0, -1.0, 0.0}; -// -// eigenDecomposition = new EigenDecomposition(evec, ivec, eval); - frequencies = new double[nrOfStates]; + freqValues = new double[nrOfStates]; setFrequencies(); } @Override public double[] getFrequencies() { - return frequencies; + return freqValues; } @Override @@ -113,7 +97,7 @@ public void getTransitionProbabilities(Node node, double fStartTime, double fEnd for( int i = 0; i < nrOfStates; ++i ) { final int r = i * nrOfStates; for( int j = 0; j < nrOfStates; ++j ) { - matrix[r + j] = frequencies[j] * e2; + matrix[r + j] = freqValues[j] * e2; } matrix[r + i] += e1; } @@ -130,6 +114,39 @@ public void getTransitionProbabilities(Node node, double fStartTime, double fEnd } } + @Override + public double[] getRateMatrix(Node node) { + if (updateFreqs) { + setFrequencies(); + } + + double[] matrix = new double[nrOfStates * nrOfStates]; + if (hasFreqs) { + for (int i = 0; i < nrOfStates; i++) { + double rowSum = 0; + for (int j = 0; j < nrOfStates; j++) { + if (i != j) { + matrix[i * nrOfStates + j] = freqValues[j] / totalSubRate; + rowSum += matrix[i * nrOfStates + j]; + } + } + matrix[i * nrOfStates + i] = -rowSum; + } + } else { + double offDiag = 1.0 / (nrOfStates - 1); + for (int i = 0; i < nrOfStates; i++) { + for (int j = 0; j < nrOfStates; j++) { + if (i == j) { + matrix[i * nrOfStates + j] = -1.0; + } else { + matrix[i * nrOfStates + j] = offDiag; + } + } + } + } + return matrix; + } + @Override public EigenDecomposition getEigenDecomposition(Node node) { return eigenDecomposition; @@ -137,11 +154,7 @@ public EigenDecomposition getEigenDecomposition(Node node) { @Override public boolean canHandleDataType(DataType dataType) { - if (dataType instanceof StandardData || dataType instanceof Binary) { - return true; - } - return false; - //throw new Exception("Can only handle StandardData and binary data"); + return dataType instanceof StandardData || dataType instanceof Binary; } protected boolean requiresRecalculation() { diff --git a/src/morphmodels/evolution/substitutionmodel/NStatesNoRatesSubstitutionModel.java b/beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/NStatesNoRatesSubstitutionModel.java similarity index 64% rename from src/morphmodels/evolution/substitutionmodel/NStatesNoRatesSubstitutionModel.java rename to beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/NStatesNoRatesSubstitutionModel.java index 13b6c1d..047992f 100644 --- a/src/morphmodels/evolution/substitutionmodel/NStatesNoRatesSubstitutionModel.java +++ b/beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/NStatesNoRatesSubstitutionModel.java @@ -3,27 +3,18 @@ import beast.base.core.Description; import beast.base.core.Input; import beast.base.core.Input.Validate; -import beast.base.evolution.substitutionmodel.*; +import beast.base.spec.evolution.substitutionmodel.BasicGeneralSubstitutionModel; /** * @author Luke Maurits */ -@Description("A simple subclass of GeneralSubstitutionModel which does not require a rates input and does require a number of states input.") -public class NStatesNoRatesSubstitutionModel extends GeneralSubstitutionModel { +@Description("A simple subclass of BasicGeneralSubstitutionModel which does not require a rates input and does require a number of states input.") +public abstract class NStatesNoRatesSubstitutionModel extends BasicGeneralSubstitutionModel { // Number of states input is required public Input nrOfStatesInput = new Input("stateNumber", "the number of character states", Validate.REQUIRED); - public NStatesNoRatesSubstitutionModel() { - // Rates input is *not* required - ratesInput.setRule(Validate.OPTIONAL); - } - @Override - // Minimally changed from parent implementation: - // Derive nrOfStates from the appropriate input, not freqs. - // Ensure frequencies has correct length. - // Do not check dimension of rates parameter. public void initAndValidate() { nrOfStates = nrOfStatesInput.get(); frequencies = frequenciesInput.get(); diff --git a/src/morphmodels/evolution/substitutionmodel/NestedOrdinal.java b/beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/NestedOrdinal.java similarity index 82% rename from src/morphmodels/evolution/substitutionmodel/NestedOrdinal.java rename to beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/NestedOrdinal.java index 186324f..bb58ec5 100644 --- a/src/morphmodels/evolution/substitutionmodel/NestedOrdinal.java +++ b/beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/NestedOrdinal.java @@ -1,18 +1,7 @@ package morphmodels.evolution.substitutionmodel; -import beast.base.core.Citation; import beast.base.core.Description; -import beast.base.core.Input; -import beast.base.core.Input.Validate; -import beast.base.inference.parameter.RealParameter; -import beast.base.evolution.datatype.Binary; -import beast.base.evolution.datatype.DataType; -import beast.base.evolution.datatype.StandardData; -import beast.base.evolution.tree.Node; - -import java.util.Arrays; -import beast.base.evolution.substitutionmodel.*; /** * @author Luke Maurits diff --git a/src/morphmodels/evolution/substitutionmodel/Ordinal.java b/beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/Ordinal.java similarity index 77% rename from src/morphmodels/evolution/substitutionmodel/Ordinal.java rename to beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/Ordinal.java index 91bb586..6d3da92 100644 --- a/src/morphmodels/evolution/substitutionmodel/Ordinal.java +++ b/beast-morph-models/src/main/java/morphmodels/evolution/substitutionmodel/Ordinal.java @@ -1,16 +1,6 @@ package morphmodels.evolution.substitutionmodel; -import beast.base.core.Citation; import beast.base.core.Description; -import beast.base.core.Input; -import beast.base.core.Input.Validate; -import beast.base.evolution.datatype.Binary; -import beast.base.evolution.datatype.DataType; -import beast.base.evolution.datatype.StandardData; -import beast.base.evolution.tree.Node; - -import java.util.Arrays; -import beast.base.evolution.substitutionmodel.*; /** * @author Luke Maurits diff --git a/beast-morph-models/src/test/java/morphmodels/evolution/substitutionmodel/LewisMKTest.java b/beast-morph-models/src/test/java/morphmodels/evolution/substitutionmodel/LewisMKTest.java new file mode 100644 index 0000000..11f3220 --- /dev/null +++ b/beast-morph-models/src/test/java/morphmodels/evolution/substitutionmodel/LewisMKTest.java @@ -0,0 +1,180 @@ +package morphmodels.evolution.substitutionmodel; + +import beast.base.evolution.tree.Node; +import beast.base.spec.evolution.substitutionmodel.Frequencies; +import beast.base.spec.inference.parameter.SimplexParam; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class LewisMKTest { + + @Test + void testLewisMKInitWithStateNumber() { + LewisMK model = new LewisMK(); + model.initByName("stateNumber", 4); + + assertEquals(4, model.getStateCount()); + + double[] freqs = model.getFrequencies(); + assertEquals(4, freqs.length); + for (double f : freqs) { + assertEquals(0.25, f, 1e-10); + } + } + + @Test + void testTransitionProbabilitiesRowsSumToOne() { + LewisMK model = new LewisMK(); + model.initByName("stateNumber", 4); + + double[] matrix = new double[16]; + Node node = new Node(); + model.getTransitionProbabilities(node, 1.0, 0.0, 1.0, matrix); + + for (int i = 0; i < 4; i++) { + double rowSum = 0; + for (int j = 0; j < 4; j++) { + rowSum += matrix[i * 4 + j]; + assertTrue(matrix[i * 4 + j] >= 0, "Transition probability should be non-negative"); + } + assertEquals(1.0, rowSum, 1e-10, "Row " + i + " should sum to 1.0"); + } + } + + @Test + void testTransitionProbabilitiesAtZeroTime() { + LewisMK model = new LewisMK(); + model.initByName("stateNumber", 3); + + double[] matrix = new double[9]; + Node node = new Node(); + // zero branch length => identity matrix + model.getTransitionProbabilities(node, 1.0, 1.0, 1.0, matrix); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (i == j) { + assertEquals(1.0, matrix[i * 3 + j], 1e-10); + } else { + assertEquals(0.0, matrix[i * 3 + j], 1e-10); + } + } + } + } + + @Test + void testWithProvidedFrequencies() { + LewisMK model = new LewisMK(); + Frequencies freqs = new Frequencies(); + freqs.initByName("frequencies", new SimplexParam(new double[]{0.1, 0.2, 0.3, 0.4})); + + model.initByName("stateNumber", 4, "frequencies", freqs); + + double[] modelFreqs = model.getFrequencies(); + assertEquals(0.1, modelFreqs[0], 1e-10); + assertEquals(0.2, modelFreqs[1], 1e-10); + assertEquals(0.3, modelFreqs[2], 1e-10); + assertEquals(0.4, modelFreqs[3], 1e-10); + + // transition probabilities should still sum to 1.0 + double[] matrix = new double[16]; + Node node = new Node(); + model.getTransitionProbabilities(node, 1.0, 0.0, 1.0, matrix); + + for (int i = 0; i < 4; i++) { + double rowSum = 0; + for (int j = 0; j < 4; j++) { + rowSum += matrix[i * 4 + j]; + } + assertEquals(1.0, rowSum, 1e-10, "Row " + i + " should sum to 1.0"); + } + } + + @Test + void testOrdinalRateMatrixIsTridiagonal() { + Ordinal model = new Ordinal(); + Frequencies freqs = new Frequencies(); + freqs.initByName("frequencies", new SimplexParam(new double[]{0.2, 0.2, 0.2, 0.2, 0.2})); + model.initByName("stateNumber", 5, "frequencies", freqs); + + // trigger rate setup + double[] matrix = new double[25]; + Node node = new Node(); + model.getTransitionProbabilities(node, 1.0, 0.0, 1.0, matrix); + + // verify transition probabilities sum to 1.0 + for (int i = 0; i < 5; i++) { + double rowSum = 0; + for (int j = 0; j < 5; j++) { + rowSum += matrix[i * 5 + j]; + } + assertEquals(1.0, rowSum, 1e-10, "Row " + i + " should sum to 1.0"); + } + + // check relative rates are tridiagonal + double[] rates = model.getRelativeRates(); + // relativeRates[0] = rate(0->1) = 1.0 + assertEquals(1.0, rates[0], 1e-10); + // relativeRates[nrOfStates*(nrOfStates-1)-1] = rate(4->3) = 1.0 + assertEquals(1.0, rates[19], 1e-10); + } + + @Test + void testNestedOrdinalRateMatrix() { + NestedOrdinal model = new NestedOrdinal(); + Frequencies freqs = new Frequencies(); + freqs.initByName("frequencies", new SimplexParam(new double[]{0.2, 0.2, 0.2, 0.2, 0.2})); + model.initByName("stateNumber", 5, "frequencies", freqs); + + // trigger rate setup + double[] matrix = new double[25]; + Node node = new Node(); + model.getTransitionProbabilities(node, 1.0, 0.0, 1.0, matrix); + + // verify transition probabilities sum to 1.0 + for (int i = 0; i < 5; i++) { + double rowSum = 0; + for (int j = 0; j < 5; j++) { + rowSum += matrix[i * 5 + j]; + } + assertEquals(1.0, rowSum, 1e-10, "Row " + i + " should sum to 1.0"); + } + + // check relative rates: first row should have all 1s (state 0 can go to any state) + double[] rates = model.getRelativeRates(); + for (int i = 0; i < 4; i++) { + assertEquals(1.0, rates[i], 1e-10, "Rate from state 0 to state " + (i+1) + " should be 1.0"); + } + } + + @Test + void testRateMatrixEqualFrequencies() { + LewisMK model = new LewisMK(); + model.initByName("stateNumber", 4); + + Node node = new Node(); + double[] rateMatrix = model.getRateMatrix(node); + assertEquals(16, rateMatrix.length); + + // all off-diagonal should be 1/(n-1) = 1/3 + // all diagonal should be -1.0 + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + if (i == j) { + assertEquals(-1.0, rateMatrix[i * 4 + j], 1e-10); + } else { + assertEquals(1.0 / 3.0, rateMatrix[i * 4 + j], 1e-10); + } + } + } + } + + @Test + void testRejectsOneState() { + LewisMK model = new LewisMK(); + assertThrows(RuntimeException.class, () -> + model.initByName("stateNumber", 1) + ); + } +} diff --git a/beast-morph-models/src/test/resources/examples/M3982.xml b/beast-morph-models/src/test/resources/examples/M3982.xml new file mode 100644 index 0000000..99f9c1e --- /dev/null +++ b/beast-morph-models/src/test/resources/examples/M3982.xml @@ -0,0 +1,274 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +beast.base.inference.distribution.Uniform +beast.base.inference.distribution.Exponential +beast.base.inference.distribution.LogNormalDistributionModel +beast.base.inference.distribution.Normal +beast.base.inference.distribution.Beta +beast.base.inference.distribution.Gamma +beast.base.inference.distribution.LaplaceDistribution +beast.base.inference.distribution.Prior +beast.base.inference.distribution.InverseGamma +beast.base.inference.distribution.OneOnX + + + + + + + + + + + + + 1.0 + + + + + 1.0 + + + + + + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + 1.0 + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/beast-morph-models/src/test/resources/examples/nonEqualFreqs.xml b/beast-morph-models/src/test/resources/examples/nonEqualFreqs.xml new file mode 100644 index 0000000..1dc589d --- /dev/null +++ b/beast-morph-models/src/test/resources/examples/nonEqualFreqs.xml @@ -0,0 +1,116 @@ + + + + +beast.base.inference.distribution.Prior + +beast.base.evolution.alignment.TaxonSet +beast.base.evolution.alignment.Taxon + + + + + + + + + + + A=1,B=2,C=3,D=1 + + + + + + + + + + + + + 1.0 + + + + + + + + + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + + + 1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/beast-morph-models/src/test/resources/examples/penguins.xml b/beast-morph-models/src/test/resources/examples/penguins.xml new file mode 100644 index 0000000..1174865 --- /dev/null +++ b/beast-morph-models/src/test/resources/examples/penguins.xml @@ -0,0 +1,678 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +beast.base.inference.distribution.Uniform +beast.base.inference.distribution.Exponential +beast.base.inference.distribution.LogNormalDistributionModel +beast.base.inference.distribution.Normal +beast.base.inference.distribution.Beta +beast.base.inference.distribution.Gamma +beast.base.inference.distribution.LaplaceDistribution +beast.base.inference.distribution.Prior +beast.base.inference.distribution.InverseGamma +beast.base.inference.distribution.OneOnX + + + + + + + Aptenodytes_forsteri_0.0=0.0, +Pygoscelis_antarctica_0.0=0.0, +Megadyptes_antipodes_0.0=0.0, +Eudyptes_chrysolophus_0.0=0.0, +Eudyptes_sclateri_0.0=0.0, +Eudyptula_minor_0.0=0.0, +Madrynornis_mirandus_10.0=10.0, +Spheniscus_demersus_0.0=0.0, +Spheniscus_muizoni_9.1=9.1, +Marplesornis_novaezealandiae_5.1=5.1, +Palaeospheniscus_patagonicus_14.58=14.58, +Eretiscus_tonnii_17.28=17.28, +Paraptenodytes_antarcticus_22.0=22.0, +Perudyptes_devriesi_41.0=41.0, +Icadyptes_salasi_36.0=36.0, +Kairuku_waitaki_26.07=26.07, +Delphinornis_wimani_34.71=34.71, +Waimanu_manneringi_61.3=61.3, +Pygoscelis_grandis_4.5=4.5 + + + + + + + + + 1.0 + 1.0 + 0.25 + 1.0 + 1.0 + 1.0 + 1.0 + 1.0 + 1.0 + 0.1 + 1 + 1.0 + 0.1 + 1 + 0.05 + 0.5 + 0.5 + 100.0 + 1.0 + + + + + 1.0 + + + + + + + 0.0 + + + + + + + 1.0 + + + + + 1.0 + + + + + + + + 0.05 + 10.0 + + + + + 0.05 + 20.0 + + + + + 0.05 + 10.0 + + + + + 0.05 + 10.0 + + + + + 0.05 + 10.0 + + + + + + + + + + + + + + -3.5 + 1.0 + + + + + -5.5 + 2.0 + + + + + 0.5396 + 0.3819 + + + + + 0.5396 + 0.3819 + + + + + + + 1.0 + + + + + 1.0 + + + + + + + + + + 1.0 + + + + + + + + + + 1.0 + + + + + + + + + + 1.0 + + + + + + + + + + 1.0 + + + + + + + + + + 1.0 + + + + + + + 1.0 + 0.0 + + 1.0 + + + + + + 1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/beast-morph-models/src/test/resources/examples/penguins_Mkv.xml b/beast-morph-models/src/test/resources/examples/penguins_Mkv.xml new file mode 100644 index 0000000..402994d --- /dev/null +++ b/beast-morph-models/src/test/resources/examples/penguins_Mkv.xml @@ -0,0 +1,679 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +beast.base.inference.distribution.Uniform +beast.base.inference.distribution.Exponential +beast.base.inference.distribution.LogNormalDistributionModel +beast.base.inference.distribution.Normal +beast.base.inference.distribution.Beta +beast.base.inference.distribution.Gamma +beast.base.inference.distribution.LaplaceDistribution +beast.base.inference.distribution.Prior +beast.base.inference.distribution.InverseGamma +beast.base.inference.distribution.OneOnX + + + + + + + Aptenodytes_forsteri_0.0=0.0, +Pygoscelis_antarctica_0.0=0.0, +Megadyptes_antipodes_0.0=0.0, +Eudyptes_chrysolophus_0.0=0.0, +Eudyptes_sclateri_0.0=0.0, +Eudyptula_minor_0.0=0.0, +Madrynornis_mirandus_10.0=10.0, +Spheniscus_demersus_0.0=0.0, +Spheniscus_muizoni_9.1=9.1, +Marplesornis_novaezealandiae_5.1=5.1, +Palaeospheniscus_patagonicus_14.58=14.58, +Eretiscus_tonnii_17.28=17.28, +Paraptenodytes_antarcticus_22.0=22.0, +Perudyptes_devriesi_41.0=41.0, +Icadyptes_salasi_36.0=36.0, +Kairuku_waitaki_26.07=26.07, +Delphinornis_wimani_34.71=34.71, +Waimanu_manneringi_61.3=61.3, +Pygoscelis_grandis_4.5=4.5 + + + + + + + + + 1.0 + 0.25 + 1.0 + 1.0 + 1.0 + 1.0 + 1.0 + 1.0 + 0.1 + 1 + 1.0 + 0.1 + 1 + 0.05 + 0.5 + 0.5 + 100.0 + 1.0 + + + + + 1.0 + + + + + + + 0.0 + + + + + + + 1.0 + + + + + + + + 0.05 + 10.0 + + + + + 0.05 + 20.0 + + + + + 0.05 + 10.0 + + + + + 0.05 + 10.0 + + + + + 0.05 + 10.0 + + + + + + + + + + + + + + + + + + + + 0.5396 + 0.3819 + + + + + 0.5396 + 0.3819 + + + + + + + 1.0 + 1.0 + + + + + 1.0 + + + + + + + + + 1.0 + 1.0 + + + + + + + + + 1.0 + 1.0 + + + + + + + + + 1.0 + 1.0 + + + + + + + + + 1.0 + 1.0 + + + + + + + + + 1.0 + 1.0 + + + + + + + 1.0 + 0.0 + + 1.0 + + + + + + 1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..854c08e --- /dev/null +++ b/pom.xml @@ -0,0 +1,100 @@ + + + 4.0.0 + + beast + morph-models-parent + 1.3.0-SNAPSHOT + pom + + Morph Models + Lewis MK/MKv substitution models for discrete morphological data + + + beast-morph-models + beast-morph-models-fx + + + + UTF-8 + UTF-8 + 25 + + 2.8.0-SNAPSHOT + 25.0.2 + 5.8.2 + + + + + + + beast + beast-pkgmgmt + ${beast.version} + + + beast + beast-base + ${beast.version} + + + beast + beast-fx + ${beast.version} + + + + + beast + beast-morph-models + ${project.version} + + + + + org.openjfx + javafx-controls + ${javafx.version} + + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + + + + github + https://maven.pkg.github.com/alexeid/beast3modular + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.15.0 + + 25 + UTF-8 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + + + From 9d4562f3560a923d7c4bb29a978baf6c501aaa95 Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sat, 28 Feb 2026 16:05:46 +1300 Subject: [PATCH 02/15] add spec example XML, fix exec plugin, update README - Move legacy 2.7 example XMLs to examples/legacy-2.7/ - Add M3982.xml using BEAST 3 spec classes (spec SiteModel, TreeLikelihood, StrictClockModel, YuleModel, ScaleTreeOperator, BactrianNodeOperator, RealScalarParam, TreeWithMetaDataLogger) - Fix exec-maven-plugin to use commandlineArgs with %classpath so multi-word beast.args are tokenized correctly - Remove test resource copies of examples needing external packages - Update README with module structure, build/run instructions --- README.md | 53 +- beast-morph-models/pom.xml | 13 + .../src/test/resources/examples/M3982.xml | 251 +++---- examples/M3982.xml | 251 +++---- examples/legacy-2.7/M3982.xml | 274 +++++++ .../legacy-2.7}/nonEqualFreqs.xml | 0 .../legacy-2.7}/penguins.xml | 0 .../legacy-2.7}/penguins_Mkv.xml | 0 examples/nonEqualFreqs.xml | 116 --- examples/penguins.xml | 678 ----------------- examples/penguins_Mkv.xml | 679 ------------------ 11 files changed, 561 insertions(+), 1754 deletions(-) create mode 100644 examples/legacy-2.7/M3982.xml rename {beast-morph-models/src/test/resources/examples => examples/legacy-2.7}/nonEqualFreqs.xml (100%) rename {beast-morph-models/src/test/resources/examples => examples/legacy-2.7}/penguins.xml (100%) rename {beast-morph-models/src/test/resources/examples => examples/legacy-2.7}/penguins_Mkv.xml (100%) delete mode 100644 examples/nonEqualFreqs.xml delete mode 100644 examples/penguins.xml delete mode 100644 examples/penguins_Mkv.xml diff --git a/README.md b/README.md index 017ee2a..3950ecd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,55 @@ morph-models ============ -Models for dealing with standard discrete morphological data. +Models for discrete morphological character data in [BEAST 3](https://github.com/alexeid/beast3modular). -Aiming at MK and MKv models from Lewis, 2001 (http://sysbio.oxfordjournals.org/content/50/6/913.short) +Implements the Lewis MK and MKv substitution models (Lewis, 2001), along with ordinal and nested ordinal variants for ordered character data. -A tutorial is available [here](http://www.beast2.org/morphological-models/) +## Modules + +- **beast-morph-models** — core substitution models and alignment classes (depends on `beast-base`) +- **beast-morph-models-fx** — BEAUti integration (depends on `beast-fx`) + +## Substitution models + +| Class | Description | +|-------|-------------| +| `LewisMK` | Equal or user-specified frequency Mk model | +| `Ordinal` | Tridiagonal rate matrix for ordered characters | +| `NestedOrdinal` | Nested ordinal rate matrix (state 0 transitions to all others) | + +## Building + +Requires BEAST 3 snapshot artifacts installed locally: + +```bash +cd ~/Git/beast3modular +mvn install -DskipTests +``` + +Then build morph-models: + +```bash +cd ~/Git/morph-models +mvn compile +mvn test -pl beast-morph-models +``` + +## Running + +```bash +# Validate an XML +mvn -pl beast-morph-models exec:exec -Dbeast.args="-validate examples/M3982.xml" + +# Run an analysis +mvn -pl beast-morph-models exec:exec -Dbeast.args="-overwrite examples/M3982.xml" +``` + +## Examples + +- `examples/M3982.xml` — Anolis lizard morphological analysis using BEAST 3 spec classes +- `examples/legacy-2.7/` — original BEAST 2.7 XML files (some require external packages) + +## References + +Lewis, P. O. (2001). A likelihood approach to estimating phylogeny from discrete morphological character data. *Systematic Biology*, 50(6), 913–925. diff --git a/beast-morph-models/pom.xml b/beast-morph-models/pom.xml index 76690a3..2f3a54e 100644 --- a/beast-morph-models/pom.xml +++ b/beast-morph-models/pom.xml @@ -49,6 +49,19 @@ + + + org.codehaus.mojo + exec-maven-plugin + 3.5.0 + + java + ${maven.multiModuleProjectDirectory} + --module-path %classpath -DBEAST_PACKAGE_PATH=${project.build.outputDirectory} -m beast.base/beast.base.minimal.BeastMain ${beast.args} + + diff --git a/beast-morph-models/src/test/resources/examples/M3982.xml b/beast-morph-models/src/test/resources/examples/M3982.xml index 99f9c1e..0b47006 100644 --- a/beast-morph-models/src/test/resources/examples/M3982.xml +++ b/beast-morph-models/src/test/resources/examples/M3982.xml @@ -1,4 +1,9 @@ - + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - -beast.base.inference.distribution.Uniform -beast.base.inference.distribution.Exponential -beast.base.inference.distribution.LogNormalDistributionModel -beast.base.inference.distribution.Normal -beast.base.inference.distribution.Beta -beast.base.inference.distribution.Gamma -beast.base.inference.distribution.LaplaceDistribution -beast.base.inference.distribution.Prior -beast.base.inference.distribution.InverseGamma -beast.base.inference.distribution.OneOnX - - - + + - 1.0 + - - - 1.0 + + + - - - - - - + + + + - - - - 1.0 - 1.0 - 0.0 - + + + + + - - 1.0 - + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + + - + - + - + - + - + @@ -247,12 +216,12 @@ spec="FilteredAlignment"> - + - + @@ -262,9 +231,13 @@ spec="FilteredAlignment"> + + + + - + diff --git a/examples/M3982.xml b/examples/M3982.xml index 99f9c1e..0b47006 100644 --- a/examples/M3982.xml +++ b/examples/M3982.xml @@ -1,4 +1,9 @@ - + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - -beast.base.inference.distribution.Uniform -beast.base.inference.distribution.Exponential -beast.base.inference.distribution.LogNormalDistributionModel -beast.base.inference.distribution.Normal -beast.base.inference.distribution.Beta -beast.base.inference.distribution.Gamma -beast.base.inference.distribution.LaplaceDistribution -beast.base.inference.distribution.Prior -beast.base.inference.distribution.InverseGamma -beast.base.inference.distribution.OneOnX - - - + + - 1.0 + - - - 1.0 + + + - - - - - - + + + + - - - - 1.0 - 1.0 - 0.0 - + + + + + - - 1.0 - + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + - - - + + + + - - 1.0 - 1.0 - 0.0 - + + + - + - + - + - + - + @@ -247,12 +216,12 @@ spec="FilteredAlignment"> - + - + @@ -262,9 +231,13 @@ spec="FilteredAlignment"> + + + + - + diff --git a/examples/legacy-2.7/M3982.xml b/examples/legacy-2.7/M3982.xml new file mode 100644 index 0000000..99f9c1e --- /dev/null +++ b/examples/legacy-2.7/M3982.xml @@ -0,0 +1,274 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +beast.base.inference.distribution.Uniform +beast.base.inference.distribution.Exponential +beast.base.inference.distribution.LogNormalDistributionModel +beast.base.inference.distribution.Normal +beast.base.inference.distribution.Beta +beast.base.inference.distribution.Gamma +beast.base.inference.distribution.LaplaceDistribution +beast.base.inference.distribution.Prior +beast.base.inference.distribution.InverseGamma +beast.base.inference.distribution.OneOnX + + + + + + + + + + + + + 1.0 + + + + + 1.0 + + + + + + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + 1.0 + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + 1.0 + 1.0 + 0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/beast-morph-models/src/test/resources/examples/nonEqualFreqs.xml b/examples/legacy-2.7/nonEqualFreqs.xml similarity index 100% rename from beast-morph-models/src/test/resources/examples/nonEqualFreqs.xml rename to examples/legacy-2.7/nonEqualFreqs.xml diff --git a/beast-morph-models/src/test/resources/examples/penguins.xml b/examples/legacy-2.7/penguins.xml similarity index 100% rename from beast-morph-models/src/test/resources/examples/penguins.xml rename to examples/legacy-2.7/penguins.xml diff --git a/beast-morph-models/src/test/resources/examples/penguins_Mkv.xml b/examples/legacy-2.7/penguins_Mkv.xml similarity index 100% rename from beast-morph-models/src/test/resources/examples/penguins_Mkv.xml rename to examples/legacy-2.7/penguins_Mkv.xml diff --git a/examples/nonEqualFreqs.xml b/examples/nonEqualFreqs.xml deleted file mode 100644 index 1dc589d..0000000 --- a/examples/nonEqualFreqs.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - -beast.base.inference.distribution.Prior - -beast.base.evolution.alignment.TaxonSet -beast.base.evolution.alignment.Taxon - - - - - - - - - - - A=1,B=2,C=3,D=1 - - - - - - - - - - - - - 1.0 - - - - - - - - - - - - - - - - - 1.0 - 1.0 - 0.0 - - - - - - - - - - - 1.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/penguins.xml b/examples/penguins.xml deleted file mode 100644 index 1174865..0000000 --- a/examples/penguins.xml +++ /dev/null @@ -1,678 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -beast.base.inference.distribution.Uniform -beast.base.inference.distribution.Exponential -beast.base.inference.distribution.LogNormalDistributionModel -beast.base.inference.distribution.Normal -beast.base.inference.distribution.Beta -beast.base.inference.distribution.Gamma -beast.base.inference.distribution.LaplaceDistribution -beast.base.inference.distribution.Prior -beast.base.inference.distribution.InverseGamma -beast.base.inference.distribution.OneOnX - - - - - - - Aptenodytes_forsteri_0.0=0.0, -Pygoscelis_antarctica_0.0=0.0, -Megadyptes_antipodes_0.0=0.0, -Eudyptes_chrysolophus_0.0=0.0, -Eudyptes_sclateri_0.0=0.0, -Eudyptula_minor_0.0=0.0, -Madrynornis_mirandus_10.0=10.0, -Spheniscus_demersus_0.0=0.0, -Spheniscus_muizoni_9.1=9.1, -Marplesornis_novaezealandiae_5.1=5.1, -Palaeospheniscus_patagonicus_14.58=14.58, -Eretiscus_tonnii_17.28=17.28, -Paraptenodytes_antarcticus_22.0=22.0, -Perudyptes_devriesi_41.0=41.0, -Icadyptes_salasi_36.0=36.0, -Kairuku_waitaki_26.07=26.07, -Delphinornis_wimani_34.71=34.71, -Waimanu_manneringi_61.3=61.3, -Pygoscelis_grandis_4.5=4.5 - - - - - - - - - 1.0 - 1.0 - 0.25 - 1.0 - 1.0 - 1.0 - 1.0 - 1.0 - 1.0 - 0.1 - 1 - 1.0 - 0.1 - 1 - 0.05 - 0.5 - 0.5 - 100.0 - 1.0 - - - - - 1.0 - - - - - - - 0.0 - - - - - - - 1.0 - - - - - 1.0 - - - - - - - - 0.05 - 10.0 - - - - - 0.05 - 20.0 - - - - - 0.05 - 10.0 - - - - - 0.05 - 10.0 - - - - - 0.05 - 10.0 - - - - - - - - - - - - - - -3.5 - 1.0 - - - - - -5.5 - 2.0 - - - - - 0.5396 - 0.3819 - - - - - 0.5396 - 0.3819 - - - - - - - 1.0 - - - - - 1.0 - - - - - - - - - - 1.0 - - - - - - - - - - 1.0 - - - - - - - - - - 1.0 - - - - - - - - - - 1.0 - - - - - - - - - - 1.0 - - - - - - - 1.0 - 0.0 - - 1.0 - - - - - - 1.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/penguins_Mkv.xml b/examples/penguins_Mkv.xml deleted file mode 100644 index 402994d..0000000 --- a/examples/penguins_Mkv.xml +++ /dev/null @@ -1,679 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -beast.base.inference.distribution.Uniform -beast.base.inference.distribution.Exponential -beast.base.inference.distribution.LogNormalDistributionModel -beast.base.inference.distribution.Normal -beast.base.inference.distribution.Beta -beast.base.inference.distribution.Gamma -beast.base.inference.distribution.LaplaceDistribution -beast.base.inference.distribution.Prior -beast.base.inference.distribution.InverseGamma -beast.base.inference.distribution.OneOnX - - - - - - - Aptenodytes_forsteri_0.0=0.0, -Pygoscelis_antarctica_0.0=0.0, -Megadyptes_antipodes_0.0=0.0, -Eudyptes_chrysolophus_0.0=0.0, -Eudyptes_sclateri_0.0=0.0, -Eudyptula_minor_0.0=0.0, -Madrynornis_mirandus_10.0=10.0, -Spheniscus_demersus_0.0=0.0, -Spheniscus_muizoni_9.1=9.1, -Marplesornis_novaezealandiae_5.1=5.1, -Palaeospheniscus_patagonicus_14.58=14.58, -Eretiscus_tonnii_17.28=17.28, -Paraptenodytes_antarcticus_22.0=22.0, -Perudyptes_devriesi_41.0=41.0, -Icadyptes_salasi_36.0=36.0, -Kairuku_waitaki_26.07=26.07, -Delphinornis_wimani_34.71=34.71, -Waimanu_manneringi_61.3=61.3, -Pygoscelis_grandis_4.5=4.5 - - - - - - - - - 1.0 - 0.25 - 1.0 - 1.0 - 1.0 - 1.0 - 1.0 - 1.0 - 0.1 - 1 - 1.0 - 0.1 - 1 - 0.05 - 0.5 - 0.5 - 100.0 - 1.0 - - - - - 1.0 - - - - - - - 0.0 - - - - - - - 1.0 - - - - - - - - 0.05 - 10.0 - - - - - 0.05 - 20.0 - - - - - 0.05 - 10.0 - - - - - 0.05 - 10.0 - - - - - 0.05 - 10.0 - - - - - - - - - - - - - - - - - - - - 0.5396 - 0.3819 - - - - - 0.5396 - 0.3819 - - - - - - - 1.0 - 1.0 - - - - - 1.0 - - - - - - - - - 1.0 - 1.0 - - - - - - - - - 1.0 - 1.0 - - - - - - - - - 1.0 - 1.0 - - - - - - - - - 1.0 - 1.0 - - - - - - - - - 1.0 - 1.0 - - - - - - - 1.0 - 0.0 - - 1.0 - - - - - - 1.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From b09d6adb5f56b4b5a5a17adde9daa3304f4d4968 Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sat, 28 Feb 2026 16:37:06 +1300 Subject: [PATCH 03/15] migrate BEAUti template to spec classes, clean up legacy artifacts - Delete build.xml (legacy Ant build) - Update version.xml to BEAST 3 format (remove BEAST 2 depends) - Migrate fxtemplate: RealScalarParam for estimated params, spec distributions-as-priors, spec SiteModel/TreeLikelihood/ YuleModel/ScaleOperator/UpDownOperator/TreeWithMetaDataLogger - Remove unused ExtensionFileFilter import --- .../BeautiMorphModelAlignmentProvider.java | 2 +- .../resources/fxtemplates/morph-models.xml | 62 +++--- build.xml | 195 ------------------ version.xml | 7 +- 4 files changed, 34 insertions(+), 232 deletions(-) delete mode 100644 build.xml diff --git a/beast-morph-models-fx/src/main/java/morphmodels/app/beauti/BeautiMorphModelAlignmentProvider.java b/beast-morph-models-fx/src/main/java/morphmodels/app/beauti/BeautiMorphModelAlignmentProvider.java index 39fe28d..00acb39 100644 --- a/beast-morph-models-fx/src/main/java/morphmodels/app/beauti/BeautiMorphModelAlignmentProvider.java +++ b/beast-morph-models-fx/src/main/java/morphmodels/app/beauti/BeautiMorphModelAlignmentProvider.java @@ -11,7 +11,7 @@ import beastfx.app.inputeditor.BeautiAlignmentProvider; import beastfx.app.inputeditor.BeautiDoc; import beastfx.app.util.Alert; -import beastfx.app.util.ExtensionFileFilter; + import beastfx.app.util.FXUtils; import javafx.scene.control.ButtonType; import beast.base.core.BEASTInterface; diff --git a/beast-morph-models-fx/src/main/resources/fxtemplates/morph-models.xml b/beast-morph-models-fx/src/main/resources/fxtemplates/morph-models.xml index eff6b9f..674b074 100644 --- a/beast-morph-models-fx/src/main/resources/fxtemplates/morph-models.xml +++ b/beast-morph-models-fx/src/main/resources/fxtemplates/morph-models.xml @@ -1,5 +1,5 @@ + namespace='morphmodels.evolution.substitutionmodel:morphmodels.evolution.alignment:morphmodels.app.beauti:beastfx.app.beauti:beastfx.app.inputeditor:beast.pkgmgmt:beast.base.core:beast.base.inference:beast.base.evolution.branchratemodel:beast.base.evolution.speciation:beast.base.evolution.tree.coalescent:beast.base.util:beast.base.math:beast.evolution.nuc:beast.base.evolution.operator:beast.base.inference.operator:beast.base.evolution.operator.kernel:beast.base.spec.evolution.sitemodel:beast.base.evolution.substitutionmodel:beast.base.evolution.likelihood:beast.evolution:beast.base.inference.distribution'> @@ -26,45 +26,47 @@ - - + - + - - - + + + - + - - + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + @@ -76,14 +78,13 @@ - + - - - + + - + ]]> @@ -154,4 +155,3 @@ - diff --git a/build.xml b/build.xml deleted file mode 100644 index 8de1abc..0000000 --- a/build.xml +++ /dev/null @@ -1,195 +0,0 @@ - - - - Build MM. - Also used by Hudson MM project. - JUnit test is available for this build. - $Id: build_MM.xml $ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ** Required file version.xml does not exist. ** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/version.xml b/version.xml index 4c2f56e..c2a9353 100644 --- a/version.xml +++ b/version.xml @@ -1,14 +1,11 @@ - - - + - + - From 305cb1138f3d2035463e88ea45dfb70f3adc81c4 Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sat, 28 Feb 2026 16:53:15 +1300 Subject: [PATCH 04/15] add maven-resources-plugin to copy version.xml for package deployment --- beast-morph-models/pom.xml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/beast-morph-models/pom.xml b/beast-morph-models/pom.xml index 2f3a54e..ad81fda 100644 --- a/beast-morph-models/pom.xml +++ b/beast-morph-models/pom.xml @@ -49,6 +49,30 @@ + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + copy-version-xml + generate-resources + copy-resources + + ${project.build.directory} + + + ${maven.multiModuleProjectDirectory} + + version.xml + + + + + + + From 6fd2fbefe1bf05340565d47a251dcc7234694229 Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sat, 28 Feb 2026 17:38:20 +1300 Subject: [PATCH 05/15] update references from alexeid/beast3modular to CompEvol/beast3 --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3950ecd..514872a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ morph-models ============ -Models for discrete morphological character data in [BEAST 3](https://github.com/alexeid/beast3modular). +Models for discrete morphological character data in [BEAST 3](https://github.com/CompEvol/beast3). Implements the Lewis MK and MKv substitution models (Lewis, 2001), along with ordinal and nested ordinal variants for ordered character data. diff --git a/pom.xml b/pom.xml index 854c08e..8fd25fc 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ github - https://maven.pkg.github.com/alexeid/beast3modular + https://maven.pkg.github.com/CompEvol/beast3 From 64a25c806ab167f6174c01766641b94bd422949a Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sun, 1 Mar 2026 12:28:07 +1300 Subject: [PATCH 06/15] add release.sh for reproducible BEAST package builds Builds JARs, assembles flat ZIP matching the BEAST package convention (version.xml, lib/, fxtemplates/, examples/ at root), and optionally creates a GitHub release with --release flag. Version and package name are extracted from version.xml. --- release.sh | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100755 release.sh diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..7e4fa31 --- /dev/null +++ b/release.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# +# Build and package MM (morph-models) for BEAST 2.8 Package Manager. +# +# Usage: +# ./release.sh # build + package ZIP +# ./release.sh --release # also create GitHub release on alexeid/morph-models +# +set -euo pipefail + +# Extract version and package name from version.xml +VERSION=$(sed -n "s/.*version='\([^']*\)'.*/\1/p" version.xml) +PKG_NAME=$(sed -n "s/.*name='\([^']*\)'.*/\1/p" version.xml) +ZIP_NAME="${PKG_NAME}.v${VERSION}.zip" + +echo "=== Building ${PKG_NAME} v${VERSION} ===" + +# Step 1: Maven build +mvn clean package -DskipTests + +# Step 2: Assemble package directory (flat, no wrapper) +STAGING=$(mktemp -d) +trap "rm -rf $STAGING" EXIT + +mkdir -p "$STAGING/lib" "$STAGING/fxtemplates" "$STAGING/examples/nexus" + +# JARs +cp beast-morph-models/target/beast-morph-models-*-SNAPSHOT.jar "$STAGING/lib/" +cp beast-morph-models-fx/target/beast-morph-models-fx-*-SNAPSHOT.jar "$STAGING/lib/" + +# version.xml +cp version.xml "$STAGING/" + +# BEAUti template +cp beast-morph-models-fx/src/main/resources/fxtemplates/morph-models.xml "$STAGING/fxtemplates/" + +# Examples (excluding legacy-2.7/) +cp examples/M3982.xml "$STAGING/examples/" +cp examples/nexus/*.nex "$STAGING/examples/nexus/" + +# Step 3: Create ZIP from contents (flat structure, no wrapper directory) +rm -f "$ZIP_NAME" +(cd "$STAGING" && zip -r - .) > "$ZIP_NAME" + +echo "" +echo "=== Package built: ${ZIP_NAME} ===" +unzip -l "$ZIP_NAME" + +# Step 4: Optionally create GitHub release +if [[ "${1:-}" == "--release" ]]; then + echo "" + echo "=== Creating GitHub release v${VERSION} ===" + gh release create "v${VERSION}" "$ZIP_NAME" \ + --repo alexeid/morph-models \ + --title "${PKG_NAME} v${VERSION}" \ + --notes "Lewis MK/MKv substitution models for BEAST 2.8" + echo "Done: https://github.com/alexeid/morph-models/releases/tag/v${VERSION}" +fi From e95b240470d8c0af1aa2c2e3379d23ad28cbc28d Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sun, 1 Mar 2026 12:42:58 +1300 Subject: [PATCH 07/15] update release.sh to canonical version from beast-package-skeleton Replaces the morph-models-specific script with the generic version that auto-discovers JARs, fxtemplates, and examples. Works for both single-module and multi-module layouts. Also adds *.v*.zip to gitignore. --- .gitignore | 3 ++ release.sh | 134 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 110 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 19229a9..bebee7c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ # Maven build output target/ + +# Package ZIPs +*.v*.zip diff --git a/release.sh b/release.sh index 7e4fa31..1266cfd 100755 --- a/release.sh +++ b/release.sh @@ -1,58 +1,138 @@ #!/bin/bash # -# Build and package MM (morph-models) for BEAST 2.8 Package Manager. +# Build and package a BEAST 2.8 package for the BEAST Package Manager. +# +# Reads the package name and version from version.xml, builds with Maven, +# assembles the standard BEAST package ZIP, and optionally creates a +# GitHub release. # # Usage: -# ./release.sh # build + package ZIP -# ./release.sh --release # also create GitHub release on alexeid/morph-models +# ./release.sh # build + create package ZIP +# ./release.sh --release # also create a GitHub release with the ZIP attached +# +# The ZIP can then be submitted to CBAN (CompEvol/CBAN) by adding an entry +# to packages2.8.xml via pull request. See README.md for details. # set -euo pipefail -# Extract version and package name from version.xml -VERSION=$(sed -n "s/.*version='\([^']*\)'.*/\1/p" version.xml) -PKG_NAME=$(sed -n "s/.*name='\([^']*\)'.*/\1/p" version.xml) +# --- Extract metadata from version.xml --- + +if [[ ! -f version.xml ]]; then + echo "ERROR: version.xml not found in $(pwd)" >&2 + exit 1 +fi + +# Parse only the element (not etc.) +PKG_LINE=$(grep '&2 + exit 1 +fi + ZIP_NAME="${PKG_NAME}.v${VERSION}.zip" +GITHUB_REPO=$(git remote get-url origin 2>/dev/null \ + | sed 's|.*github.com[:/]\(.*\)\.git$|\1|; s|.*github.com[:/]\(.*\)$|\1|') + +echo "=== ${PKG_NAME} v${VERSION} ===" +echo "" -echo "=== Building ${PKG_NAME} v${VERSION} ===" +# --- Step 1: Maven build --- -# Step 1: Maven build +echo "--- Building with Maven ---" mvn clean package -DskipTests +echo "" + +# --- Step 2: Assemble BEAST package (flat ZIP structure) --- -# Step 2: Assemble package directory (flat, no wrapper) +echo "--- Assembling package ZIP ---" STAGING=$(mktemp -d) trap "rm -rf $STAGING" EXIT -mkdir -p "$STAGING/lib" "$STAGING/fxtemplates" "$STAGING/examples/nexus" +mkdir -p "$STAGING/lib" -# JARs -cp beast-morph-models/target/beast-morph-models-*-SNAPSHOT.jar "$STAGING/lib/" -cp beast-morph-models-fx/target/beast-morph-models-fx-*-SNAPSHOT.jar "$STAGING/lib/" - -# version.xml +# version.xml (required) cp version.xml "$STAGING/" -# BEAUti template -cp beast-morph-models-fx/src/main/resources/fxtemplates/morph-models.xml "$STAGING/fxtemplates/" +# Package JARs: collect from target/ (single module) or */target/ (multi-module). +# Only include the project's own JARs — BEAST core deps (beast-base, beast-pkgmgmt, +# beagle, colt, etc.) are already in the BEAST installation and must NOT be bundled. +jar_count=0 +for jar in $(find . -path '*/target/*.jar' \ + -not -name '*-javadoc.jar' \ + -not -name '*-sources.jar' \ + -not -path '*/test-classes/*' \ + -not -path '*/target/lib/*' | sort); do + cp "$jar" "$STAGING/lib/" + jar_count=$((jar_count + 1)) +done + +if [[ $jar_count -eq 0 ]]; then + echo "ERROR: no JARs found in target directories" >&2 + exit 1 +fi + +# fxtemplates/ (optional — check common locations) +fxtemplates_found=false +for dir in fxtemplates \ + src/main/resources/fxtemplates \ + */src/main/resources/fxtemplates; do + if compgen -G "$dir/*.xml" > /dev/null 2>&1; then + mkdir -p "$STAGING/fxtemplates" + cp "$dir"/*.xml "$STAGING/fxtemplates/" + fxtemplates_found=true + fi +done -# Examples (excluding legacy-2.7/) -cp examples/M3982.xml "$STAGING/examples/" -cp examples/nexus/*.nex "$STAGING/examples/nexus/" +# examples/ (optional — check common locations) +if [[ -d examples ]]; then + # Top-level examples dir: copy everything except legacy directories + rsync -a --exclude='legacy*' examples/ "$STAGING/examples/" +elif [[ -d src/test/resources/examples ]]; then + # Skeleton convention: examples in test resources + mkdir -p "$STAGING/examples" + cp -R src/test/resources/examples/* "$STAGING/examples/" +fi -# Step 3: Create ZIP from contents (flat structure, no wrapper directory) +# Create ZIP from staging contents (flat, no wrapper directory) rm -f "$ZIP_NAME" (cd "$STAGING" && zip -r - .) > "$ZIP_NAME" echo "" -echo "=== Package built: ${ZIP_NAME} ===" +echo "=== Package: ${ZIP_NAME} ===" unzip -l "$ZIP_NAME" -# Step 4: Optionally create GitHub release +# --- Step 3: Optionally create GitHub release --- + if [[ "${1:-}" == "--release" ]]; then + if [[ -z "$GITHUB_REPO" ]]; then + echo "ERROR: could not determine GitHub repo from git remote" >&2 + exit 1 + fi + echo "" - echo "=== Creating GitHub release v${VERSION} ===" + echo "--- Creating GitHub release v${VERSION} on ${GITHUB_REPO} ---" gh release create "v${VERSION}" "$ZIP_NAME" \ - --repo alexeid/morph-models \ + --repo "$GITHUB_REPO" \ --title "${PKG_NAME} v${VERSION}" \ - --notes "Lewis MK/MKv substitution models for BEAST 2.8" - echo "Done: https://github.com/alexeid/morph-models/releases/tag/v${VERSION}" + --generate-notes + + DOWNLOAD_URL="https://github.com/${GITHUB_REPO}/releases/download/v${VERSION}/${ZIP_NAME}" + echo "" + echo "=== Release created ===" + echo "URL: https://github.com/${GITHUB_REPO}/releases/tag/v${VERSION}" + echo "" + echo "--- Next step: submit to CBAN ---" + echo "Add this entry to packages2.8.xml in https://github.com/CompEvol/CBAN via pull request:" + echo "" + cat < + + +XMLEOF fi From 8d714ac31518a6127bd7cc77548674cc6efcdda1 Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sun, 1 Mar 2026 13:15:48 +1300 Subject: [PATCH 08/15] replace release.sh ZIP assembly with maven-assembly-plugin Move BEAST package ZIP assembly from shell script into Maven so `mvn package` reproducibly produces the correct ZIP on any platform. BEAST core and JavaFX deps marked as provided scope to exclude them and their transitives from the package. Assembly descriptor placed in beast-morph-models-fx (last reactor module). release.sh becomes a thin wrapper for GitHub release creation only. --- beast-morph-models-fx/pom.xml | 25 +++++++ .../src/assembly/beast-package.xml | 42 +++++++++++ pom.xml | 10 ++- release.sh | 69 +++---------------- 4 files changed, 86 insertions(+), 60 deletions(-) create mode 100644 beast-morph-models-fx/src/assembly/beast-package.xml diff --git a/beast-morph-models-fx/pom.xml b/beast-morph-models-fx/pom.xml index 535fd24..1e77ca0 100644 --- a/beast-morph-models-fx/pom.xml +++ b/beast-morph-models-fx/pom.xml @@ -38,4 +38,29 @@ javafx-controls + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.7.1 + + + src/assembly/beast-package.xml + + ${beast.pkg.name}.v${beast.pkg.version} + false + + + + beast-package + package + single + + + + + diff --git a/beast-morph-models-fx/src/assembly/beast-package.xml b/beast-morph-models-fx/src/assembly/beast-package.xml new file mode 100644 index 0000000..2922365 --- /dev/null +++ b/beast-morph-models-fx/src/assembly/beast-package.xml @@ -0,0 +1,42 @@ + + beast-package + + zip + + false + + + + + ${project.parent.basedir}/version.xml + / + + + + + + + /lib + true + runtime + + + + + + + ${project.basedir}/src/main/resources/fxtemplates + /fxtemplates + + + + ${project.parent.basedir}/examples + /examples + + legacy*/** + + + + diff --git a/pom.xml b/pom.xml index 8fd25fc..fc29e3e 100644 --- a/pom.xml +++ b/pom.xml @@ -25,25 +25,30 @@ 2.8.0-SNAPSHOT 25.0.2 5.8.2 + MM + 1.3.0 - + beast beast-pkgmgmt ${beast.version} + provided beast beast-base ${beast.version} + provided beast beast-fx ${beast.version} + provided @@ -53,11 +58,12 @@ ${project.version} - + org.openjfx javafx-controls ${javafx.version} + provided diff --git a/release.sh b/release.sh index 1266cfd..606044d 100755 --- a/release.sh +++ b/release.sh @@ -1,10 +1,9 @@ #!/bin/bash # -# Build and package a BEAST 2.8 package for the BEAST Package Manager. +# Build and release a BEAST 2.8 package. # -# Reads the package name and version from version.xml, builds with Maven, -# assembles the standard BEAST package ZIP, and optionally creates a -# GitHub release. +# Runs `mvn package` to produce the BEAST package ZIP (via maven-assembly-plugin), +# then optionally creates a GitHub release with the ZIP attached. # # Usage: # ./release.sh # build + create package ZIP @@ -39,72 +38,26 @@ GITHUB_REPO=$(git remote get-url origin 2>/dev/null \ echo "=== ${PKG_NAME} v${VERSION} ===" echo "" -# --- Step 1: Maven build --- +# --- Step 1: Maven build + assemble package ZIP --- echo "--- Building with Maven ---" mvn clean package -DskipTests echo "" -# --- Step 2: Assemble BEAST package (flat ZIP structure) --- - -echo "--- Assembling package ZIP ---" -STAGING=$(mktemp -d) -trap "rm -rf $STAGING" EXIT - -mkdir -p "$STAGING/lib" - -# version.xml (required) -cp version.xml "$STAGING/" - -# Package JARs: collect from target/ (single module) or */target/ (multi-module). -# Only include the project's own JARs — BEAST core deps (beast-base, beast-pkgmgmt, -# beagle, colt, etc.) are already in the BEAST installation and must NOT be bundled. -jar_count=0 -for jar in $(find . -path '*/target/*.jar' \ - -not -name '*-javadoc.jar' \ - -not -name '*-sources.jar' \ - -not -path '*/test-classes/*' \ - -not -path '*/target/lib/*' | sort); do - cp "$jar" "$STAGING/lib/" - jar_count=$((jar_count + 1)) -done - -if [[ $jar_count -eq 0 ]]; then - echo "ERROR: no JARs found in target directories" >&2 +# Locate the ZIP produced by maven-assembly-plugin (in the -fx module for multi-module) +ZIP_PATH="beast-morph-models-fx/target/${ZIP_NAME}" +if [[ ! -f "$ZIP_PATH" ]]; then + echo "ERROR: expected ZIP not found at ${ZIP_PATH}" >&2 exit 1 fi -# fxtemplates/ (optional — check common locations) -fxtemplates_found=false -for dir in fxtemplates \ - src/main/resources/fxtemplates \ - */src/main/resources/fxtemplates; do - if compgen -G "$dir/*.xml" > /dev/null 2>&1; then - mkdir -p "$STAGING/fxtemplates" - cp "$dir"/*.xml "$STAGING/fxtemplates/" - fxtemplates_found=true - fi -done - -# examples/ (optional — check common locations) -if [[ -d examples ]]; then - # Top-level examples dir: copy everything except legacy directories - rsync -a --exclude='legacy*' examples/ "$STAGING/examples/" -elif [[ -d src/test/resources/examples ]]; then - # Skeleton convention: examples in test resources - mkdir -p "$STAGING/examples" - cp -R src/test/resources/examples/* "$STAGING/examples/" -fi +# Copy to project root for convenience +cp "$ZIP_PATH" "$ZIP_NAME" -# Create ZIP from staging contents (flat, no wrapper directory) -rm -f "$ZIP_NAME" -(cd "$STAGING" && zip -r - .) > "$ZIP_NAME" - -echo "" echo "=== Package: ${ZIP_NAME} ===" unzip -l "$ZIP_NAME" -# --- Step 3: Optionally create GitHub release --- +# --- Step 2: Optionally create GitHub release --- if [[ "${1:-}" == "--release" ]]; then if [[ -z "$GITHUB_REPO" ]]; then From 5f52e3811bcf3ede23e0e053cdaf2c05425b1932 Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sun, 1 Mar 2026 16:58:00 +1300 Subject: [PATCH 09/15] add Maven Central publishing support (#40) --- README.md | 27 +++++++++++ beast-morph-models-fx/pom.xml | 10 ++-- beast-morph-models/pom.xml | 22 +++++++-- pom.xml | 88 +++++++++++++++++++++++++++++++++-- 4 files changed, 134 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 514872a..766c9e8 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,33 @@ mvn -pl beast-morph-models exec:exec -Dbeast.args="-overwrite examples/M3982.xml - `examples/M3982.xml` — Anolis lizard morphological analysis using BEAST 3 spec classes - `examples/legacy-2.7/` — original BEAST 2.7 XML files (some require external packages) +## Releasing + +### ZIP / CBAN + +The existing `release.sh` script builds a BEAST package ZIP and optionally +creates a GitHub release for submission to [CBAN](https://github.com/CompEvol/CBAN): + +```bash +./release.sh # build ZIP only +./release.sh --release # build ZIP + create GitHub release +``` + +### Maven Central + +```bash +mvn clean deploy -Prelease +``` + +This builds the JARs (with `version.xml` embedded for service discovery), generates +sources and javadoc JARs, signs everything with GPG, and uploads to Maven Central. + +BEAST 3 users can then install with: + +``` +Package Manager > Install from Maven > io.github.alexeid:beast-morph-models:1.3.0 +``` + ## References Lewis, P. O. (2001). A likelihood approach to estimating phylogeny from discrete morphological character data. *Systematic Biology*, 50(6), 913–925. diff --git a/beast-morph-models-fx/pom.xml b/beast-morph-models-fx/pom.xml index 1e77ca0..14f3bc7 100644 --- a/beast-morph-models-fx/pom.xml +++ b/beast-morph-models-fx/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - beast + io.github.alexeid morph-models-parent 1.3.0-SNAPSHOT @@ -16,19 +16,19 @@ - beast + io.github.alexeid beast-morph-models - beast + io.github.alexeid beast-base - beast + io.github.alexeid beast-fx - beast + io.github.alexeid beast-pkgmgmt diff --git a/beast-morph-models/pom.xml b/beast-morph-models/pom.xml index ad81fda..29cd2d3 100644 --- a/beast-morph-models/pom.xml +++ b/beast-morph-models/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - beast + io.github.alexeid morph-models-parent 1.3.0-SNAPSHOT @@ -16,11 +16,11 @@ - beast + io.github.alexeid beast-base - beast + io.github.alexeid beast-pkgmgmt @@ -71,6 +71,22 @@ + + embed-version-xml-in-jar + generate-resources + copy-resources + + ${project.build.outputDirectory} + + + ${maven.multiModuleProjectDirectory} + + version.xml + + + + + - beast + io.github.alexeid beast-pkgmgmt ${beast.version} provided - beast + io.github.alexeid beast-base ${beast.version} provided - beast + io.github.alexeid beast-fx ${beast.version} provided @@ -53,7 +75,7 @@ - beast + io.github.alexeid beast-morph-models ${project.version} @@ -103,4 +125,60 @@ + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + jar-no-fork + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.11.2 + + none + + + + attach-javadocs + jar + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + verify + sign + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.6.0 + true + + central + + + + + + From 91f850d134b2d885a5f9de99fba81419d26d4cfe Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sun, 1 Mar 2026 17:54:03 +1300 Subject: [PATCH 10/15] add packagemanager CLI install command to README (#40) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 766c9e8..d758141 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,12 @@ BEAST 3 users can then install with: Package Manager > Install from Maven > io.github.alexeid:beast-morph-models:1.3.0 ``` +Or from the command line: + +```bash +packagemanager -maven io.github.alexeid:beast-morph-models:1.3.0 +``` + ## References Lewis, P. O. (2001). A likelihood approach to estimating phylogeny from discrete morphological character data. *Systematic Biology*, 50(6), 913–925. From ac04d6723059c1c933f783126d91840e355bf816 Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sun, 1 Mar 2026 19:08:38 +1300 Subject: [PATCH 11/15] rename groupId from io.github.alexeid to io.github.compevol Updates all POMs and README to use the CompEvol organisation namespace. --- README.md | 6 +++--- beast-morph-models-fx/pom.xml | 10 +++++----- beast-morph-models/pom.xml | 6 +++--- pom.xml | 10 +++++----- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d758141..eefe8ea 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Implements the Lewis MK and MKv substitution models (Lewis, 2001), along with or Requires BEAST 3 snapshot artifacts installed locally: ```bash -cd ~/Git/beast3modular +cd ~/Git/beast3 mvn install -DskipTests ``` @@ -74,13 +74,13 @@ sources and javadoc JARs, signs everything with GPG, and uploads to Maven Centra BEAST 3 users can then install with: ``` -Package Manager > Install from Maven > io.github.alexeid:beast-morph-models:1.3.0 +Package Manager > Install from Maven > io.github.compevol:beast-morph-models:1.3.0 ``` Or from the command line: ```bash -packagemanager -maven io.github.alexeid:beast-morph-models:1.3.0 +packagemanager -maven io.github.compevol:beast-morph-models:1.3.0 ``` ## References diff --git a/beast-morph-models-fx/pom.xml b/beast-morph-models-fx/pom.xml index 14f3bc7..25f7d97 100644 --- a/beast-morph-models-fx/pom.xml +++ b/beast-morph-models-fx/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - io.github.alexeid + io.github.compevol morph-models-parent 1.3.0-SNAPSHOT @@ -16,19 +16,19 @@ - io.github.alexeid + io.github.compevol beast-morph-models - io.github.alexeid + io.github.compevol beast-base - io.github.alexeid + io.github.compevol beast-fx - io.github.alexeid + io.github.compevol beast-pkgmgmt diff --git a/beast-morph-models/pom.xml b/beast-morph-models/pom.xml index 29cd2d3..f1383d3 100644 --- a/beast-morph-models/pom.xml +++ b/beast-morph-models/pom.xml @@ -5,7 +5,7 @@ 4.0.0 - io.github.alexeid + io.github.compevol morph-models-parent 1.3.0-SNAPSHOT @@ -16,11 +16,11 @@ - io.github.alexeid + io.github.compevol beast-base - io.github.alexeid + io.github.compevol beast-pkgmgmt diff --git a/pom.xml b/pom.xml index 2396c60..88ba4f8 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - io.github.alexeid + io.github.compevol morph-models-parent 1.3.0-SNAPSHOT pom @@ -55,19 +55,19 @@ - io.github.alexeid + io.github.compevol beast-pkgmgmt ${beast.version} provided - io.github.alexeid + io.github.compevol beast-base ${beast.version} provided - io.github.alexeid + io.github.compevol beast-fx ${beast.version} provided @@ -75,7 +75,7 @@ - io.github.alexeid + io.github.compevol beast-morph-models ${project.version} From 11cf73dbed7dc7f118b883df1e1f75dc90882a3a Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Sun, 1 Mar 2026 20:09:57 +1300 Subject: [PATCH 12/15] update README: document GitHub Packages auth for building Notes Maven Central as alternative source if published there. --- README.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index eefe8ea..38d6d4e 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,18 @@ Implements the Lewis MK and MKv substitution models (Lewis, 2001), along with or ## Building -Requires BEAST 3 snapshot artifacts installed locally: - -```bash -cd ~/Git/beast3 -mvn install -DskipTests +BEAST 3 dependencies are resolved from [GitHub Packages](https://github.com/CompEvol/beast3/packages) (or Maven Central, if published there). For GitHub Packages, add a [personal access token](https://github.com/settings/tokens) (classic) with `read:packages` scope to `~/.m2/settings.xml`: + +```xml + + + + github + YOUR_GITHUB_USERNAME + YOUR_GITHUB_PAT + + + ``` Then build morph-models: @@ -35,6 +42,13 @@ mvn compile mvn test -pl beast-morph-models ``` +Alternatively, you can install BEAST 3 from source (no GitHub auth needed): + +```bash +cd ~/Git/beast3 +mvn install -DskipTests +``` + ## Running ```bash From 577418e3953aadec423010fe60cf4d548902f459 Mon Sep 17 00:00:00 2001 From: alexeid Date: Mon, 2 Mar 2026 14:32:12 +1300 Subject: [PATCH 13/15] change BEAST deps from provided to compile scope Allows third-party developers to run BEAST via exec:exec to test their package. The assembly descriptor now uses an includes whitelist to keep only morph-models JARs in the distribution ZIP. --- beast-morph-models-fx/src/assembly/beast-package.xml | 6 +++++- pom.xml | 8 ++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/beast-morph-models-fx/src/assembly/beast-package.xml b/beast-morph-models-fx/src/assembly/beast-package.xml index 2922365..0ec4ca0 100644 --- a/beast-morph-models-fx/src/assembly/beast-package.xml +++ b/beast-morph-models-fx/src/assembly/beast-package.xml @@ -15,12 +15,16 @@ - + /lib true runtime + + io.github.compevol:beast-morph-models + io.github.compevol:beast-morph-models-fx + diff --git a/pom.xml b/pom.xml index 88ba4f8..bcde966 100644 --- a/pom.xml +++ b/pom.xml @@ -53,24 +53,21 @@ - + io.github.compevol beast-pkgmgmt ${beast.version} - provided io.github.compevol beast-base ${beast.version} - provided io.github.compevol beast-fx ${beast.version} - provided @@ -80,12 +77,11 @@ ${project.version} - + org.openjfx javafx-controls ${javafx.version} - provided From bfda24e3dacee41f7db8a07c8af2560a0d908fb9 Mon Sep 17 00:00:00 2001 From: alexeid Date: Tue, 3 Mar 2026 11:11:51 +1300 Subject: [PATCH 14/15] add BEAUti exec:exec and move fxtemplates to JPMS-safe path Move fxtemplates to beast.morph.models.fx/fxtemplates/ to avoid JPMS split-package conflict with beast-fx. Add exec-maven-plugin to beast-morph-models-fx for running BEAUti with morph-models on the module path. Add beast.main and beast.args default properties. --- beast-morph-models-fx/pom.xml | 14 ++++++++++++++ .../src/assembly/beast-package.xml | 2 +- .../fxtemplates/morph-models.xml | 0 pom.xml | 2 ++ 4 files changed, 17 insertions(+), 1 deletion(-) rename beast-morph-models-fx/src/main/resources/{ => beast.morph.models.fx}/fxtemplates/morph-models.xml (100%) diff --git a/beast-morph-models-fx/pom.xml b/beast-morph-models-fx/pom.xml index 25f7d97..fb43515 100644 --- a/beast-morph-models-fx/pom.xml +++ b/beast-morph-models-fx/pom.xml @@ -41,6 +41,20 @@ + + + org.codehaus.mojo + exec-maven-plugin + 3.5.0 + + java + ${maven.multiModuleProjectDirectory} + --module-path %classpath -m beast.fx/${beast.main} ${beast.args} + + + org.apache.maven.plugins diff --git a/beast-morph-models-fx/src/assembly/beast-package.xml b/beast-morph-models-fx/src/assembly/beast-package.xml index 0ec4ca0..e4d1ace 100644 --- a/beast-morph-models-fx/src/assembly/beast-package.xml +++ b/beast-morph-models-fx/src/assembly/beast-package.xml @@ -31,7 +31,7 @@ - ${project.basedir}/src/main/resources/fxtemplates + ${project.basedir}/src/main/resources/beast.morph.models.fx/fxtemplates /fxtemplates diff --git a/beast-morph-models-fx/src/main/resources/fxtemplates/morph-models.xml b/beast-morph-models-fx/src/main/resources/beast.morph.models.fx/fxtemplates/morph-models.xml similarity index 100% rename from beast-morph-models-fx/src/main/resources/fxtemplates/morph-models.xml rename to beast-morph-models-fx/src/main/resources/beast.morph.models.fx/fxtemplates/morph-models.xml diff --git a/pom.xml b/pom.xml index bcde966..21dbec9 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,8 @@ 2.8.0-SNAPSHOT 25.0.2 5.8.2 + beastfx.app.beauti.Beauti + MM 1.3.0 From 208c583e37008cc14a84d8aa16f9ffc9e6935696 Mon Sep 17 00:00:00 2001 From: Alexei Drummond Date: Fri, 6 Mar 2026 18:31:05 +1300 Subject: [PATCH 15/15] Add BEAST.base dependency to version.xml and CI workflow Add to version.xml for consistency with other BEAST3 packages. Add GitHub Actions workflow for running tests on push/PR. --- .github/workflows/main.yml | 18 ++++++++++++++++++ version.xml | 1 + 2 files changed, 19 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..20cd5b3 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,18 @@ +name: Unit/integration tests +on: [ push, pull_request, workflow_dispatch ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 25 + server-id: github + server-username: GITHUB_ACTOR + server-password: GITHUB_TOKEN + - run: mvn test + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/version.xml b/version.xml index c2a9353..06984e3 100644 --- a/version.xml +++ b/version.xml @@ -1,4 +1,5 @@ +