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
47 changes: 26 additions & 21 deletions beast-fx/src/main/java/beastfx/app/tools/LogAnalyser.java
Original file line number Diff line number Diff line change
Expand Up @@ -250,21 +250,29 @@ protected void readLogFile(String fileName, int burnInPercentage) throws IOExcep
if (str.indexOf('#') < 0 && str.matches("[-0-9].*"))
if (++data >= 0 && data < m_fTraces[0].length)
for (String str2 : str.split("\\s")) {
try {
if (str2.indexOf('.') >= 0) {
m_types[i] = type.REAL;
if ("true".equalsIgnoreCase(str2) || "false".equalsIgnoreCase(str2)) {
// set true = 1 false = 0
m_types[i] = type.BOOL;
m_fTraces[i][data] = str2.equals("true") ? 1.0 : 0.0;
i++;
} else {
try {
if (str2.indexOf('.') >= 0) {
m_types[i] = type.REAL;
}
m_fTraces[i][data] = Double.parseDouble(str2);
} catch (Exception e) {

if (m_ranges[i] == null) {
m_ranges[i] = new ArrayList<>();
}
if (!m_ranges[i].contains(str2)) {
m_ranges[i].add(str2);
}
m_fTraces[i][data] = 1.0 * m_ranges[i].indexOf(str2);
}
m_fTraces[i][data] = Double.parseDouble(str2);
} catch (Exception e) {
if (m_ranges[i] == null) {
m_ranges[i] = new ArrayList<>();
}
if (!m_ranges[i].contains(str2)) {
m_ranges[i].add(str2);
}
m_fTraces[i][data] = 1.0 * m_ranges[i].indexOf(str2);
i++;
}
i++;
}
while (reported < 81 && 1000.0 * reported < 81000.0 * (data + 1)/ total) {
log("*");
Expand All @@ -274,12 +282,9 @@ protected void readLogFile(String fileName, int burnInPercentage) throws IOExcep
logln("");
// determine types
for (int i = 0; i < items; i++)
if (m_ranges[i] != null)
if (m_ranges[i].size() == 2 && m_ranges[i].contains("true") && m_ranges[i].contains("false") ||
m_ranges[i].size() == 1 && (m_ranges[i].contains("true") || m_ranges[i].contains("false")))
m_types[i] = type.BOOL;
else
m_types[i] = type.NOMINAL;
if (m_ranges[i] != null && (! m_types[i].equals(type.BOOL)) ) {
m_types[i] = type.NOMINAL;
}

fin.close();
} // readLogFile
Expand Down Expand Up @@ -323,8 +328,8 @@ public void calcStats(String [] tags) {
m_fStdDev[i] = Double.NaN;
}

if (m_types[i] == type.REAL || m_types[i] == type.INTEGER) {
// calc median, and 95% HPD interval
if (m_types[i] == type.REAL || m_types[i] == type.INTEGER || m_types[i] == type.BOOL) {
// calc median, and 95% HPD interval (BOOL traces are true = 1, false = 0)
Double[] sorted = trace.clone();
Arrays.sort(sorted);
m_fMedian[i] = sorted[trace.length / 2];
Expand Down
96 changes: 96 additions & 0 deletions beast-fx/src/test/java/test/beastfx/app/tools/LogAnalyserTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package test.beastfx.app.tools;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

import org.junit.jupiter.api.Test;

import beastfx.app.tools.LogAnalyser;

import static org.junit.jupiter.api.Assertions.*;

public class LogAnalyserTest {

/** writes a trace log with the given rows under the header Sample, x, y, z */
private static File log(String... rows) throws IOException {
File file = File.createTempFile("LogAnalyserTest", ".log");
file.deleteOnExit();
StringBuilder b = new StringBuilder("Sample\tx\ty\tz\n");
for (String row : rows) {
b.append(row).append('\n');
}
Files.writeString(file.toPath(), b.toString());
return file;
}

/**
* Boolean columns must read as true = 1 and false = 0, whichever value appears first.
*/
@Test
void testBooleansAreOneAndZero() throws IOException {
LogAnalyser analyser = new LogAnalyser(log(
"0\ttrue\tfalse\tA",
"1\tfalse\ttrue\tB",
"2\tfalse\ttrue\tA",
"3\tfalse\ttrue\tA").getPath(), 0, true, true);

// starts with true
assertArrayEquals(new Double[] {1.0, 0.0, 0.0, 0.0}, analyser.getTrace("x"));
assertEquals(0.25, analyser.getMean("x"), 1e-12);
// starts with false
assertArrayEquals(new Double[] {0.0, 1.0, 1.0, 1.0}, analyser.getTrace("y"));
assertEquals(0.75, analyser.getMean("y"), 1e-12);
// other nominal values are still numbered in order of first appearance
assertArrayEquals(new Double[] {0.0, 1.0, 0.0, 0.0}, analyser.getTrace("z"));
}

/** the same holds when burn-in removes the first rows */
@Test
void testBooleansAfterBurnIn() throws IOException {
LogAnalyser analyser = new LogAnalyser(log(
"0\tfalse\tfalse\tA",
"1\ttrue\tfalse\tA",
"2\tfalse\ttrue\tA",
"3\ttrue\ttrue\tA",
"4\ttrue\ttrue\tA",
"5\tfalse\ttrue\tA",
"6\ttrue\ttrue\tA",
"7\ttrue\ttrue\tA",
"8\ttrue\ttrue\tA",
"9\ttrue\ttrue\tA").getPath(), 10, true, true);

// burn-in 10% drops row 0; x now starts with true
assertArrayEquals(new Double[] {1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0}, analyser.getTrace("x"));
// y: one false, then eight true
assertEquals(8.0 / 9.0, analyser.getMean("y"), 1e-12);
}

/**
* A boolean column gets every statistic, the same as the 1/0 column it stands for:
* x is true/false, y the same values as 1/0.
*/
@Test
void testBooleanStatsMatchOneZero() throws IOException {
String[] rows = new String[200];
java.util.Random random = new java.util.Random(1);
boolean on = true;
for (int k = 0; k < rows.length; k++) {
if (random.nextDouble() < 0.2) {
on = !on;
}
rows[k] = (k * 1000) + "\t" + on + "\t" + (on ? 1 : 0) + "\t" + random.nextDouble();
}
LogAnalyser analyser = new LogAnalyser(log(rows).getPath(), 10, true, true);

assertEquals(analyser.getMean("y"), analyser.getMean("x"), 1e-12);
assertEquals(analyser.getStdError("y"), analyser.getStdError("x"), 1e-12);
assertEquals(analyser.getStdDev("y"), analyser.getStdDev("x"), 1e-12);
assertEquals(analyser.getMedian("y"), analyser.getMedian("x"), 1e-12);
assertEquals(analyser.get95HPDlow("y"), analyser.get95HPDlow("x"), 1e-12);
assertEquals(analyser.get95HPDup("y"), analyser.get95HPDup("x"), 1e-12);
assertEquals(analyser.getACT("y"), analyser.getACT("x"), 1e-12);
assertEquals(analyser.getESS("y"), analyser.getESS("x"), 1e-12);
assertFalse(Double.isNaN(analyser.getESS("x")));
}
}