Skip to content

Commit 11feb9f

Browse files
committed
#558 WWZ: implement threaded execution with auto-benefit heuristic
Implement parallel WWZ execution across tau ranges with deterministic output ordering, add an auto-benefit heuristic to fall back to single-thread for smaller workloads, and add a recommended-thread getter used by the WWZ parameter dialog default. This commit also updates WWZ benchmarking code to force single-thread when comparing matinv variants, reruns WWZ unit tests, and reruns thread-scaling benchmarks (including V_VStar.csv) to validate behavior and performance. Acknowledgement: I implemented all of this with assistance from Cursor AI. Made-with: Cursor
1 parent 7e66002 commit 11feb9f

3 files changed

Lines changed: 119 additions & 38 deletions

File tree

src/org/aavso/tools/vstar/plugin/period/impl/WeightedWaveletZTransformPluginBase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public String getGroup() {
9292
public void reset() {
9393
currDecay = 0.001;
9494
currTimeDivisions = 50.0;
95-
currThreadCount = Math.max(1, Runtime.getRuntime().availableProcessors());
95+
currThreadCount = WeightedWaveletZTransform.getRecommendedThreadCount();
9696
}
9797

9898
/**

src/org/aavso/tools/vstar/util/period/wwz/WeightedWaveletZTransform.java

Lines changed: 116 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919

2020
import java.util.ArrayList;
2121
import java.util.List;
22+
import java.util.concurrent.Callable;
23+
import java.util.concurrent.ExecutionException;
24+
import java.util.concurrent.ExecutorService;
25+
import java.util.concurrent.Executors;
26+
import java.util.concurrent.Future;
2227

2328
import org.aavso.tools.vstar.data.ValidObservation;
2429
import org.aavso.tools.vstar.exception.AlgorithmError;
@@ -60,6 +65,8 @@ public class WeightedWaveletZTransform implements IAlgorithm {
6065
private static final double WEIGHT_CUTOFF = 1.0e-9;
6166
private static final double NEG_LOG_WEIGHT_CUTOFF = -Math.log(WEIGHT_CUTOFF);
6267
private static final int MAX_AVAILABLE_THREADS = Math.max(1, Runtime.getRuntime().availableProcessors());
68+
private static final long MIN_PARALLEL_GRID_POINTS = 5000L;
69+
private static final int MIN_PARALLEL_OBSERVATIONS = 1000;
6370

6471
// Observations to be analysed.
6572
private List<ValidObservation> obs;
@@ -77,7 +84,6 @@ public class WeightedWaveletZTransform implements IAlgorithm {
7784
private double maxWWZ;
7885

7986
private double dcon;
80-
private double dmat[][] = new double[3][3];
8187
private double dt[];
8288
private double dx[];
8389
private double fhi;
@@ -88,7 +94,7 @@ public class WeightedWaveletZTransform implements IAlgorithm {
8894
private int numdat;
8995
private double tau[];
9096

91-
private boolean interrupted;
97+
private volatile boolean interrupted;
9298
private int threadCount;
9399

94100
/**
@@ -200,6 +206,14 @@ public int getMaxAvailableThreads() {
200206
return MAX_AVAILABLE_THREADS;
201207
}
202208

209+
/**
210+
* Recommended thread count for UI defaults. This reflects machine capacity;
211+
* execute() applies a workload heuristic and may still choose fewer threads.
212+
*/
213+
public static int getRecommendedThreadCount() {
214+
return MAX_AVAILABLE_THREADS;
215+
}
216+
203217
/**
204218
* @return the stats
205219
*/
@@ -442,51 +456,46 @@ public void make_freqs_from_period_range(double minPer, double maxPer,
442456
}
443457

444458
/**
445-
* Invert the 3×3 symmetric matrix in dmat (in-place).
446-
* Uses a closed-form cofactor formula. Faster than Gauss-Jordan for fixed 3×3,
447-
* no pivoting logic, and avoids the per-(tau,freq) loop overhead.
459+
* Invert a 3x3 symmetric matrix in place.
448460
*/
449-
private void matinv() throws InterruptedException {
461+
private void matinv(double[][] mat) throws InterruptedException {
450462
if (useLegacyMatinv) {
451-
matinvGaussJordan();
463+
matinvGaussJordan(mat);
452464
return;
453465
}
454-
// Symmetric 3×3: a=dmat[0][0], b=dmat[0][1], c=dmat[0][2], d=dmat[1][1], e=dmat[1][2], f=dmat[2][2]
455-
double a = dmat[0][0], b = dmat[0][1], c = dmat[0][2];
456-
double d = dmat[1][1], e = dmat[1][2], f = dmat[2][2];
466+
double a = mat[0][0], b = mat[0][1], c = mat[0][2];
467+
double d = mat[1][1], e = mat[1][2], f = mat[2][2];
457468

458-
// det = a(df - e²) - b(bf - ce) + c(be - cd)
459469
double det = a * (d * f - e * e) - b * (b * f - c * e) + c * (b * e - c * d);
460470
if (Math.abs(det) <= 1.0e-14) {
461-
return; // singular, leave dmat unchanged (matches previous behaviour)
471+
return;
462472
}
463473
double invDet = 1.0 / det;
464474

465475
if (interrupted) {
466476
throw new InterruptedException();
467477
}
468478

469-
// Adjugate (symmetric for symmetric input), then inv = adj/det
470479
double a00 = (d * f - e * e) * invDet;
471480
double a01 = (c * e - b * f) * invDet;
472481
double a02 = (b * e - c * d) * invDet;
473482
double a11 = (a * f - c * c) * invDet;
474483
double a12 = (c * b - a * e) * invDet;
475484
double a22 = (a * d - b * b) * invDet;
476485

477-
dmat[0][0] = a00;
478-
dmat[0][1] = a01;
479-
dmat[0][2] = a02;
480-
dmat[1][0] = a01;
481-
dmat[1][1] = a11;
482-
dmat[1][2] = a12;
483-
dmat[2][0] = a02;
484-
dmat[2][1] = a12;
485-
dmat[2][2] = a22;
486+
mat[0][0] = a00;
487+
mat[0][1] = a01;
488+
mat[0][2] = a02;
489+
mat[1][0] = a01;
490+
mat[1][1] = a11;
491+
mat[1][2] = a12;
492+
mat[2][0] = a02;
493+
mat[2][1] = a12;
494+
mat[2][2] = a22;
486495
}
487496

488-
/** Legacy Gauss-Jordan 3×3 in-place inverse; used only when useLegacyMatinv is true (benchmark). */
489-
private void matinvGaussJordan() throws InterruptedException {
497+
/** Legacy Gauss-Jordan 3x3 in-place inverse; used only when useLegacyMatinv is true (benchmark). */
498+
private void matinvGaussJordan(double[][] mat) throws InterruptedException {
490499
double dsol[][] = new double[3][3];
491500
double dfac;
492501
int ndim = 2;
@@ -500,13 +509,13 @@ private void matinvGaussJordan() throws InterruptedException {
500509
}
501510
}
502511
for (int i = 0; i <= ndim; i++) {
503-
if (dmat[i][i] == 0.0) {
512+
if (mat[i][i] == 0.0) {
504513
if (i == ndim)
505514
return;
506515
for (int j = i + 1; j <= ndim; j++) {
507-
if (dmat[j][i] != 0.0) {
516+
if (mat[j][i] != 0.0) {
508517
for (int k = 0; k <= ndim; k++) {
509-
dmat[i][k] = dmat[i][k] + dmat[j][k];
518+
mat[i][k] = mat[i][k] + mat[j][k];
510519
dsol[i][k] = dsol[i][k] + dsol[j][k];
511520
}
512521
}
@@ -515,16 +524,16 @@ private void matinvGaussJordan() throws InterruptedException {
515524
throw new InterruptedException();
516525
}
517526
}
518-
dfac = dmat[i][i];
527+
dfac = mat[i][i];
519528
for (int j = 0; j <= ndim; j++) {
520-
dmat[i][j] = dmat[i][j] / dfac;
529+
mat[i][j] = mat[i][j] / dfac;
521530
dsol[i][j] = dsol[i][j] / dfac;
522531
}
523532
for (int j = 0; j <= ndim; j++) {
524533
if (j != i) {
525-
dfac = dmat[j][i];
534+
dfac = mat[j][i];
526535
for (int k = 0; k <= ndim; k++) {
527-
dmat[j][k] = dmat[j][k] - (dmat[i][k] * dfac);
536+
mat[j][k] = mat[j][k] - (mat[i][k] * dfac);
528537
dsol[j][k] = dsol[j][k] - (dsol[i][k] * dfac);
529538
}
530539
if (interrupted) {
@@ -535,7 +544,7 @@ private void matinvGaussJordan() throws InterruptedException {
535544
}
536545
for (int i = 0; i <= ndim; i++) {
537546
for (int j = 0; j <= ndim; j++) {
538-
dmat[i][j] = dsol[i][j];
547+
mat[i][j] = dsol[i][j];
539548
}
540549
if (interrupted) {
541550
throw new InterruptedException();
@@ -544,8 +553,80 @@ private void matinvGaussJordan() throws InterruptedException {
544553
}
545554

546555
private void wwt() throws InterruptedException {
556+
final WWZStatistic[] statsOut = new WWZStatistic[ntau * nfreq];
557+
final WWZStatistic[] maxOut = new WWZStatistic[ntau];
558+
final int effectiveThreadCount = getEffectiveThreadCount();
559+
560+
if (effectiveThreadCount <= 1 || ntau <= 1) {
561+
processTauRange(1, ntau, statsOut, maxOut);
562+
} else {
563+
int threads = Math.min(effectiveThreadCount, ntau);
564+
ExecutorService executor = Executors.newFixedThreadPool(threads);
565+
List<Future<Void>> futures = new ArrayList<Future<Void>>();
566+
int chunk = (ntau + threads - 1) / threads;
567+
for (int t = 0; t < threads; t++) {
568+
final int tauStart = 1 + t * chunk;
569+
final int tauEnd = Math.min(ntau, tauStart + chunk - 1);
570+
if (tauStart > tauEnd) {
571+
continue;
572+
}
573+
futures.add(executor.submit(new Callable<Void>() {
574+
@Override
575+
public Void call() throws Exception {
576+
processTauRange(tauStart, tauEnd, statsOut, maxOut);
577+
return null;
578+
}
579+
}));
580+
}
581+
try {
582+
for (Future<Void> f : futures) {
583+
f.get();
584+
}
585+
} catch (ExecutionException e) {
586+
Throwable cause = e.getCause();
587+
interrupted = true;
588+
if (cause instanceof InterruptedException) {
589+
throw (InterruptedException) cause;
590+
}
591+
throw new RuntimeException(cause);
592+
} catch (InterruptedException e) {
593+
interrupted = true;
594+
throw e;
595+
} finally {
596+
executor.shutdownNow();
597+
}
598+
}
599+
600+
stats = new ArrayList<WWZStatistic>(statsOut.length);
601+
maximalStats = new ArrayList<WWZStatistic>(maxOut.length);
602+
for (WWZStatistic stat : statsOut) {
603+
if (stat != null) {
604+
stats.add(stat);
605+
}
606+
}
607+
for (WWZStatistic maxStat : maxOut) {
608+
if (maxStat != null) {
609+
maximalStats.add(maxStat);
610+
}
611+
}
612+
}
613+
614+
private int getEffectiveThreadCount() {
615+
if (threadCount <= 1) {
616+
return 1;
617+
}
618+
long gridPoints = (long) ntau * (long) Math.max(1, nfreq);
619+
if (gridPoints < MIN_PARALLEL_GRID_POINTS || numdat < MIN_PARALLEL_OBSERVATIONS) {
620+
return 1;
621+
}
622+
return threadCount;
623+
}
624+
625+
private void processTauRange(int itau1, int itau2, WWZStatistic[] statsOut, WWZStatistic[] maxOut)
626+
throws InterruptedException {
547627
double dvec[] = new double[3];
548628
double dcoef[] = new double[3];
629+
double[][] dmat = new double[3][3];
549630
int itau, ifreq, idat;
550631
double domega, dweight2, dz, dweight;
551632
double dcc, dcw, dss, dsw, dxw, dvarw;
@@ -569,8 +650,6 @@ private void wwt() throws InterruptedException {
569650
double dz2Cutoff = (dcon > 0.0) ? (NEG_LOG_WEIGHT_CUTOFF / dcon) : Double.POSITIVE_INFINITY;
570651

571652
int ndim = 2;
572-
int itau1 = 1;
573-
int itau2 = ntau;
574653
int ifreq1 = 1;
575654
int ifreq2 = nfreq;
576655

@@ -679,7 +758,7 @@ private void wwt() throws InterruptedException {
679758
}
680759
}
681760

682-
matinv();
761+
matinv(dmat);
683762

684763
for (n1 = 0; n1 <= ndim; n1++) {
685764
for (n2 = 0; n2 <= ndim; n2++) {
@@ -724,7 +803,7 @@ private void wwt() throws InterruptedException {
724803
WWZStatistic stat = new WWZStatistic(dtau, dfre, dpowz, damp,
725804
dcoef[0], dneff);
726805

727-
stats.add(stat);
806+
statsOut[(itau - 1) * nfreq + (ifreq - 1)] = stat;
728807

729808
if (dpowz > dmz) {
730809
dmz = dpowz;
@@ -739,7 +818,7 @@ private void wwt() throws InterruptedException {
739818
WWZStatistic maximalStat = new WWZStatistic(dtau, dmfre, dmz,
740819
dmamp, dmcon, dmneff);
741820

742-
maximalStats.add(maximalStat);
821+
maxOut[itau - 1] = maximalStat;
743822
}
744823
}
745824

test/org/aavso/tools/vstar/util/period/wwz/WWZMatinvBenchmarkTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ private void runWarmup(java.util.List<org.aavso.tools.vstar.data.ValidObservatio
6868
double decay, double timeDivisions, double minFreq, double maxFreq, double deltaFreq) throws Exception {
6969
for (int i = 0; i < WARMUP; i++) {
7070
WeightedWaveletZTransform wwt = new WeightedWaveletZTransform(obs, decay, timeDivisions);
71+
wwt.setThreadCount(1);
7172
wwt.make_freqs_from_freq_range(minFreq, maxFreq, deltaFreq);
7273
wwt.execute();
7374
}
@@ -78,6 +79,7 @@ private long runIterations(java.util.List<org.aavso.tools.vstar.data.ValidObserv
7879
long start = System.nanoTime();
7980
for (int i = 0; i < n; i++) {
8081
WeightedWaveletZTransform wwt = new WeightedWaveletZTransform(obs, decay, timeDivisions);
82+
wwt.setThreadCount(1);
8183
wwt.make_freqs_from_freq_range(minFreq, maxFreq, deltaFreq);
8284
wwt.execute();
8385
}

0 commit comments

Comments
 (0)