diff --git a/Figaro/figaro_build.properties b/Figaro/figaro_build.properties index 811a88b7..1c22dc9d 100644 --- a/Figaro/figaro_build.properties +++ b/Figaro/figaro_build.properties @@ -1 +1 @@ -version=3.2.1.0 +version=3.2.1.1 diff --git a/Figaro/src/main/scala/com/cra/figaro/library/atomic/continuous/Exponential.scala b/Figaro/src/main/scala/com/cra/figaro/library/atomic/continuous/Exponential.scala index 553b73a2..dac59eb5 100644 --- a/Figaro/src/main/scala/com/cra/figaro/library/atomic/continuous/Exponential.scala +++ b/Figaro/src/main/scala/com/cra/figaro/library/atomic/continuous/Exponential.scala @@ -20,7 +20,7 @@ import scala.math.{ log, exp } /** * An exponential distribution in which the parameter is a constant. */ -class AtomicExponential(name: Name[Double], lambda: Double, collection: ElementCollection) +class AtomicExponential(name: Name[Double], val lambda: Double, collection: ElementCollection) extends Element[Double](name, collection) with Atomic[Double] { type Randomness = Double diff --git a/Figaro/src/main/scala/com/cra/figaro/util/ColorGradient.scala b/Figaro/src/main/scala/com/cra/figaro/util/ColorGradient.scala index 51577bee..af6aabd9 100644 --- a/Figaro/src/main/scala/com/cra/figaro/util/ColorGradient.scala +++ b/Figaro/src/main/scala/com/cra/figaro/util/ColorGradient.scala @@ -1,5 +1,14 @@ -/** - * +/* + * ColorGradient.scala + * A Factory for color schemes to be used in tables, charts, histograms, etc + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Apr 9, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. */ package com.cra.figaro.util diff --git a/Figaro/src/main/scala/com/cra/figaro/util/visualization/DataView.scala b/Figaro/src/main/scala/com/cra/figaro/util/visualization/DataView.scala new file mode 100644 index 00000000..e21dc505 --- /dev/null +++ b/Figaro/src/main/scala/com/cra/figaro/util/visualization/DataView.scala @@ -0,0 +1,36 @@ +/* + * DataView.scala + * Internal data repository/method for use by tables, charts, histograms, etc + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Apr 9, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. + */ +package com.cra.figaro.util.visualization + +import prefuse.data.Table +import prefuse.data.query.NumberRangeModel +import prefuse.util.ui.ValuedRangeModel + +/** + * @author Glenn Takata (gtakata@cra.com) + * + * Mar 17, 2015 + */ +trait DataView { + def name: String + def title: String + def range: ValuedRangeModel + + def nValues: Int + + def getTable: Table + + def yMax: Double + def dataType: Int + def yRangeModel: NumberRangeModel +} \ No newline at end of file diff --git a/Figaro/src/main/scala/com/cra/figaro/util/visualization/ResultsGUI.scala b/Figaro/src/main/scala/com/cra/figaro/util/visualization/ResultsGUI.scala index 07839117..36e132cd 100644 --- a/Figaro/src/main/scala/com/cra/figaro/util/visualization/ResultsGUI.scala +++ b/Figaro/src/main/scala/com/cra/figaro/util/visualization/ResultsGUI.scala @@ -1,21 +1,36 @@ +/* + * ResultsGUI.scala + * The main controller for visualizations. + * Coordinates data input and display as well as user interaction with displays. + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Mar 16, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. + */ package com.cra.figaro.util.visualization import scala.swing._ -import com.cra.figaro.util.visualization.results.{ ResultsTable, ResultsView } +import com.cra.figaro.language.{Element} +import com.cra.figaro.util.visualization.results.{ ContinuousData, DiscreteData, ResultsData, ResultsTable, ResultsView } import com.cra.figaro.util.visualization.histogram.{ Histogram } +import com.cra.figaro.util.visualization.distribution.{Distribution} +import com.cra.figaro.util.visualization.reduction.DataReduction import com.cra.figaro.util.ColorGradient import scala.swing.event.Event import scala.swing.event.TableRowsSelected -import com.cra.figaro.util.visualization.results.ResultsData /** * @author Glenn Takata (gtakata@cra.com) */ -case class NewResult(result: ResultsData[_]) extends Event +case class NewResult(result: ResultsData) extends Event class ResultHandler extends Publisher { - def newResult(result: ResultsData[_]) { + def newResult(result: ResultsData) { Swing.onEDT( publish(NewResult(result)) ) @@ -28,14 +43,19 @@ class EmptyTab extends BoxPanel(Orientation.Vertical) { } object ResultsGUI extends SimpleSwingApplication { - val TAB_WIDTH = 400 + val TAB_WIDTH = 600 val TAB_HEIGHT = 300 - val TABLE_WIDTH = 400 + val TABLE_WIDTH = 600 val TABLE_HEIGHT = 250 val results = new ResultHandler - def addResult(result: ResultsData[_]) { +// def addResult(result: ResultsData) { + def addResult(name: String, dist: Any) { + val result = dist match { + case l: List[(Double, Double)] => DiscreteData(name, DataReduction.binToDistribution(l)) + case e: Element[_] => ContinuousData(name, e) + } results.newResult(result) } @@ -89,18 +109,31 @@ object ResultsGUI extends SimpleSwingApplication { } case TableRowsSelected(source, range, false) => { val row = table.getSelectedRow - println(row) +// println(row) updateHistogram(row) mainPanel.revalidate() mainPanel.repaint } } - private def updateHistogram(result: ResultsData[_]) { + private def updateHistogram(result: ResultsData) { graphs.pages.clear() - - val histogram = new Histogram(new ResultsView(result), currentColor) - graphs.pages += new TabbedPane.Page(result.name, histogram) + + result match { + case DiscreteData(name, dist) => { + val color = currentColor + val histogramTab = new Histogram(new ResultsView(result), color) + graphs.pages += new TabbedPane.Page(result.name + " Distribution", histogramTab) + } + case ContinuousData(name, dist) => { + val color = ColorGradient.HEATMAP + val distributionTab = new Distribution(new ResultsView(result), color) + graphs.pages += new TabbedPane.Page(result.name + " Density", distributionTab) + + } + case _ => + } + graphs.revalidate graphs.repaint } diff --git a/Figaro/src/main/scala/com/cra/figaro/util/visualization/distribution/Distribution.scala b/Figaro/src/main/scala/com/cra/figaro/util/visualization/distribution/Distribution.scala new file mode 100644 index 00000000..85cf5691 --- /dev/null +++ b/Figaro/src/main/scala/com/cra/figaro/util/visualization/distribution/Distribution.scala @@ -0,0 +1,237 @@ +/* + * Distribution.scala + * Setup and display distributions based on continuous element data + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Jul 6, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. + */ +package com.cra.figaro.util.visualization.distribution + +import java.awt.Color +import java.awt.event._ +import java.awt.geom.Rectangle2D +import java.text.NumberFormat +import javax.swing.BorderFactory +import javax.swing.Box +import scala.collection.JavaConversions +import scala.swing._ +import scala.swing.BorderPanel.Position._ +import prefuse.Constants +import prefuse.Display +import prefuse.Visualization +import prefuse.action.ActionList +import prefuse.action.RepaintAction +import prefuse.action.assignment.ColorAction +import prefuse.action.filter.VisibilityFilter +import prefuse.action.layout.AxisLayout +import prefuse.action.layout.AxisLabelLayout +import prefuse.controls.ControlAdapter +import prefuse.data.Table +import prefuse.data.expression.AndPredicate +import prefuse.data.expression.Expression +import prefuse.data.expression.Predicate +import prefuse.data.expression.parser.ExpressionParser +import prefuse.data.io.CSVTableReader +import prefuse.data.query.NumberRangeModel +import prefuse.data.query.RangeQueryBinding +import prefuse.render.RendererFactory +import prefuse.render.AxisRenderer +import prefuse.util.ColorLib +import prefuse.util.FontLib +import prefuse.util.UpdateListener +import prefuse.util.ui.JFastLabel +import prefuse.util.ui.UILib +import prefuse.visual.VisualItem +import prefuse.visual.expression.VisiblePredicate +import com.cra.figaro.util.visualization.ResultsGUI._ +import com.cra.figaro.util.visualization.DataView + +/** + * @author Glenn Takata (gtakata@cra.com) + */ +class Distribution(val dataview: DataView, var color: String) extends BorderPanel { + // fonts, colours, etc. + UILib.setColor(peer, ColorLib.getColor(0, 0, 0), Color.BLACK); + val itemRenderer = new DistributionRenderer(color, dataview) + + // title + val title = new Label(dataview.title) + title.preferredSize = new Dimension(200, 20) + title.verticalAlignment = Alignment.Top + title.font = FontLib.getFont("Tahoma", 18) + + // visualization (main container) + val vis: Visualization = new Visualization() + val visualTable = vis.addTable(dataview.name, dataview.getTable) + + // dynamic query based on name + val valueQ: RangeQueryBinding = new RangeQueryBinding(visualTable, "Value"); + val filter: AndPredicate = new AndPredicate(valueQ.getPredicate()); + val nf = NumberFormat.getIntegerInstance(); + nf.setMaximumFractionDigits(2); + + // X-axis + val xaxis: AxisLayout = new AxisLayout(dataview.name, "Value", Constants.X_AXIS, VisiblePredicate.TRUE); + + // add the labels to the x-axis + val xlabels: AxisLabelLayout = new AxisLabelLayout("xlab", xaxis) + xlabels.setNumberFormat(nf) + vis.putAction("xlabels", xlabels) + + // Y-axis + val yaxis: AxisLayout = new AxisLayout(dataview.name, "Probability", Constants.Y_AXIS, VisiblePredicate.TRUE); + + // ensure the y-axis spans the height of the data container + yaxis.setRangeModel(dataview.yRangeModel) + // add the labels to the y-axis + val ylabels: AxisLabelLayout = new AxisLabelLayout("ylab", yaxis); + ylabels.setNumberFormat(nf); + + // drawing actions + // specify the fill (interior) as a static colour (white) + val fill: ColorAction = new ColorAction(dataview.name, VisualItem.FILLCOLOR, 0); + + val draw: ActionList = new ActionList() + draw.add(fill) + draw.add(xaxis) + draw.add(yaxis) + draw.add(ylabels) + draw.add(new RepaintAction()) + vis.putAction("draw", draw) + + // update actions + val update: ActionList = new ActionList() + update.add(new VisibilityFilter(dataview.name, filter)); // filter performs the size/name filtering + update.add(xaxis) + update.add(yaxis) + update.add(ylabels) + update.add(new RepaintAction()) + vis.putAction("update", update) + + // create an update listener that will update the visualization when fired + val lstnr: UpdateListener = new UpdateListener() { + def update(src: Object) { + vis.run("update"); + } + }; + + // add this update listener to the filter, so that when the filter changes (i.e., + // the user adjusts the axis parameters, or enters a name for filtering), the + // visualization is updated + filter.addExpressionListener(lstnr); + + // add the listener to this component + peer.addComponentListener(lstnr); + + + val display = setupDisplay(vis) + + vis.setRendererFactory(new RendererFactory() { + + val yAxisRenderer = new AxisRenderer(Constants.FAR_LEFT, Constants.CENTER) + val xAxisRenderer = new AxisRenderer(Constants.CENTER, Constants.BOTTOM) + + def getRenderer(item: VisualItem) = { + if (item.isInGroup("ylab")) { + yAxisRenderer + } else if (item.isInGroup("xlab")) { + xAxisRenderer + } else { + itemRenderer + } + } + }) + + // container for elements at the top of the screen + val topContainer = new BoxPanel(Orientation.Horizontal) { + peer.add(Box.createHorizontalStrut(5)); + contents += title + peer.add(Box.createHorizontalGlue()); + peer.add(Box.createHorizontalStrut(5)); + } + + // add the containers to the JPanel + layout(topContainer) = North + layout(Component.wrap(display)) = Center + // add(slider, BorderLayout.SOUTH); + + vis.run("draw"); + vis.run("xlabels"); + + def setupDisplay(visualization: Visualization) = { + val disp = new Display(visualization); + + // set the display properties + disp.setBorder(BorderFactory.createEmptyBorder(5, 20, 5, 10)); + disp.setSize(new Dimension(TAB_WIDTH, TAB_HEIGHT)); + disp.setHighQuality(true); + + // call the function that sets the sizes of the containers that contain + // the data and the axes + displayLayout(disp, dataview); + + // whenever the window is re-sized, update the layout of the axes + disp.addComponentListener(new ComponentAdapter() { + + override def componentResized(e: ComponentEvent) { + displayLayout(disp, dataview); + } + }); + + disp.addControlListener(new ControlAdapter() { + override def itemClicked(item: VisualItem, event: MouseEvent) { + val table = dataview.getTable + val filter = ExpressionParser.predicate("Name = " + item.getSourceTuple.get("Name")) + val rows = table.rows(filter) + for (item <- JavaConversions.asScalaIterator(table.tuples(rows))) { + println(item) + } + } + }); + + disp + } + + /* + * calculate the sizes of the data and axes containers based on the + * display size, and then tell the visualization to update itself and + * re-draw the x-axis labels + */ + def displayLayout(display: Display, data: DataView) { + val insets = display.getInsets(); + val width = display.getWidth(); + val height = display.getHeight(); + val insetWidth = insets.left + insets.right; + val insetHeight = insets.top + insets.bottom; + + val viewXOffset = 20 + val viewYOffset = 15 + val yAxisWidth = 5; + val xAxisHeight = 10; + val displayHeight = height - xAxisHeight - insetHeight - 2 * viewYOffset + val maxDisplayWidth = width - yAxisWidth - insetWidth - 2 * viewXOffset + + val displayWidth = math.min(data.range.getExtent * 60, maxDisplayWidth) + + val dataView: Rectangle2D = new Rectangle2D.Double(insets.left + yAxisWidth + viewXOffset, insets.top, displayWidth, displayHeight) + val xView: Rectangle2D = new Rectangle2D.Double(insets.left + yAxisWidth + viewXOffset, insets.top + displayHeight + viewYOffset , displayWidth, xAxisHeight) + val yView: Rectangle2D = new Rectangle2D.Double(insets.left, insets.top, yAxisWidth, displayHeight) + + // reset all the bounds + itemRenderer.setBounds(dataView) + + xaxis.setLayoutBounds(dataView); + xlabels.setLayoutBounds(xView) + + yaxis.setLayoutBounds(dataView); + ylabels.setLayoutBounds(yView) + + vis.run("update"); + vis.run("xlabels"); + } +} \ No newline at end of file diff --git a/Figaro/src/main/scala/com/cra/figaro/util/visualization/distribution/DistributionRenderer.scala b/Figaro/src/main/scala/com/cra/figaro/util/visualization/distribution/DistributionRenderer.scala new file mode 100644 index 00000000..c056ea92 --- /dev/null +++ b/Figaro/src/main/scala/com/cra/figaro/util/visualization/distribution/DistributionRenderer.scala @@ -0,0 +1,97 @@ +/* + * DistributionRenderer.scala + * Display distribution elements based on position, value, color gradient + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Jul 6, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. + */package com.cra.figaro.util.visualization.distribution + +import java.awt.Graphics2D +import java.awt.Shape +import java.awt.geom.Rectangle2D + +import prefuse.Constants +import prefuse.render.AbstractShapeRenderer +import prefuse.util.ColorLib +import prefuse.util.GraphicsLib +import prefuse.visual.VisualItem + +import com.cra.figaro.util.ColorGradient +import com.cra.figaro.util.visualization.DataView + +/** + * @author Glenn Takata + * + */ +class DistributionRenderer(color: String, dataview: DataView) extends AbstractShapeRenderer { + var bounds: Rectangle2D = _ + var isVertical: Boolean = true + var orientation = Constants.ORIENT_BOTTOM_TOP; + var barWidth: Int = 5 + var nBars: Int = dataview.nValues + var pMax: Double = dataview.yMax + var rect = new Rectangle2D.Double(); + + val gradient = new ColorGradient + gradient.setGradient(color) + + def setBounds(newBounds: Rectangle2D) { + bounds = newBounds; + barWidth = (bounds.getWidth / nBars).toInt + } + + def setOrientation(orient: Int) { + + if (orient != Constants.ORIENT_LEFT_RIGHT && + orient != Constants.ORIENT_RIGHT_LEFT) + { + throw new IllegalArgumentException( + "Invalid orientation value: " + orient); + } + orientation = orient; + isVertical = (orientation == Constants.ORIENT_TOP_BOTTOM || + orientation == Constants.ORIENT_BOTTOM_TOP); + } + + override def getRawShape(item: VisualItem): Shape = { + var width: Double = 0 + var height: Double = 0 + + var x = item.getX() + var y = item.getY() + + width = math.min(bounds.getWidth / nBars, 30) + height = bounds.getHeight - y + + // Center the bar around the x-location + if (width > 1) { + x = x - width / 2; + + } + + rect.setFrame(x, y, width, height); + return rect; + + } + + override def render(g: Graphics2D, item: VisualItem) { + val shape = getShape(item); + + val probability = item.getSourceTuple.getFloat("Probability") + val value = probability / pMax + + gradient.getColorAtValue(value.floatValue()) match { + case Some(color) => + item.setFillColor(ColorLib.rgba(color.red, color.green, color.blue, 1)) + GraphicsLib.paint(g, item, shape, null, AbstractShapeRenderer.RENDER_TYPE_FILL); + case _ => + } + + } + +} \ No newline at end of file diff --git a/Figaro/src/main/scala/com/cra/figaro/util/visualization/histogram/DataView.scala b/Figaro/src/main/scala/com/cra/figaro/util/visualization/histogram/DataView.scala deleted file mode 100644 index c9c81f3d..00000000 --- a/Figaro/src/main/scala/com/cra/figaro/util/visualization/histogram/DataView.scala +++ /dev/null @@ -1,23 +0,0 @@ -/** - * - */ -package com.cra.figaro.util.visualization.histogram - -import prefuse.data.Table -import prefuse.data.query.ObjectRangeModel - -/** - * @author Glenn Takata (gtakata@cra.com) - * - * Mar 17, 2015 - */ -trait DataView { - def name: String - def title: String - def range: ObjectRangeModel - - def nValues: Int - - def getTable: Table - -} \ No newline at end of file diff --git a/Figaro/src/main/scala/com/cra/figaro/util/visualization/histogram/Histogram.scala b/Figaro/src/main/scala/com/cra/figaro/util/visualization/histogram/Histogram.scala index 9a703233..404f5f15 100644 --- a/Figaro/src/main/scala/com/cra/figaro/util/visualization/histogram/Histogram.scala +++ b/Figaro/src/main/scala/com/cra/figaro/util/visualization/histogram/Histogram.scala @@ -1,3 +1,15 @@ +/* + * Histogram.scala + * Setup and display histograms based on distribution (prob, value) data + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Apr 9, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. + */ package com.cra.figaro.util.visualization.histogram import java.awt.Color @@ -6,11 +18,9 @@ import java.awt.geom.Rectangle2D import java.text.NumberFormat import javax.swing.BorderFactory import javax.swing.Box - import scala.collection.JavaConversions import scala.swing._ import scala.swing.BorderPanel.Position._ - import prefuse.Constants import prefuse.Display import prefuse.Visualization @@ -38,8 +48,8 @@ import prefuse.util.ui.JFastLabel import prefuse.util.ui.UILib import prefuse.visual.VisualItem import prefuse.visual.expression.VisiblePredicate - import com.cra.figaro.util.visualization.ResultsGUI._ +import com.cra.figaro.util.visualization.DataView /** * @author Glenn Takata (gtakata@cra.com) @@ -62,6 +72,8 @@ class Histogram(val dataview: DataView, var color: String) extends BorderPanel { // dynamic query based on name val valueQ: RangeQueryBinding = new RangeQueryBinding(visualTable, "Value"); val filter: AndPredicate = new AndPredicate(valueQ.getPredicate()); + val nf = NumberFormat.getIntegerInstance(); + nf.setMaximumFractionDigits(2); // X-axis val xaxis: AxisLayout = new AxisLayout(dataview.name, "Value", Constants.X_AXIS, VisiblePredicate.TRUE); @@ -72,20 +84,16 @@ class Histogram(val dataview: DataView, var color: String) extends BorderPanel { // add the labels to the x-axis val xlabels: AxisLabelLayout = new AxisLabelLayout("xlab", xaxis); + xlabels.setNumberFormat(nf) vis.putAction("xlabels", xlabels) // Y-axis val yaxis: AxisLayout = new AxisLayout(dataview.name, "Probability", Constants.Y_AXIS, VisiblePredicate.TRUE); - // ensure the y-axis spans the height of the data container - // set the y-axis range - val rangeModel = new NumberRangeModel(0, 1.0, 0, 1.0); - yaxis.setRangeModel(rangeModel) + yaxis.setRangeModel(dataview.yRangeModel) // add the labels to the y-axis val ylabels: AxisLabelLayout = new AxisLabelLayout("ylab", yaxis); - val nf = NumberFormat.getIntegerInstance(); - nf.setMaximumFractionDigits(2); ylabels.setNumberFormat(nf); // drawing actions diff --git a/Figaro/src/main/scala/com/cra/figaro/util/visualization/histogram/HistogramRenderer.scala b/Figaro/src/main/scala/com/cra/figaro/util/visualization/histogram/HistogramRenderer.scala index e22ce8ae..ac89da91 100644 --- a/Figaro/src/main/scala/com/cra/figaro/util/visualization/histogram/HistogramRenderer.scala +++ b/Figaro/src/main/scala/com/cra/figaro/util/visualization/histogram/HistogramRenderer.scala @@ -1,5 +1,14 @@ -/** - * +/* + * HistogramRenderer.scala + * Renders individual bars for a histogram, taking into account color and position + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Apr 9, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. */ package com.cra.figaro.util.visualization.histogram diff --git a/Figaro/src/main/scala/com/cra/figaro/util/visualization/reduction/DataReduction.scala b/Figaro/src/main/scala/com/cra/figaro/util/visualization/reduction/DataReduction.scala new file mode 100644 index 00000000..45e6a455 --- /dev/null +++ b/Figaro/src/main/scala/com/cra/figaro/util/visualization/reduction/DataReduction.scala @@ -0,0 +1,53 @@ +package com.cra.figaro.util.visualization.reduction + +import scala.collection._ + +/** + * @author gtakata + */ +object DataReduction { + def binToDistribution(data: List[(Double, Double)]): List[(Double, Double)] = { + if (data.size > 50) { + var mean = 0.0 + var totalProb = 0.0 + var count = 0 + var min = Double.MaxValue + var max = Double.MinValue + var ss = 0.0 + + for ((prob, value) <- data) { + totalProb += prob + mean += prob * value + min = math.min(min, value) + max = math.max(max, value) + ss += prob * value * value + count += 1 + } + + val variance = ss - mean * mean + val sd = math.sqrt(variance) + + val distMax = mean + 3 * sd + val distMin = mean - 3 * sd + + val nInterval = math.min(count, 300) + val interval = (distMax - distMin) / nInterval + var dist = Array.fill[Double](nInterval)(0) + + for ((prob, value) <- data) { + val pos = math.max(math.min(math.floor((value - distMin) / interval).toInt, nInterval - 1), 0) + val posProb = dist(pos) + dist(pos) = posProb + prob + } + + val probDist = for (i <- 1 to nInterval) yield { + val value = distMin + i * interval + (dist(i - 1), value) + } + + probDist.toList + } else { + data + } + } +} \ No newline at end of file diff --git a/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsData.scala b/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsData.scala index 094d49b1..6832c34c 100644 --- a/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsData.scala +++ b/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsData.scala @@ -1,15 +1,98 @@ +/* + * ResultsData.scala + * Trait and classes representing data input by the user. + * Includes discrete (distribution List) and continuous (element) + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Apr 9, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. + */ package com.cra.figaro.util.visualization.results +import org.apache.commons.math3.distribution._ +import scala.collection.mutable.{ ListBuffer } +import com.cra.figaro.language.{ Atomic, Element, HasDensity } +import com.cra.figaro.library.atomic.continuous.{ AtomicExponential, AtomicNormal, AtomicUniform } + /** - * @author gtakata + * @author Glenn Takata (gtakata@comcast.net) */ -case class ResultsData[T](val name: String, val distribution: List[(Double, T)]) { - def resultString: String = { +trait ResultsData { + val name: String + def resultString: String + def distribution: List[(Double, _)] +} + +case class DiscreteData[T](override val name: String, override val distribution: List[(Double, T)]) extends ResultsData { + override def resultString: String = { val buffer = new StringBuffer("{") for ((prob, value) <- distribution) { buffer.append("(").append("%06.4f".format(prob)).append("->").append(value).append(")") - } + } buffer.append("}") return buffer.toString() } +} + +case class ContinuousData[T](override val name: String, val element: Element[_]) extends ResultsData { + val intervals = 200 + + override def resultString: String = { + if (element.isInstanceOf[AtomicNormal]) { + val normal = element.asInstanceOf[AtomicNormal] + val mean = normal.mean + val variance = normal.variance + s"Normal($mean, $variance)" + } else if (element.isInstanceOf[AtomicUniform]) { + val uniform = element.asInstanceOf[AtomicUniform] + val lower = uniform.lower + val upper = uniform.upper + s"Uniform($lower, $upper)" + } else if (element.isInstanceOf[AtomicExponential]) { + val exponential = element.asInstanceOf[AtomicExponential] + val lambda = exponential.lambda + s"Exponential($lambda)" + } else { + "" + } + } + + override def distribution: List[(Double, Double)] = { + if (element.isInstanceOf[AtomicNormal]) { + val normal = element.asInstanceOf[AtomicNormal] + val min = normal.mean - 3 * normal.standardDeviation + val max = normal.mean + 3 * normal.standardDeviation + constructDistribution(new NormalDistribution(normal.mean, normal.standardDeviation), min, max) + } else if (element.isInstanceOf[AtomicUniform]) { + val uniform = element.asInstanceOf[AtomicUniform] + val min = uniform.lower + val max = uniform.upper + constructDistribution(new UniformRealDistribution(min, max), min, max) + } else if (element.isInstanceOf[AtomicExponential]) { + val exponential = element.asInstanceOf[AtomicExponential] + val min = 0 + val max = 3.0 / exponential.lambda + constructDistribution(new ExponentialDistribution(1 / exponential.lambda), min, max) + } else { + val buffer = new ListBuffer[(Double, Double)]() + buffer.toList + } + } + + private def constructDistribution(function: AbstractRealDistribution, min: Double, max: Double): List[(Double, Double)] = { + val buffer = new ListBuffer[(Double, Double)]() + val spacing = (max - min) / intervals + var previous = min + for (x <- 1 until intervals) { + val value = min + x * spacing + val density = function.density(value) + buffer.append((density, value)) + previous = value + } + buffer.toList + } } \ No newline at end of file diff --git a/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsTable.scala b/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsTable.scala index 41ecaa53..37c12a07 100644 --- a/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsTable.scala +++ b/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsTable.scala @@ -1,3 +1,16 @@ +/* + * ResultsTable.scala + * Visual element for a table to display user inputs. + * Includes discrete (distribution List) and continuous (element) + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Apr 9, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. + */ package com.cra.figaro.util.visualization.results import java.awt.Dimension @@ -14,7 +27,7 @@ class ResultsTable extends BoxPanel(Orientation.Vertical) { preferredSize = new Dimension(TAB_WIDTH, TABLE_HEIGHT) - def add(result: ResultsData[_]) { + def add(result: ResultsData) { tableModel.addResult(result) table.revalidate @@ -41,7 +54,7 @@ class ResultsTable extends BoxPanel(Orientation.Vertical) { class ResultsTableModel(var rowData: Array[Array[Any]], val columnNames: Seq[String]) extends AbstractTableModel { override def getColumnName(column: Int) = columnNames(column).toString() - var results = new mutable.ListBuffer[ResultsData[_]]() + var results = new mutable.ListBuffer[ResultsData]() def getRowCount() = rowData.length def getColumnCount() = columnNames.length @@ -64,7 +77,7 @@ class ResultsTableModel(var rowData: Array[Array[Any]], val columnNames: Seq[Str rowData ++= Array(data.asInstanceOf[Array[Any]]) } - def addResult(result: ResultsData[_]) { + def addResult(result: ResultsData) { results.append(result) addRow(Array[AnyRef](result.name, result.distribution)) } diff --git a/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsView.scala b/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsView.scala index e299e6fc..7466026a 100644 --- a/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsView.scala +++ b/Figaro/src/main/scala/com/cra/figaro/util/visualization/results/ResultsView.scala @@ -1,32 +1,43 @@ -/** - * +/* + * ResultsView.scala + * A visual component to display a table of user data. + * Includes discrete (distribution List) and continuous (element) + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Mar 16, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. */ package com.cra.figaro.util.visualization.results import scala.collection.JavaConversions._ +import prefuse.Constants import prefuse.data.Table import prefuse.data.Tuple import prefuse.data.io.CSVTableReader -import prefuse.data.query.ObjectRangeModel +import prefuse.data.query.{NumberRangeModel, ObjectRangeModel} +import prefuse.util.ui.ValuedRangeModel import scala.collection.JavaConversions -import com.cra.figaro.util.visualization.histogram.DataView +import com.cra.figaro.util.visualization.DataView /** * @author Glenn Takata * * Mar 16, 2015 */ -class ResultsView[T](data: ResultsData[T]) extends DataView { +class ResultsView[T](data: ResultsData) extends DataView { val name = data.name val title = name def nValues = data.distribution.size - def range: ObjectRangeModel = { + def range: ValuedRangeModel = { val values = JavaConversions.asJavaCollection(data.distribution.map(_._2)).toArray() - val range = new ObjectRangeModel(values.asInstanceOf[Array[Object]]) - range + new ObjectRangeModel(values.asInstanceOf[Array[Object]]) } def getTable = readTable @@ -37,7 +48,7 @@ class ResultsView[T](data: ResultsData[T]) extends DataView { resultsTable.addColumn("Value", classOf[Object]) resultsTable.addColumn("Probability", classOf[Float]) - resultsTable.addRows(data.distribution.size) + resultsTable.addRows(nValues) var row = 0 val name = data.name @@ -50,8 +61,28 @@ class ResultsView[T](data: ResultsData[T]) extends DataView { row += 1 } resultsTable - + } + + def dataType = { + data match { + case ContinuousData(_, _) => Constants.NUMERICAL + case _ => Constants.NOMINAL + } + } + + def yMax = math.min(1.1 * data.distribution.reduceLeft((a, b) => if (a._1 > b._1) a else b)._1, 1.0) + def yRangeModel = { + data match { + case ContinuousData(_, _) => + new NumberRangeModel(0, yMax, 0, yMax ) + case _ => { + if (yMax < 0.5) + new NumberRangeModel(0, yMax, 0, yMax) + else + new NumberRangeModel(0, 1.0, 0, 1.0) + } + } } } -class NoResult() extends ResultsView(ResultsData("No Data", List((1.0, true)))) \ No newline at end of file +class NoResult() extends ResultsView(DiscreteData("No Data", List((1.0, true)))) \ No newline at end of file diff --git a/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/Burglary.scala b/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/Burglary.scala index 13b15c54..e1605d19 100644 --- a/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/Burglary.scala +++ b/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/Burglary.scala @@ -1,23 +1,22 @@ /* * Burglary.scala - * A Bayesian network example. + * A Bayesian network example with visualization * - * Created By: Avi Pfeffer (apfeffer@cra.com) - * Creation Date: Jan 1, 2009 + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Jun 15, 2015 * - * Copyright 2013 Avrom J. Pfeffer and Charles River Analytics, Inc. + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. * See http://www.cra.com or email figaro@cra.com for information. * * See http://www.github.com/p2t2/figaro for a copy of the software license. */ - package com.cra.figaro.example.visualization import com.cra.figaro.algorithm.factored._ import com.cra.figaro.language._ import com.cra.figaro.library.compound._ import com.cra.figaro.util.visualization.{ResultsGUI} -import com.cra.figaro.util.visualization.results.ResultsData +import com.cra.figaro.util.visualization.results.{DiscreteData} /** * A Bayesian network example */ @@ -47,7 +46,7 @@ object Burglary { alg.start() println("Probability of burglary: " + alg.probability(burglary, true)) - gui.addResult(ResultsData("burglary", alg.distribution(burglary).toList)) + gui.addResult("burglary", alg.distribution(burglary).toList) alg.kill } diff --git a/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/ContinuousExample.scala b/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/ContinuousExample.scala new file mode 100644 index 00000000..ac248a42 --- /dev/null +++ b/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/ContinuousExample.scala @@ -0,0 +1,30 @@ +/* + * ContinuousExample.scala + * An example using continuous elements. + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Jul 3, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. + */ +package com.cra.figaro.example.visualization + +import com.cra.figaro.library.atomic.continuous.{Exponential, Normal} +import com.cra.figaro.util.visualization._ +import com.cra.figaro.util.visualization.results.{ContinuousData} + +/** + * @author gtakata + */ +object ContinuousExample { + + def main(args: Array[String]) { + val gui = ResultsGUI + gui.startup(args) + gui.addResult("normal", Normal(20, 25)) + gui.addResult("exponential", Exponential(.2)) + } +} \ No newline at end of file diff --git a/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/DiscreteExample.scala b/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/DiscreteExample.scala new file mode 100644 index 00000000..5306f87c --- /dev/null +++ b/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/DiscreteExample.scala @@ -0,0 +1,30 @@ +/* + * DiscreteExample.scala + * An example using discrete distributions that demonstrates user interaction. + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Jun 16, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. + */ +package com.cra.figaro.example.visualization + +import com.cra.figaro.util.visualization._ +import com.cra.figaro.util.visualization.results.{DiscreteData} + +/** + * @author gtakata + */ +object DiscreteExample { + + def main(args: Array[String]) { + val gui = ResultsGUI + gui.startup(args) + gui.addResult("test", List((.20, true), (.80, false))) + gui.addResult("test2", List((1.0, true), (.00, false))) + + } +} \ No newline at end of file diff --git a/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/Regression.scala b/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/Regression.scala new file mode 100644 index 00000000..de018365 --- /dev/null +++ b/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/Regression.scala @@ -0,0 +1,48 @@ +/* + * Regression.scala + * A Bayesian network example with visualization + * + * Created By: Glenn Takata (gtakata@cra.com) + * Creation Date: Jul 7, 2015 + * + * Copyright 2015 Avrom J. Pfeffer and Charles River Analytics, Inc. + * See http://www.cra.com or email figaro@cra.com for information. + * + * See http://www.github.com/p2t2/figaro for a copy of the software license. + */ +package com.cra.figaro.example.visualization + +import com.cra.figaro.language.{Universe} +import com.cra.figaro.algorithm.sampling.{Importance} +import com.cra.figaro.library.atomic.continuous.{Normal, Uniform} +import com.cra.figaro.util.visualization.ResultsGUI + +/** + * @author Glenn Takata (gtakata@cra.com) + */ +object Regression { + Universe.createNew() + + private val mean = Uniform(0, 1) + + for (_ <- 0 until 100) { + val n = Normal(mean, 1.0) + n.addConstraint((m: Double) => m + 5) + } + + + def main(args: Array[String]) { + val gui = ResultsGUI + gui.startup(args) + + val alg = Importance(1000, mean) + alg.start() + + val dist = alg.distribution(mean) + println("Probability of mean: " + dist.toList) + + gui.addResult("mean", alg.distribution(mean).toList) + + alg.kill + } +} \ No newline at end of file diff --git a/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/ResultsExample.scala b/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/ResultsExample.scala deleted file mode 100644 index cc10de83..00000000 --- a/FigaroExamples/src/main/scala/com/cra/figaro/example/visualization/ResultsExample.scala +++ /dev/null @@ -1,19 +0,0 @@ -package com.cra.figaro.example.visualization - -import com.cra.figaro.util.visualization._ -import com.cra.figaro.util.visualization.results.ResultsData - -/** - * @author gtakata - */ -object ResultsExample { - val result1 = ResultsData("test", List((.20, true), (.80, false))) - val result2 = ResultsData("test2", List((1.0, true), (.00, false))) - - def main(args: Array[String]) { - val gui = ResultsGUI - gui.startup(args) - gui.addResult(result1) - gui.addResult(result2) - } -} \ No newline at end of file diff --git a/project/Build.scala b/project/Build.scala index ef430b07..dbcb0025 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -24,7 +24,7 @@ object FigaroBuild extends Build { override val settings = super.settings ++ Seq( organization := "com.cra.figaro", description := "Figaro: a language for probablistic programming", - version := "3.2.1.0", + version := "3.2.1.1", scalaVersion := "2.11.6", crossPaths := true, publishMavenStyle := true,