Skip to content

Commit e1049f4

Browse files
committed
Add regression tests for Covariate constructor and GLM intervalNr fixes
CovariateTest covers the Covariate(Double[], String) constructor populating valuesInput so that initAndValidate does not clobber values. GLMTest covers single-epoch GLM rate lookups (firstlargerzero == 0), the case where the old hardcoded intervalNr = dim - 2 produced -1 and the downstream Ne/migration covariate lookup threw ArrayIndexOutOfBounds.
1 parent 8e14d8d commit e1049f4

2 files changed

Lines changed: 134 additions & 0 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package mascot.dynamics;
2+
3+
import beast.base.spec.domain.Real;
4+
import beast.base.spec.inference.parameter.BoolVectorParam;
5+
import beast.base.spec.inference.parameter.RealScalarParam;
6+
import beast.base.spec.inference.parameter.RealVectorParam;
7+
import mascot.glmmodel.Covariate;
8+
import mascot.glmmodel.CovariateList;
9+
import mascot.glmmodel.LogLinear;
10+
import org.junit.jupiter.api.Test;
11+
12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
15+
public class GLMTest {
16+
17+
/**
18+
* Regression for the single-epoch GLM intervalNr bug.
19+
* <p>
20+
* In a single-epoch model (rateShifts = [Infinity]), firstlargerzero == 0
21+
* and dimension == 1, so the boundary branch in getCoalescentRate / getNe /
22+
* getMig is always taken for any i. The old hardcoded fallback
23+
* intervalNr = dim - 2 evaluated to -1 for dim=1, causing
24+
* ArrayIndexOutOfBoundsException when rates were looked up. The fix
25+
* uses dim - firstlargerzero - 1, which is the correct last valid
26+
* interval index for any rateShifts configuration.
27+
*/
28+
@Test
29+
public void testSingleEpochGLMRatesDoNotThrow() {
30+
int dim = 2;
31+
32+
// single-epoch rate shifts: dim = 1 with the only value > 0, so firstlargerzero = 0
33+
RateShifts rateShifts = new RateShifts();
34+
rateShifts.initByName("value", "1.0");
35+
36+
GLM glm = buildGLM(dim, rateShifts);
37+
38+
double[] coalRate = glm.getCoalescentRate(0);
39+
assertEquals(dim, coalRate.length);
40+
for (double v : coalRate)
41+
assertTrue(Double.isFinite(v), "coalescent rate must be finite, got " + v);
42+
43+
for (int s = 0; s < dim; s++) {
44+
double ne = glm.getNe(s, 0);
45+
assertTrue(Double.isFinite(ne), "Ne must be finite, got " + ne);
46+
}
47+
48+
double mig = glm.getMig(0, 1, 0);
49+
assertTrue(Double.isFinite(mig), "migration rate must be finite, got " + mig);
50+
}
51+
52+
private GLM buildGLM(int dim, RateShifts rateShifts) {
53+
LogLinear migGLM = buildLogLinear(dim * (dim - 1));
54+
LogLinear neGLM = buildLogLinear(dim);
55+
56+
GLM glm = new GLM();
57+
glm.initByName(
58+
"dimension", dim,
59+
"rateShifts", rateShifts,
60+
"migrationGLM", migGLM,
61+
"NeGLM", neGLM,
62+
"types", "a b");
63+
return glm;
64+
}
65+
66+
private LogLinear buildLogLinear(int covariateDim) {
67+
Double[] vals = new Double[covariateDim];
68+
for (int i = 0; i < covariateDim; i++)
69+
vals[i] = 1.0;
70+
71+
Covariate cov = new Covariate(vals, "cov");
72+
cov.initAndValidate();
73+
74+
CovariateList covList = new CovariateList();
75+
covList.initByName("covariates", cov);
76+
77+
RealVectorParam<Real> scaler = new RealVectorParam<>(new double[]{0.0}, Real.INSTANCE);
78+
BoolVectorParam indicator = new BoolVectorParam(new boolean[]{true});
79+
RealScalarParam<Real> clock = new RealScalarParam<>();
80+
clock.initByName("value", "1.0");
81+
82+
LogLinear glm = new LogLinear();
83+
glm.initByName(
84+
"covariateList", covList,
85+
"scaler", scaler,
86+
"indicator", indicator,
87+
"clock", clock);
88+
return glm;
89+
}
90+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package mascot.glmmodel;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static org.junit.jupiter.api.Assertions.assertEquals;
6+
7+
public class CovariateTest {
8+
9+
@Test
10+
public void testDoubleArrayConstructorPopulatesValuesInput() {
11+
Double[] vals = new Double[]{1.0, 2.0, 3.0};
12+
Covariate c = new Covariate(vals, "test");
13+
assertEquals(3, c.valuesInput.get().size());
14+
assertEquals(1.0, c.valuesInput.get().get(0));
15+
assertEquals(2.0, c.valuesInput.get().get(1));
16+
assertEquals(3.0, c.valuesInput.get().get(2));
17+
}
18+
19+
@Test
20+
public void testInitAndValidateAfterDoubleArrayConstructorPreservesValues() {
21+
// Regression for the bug where initAndValidate() rebuilt `values` from an
22+
// empty valuesInput, clobbering the values set by the constructor.
23+
Double[] vals = new Double[]{4.0, 5.0, 6.0};
24+
Covariate c = new Covariate(vals, "test");
25+
c.initAndValidate();
26+
assertEquals(3, c.getDimension());
27+
assertEquals(4.0, c.getArrayValue(0));
28+
assertEquals(5.0, c.getArrayValue(1));
29+
assertEquals(6.0, c.getArrayValue(2));
30+
}
31+
32+
@Test
33+
public void testInitAndValidateViaValuesInputStillWorks() {
34+
// Existing code path: default constructor + valuesInput populated externally.
35+
Covariate c = new Covariate();
36+
c.valuesInput.get().add(7.0);
37+
c.valuesInput.get().add(8.0);
38+
c.setID("test");
39+
c.initAndValidate();
40+
assertEquals(2, c.getDimension());
41+
assertEquals(7.0, c.getArrayValue(0));
42+
assertEquals(8.0, c.getArrayValue(1));
43+
}
44+
}

0 commit comments

Comments
 (0)