/*
* Created on Jan 9, 2004
*/
package de.torstennahm.integrate.sparse.visualize;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.text.DecimalFormat;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.border.BevelBorder;
import de.torstennahm.integrate.IntegrationFailedException;
import de.torstennahm.integrate.sparse.evaluateindex.Evaluator;
import de.torstennahm.integrate.sparse.index.FlatIndexGenerator;
import de.torstennahm.integrate.sparse.index.Index;
import de.torstennahm.integrate.visualize.Visualizer;
import de.torstennahm.integrate.visualizerdata.Integrand;
import de.torstennahm.integrate.visualizerdata.StopIntegration;
import de.torstennahm.integrate.visualizerdata.VisualizerData;
import de.torstennahm.math.MathTN;
import de.torstennahm.util.Util;
/**
* This visualizer displays two sets of indices. The head set is displayed in the
* upper panel and shows the integral contributions for all indices that have
* been evaluated by the integrator. The tail set consists of all those indices
* that have not been evaluated, and is displayed in the lower panel.
* It is generated by a background thread after integration finishes.
* The logarithms of the absolute values are displayed, with black marking positive
* and red marking negative values.
*
* @author Torsten Nahm
*/
public class ContributionVisualizer implements Visualizer {
private final JFrame frame;
private String title;
private JPanel[] indexPanel;
private JPanel mainPanel;
private JTextField ratingField;
private JScrollBar scrollBar;
private JScrollBar zoomYBar;
private JToggleButton activeButton;
private ContributionWorker worker = null;
private Set<Index> evaluatedIndices = new TreeSet<Index>();
/* synchronized by class */
private boolean destroyed = false;
/* synchronized by lock */
Object lock = new Object();
boolean stopped;
boolean active;
Evaluator evaluator;
/* synchronized by headList */
private List<IndexEntry> headList = new LinkedList<IndexEntry>();
/* synchronized by tailSet */
private SortedSet<IndexEntry> tailSet = new TreeSet<IndexEntry>();
public ContributionVisualizer(JFrame frame) {
this.frame = frame;
if (frame != null) {
title = frame.getTitle();
}
}
public void init() {
if (destroyed) { return; }
if (frame != null) {
initGUI();
worker = new ContributionWorker(this);
worker.start();
}
}
private void initGUI() {
indexPanel = new JPanel[2];
mainPanel = new JPanel();
ratingField = new JTextField();
scrollBar = new JScrollBar(JScrollBar.HORIZONTAL);
zoomYBar = new JScrollBar(JScrollBar.VERTICAL, -120, 1, -150, -40);
activeButton = new JToggleButton("Active");
for (int i = 0; i < 2; i++) {
indexPanel[i] = new IndexPanel(i);
indexPanel[i].setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
}
ratingField.setEditable(false);
GridBagLayout g = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
mainPanel.setLayout(g);
c.fill = GridBagConstraints.BOTH;
c.weightx = 1.0;
c.gridwidth = GridBagConstraints.REMAINDER;
JPanel panel = new JPanel();
GridBagConstraints cp = new GridBagConstraints();
cp.fill = GridBagConstraints.BOTH;
panel.setLayout(new GridBagLayout());
cp.weightx = 3.0;
panel.add(ratingField, cp);
cp.weightx = 0.0;
panel.add(activeButton, cp);
c.weighty = 0.0;
mainPanel.add(panel, c);
panel = new JPanel();
panel.setLayout(new GridBagLayout());
cp.gridx = 0;
cp.gridy = 0;
cp.gridheight = 1;
cp.weightx = 1.0;
cp.weighty = 1.0;
panel.add(indexPanel[0], cp);
cp.gridx++;
cp.gridheight = 2;
cp.weightx = 0.0;
cp.gridwidth = GridBagConstraints.REMAINDER;
panel.add(zoomYBar, cp);
cp.gridx = 0;
cp.gridy++;
cp.gridwidth = 1;
cp.gridheight = 1;
cp.weightx = 1.0;
panel.add(indexPanel[1], cp);
c.weighty = 1.0;
mainPanel.add(panel, c);
c.weighty = 0.0;
mainPanel.add(scrollBar, c);
AdjustmentListener listener = new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
indexPanel[0].repaint();
indexPanel[1].repaint();
}
};
scrollBar.addAdjustmentListener(listener);
zoomYBar.addAdjustmentListener(listener);
frame.getContentPane().add(mainPanel);
activeButton.setSelected(false);
activeButton.setEnabled(false);
activeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
updateWorkerStatus();
}
});
frame.setVisible(true);
}
public void start() {
if (destroyed) { return; }
synchronized (headList) {
headList = new LinkedList<IndexEntry>();
}
synchronized (tailSet) {
tailSet = new TreeSet<IndexEntry>();
}
evaluatedIndices = new HashSet<Index>();
synchronized (lock) {
active = false;
evaluator = null;
}
updateWorkerStatus();
if (frame != null) {
activeButton.setEnabled(true);
frame.setTitle(title);
ratingField.setText("No rating data available");
indexPanel[0].repaint();
indexPanel[1].repaint();
}
}
public void submit(VisualizerData data) {
if (destroyed) { return; }
if (data instanceof Integrand) {
if (((Integrand) data).integrand instanceof Evaluator) {
evaluator = (Evaluator) ((Integrand) data).integrand;
}
} else if (data instanceof StopIntegration) {
updateDisplay();
synchronized (lock) {
active = true;
}
updateWorkerStatus();
if (evaluator != null && worker != null) {
worker.startWorker(evaluator, evaluatedIndices, tailSet);
}
} else if (data instanceof IndexContribution) {
Index index = ((IndexContribution) data).index;
double contribution = ((IndexContribution) data).contribution;
double relResult = Double.NaN;
if (evaluator != null) {
relResult = contribution / evaluator.pointsForIndex(index);
synchronized (headList) {
headList.add(new IndexEntry(relResult));
}
if (frame != null) {
indexPanel[0].repaint();
}
}
evaluatedIndices.add(index);
}
}
public void stop() {
if (destroyed) { return; }
if (frame != null) {
frame.setTitle(title + " (stopped)");
activeButton.setEnabled(false);
}
synchronized (lock) {
active = false;
}
updateWorkerStatus();
}
synchronized public void destroy() {
if (destroyed) { return; }
destroyed = true;
if (worker != null) {
worker.terminate();
}
if (frame != null) {
frame.dispose();
}
}
private void updateWorkerStatus() {
synchronized (lock) {
if (worker != null) {
worker.setActive(active && activeButton.isSelected());
}
}
}
public void updateDisplay() {
if (frame != null) {
double[] rating;
synchronized (tailSet) {
rating = getRatings(headList, tailSet);
scrollBar.setValues(scrollBar.getValue(), headList.size(), 0, tailSet.size());
}
DecimalFormat df = new DecimalFormat("0.####");
ratingField.setText("Quotient:" + Util.format(rating[0]) + " Plain: " + df.format(rating[1]) + " Weighted: " + df.format(rating[2]));
indexPanel[1].repaint();
}
}
public double[] getRatings(long numPoints) {
if (evaluator != null) {
SortedSet<IndexEntry> tailSet = new TreeSet<IndexEntry>();
FlatIndexGenerator indexIter = new FlatIndexGenerator(evaluator.dimension());
long points = 0;
while (points < numPoints) {
Index index = indexIter.next();
if (! evaluatedIndices.contains(index)) {
try {
double contribution = evaluator.deltaEvaluate(index);
points += evaluator.pointsForIndex(index);
double relResult = contribution / evaluator.pointsForIndex(index);
tailSet.add(new IndexEntry(relResult));
} catch (IntegrationFailedException e) {
}
}
}
}
return getRatings(headList, tailSet);
}
static private double[] getRatings(List<IndexEntry> headList, SortedSet<IndexEntry> tailSet) {
SortedSet<IndexEntry> allSet;
int headSize;
double headContributionSum = 0;
double tailContributionSum = 0;
headSize = headList.size();
allSet = new TreeSet<IndexEntry>();
allSet.addAll(headList);
allSet.addAll(tailSet);
for (IndexEntry entry : headList) {
headContributionSum += Math.abs(entry.relR);
}
for (IndexEntry entry : tailSet) {
tailContributionSum += Math.abs(entry.relR);
}
int c1 = 0, c2 = 0;
for (IndexEntry entry : tailSet) {
int pos = allSet.headSet(entry).size();
if (pos >= headSize) {
break;
}
c1++;
c2 += (headSize - pos);
}
double r1 = tailContributionSum / headContributionSum;
double r2 = (double)c1 / headSize;
double r3 = (double)c2 / (headSize * (headSize + 1) / 2);
return new double[] { r1, r2, r3 };
}
static public class IndexEntry implements Comparable {
static private int current = 0;
public final double relR;
public final int count;
IndexEntry(double relResult) {
relR = relResult;
count = current++;
}
public int compareTo(Object o) {
IndexEntry i = (IndexEntry) o;
if (Math.abs(relR) < Math.abs(i.relR)) {
return 1;
} else if (Math.abs(relR) > Math.abs(i.relR)) {
return -1;
} else {
return count - i.count;
}
}
}
private class IndexPanel extends JPanel {
private static final long serialVersionUID = -3416547179794894270L;
private int listNumber;
public IndexPanel(int list) {
this.listNumber = list;
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
int barY;
int p = -14;
do {
g.setColor((p % 5 == 0) ? new Color(0, 0, 0.6f) : Color.GRAY);
double barValue = Math.pow(10.0, p);
barY = (int) (-foldY(barValue) * height + height);
g.drawLine(0, barY, width - 1, barY);
p++;
} while (barY >= 0);
int headEntries = 0;
Iterator<IndexEntry> iter = null;
int start = 0;
if (listNumber == 0) {
synchronized (headList) {
iter = new LinkedList<IndexEntry>(headList).iterator();
headEntries = headList.size();
}
start = 0;
} else if (listNumber == 1) {
synchronized (headList) {
headEntries = headList.size();
}
synchronized (tailSet) {
iter = new LinkedList<IndexEntry>(tailSet).iterator();
}
start = scrollBar.getValue();
}
for (int i = 0; i < start && iter.hasNext(); i++) {
iter.next();
}
if (iter != null) {
int x, lastx = -1, y, lasty = 0;
for (int i = 0; i < headEntries && iter.hasNext(); i++) {
IndexEntry entry = iter.next();
x = (int) (foldX(i, headEntries) * width);
double h = Math.abs(entry.relR);
y = (int) (-foldY(h) * height + height);
g.setColor(entry.relR >= 0 ? Color.BLACK : Color.RED);
if (lastx != -1) {
g.drawLine(lastx, lasty, x, y);
}
lastx = x;
lasty = y;
}
}
// if (! efficiency && regressionVisualizer != null) {
// g.setColor(Color.GREEN);
// double a, b;
// a = regressionVisualizer.getSlope();
// b = regressionVisualizer.getYIntercept();
//
// int firstIndex = (listNumber == 0) ? start : start + headEntries;
// int y0 = (int) (-foldY(Math.exp(b + a * firstIndex)) * height + height);
// int y1 = (int) (-foldY(Math.exp(b + a * (firstIndex + headEntries))) * height + height);
// g.drawLine(0, y0, width, y1);
// }
}
}
private double foldX(double x, double maxX) {
return x / maxX;
}
private double foldY(double y) {
double mul = Math.pow(10.0, zoomYBar.getValue() / 100.0);
double yy = MathTN.log10(y / 1e-14) * mul;
return Math.max(0, yy);
}
}