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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Set up JDK 25 (Azul Zulu)
if: "!startsWith(github.ref, 'refs/tags/v')"
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: '25'
cache: maven

- name: Set up JDK 25 (Azul Zulu) for Maven Central
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: '25'
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.6.0</version>
<version>0.11.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
Expand Down
84 changes: 79 additions & 5 deletions src/main/java/ccd/model/AbstractCCD.java
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,64 @@ public Tree sampleTree(HeightSettingStrategy heightStrategy) {
return getTreeBasedOnStrategy(SamplingStrategy.Sampling, heightStrategy);
}

/** As {@link #sampleTrees(int, HeightSettingStrategy)} with {@link HeightSettingStrategy#None}. */
public List<Tree> sampleTrees(int count) {
return sampleTrees(count, HeightSettingStrategy.None);
}

/**
* Draws {@code count} trees, in parallel. Individual draws are independent -- they only read
* the clade DAG and its cached probabilities -- so the one-at-a-time
* {@link #sampleTree(HeightSettingStrategy)} loop leaves all but one core idle. This warms
* every lazily-built structure first (so the draws themselves are pure reads) and then fans
* the draws out.
*
* <p>The result is reproducible: the per-draw seeds are taken from the shared {@link #random}
* <em>serially</em>, before the fan-out, so a given {@link #setRandom} state yields the same
* trees in the same order however the draws happen to be scheduled.
*
* @param count number of trees to draw
* @param heightStrategy how node heights are set (see {@link HeightSettingStrategy})
* @return the sampled trees, in draw order
*/
public List<Tree> sampleTrees(int count, HeightSettingStrategy heightStrategy) {
if (count < 0) {
throw new IllegalArgumentException("count must be non-negative, got " + count);
}
if (count == 0) {
return List.of();
}

// Warm everything lazily built, serially -- the parallel draws below must only read.
tidyUpCacheIfDirty();
computeCladeProbabilitiesIfDirty();
if (heightStrategy == HeightSettingStrategy.CommonAncestorHeights) {
setupCommonAncestorHeightsIfDirty();
}
prepareForSampling();

long[] seeds = new long[count];
for (int i = 0; i < count; i++) {
seeds[i] = random.nextLong();
}

return java.util.stream.IntStream.range(0, count).parallel().mapToObj(i -> {
threadRandom.set(new Random(seeds[i]));
try {
return getTreeBasedOnStrategy(SamplingStrategy.Sampling, heightStrategy);
} finally {
threadRandom.remove();
}
}).toList();
}

/**
* Hook for subclasses to precompute any per-clade state that a draw would otherwise solve
* lazily, so that {@link #sampleTrees} fans out over pure reads. Does nothing by default.
*/
protected void prepareForSampling() {
}

@Override
public Tree getMAPTree() {
return this.getMAPTree(HeightSettingStrategy.One);
Expand All @@ -881,8 +939,24 @@ public Tree getMAPTree(HeightSettingStrategy heightStrategy) {
return getTreeBasedOnStrategy(SamplingStrategy.MAP, heightStrategy);
}

/* Helper for methods to assign indices to inner vertices */
private int runningInnerIndex;
/* Helper for methods to assign indices to inner vertices. Thread-local so that concurrent
* draws (see sampleTrees) each number their own tree's inner nodes from
* getSizeOfLeavesArray() upwards, without treading on one another. */
private final ThreadLocal<int[]> runningInnerIndex = ThreadLocal.withInitial(() -> new int[1]);

/* Per-thread RNG installed by sampleTrees for the duration of one draw; null (meaning "use
* the shared `random`") on any thread that is not inside a parallel sampling run. */
private final ThreadLocal<Random> threadRandom = new ThreadLocal<>();

/**
* The RNG the calling thread should sample with: its own stream inside a
* {@link #sampleTrees} draw, otherwise the shared {@link #random} (so single-threaded
* sampling and {@link #setRandom} behave exactly as before).
*/
protected Random random() {
Random threadLocal = threadRandom.get();
return (threadLocal != null) ? threadLocal : random;
}

/* Helper for methods to assign indices to leaves */
// private int runningLeafIndex;
Expand All @@ -896,7 +970,7 @@ protected Tree getTreeBasedOnStrategy(SamplingStrategy samplingStrategy, HeightS
setupCommonAncestorHeightsIfDirty();
}

runningInnerIndex = this.getSizeOfLeavesArray();
runningInnerIndex.get()[0] = this.getSizeOfLeavesArray();
Node root = getVertexBasedOnStrategy(this.rootClade, samplingStrategy, heightStrategy);

if (this instanceof FilteredCCD) {
Expand Down Expand Up @@ -1019,7 +1093,7 @@ protected Node buildInternalVertex(Clade clade, CladePartition partition,

/** Returns and advances the running inner-node index used when materialising sampled trees. */
protected int nextRunningInnerIndex() {
return runningInnerIndex++;
return runningInnerIndex.get()[0]++;
}

/* Helper method */
Expand Down Expand Up @@ -1070,7 +1144,7 @@ protected CladePartition getPartitionBasedOnStrategy(Clade clade, SamplingStrate

// the sum of probabilities over all partitions of a clade
// should be 1, so we can sample with a random value
double sampleWithMe = random.nextDouble();
double sampleWithMe = random().nextDouble();

double probabilitySum = 0;
for (CladePartition nextPartition : partitions) {
Expand Down
Loading