From 4920e0aeb40cf784b37030dba6db20523b3d1558 Mon Sep 17 00:00:00 2001 From: Andy Maloney Date: Wed, 2 Nov 2022 17:33:26 -0500 Subject: [PATCH 1/2] Marginal 2D diagnostic tool - Removed items in the `pointStatistic.ts` module and placed them in other modules for better structure. Updated all modules that needed to be updated due to the move. - Moved the method `updateAxisLabel` to a new module called `utils/plottingUtils.ts` for reuse. - Significantly reduced the complexity in `webpack.config.js` and `tsconfig.json`. - Updated the `viz.py` file to include the new marginal 2d tool. - Added the marginal 2d tool. --- .../tools/js/src/marginal1d/callbacks.ts | 22 +- .../tools/js/src/marginal2d/callbacks.ts | 243 ++++++ .../tools/js/src/marginal2d/index.ts | 12 + .../tools/js/src/marginal2d/interfaces.ts | 121 +++ .../diagnostics/tools/js/src/stats/array.ts | 29 +- .../js/src/stats/highestDensityInterval.ts | 79 ++ .../tools/js/src/stats/histogram.ts | 10 +- .../tools/js/src/stats/marginal.ts | 42 + .../tools/js/src/stats/pointStatistic.ts | 29 - .../tools/js/src/trace/callbacks.ts | 22 +- .../tools/js/src/utils/plottingUtils.ts | 24 + .../ppl/diagnostics/tools/js/tsconfig.json | 7 +- .../diagnostics/tools/js/webpack.config.js | 19 +- .../ppl/diagnostics/tools/js/yarn.lock | 172 ++-- .../ppl/diagnostics/tools/marginal1d/utils.py | 2 +- .../diagnostics/tools/marginal2d/__init__.py | 4 + .../ppl/diagnostics/tools/marginal2d/tool.py | 200 +++++ .../diagnostics/tools/marginal2d/typing.py | 204 +++++ .../ppl/diagnostics/tools/marginal2d/utils.py | 776 ++++++++++++++++++ .../tools/utils/model_serializers.py | 2 + src/beanmachine/ppl/diagnostics/tools/viz.py | 12 + 21 files changed, 1851 insertions(+), 180 deletions(-) create mode 100644 src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/callbacks.ts create mode 100644 src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/index.ts create mode 100644 src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/interfaces.ts delete mode 100644 src/beanmachine/ppl/diagnostics/tools/js/src/stats/pointStatistic.ts create mode 100644 src/beanmachine/ppl/diagnostics/tools/js/src/utils/plottingUtils.ts create mode 100644 src/beanmachine/ppl/diagnostics/tools/marginal2d/__init__.py create mode 100644 src/beanmachine/ppl/diagnostics/tools/marginal2d/tool.py create mode 100644 src/beanmachine/ppl/diagnostics/tools/marginal2d/typing.py create mode 100644 src/beanmachine/ppl/diagnostics/tools/marginal2d/utils.py diff --git a/src/beanmachine/ppl/diagnostics/tools/js/src/marginal1d/callbacks.ts b/src/beanmachine/ppl/diagnostics/tools/js/src/marginal1d/callbacks.ts index d4c85f5e28..18a1d6ad5b 100644 --- a/src/beanmachine/ppl/diagnostics/tools/js/src/marginal1d/callbacks.ts +++ b/src/beanmachine/ppl/diagnostics/tools/js/src/marginal1d/callbacks.ts @@ -5,36 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import {Axis} from '@bokehjs/models/axes/axis'; -import {cumulativeSum} from '../stats/array'; +import {arrayMean, cumulativeSum} from '../stats/array'; import {scaleToOne} from '../stats/dataTransformation'; import { interval as hdiInterval, data as hdiData, } from '../stats/highestDensityInterval'; import {oneD} from '../stats/marginal'; -import {mean as computeMean} from '../stats/pointStatistic'; import {interpolatePoints} from '../stats/utils'; +import {updateAxisLabel} from '../utils/plottingUtils'; import * as interfaces from './interfaces'; // Define the names of the figures used for this Bokeh application. const figureNames = ['marginal', 'cumulative']; -/** - * Update the given Bokeh Axis object with the new label string. You must use this - * method to update axis strings using TypeScript, otherwise the ts compiler will throw - * a type check error. - * - * @param {Axis} axis - The Bokeh Axis object needing a new label. - * @param {string | null} label - The new label for the Bokeh Axis object. - */ -export const updateAxisLabel = (axis: Axis, label: string | null): void => { - // Type check requirement. - if ('axis_label' in axis) { - axis.axis_label = label; - } -}; - /** * Compute the following statistics for the given random variable data * @@ -72,7 +56,7 @@ export const computeStats = ( // Compute the point statistics for the KDE, and create labels to display them in the // figures. - const mean = computeMean(rawData); + const mean = arrayMean(rawData); const hdiBounds = hdiInterval(rawData, hdiProbability); const x = [hdiBounds.lowerBound, mean, hdiBounds.upperBound]; const y = interpolatePoints({x: marginalX, y: marginalY, points: x}); diff --git a/src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/callbacks.ts b/src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/callbacks.ts new file mode 100644 index 0000000000..a1a88bd4c6 --- /dev/null +++ b/src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/callbacks.ts @@ -0,0 +1,243 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {Axis} from '@bokehjs/models/axes/axis'; +import {arrayMean, linearRange} from '../stats/array'; +import { + data as computeHdiData, + data90Degrees, + interval as hdiInterval, +} from '../stats/highestDensityInterval'; +import {oneD} from '../stats/marginal'; +import {interpolatePoints} from '../stats/utils'; +import * as interfaces from './interfaces'; +import {updateAxisLabel} from '../utils/plottingUtils'; + +export const computeXData = ( + data: number[][], + hdiProbability: number, + bwFactor: number, +): interfaces.XData => { + const flatData = data.flat(); + + // Distribution + const distribution = oneD(flatData, bwFactor); + + // HDI + const hdi = computeHdiData(flatData, distribution.x, distribution.y, hdiProbability); + const hdiData = {base: hdi.base, lower: hdi.lower, upper: hdi.upper}; + + // Stats + const mean = arrayMean(flatData); + const hdiBounds = hdiInterval(flatData, hdiProbability); + const x = [hdiBounds.lowerBound, mean, hdiBounds.upperBound]; + const y = interpolatePoints({x: distribution.x, y: distribution.y, points: x}); + const text = [ + `Lower HDI: ${hdiBounds.lowerBound.toFixed(3)}`, + `Mean: ${mean.toFixed(3)}`, + `Upper HDI: ${hdiBounds.upperBound.toFixed(3)}`, + ]; + const output = { + distribution: distribution, + hdi: hdiData, + stats: {x: x, y: y, text: text}, + }; + + return output; +}; + +export const computeYData = ( + data: number[][], + hdiProbability: number, + bwFactor: number, +): interfaces.YData => { + const flatData = data.flat(); + + // Distribution + const distribution = oneD(flatData, bwFactor); + + // HDI + const hdi = data90Degrees(flatData, distribution.x, distribution.y, hdiProbability); + const hdiData = { + lower: {base: hdi.upper.base, lower: hdi.upper.lower, upper: hdi.upper.upper}, + upper: {base: hdi.lower.base, lower: hdi.lower.lower, upper: hdi.lower.upper}, + }; + + // Stats + const mean = arrayMean(flatData); + const hdiBounds = hdiInterval(flatData, hdiProbability); + const x = [hdiBounds.lowerBound, mean, hdiBounds.upperBound]; + const y = interpolatePoints({x: distribution.x, y: distribution.y, points: x}); + const text = [ + `Lower HDI: ${hdiBounds.lowerBound.toFixed(3)}`, + `Mean: ${mean.toFixed(3)}`, + `Upper HDI: ${hdiBounds.upperBound.toFixed(3)}`, + ]; + const output = { + distribution: distribution, + hdi: hdiData, + stats: {x: x, y: y, text: text}, + }; + + return output; +}; + +export const computeXYData = ( + rawX: number[][], + computedX: interfaces.XData, + rawY: number[][], + computedY: interfaces.YData, +): interfaces.XYData => { + const flatDataX = rawX.flat(); + const flatDataY = rawY.flat(); + + // NOTE: Falling back to displaying data from the samples as the 2D KDE is not + // rendering properly. + const dataDistribution = {x: flatDataX, y: flatDataY}; + + // Stats: Create the stats for the 2D marginal. This is just a central point on the + // figure showing the mean values of both 1D marginals. + const stats = { + x: [computedX.stats.x[1]], + y: [computedY.stats.x[1]], + text: [ + `Mean: ${computedX.stats.x[1].toFixed(3)}/${computedY.stats.x[1].toFixed(3)}`, + ], + }; + + // HDI: Create the HDI guide lines in the 2D marginal distribution. These help the + // user understand how the 2D probability space is affected by changing the HDI + // regions of the 1D marginals independently. + const x = linearRange( + Math.min(...computedX.hdi.base), + Math.max(...computedX.hdi.base), + 1, + true, + 100, + ); + const y = linearRange( + Math.min(...computedY.hdi.lower.lower), + Math.max(...computedY.hdi.upper.upper), + 1, + true, + 100, + ); + const hdi = { + x: { + lower: {x: Array(y.length).fill(Math.min(...computedX.hdi.base)), y: y}, + upper: {x: Array(y.length).fill(Math.max(...computedX.hdi.base)), y: y}, + }, + y: { + lower: {x: x, y: Array(x.length).fill(Math.min(...computedY.hdi.lower.lower))}, + upper: {x: x, y: Array(x.length).fill(Math.max(...computedY.hdi.upper.upper))}, + }, + }; + const output = { + distribution: dataDistribution, + hdi: hdi, + stats: stats, + }; + return output; +}; + +export const computeData = ( + dataX: number[][], + hdiProbabilityX: number, + dataY: number[][], + hdiProbabilityY: number, + bwFactor: number, +): interfaces.Data => { + const xData = computeXData(dataX, hdiProbabilityX, bwFactor); + const yData = computeYData(dataY, hdiProbabilityY, bwFactor); + const xyData = computeXYData(dataX, xData, dataY, yData); + return {x: xData, y: yData, xy: xyData}; +}; + +export const update = ( + dataX: number[][], + hdiProbabilityX: number, + dataY: number[][], + hdiProbabilityY: number, + bwFactor: number, + xAxisLabel: string, + yAxisLabel: string, + figures: interfaces.Figures, + sources: interfaces.Sources, + tooltips: interfaces.Tooltips, + widgets: interfaces.Widgets, + glyphs: interfaces.Glyphs, +): number[] => { + const computedData = computeData( + dataX, + hdiProbabilityX, + dataY, + hdiProbabilityY, + bwFactor, + ); + // Update the x figure. + const xDistribution = { + x: computedData.x.distribution.x, + y: computedData.x.distribution.y, + }; + const bandwidthX = computedData.x.distribution.bandwidth; + sources.x.distribution.data = xDistribution; + sources.x.hdi.data = computedData.x.hdi; + sources.x.stats.data = computedData.x.stats; + tooltips.x.distribution.tooltips = [[xAxisLabel, '@x']]; + figures.xy.x_range = figures.x.x_range; + + // Update the y figure. + const yDistribution = { + x: computedData.y.distribution.y, + y: computedData.y.distribution.x, + }; + const bandwidthY = computedData.y.distribution.bandwidth; + sources.y.distribution.data = yDistribution; + sources.y.hdi.lower.data = computedData.y.hdi.lower; + sources.y.hdi.upper.data = computedData.y.hdi.upper; + const yStats = { + x: computedData.y.stats.y, + y: computedData.y.stats.x, + text: computedData.y.stats.text, + }; + sources.y.stats.data = yStats; + tooltips.y.distribution.tooltips = [[yAxisLabel, '@y']]; + figures.xy.y_range = figures.y.y_range; + + // Update the xy figure. + sources.xy.distribution.data = computedData.xy.distribution; + tooltips.xy.distribution.tooltips = [ + [xAxisLabel, '@x'], + [yAxisLabel, '@y'], + ]; + sources.xy.hdi.x.lower.data = computedData.xy.hdi.x.lower; + sources.xy.hdi.x.upper.data = computedData.xy.hdi.x.upper; + tooltips.xy.hdi.x.lower.tooltips = [[xAxisLabel, '@x']]; + tooltips.xy.hdi.x.upper.tooltips = [[xAxisLabel, '@x']]; + sources.xy.hdi.y.lower.data = computedData.xy.hdi.y.lower; + sources.xy.hdi.y.upper.data = computedData.xy.hdi.y.upper; + tooltips.xy.hdi.y.lower.tooltips = [[yAxisLabel, '@y']]; + tooltips.xy.hdi.y.upper.tooltips = [[yAxisLabel, '@y']]; + sources.xy.stats.data = computedData.xy.stats; + tooltips.xy.stats.tooltips = [ + [xAxisLabel, '@x'], + [yAxisLabel, '@y'], + ]; + + (window as any).data = computedData; + (window as any).figures = figures; + (window as any).glyphs = glyphs; + (window as any).sources = sources; + + updateAxisLabel(figures.xy.below[0] as Axis, xAxisLabel); + updateAxisLabel(figures.xy.left[0] as Axis, yAxisLabel); + + // Update widgets. + widgets.bw_div_x.text = `Bandwidth ${xAxisLabel}: ${bwFactor * bandwidthX}`; + widgets.bw_div_y.text = `Bandwidth ${yAxisLabel}: ${bwFactor * bandwidthY}`; + return [bandwidthX, bandwidthY]; +}; diff --git a/src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/index.ts b/src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/index.ts new file mode 100644 index 0000000000..1786ac0434 --- /dev/null +++ b/src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/index.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as marginal2d from './callbacks'; + +// The CustomJS methods used by Bokeh require us to make the JavaScript available in the +// browser, which is done by defining it below. +(window as any).marginal2d = marginal2d; diff --git a/src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/interfaces.ts b/src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/interfaces.ts new file mode 100644 index 0000000000..d5ae207801 --- /dev/null +++ b/src/beanmachine/ppl/diagnostics/tools/js/src/marginal2d/interfaces.ts @@ -0,0 +1,121 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {Figure} from '@bokehjs/api/plotting'; +import {Circle, Line} from '@bokehjs/models/glyphs'; +import {ColumnDataSource} from '@bokehjs/models/sources/column_data_source'; +import {HoverTool} from '@bokehjs/models/tools/inspectors/hover_tool'; +import {Div} from '@bokehjs/models/widgets/div'; +import {Select} from '@bokehjs/models/widgets/selectbox'; +import {Slider} from '@bokehjs/models/widgets/slider'; + +export interface XData { + distribution: {x: number[]; y: number[]; bandwidth: number}; + hdi: {base: number[]; lower: number[]; upper: number[]}; + stats: {x: number[]; y: number[]; text: string[]}; +} + +export interface YData { + distribution: {x: number[]; y: number[]; bandwidth: number}; + hdi: { + lower: {base: number[]; lower: number[]; upper: number[]}; + upper: {base: number[]; lower: number[]; upper: number[]}; + }; + stats: {x: number[]; y: number[]; text: string[]}; +} + +export interface XYData { + distribution: {x: number[]; y: number[]}; + hdi: { + x: { + lower: {x: number[]; y: number[]}; + upper: {x: number[]; y: number[]}; + }; + y: { + lower: {x: number[]; y: number[]}; + upper: {x: number[]; y: number[]}; + }; + }; + stats: {x: number[]; y: number[]; text: string[]}; +} + +export interface Data { + x: XData; + y: YData; + xy: XYData; +} + +export interface Glyphs { + x: { + distribution: {glyph: Line; hover_glyph: Line}; + stats: {glyph: Circle; hover_glyph: Circle}; + }; + y: { + distribution: {glyph: Line; hover_glyph: Line}; + stats: {glyph: Circle; hover_glyph: Circle}; + }; + xy: { + distribution: Circle; + hdi: { + x: { + lower: {glyph: Line; hover_glyph: Line}; + upper: {glyph: Line; hover_glyph: Line}; + }; + y: { + lower: {glyph: Line; hover_glyph: Line}; + upper: {glyph: Line; hover_glyph: Line}; + }; + }; + stats: {glyph: Circle; hover_glyph: Circle}; + }; +} + +export interface Figures { + x: Figure; + y: Figure; + xy: Figure; +} + +export interface Sources { + x: {distribution: ColumnDataSource; hdi: ColumnDataSource; stats: ColumnDataSource}; + y: { + distribution: ColumnDataSource; + hdi: {lower: ColumnDataSource; upper: ColumnDataSource}; + stats: ColumnDataSource; + }; + xy: { + distribution: ColumnDataSource; + hdi: { + x: {lower: ColumnDataSource; upper: ColumnDataSource}; + y: {lower: ColumnDataSource; upper: ColumnDataSource}; + }; + stats: ColumnDataSource; + }; +} + +export interface Tooltips { + x: {distribution: HoverTool; stats: HoverTool}; + y: {distribution: HoverTool; stats: HoverTool}; + xy: { + distribution: HoverTool; + hdi: { + x: {lower: HoverTool; upper: HoverTool}; + y: {lower: HoverTool; upper: HoverTool}; + }; + stats: HoverTool; + }; +} + +export interface Widgets { + rv_select_x: Select; + rv_select_y: Select; + bw_factor_slider: Slider; + hdi_slider_x: Slider; + hdi_slider_y: Slider; + bw_div_x: Div; + bw_div_y: Div; +} diff --git a/src/beanmachine/ppl/diagnostics/tools/js/src/stats/array.ts b/src/beanmachine/ppl/diagnostics/tools/js/src/stats/array.ts index 39dcf65bd0..ba5f5260e7 100644 --- a/src/beanmachine/ppl/diagnostics/tools/js/src/stats/array.ts +++ b/src/beanmachine/ppl/diagnostics/tools/js/src/stats/array.ts @@ -5,10 +5,33 @@ * LICENSE file in the root directory of this source tree. */ +/** + * Syntactic sugar for summing an array of numbers. + * + * @param {number[]} data - The array of data. + * @returns {number} The sum of the array of data. + */ +export const arraySum = (data: number[]): number => { + return data.reduce((previousValue, currentValue) => { + return previousValue + currentValue; + }); +}; + +/** + * Calculate the mean of the given array of data. + * + * @param {number[]} data - The array of data. + * @returns {number} The mean of the given data. + */ +export const arrayMean = (data: number[]): number => { + const dataSum = arraySum(data); + return dataSum / data.length; +}; + /** * Cumulative sum of the given data. * - * @param {number[]} data - Any array of data. + * @param {number[]} data - Any one dimensional array of data. * @returns {number[]} The cumulative sum of the given data. */ export const cumulativeSum = (data: number[]): number[] => { @@ -115,9 +138,9 @@ export const argSort = (data: number[]): number[] => { }; /** - * Count the number of time a value appears in an array. + * Count the number of times a value appears in an array. * - * @param {number[]} data - The numeric array to count objects for. + * @param {number[]} data - The numeric array that we will count the values in. * @returns {{[key: string]: number}} An object that contains the keys as the items in * the original array, and values that are counts of the key. */ diff --git a/src/beanmachine/ppl/diagnostics/tools/js/src/stats/highestDensityInterval.ts b/src/beanmachine/ppl/diagnostics/tools/js/src/stats/highestDensityInterval.ts index e0356eb0b2..ab6e22609d 100644 --- a/src/beanmachine/ppl/diagnostics/tools/js/src/stats/highestDensityInterval.ts +++ b/src/beanmachine/ppl/diagnostics/tools/js/src/stats/highestDensityInterval.ts @@ -94,3 +94,82 @@ export const data = ( upperBound: hdi.upperBound, }; }; + +/** + * Create the HDI Bokeh annotation for a marginal that has been rotated and flipped. We + * must break up the annotation into a lower and upper component so that it renders + * correctly in the browser. + * + * @param {number[]} rvData - Raw random variable data from the model. + * @param {number[]} marginalX - The support of the Kernel Density Estimate of the + * random variable. + * @param {number[]} marginalY - The Kernel Density Estimate of the random variable. + * @param {number} hdiProbability - The highest density interval probability to use when + * calculating the HDI. + * @returns {{ + * lower: {base: number[]; lower: number[]; upper: number[]}; + * upper: {base: number[]; lower: number[]; upper: number[]}; + * lowerBound: number; + * upperBound: number; + * }} The lower and upper components are drawn on the same figure. + */ +export const data90Degrees = ( + rvData: number[], + marginalX: number[], + marginalY: number[], + hdiProbability: number, +): { + lower: { + base: number[]; + lower: number[]; + upper: number[]; + }; + upper: { + base: number[]; + lower: number[]; + upper: number[]; + }; + lowerBound: number; + upperBound: number; +} => { + const hdiBounds = interval(rvData, hdiProbability); + const hdiData = data(rvData, marginalX, marginalY, hdiProbability); + const hdiX = hdiData.base; + const n = hdiX.length; + const halfIndex = Math.floor(n / 2); + const xAtHalfIndex = hdiX[halfIndex]; + + const lowerBase = [0]; + const lowerLower = [hdiBounds.lowerBound]; + for (let i = 0; i < marginalX.length; i += 1) { + if (marginalX[i] <= xAtHalfIndex && marginalX[i] >= hdiBounds.lowerBound) { + lowerBase.push(marginalY[i]); + lowerLower.push(marginalX[i]); + } + } + const lowerUpper = Array(lowerBase.length).fill(xAtHalfIndex); + + const upperBase = [0]; + const upperUpper = [hdiBounds.upperBound]; + for (let i = marginalX.length - 1; i >= 0; i -= 1) { + if (marginalX[i] >= xAtHalfIndex && marginalX[i] <= hdiBounds.upperBound) { + upperBase.push(marginalY[i]); + upperUpper.push(marginalX[i]); + } + } + const upperLower = Array(upperBase.length).fill(xAtHalfIndex); + return { + lower: { + base: upperBase, + lower: upperLower, + upper: upperUpper, + }, + upper: { + base: lowerBase, + lower: lowerLower, + upper: lowerUpper, + }, + lowerBound: hdiBounds.lowerBound, + upperBound: hdiBounds.upperBound, + }; +}; diff --git a/src/beanmachine/ppl/diagnostics/tools/js/src/stats/histogram.ts b/src/beanmachine/ppl/diagnostics/tools/js/src/stats/histogram.ts index 40a4868571..003a72f2db 100644 --- a/src/beanmachine/ppl/diagnostics/tools/js/src/stats/histogram.ts +++ b/src/beanmachine/ppl/diagnostics/tools/js/src/stats/histogram.ts @@ -5,9 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import {linearRange, numericalSort, shape} from './array'; +import {arrayMean, linearRange, numericalSort, shape} from './array'; import {rankData, scaleToOne} from './dataTransformation'; -import {mean as computeMean} from './pointStatistic'; /** * Compute the histogram of the given data. @@ -16,7 +15,8 @@ import {mean as computeMean} from './pointStatistic'; * @param {number} [numBins] - The number of bins to use for the histogram. If none is * given, then we follow ArviZ's implementation by using twice then number of bins * of the Sturges formula. - * @returns {number[][]} [TODO:description] + * @returns {number[][]} A two-dimensional array where the first row is the bins and the + * second row is the histogram count. */ export const calculateHistogram = (data: number[], numBins: number = 0): number[][] => { const sortedData = numericalSort(data); @@ -123,7 +123,7 @@ export const rankHistogram = (data: number[][]): RankHistogram => { return value + i; }); - const chainRankMean = computeMean(chainCounts); + const chainRankMean = arrayMean(chainCounts); const left = binEdges.slice(0, binEdges.length - 1); const right = binEdges.slice(1); const binLabel = []; @@ -145,7 +145,7 @@ export const rankHistogram = (data: number[][]): RankHistogram => { line: {x: x, y: y}, chain: Array(x.length).fill(i + 1), rankMean: Array(x.length).fill(chainIndex - chainRankMean), - mean: Array(x.length).fill(computeMean(counts)), + mean: Array(x.length).fill(arrayMean(counts)), }; } return output; diff --git a/src/beanmachine/ppl/diagnostics/tools/js/src/stats/marginal.ts b/src/beanmachine/ppl/diagnostics/tools/js/src/stats/marginal.ts index fed70ee34c..87fde27d2e 100644 --- a/src/beanmachine/ppl/diagnostics/tools/js/src/stats/marginal.ts +++ b/src/beanmachine/ppl/diagnostics/tools/js/src/stats/marginal.ts @@ -6,6 +6,7 @@ */ import {density1d} from 'fast-kde/src/density1d'; +import {density2d} from 'fast-kde/src/density2d'; import {scaleToOne} from './dataTransformation'; /** @@ -47,3 +48,44 @@ export const oneD = ( bandwidth: kde1d.bandwidth(), }; }; + +/** + * Computes the 2D Kernel Density Estimate. + * + * @param {number[]} x - The raw random variable data of the model in the x direction. + * @param {number[]} y - The raw random variable data of the model in the y direction. + * @param {number} bwFactor - Multiplicative factor to be applied to the bandwidth when + * calculating the Kernel Density Estimate (KDE). + * @param {number[]} [bins] - The number of bins to use for calculating the 2D KDE. + * @returns {{x: number[]; y: number[]; z: number[]; bw: {x: number; y: number}}} + * The computed 2D KDE with bandwidths for both sets of data. + */ +export const twoD = ( + x: number[], + y: number[], + bwFactor: number, + bins: number[] = [128, 128], +): {x: number[]; y: number[]; z: number[]; bw: {x: number; y: number}} => { + // Prepare the random variables for calculating the 2D KDE using fast-kde. + const data = []; + for (let i: number = 0; i < x.length; i += 1) { + data.push({u: x[i], v: y[i]}); + } + + // Calculate the 2D KDE. + const kde2d = density2d(data, {x: 'u', y: 'v', bins: bins, adjust: bwFactor, pad: 3}); + const [bwX, bwY] = kde2d.bandwidth(); + + // Extract the 2D data points from the 2D KDE calculation. + const points: {x: number; y: number; z: number}[] = [...kde2d]; + const X: number[] = []; + const Y: number[] = []; + const Z: number[] = []; + for (let i: number = 0; i < points.length; i += 1) { + X[i] = points[i].x; + Y[i] = points[i].y; + Z[i] = points[i].z; + } + + return {x: X, y: Y, z: Z, bw: {x: bwX, y: bwY}}; +}; diff --git a/src/beanmachine/ppl/diagnostics/tools/js/src/stats/pointStatistic.ts b/src/beanmachine/ppl/diagnostics/tools/js/src/stats/pointStatistic.ts deleted file mode 100644 index 3ee47b9876..0000000000 --- a/src/beanmachine/ppl/diagnostics/tools/js/src/stats/pointStatistic.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/** - * Syntactic sugar for summing an array of numbers. - * - * @param {number[]} data - The array of data. - * @returns {number} The sum of the array of data. - */ -export const sum = (data: number[]): number => { - return data.reduce((previousValue, currentValue) => { - return previousValue + currentValue; - }); -}; - -/** - * Calculate the mean of the given array of data. - * - * @param {number[]} data - The array of data. - * @returns {number} The mean of the given data. - */ -export const mean = (data: number[]): number => { - const dataSum = sum(data); - return dataSum / data.length; -}; diff --git a/src/beanmachine/ppl/diagnostics/tools/js/src/trace/callbacks.ts b/src/beanmachine/ppl/diagnostics/tools/js/src/trace/callbacks.ts index 1c4d4ab362..05fded3c17 100644 --- a/src/beanmachine/ppl/diagnostics/tools/js/src/trace/callbacks.ts +++ b/src/beanmachine/ppl/diagnostics/tools/js/src/trace/callbacks.ts @@ -5,31 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -import {Axis} from '@bokehjs/models/axes/axis'; import * as interfaces from './interfaces'; -import {linearRange, shape} from '../stats/array'; +import {arrayMean, linearRange, shape} from '../stats/array'; import {interval as hdiInterval} from '../stats/highestDensityInterval'; import {rankHistogram} from '../stats/histogram'; import {oneD} from '../stats/marginal'; -import {mean} from '../stats/pointStatistic'; +import {updateAxisLabel} from '../utils/plottingUtils'; const figureNames = ['marginals', 'forests', 'traces', 'ranks']; -/** - * Update the given Bokeh Axis object with the new label string. You must use this - * method to update axis strings using TypeScript, otherwise the ts compiler will throw - * a type check error. - * - * @param {Axis} axis - The Bokeh Axis object needing a new label. - * @param {string | null} label - The new label for the Bokeh Axis object. - */ -export const updateAxisLabel = (axis: Axis, label: string | null): void => { - // Type check requirement. - if ('axis_label' in axis) { - axis.axis_label = label; - } -}; - /** * Compute data for the trace diagnostic tool. * @@ -69,7 +53,7 @@ export const computeData = ( const chainName = `chain${chainIndex}`; const chainData = data[j]; const marginal = oneD(chainData, bwFactor); - const marginalMean = mean(marginal.x); + const marginalMean = arrayMean(marginal.x); let hdiBounds; switch (figureName) { case 'marginals': diff --git a/src/beanmachine/ppl/diagnostics/tools/js/src/utils/plottingUtils.ts b/src/beanmachine/ppl/diagnostics/tools/js/src/utils/plottingUtils.ts new file mode 100644 index 0000000000..27730e62d6 --- /dev/null +++ b/src/beanmachine/ppl/diagnostics/tools/js/src/utils/plottingUtils.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {Axis} from '@bokehjs/models/axes/axis'; + +/** + * Update the given Bokeh Axis object with the new label string. You must use this + * method to update axis strings using TypeScript, otherwise the ts compiler will throw + * a type check error. + * + * @param {Axis} axis - The Bokeh Axis object needing a new label. + * @param {string | null} label - The new label for the Bokeh Axis object. + */ +// eslint-disable-next-line import/prefer-default-export +export const updateAxisLabel = (axis: Axis, label: string | null): void => { + // Type check requirement. + if ('axis_label' in axis) { + axis.axis_label = label; + } +}; diff --git a/src/beanmachine/ppl/diagnostics/tools/js/tsconfig.json b/src/beanmachine/ppl/diagnostics/tools/js/tsconfig.json index 2212d273f3..22146c9a06 100644 --- a/src/beanmachine/ppl/diagnostics/tools/js/tsconfig.json +++ b/src/beanmachine/ppl/diagnostics/tools/js/tsconfig.json @@ -23,12 +23,7 @@ "node_modules/@bokeh/bokehjs/build/js/lib/*", "node_modules/@bokeh/bokehjs/build/js/types/*" ], - "fast-kde/*": ["node_modules/fast-kde/*"], - "ndarray/*": ["node_modules/ndarray/*"], - "ndarray-fft/*": [ - "node_modules/ndarray-fft/*", - "bokeh_extensions/types/ndarray-fft.d.ts" - ] + "fast-kde/*": ["node_modules/fast-kde/*"] }, "resolveJsonModule": true, "skipLibCheck": true, diff --git a/src/beanmachine/ppl/diagnostics/tools/js/webpack.config.js b/src/beanmachine/ppl/diagnostics/tools/js/webpack.config.js index e13c2507f6..b7ed1b9be7 100644 --- a/src/beanmachine/ppl/diagnostics/tools/js/webpack.config.js +++ b/src/beanmachine/ppl/diagnostics/tools/js/webpack.config.js @@ -10,38 +10,27 @@ const path = require('path'); module.exports = { entry: { marginal1d: './src/marginal1d/index.ts', + marginal2d: './src/marginal2d/index.ts', trace: './src/trace/index.ts', }, output: { filename: '[name].js', path: path.resolve(__dirname, 'dist'), }, + mode: 'production', module: { rules: [ { test: /\.ts$/, use: 'ts-loader', - exclude: /node_modules\/(?!(@bokeh\/bokehjs\/build\/js\/lib)\/).*/, + exclude: /node_modules/, }, ], }, - target: 'web', - mode: 'production', resolve: { extensions: ['.ts', '.js'], - modules: ['./stats', './interfaces', './types', './node_modules'], alias: { - 'fast-kde/src/density1d': path.resolve( - __dirname, - 'node_modules/fast-kde/src/density1d.js', - ), - '@bokehjs/models/ranges/range1d': path.resolve( - __dirname, - 'node_modules/@bokeh/bokehjs/build/js/lib/models/ranges/range1d.js', - ), + '@bokehjs': path.resolve(__dirname, './node_modules/@bokeh/bokehjs/build/js/lib'), }, }, - optimization: { - minimize: false, - }, }; diff --git a/src/beanmachine/ppl/diagnostics/tools/js/yarn.lock b/src/beanmachine/ppl/diagnostics/tools/js/yarn.lock index 7f512032f9..216e2b2dad 100644 --- a/src/beanmachine/ppl/diagnostics/tools/js/yarn.lock +++ b/src/beanmachine/ppl/diagnostics/tools/js/yarn.lock @@ -3,19 +3,19 @@ "@babel/runtime-corejs3@^7.10.2": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.19.4.tgz#870dbfd9685b3dad5aeb2d00841bb8b6192e3095" - integrity sha512-HzjQ8+dzdx7dmZy4DQ8KV8aHi/74AjEbBGTFutBmg/pd3dY5/q1sfuOGPTFGEytlQhWoeVXqcK5BwMgIkRkNDQ== + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.20.1.tgz#d0775a49bb5fba77e42cbb7276c9955c7b05af8d" + integrity sha512-CGulbEDcg/ND1Im7fUNRZdGXmX2MTWVVZacQi/6DiKE5HNwZ3aVTm5PV4lO8HHz0B2h8WQyvKKjbX5XgTtydsg== dependencies: core-js-pure "^3.25.1" - regenerator-runtime "^0.13.4" + regenerator-runtime "^0.13.10" "@babel/runtime@^7.10.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.9.2": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" - integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== + version "7.20.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9" + integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg== dependencies: - regenerator-runtime "^0.13.4" + regenerator-runtime "^0.13.10" "@bokeh/bokehjs@^2.4.3": version "2.4.3" @@ -78,14 +78,14 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.10.5": - version "0.10.7" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.7.tgz#6d53769fd0c222767e6452e8ebda825c22e9f0dc" - integrity sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w== +"@humanwhocodes/config-array@^0.11.6": + version "0.11.7" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" + integrity sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" - minimatch "^3.0.4" + minimatch "^3.0.5" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" @@ -130,9 +130,9 @@ integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.16" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.16.tgz#a7982f16c18cae02be36274365433e5b49d7b23f" - integrity sha512-LCQ+NeThyJ4k1W2d+vIKdxuSt9R3pQSZ4P92m7EakaYuXcVWbHuT5bjNcqLd4Rdgi6xYWYDvBJZJLZSLanjDcA== + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== dependencies: "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" @@ -150,7 +150,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -167,9 +167,9 @@ "@types/estree" "*" "@types/eslint@*": - version "8.4.6" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.6.tgz#7976f054c1bccfcf514bff0564c0c41df5c08207" - integrity sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g== + version "8.4.9" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.9.tgz#f7371980148697f4b582b086630319b55324b5aa" + integrity sha512-jFCSo4wJzlHQLCpceUhUnXdrPuCNOjGFMQ8Eg6JXxlz3QaCKOb7eGi2cephQdM4XTYsNej69P9JDJ1zqNIbncQ== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -202,9 +202,9 @@ integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/node@*": - version "18.8.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.8.5.tgz#6a31f820c1077c3f8ce44f9e203e68a176e8f59e" - integrity sha512-Bq7G3AErwe5A/Zki5fdD3O6+0zDChhg671NfPjtIcbtzDNZTv4NPKMRFr7gtYPG7y+B8uTiNK4Ngd9T0FTar6Q== + version "18.11.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" + integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== "@types/sizzle@*": version "2.3.3" @@ -219,47 +219,47 @@ "@types/jquery" "*" "@typescript-eslint/parser@^5.30.5": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.40.0.tgz#432bddc1fe9154945660f67c1ba6d44de5014840" - integrity sha512-Ah5gqyX2ySkiuYeOIDg7ap51/b63QgWZA7w6AHtFrag7aH0lRQPbLzUjk0c9o5/KZ6JRkTTDKShL4AUrQa6/hw== + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.42.0.tgz#be0ffbe279e1320e3d15e2ef0ad19262f59e9240" + integrity sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA== dependencies: - "@typescript-eslint/scope-manager" "5.40.0" - "@typescript-eslint/types" "5.40.0" - "@typescript-eslint/typescript-estree" "5.40.0" + "@typescript-eslint/scope-manager" "5.42.0" + "@typescript-eslint/types" "5.42.0" + "@typescript-eslint/typescript-estree" "5.42.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.40.0.tgz#d6ea782c8e3a2371ba3ea31458dcbdc934668fc4" - integrity sha512-d3nPmjUeZtEWRvyReMI4I1MwPGC63E8pDoHy0BnrYjnJgilBD3hv7XOiETKLY/zTwI7kCnBDf2vWTRUVpYw0Uw== +"@typescript-eslint/scope-manager@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.42.0.tgz#e1f2bb26d3b2a508421ee2e3ceea5396b192f5ef" + integrity sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow== dependencies: - "@typescript-eslint/types" "5.40.0" - "@typescript-eslint/visitor-keys" "5.40.0" + "@typescript-eslint/types" "5.42.0" + "@typescript-eslint/visitor-keys" "5.42.0" -"@typescript-eslint/types@5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.40.0.tgz#8de07e118a10b8f63c99e174a3860f75608c822e" - integrity sha512-V1KdQRTXsYpf1Y1fXCeZ+uhjW48Niiw0VGt4V8yzuaDTU8Z1Xl7yQDyQNqyAFcVhpYXIVCEuxSIWTsLDpHgTbw== +"@typescript-eslint/types@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.42.0.tgz#5aeff9b5eced48f27d5b8139339bf1ef805bad7a" + integrity sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw== -"@typescript-eslint/typescript-estree@5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.40.0.tgz#e305e6a5d65226efa5471ee0f12e0ffaab6d3075" - integrity sha512-b0GYlDj8TLTOqwX7EGbw2gL5EXS2CPEWhF9nGJiGmEcmlpNBjyHsTwbqpyIEPVpl6br4UcBOYlcI2FJVtJkYhg== +"@typescript-eslint/typescript-estree@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.0.tgz#2592d24bb5f89bf54a63384ff3494870f95b3fd8" + integrity sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg== dependencies: - "@typescript-eslint/types" "5.40.0" - "@typescript-eslint/visitor-keys" "5.40.0" + "@typescript-eslint/types" "5.42.0" + "@typescript-eslint/visitor-keys" "5.42.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.40.0": - version "5.40.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.40.0.tgz#dd2d38097f68e0d2e1e06cb9f73c0173aca54b68" - integrity sha512-ijJ+6yig+x9XplEpG2K6FUdJeQGGj/15U3S56W9IqXKJqleuD7zJ2AX/miLezwxpd7ZxDAqO87zWufKg+RPZyQ== +"@typescript-eslint/visitor-keys@5.42.0": + version "5.42.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.0.tgz#ee8d62d486f41cfe646632fab790fbf0c1db5bb0" + integrity sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg== dependencies: - "@typescript-eslint/types" "5.40.0" + "@typescript-eslint/types" "5.42.0" eslint-visitor-keys "^3.3.0" "@webassemblyjs/ast@1.11.1": @@ -421,9 +421,9 @@ acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: - version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== ajv-keywords@^3.5.2: version "3.5.2" @@ -507,9 +507,9 @@ ast-types-flow@^0.0.7: integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== axe-core@^4.4.3: - version "4.4.3" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f" - integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w== + version "4.5.1" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.5.1.tgz#04d561c11b6d76d096d34e9d14ba2c294fb20cdc" + integrity sha512-1exVbW0X1O/HSr/WMwnaweyqcWOgZgLiVxdLG34pvSQk4NlYQr9OUy0JLwuhFfuVNQzzqgH57eYzkFBCb3bIsQ== axobject-query@^2.2.0: version "2.2.0" @@ -565,9 +565,9 @@ callsites@^3.0.0: integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== caniuse-lite@^1.0.30001400: - version "1.0.30001418" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz#5f459215192a024c99e3e3a53aac310fc7cf24e6" - integrity sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg== + version "1.0.30001429" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz#70cdae959096756a85713b36dd9cb82e62325639" + integrity sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg== chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" @@ -643,9 +643,9 @@ confusing-browser-globals@^1.0.10: integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== core-js-pure@^3.25.1: - version "3.25.5" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.25.5.tgz#79716ba54240c6aa9ceba6eee08cf79471ba184d" - integrity sha512-oml3M22pHM+igfWHDfdLVq2ShWmjM2V4L+dQEBs0DWVIqEm9WHCwGAlZ6BmyBQGy5sFrJmcx+856D9lVKyGWYg== + version "3.26.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.26.0.tgz#7ad8a5dd7d910756f3124374b50026e23265ca9a" + integrity sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA== cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" @@ -735,9 +735,9 @@ doctrine@^3.0.0: esutils "^2.0.2" electron-to-chromium@^1.4.251: - version "1.4.279" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.279.tgz#84267fec806a8b1c5a1daebf726c4e296e5bcdf9" - integrity sha512-xs7vEuSZ84+JsHSTFqqG0TE3i8EAivHomRQZhhcRvsmnjsh5C2KdhwNKf4ZRYtzq75wojpFyqb62m32Oam57wA== + version "1.4.284" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" + integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== emoji-regex@^9.2.2: version "9.2.2" @@ -1024,13 +1024,14 @@ eslint-visitor-keys@^3.3.0: integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== eslint@^8.19.0: - version "8.25.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.25.0.tgz#00eb962f50962165d0c4ee3327708315eaa8058b" - integrity sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A== + version "8.26.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.26.0.tgz#2bcc8836e6c424c4ac26a5674a70d44d84f2181d" + integrity sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg== dependencies: "@eslint/eslintrc" "^1.3.3" - "@humanwhocodes/config-array" "^0.10.5" + "@humanwhocodes/config-array" "^0.11.6" "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -1046,14 +1047,14 @@ eslint@^8.19.0: fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" find-up "^5.0.0" - glob-parent "^6.0.1" + glob-parent "^6.0.2" globals "^13.15.0" - globby "^11.1.0" grapheme-splitter "^1.0.4" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" + is-path-inside "^3.0.3" js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" @@ -1289,7 +1290,7 @@ glob-parent@^5.1.2: dependencies: is-glob "^4.0.1" -glob-parent@^6.0.1: +glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== @@ -1457,9 +1458,9 @@ is-callable@^1.1.4, is-callable@^1.2.7: integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== is-core-module@^2.8.1, is-core-module@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" - integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== dependencies: has "^1.0.3" @@ -1499,6 +1500,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -1733,7 +1739,7 @@ mime-types@^2.1.27: dependencies: mime-db "1.52.0" -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -2009,10 +2015,10 @@ redux@^4.1.2: dependencies: "@babel/runtime" "^7.9.2" -regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" - integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.13.10: + version "0.13.10" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" + integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: version "1.4.3" @@ -2326,9 +2332,9 @@ tslib@^1.10.0, tslib@^1.8.1: integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.3.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== tsutils@^3.21.0: version "3.21.0" diff --git a/src/beanmachine/ppl/diagnostics/tools/marginal1d/utils.py b/src/beanmachine/ppl/diagnostics/tools/marginal1d/utils.py index ff148175c3..3827776eda 100644 --- a/src/beanmachine/ppl/diagnostics/tools/marginal1d/utils.py +++ b/src/beanmachine/ppl/diagnostics/tools/marginal1d/utils.py @@ -62,7 +62,7 @@ def create_sources() -> typing.Sources: - """Create Bokeh sources from the given data that will be bound to glyphs. + """Create Bokeh sources that will be bound to glyphs. Returns ------- diff --git a/src/beanmachine/ppl/diagnostics/tools/marginal2d/__init__.py b/src/beanmachine/ppl/diagnostics/tools/marginal2d/__init__.py new file mode 100644 index 0000000000..7bec24cb17 --- /dev/null +++ b/src/beanmachine/ppl/diagnostics/tools/marginal2d/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. diff --git a/src/beanmachine/ppl/diagnostics/tools/marginal2d/tool.py b/src/beanmachine/ppl/diagnostics/tools/marginal2d/tool.py new file mode 100644 index 0000000000..1103483140 --- /dev/null +++ b/src/beanmachine/ppl/diagnostics/tools/marginal2d/tool.py @@ -0,0 +1,200 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +"""Marginal 2D diagnostic tool for a Bean Machine model.""" +from __future__ import annotations + +from beanmachine.ppl.diagnostics.tools.marginal2d import utils +from beanmachine.ppl.diagnostics.tools.utils.diagnostic_tool_base import ( + DiagnosticToolBaseClass, +) +from beanmachine.ppl.inference.monte_carlo_samples import MonteCarloSamples +from bokeh.models import Model +from bokeh.models.callbacks import CustomJS + + +class Marginal2d(DiagnosticToolBaseClass): + """ + Marginal 2D diagnostic tool. + + Args: + mcs (MonteCarloSamples): The return object from running a Bean Machine model. + + Attributes: + data (Dict[str, List[List[float]]]): JSON serializable representation of the + given `mcs` object. + rv_names (List[str]): The list of random variables string names for the given + model. + num_chains (int): The number of chains of the model. + num_draws (int): The number of draws of the model for each chain. + palette (List[str]): A list of color values used for the glyphs in the figures. + The colors are specifically chosen from the Colorblind palette defined in + Bokeh. + tool_js (str):The JavaScript callbacks needed to render the Bokeh tool + independently from a Python server. + """ + + def __init__(self: Marginal2d, mcs: MonteCarloSamples) -> None: + super(Marginal2d, self).__init__(mcs) + + def create_document(self: Marginal2d) -> Model: + # Initialize widget values using Python. + rv_name_x = self.rv_names[0] + rv_name_y = self.rv_names[0] + bw_factor = 1.0 + bandwidth_x = 1.0 + bandwidth_y = 1.0 + + # NOTE: We are going to use Python and Bokeh to render the tool in the notebook + # output cell, however, we WILL NOT use Python to calculate any of the + # statistics displayed in the tool. We do this so we can make the BROWSER + # run all the calculations based on user interactions. If we did not + # employ this strategy, then the initial display a user would receive + # would be calculated by Python, and any subsequent updates would be + # calculated by JavaScript. The side-effect of having two backends + # calculate data could cause the figures to flicker, which would not be a + # good end user experience. + # + # Bokeh 3.0 is implementing an "on load" feature, which would nullify this + # requirement, and until that version is released, we have to employ this + # work-around. + + # Create empty Bokeh sources using Python. + sources = utils.create_sources() + + # Create empty figures for the tool using Python. + figures = utils.create_figures(rv_name_x=rv_name_x, rv_name_y=rv_name_y) + + # Create empty glyphs and attach them to the figures using Python. + glyphs = utils.create_glyphs() + utils.add_glyphs(figures=figures, glyphs=glyphs, sources=sources) + + # Create empty annotations and attach them to the figures using Python. + annotations = utils.create_annotations(sources=sources) + utils.add_annotations(figures=figures, annotations=annotations) + + # Create empty tool tips and attach them to the figures using Python. + tooltips = utils.create_tooltips( + rv_name_x=rv_name_x, + rv_name_y=rv_name_y, + figures=figures, + ) + utils.add_tooltips(figures=figures, tooltips=tooltips) + + # Create the widgets for the tool using Python. + widgets = utils.create_widgets( + rv_name_x=rv_name_x, + rv_name_y=rv_name_y, + rv_names=self.rv_names, + bw_factor=bw_factor, + bandwidth_x=bandwidth_x, + bandwidth_y=bandwidth_y, + ) + + # Create the view of the tool and serialize it into HTML using static resources + # from Bokeh. Embedding the tool in this manner prevents external CDN calls for + # JavaScript resources, and prevents the user from having to know where the + # Bokeh server is. + tool_view = utils.create_view(figures=figures, widgets=widgets) + + # Create callbacks for the tool using JavaScript. + callback_js = f""" + const rvNameX = widgets.rv_select_x.value; + const rvDataX = data[rvNameX]; + const rvNameY = widgets.rv_select_y.value; + const rvDataY = data[rvNameY]; + const hdiProbabilityX = widgets.hdi_slider_x.value / 100; + const hdiProbabilityY = widgets.hdi_slider_y.value / 100; + const bwFactor = widgets.bw_factor_slider.value; + // Remove the CSS classes that dim the tool output on initial load. + const rvX = widgets.rv_select_x.value; + const rvY = widgets.rv_select_y.value; + const rvXCheck = rvX !== 'Select a random variable...'; + const rvYCheck = rvY !== 'Select a random variable...'; + const cssRemovalCheck = rvXCheck && rvYCheck; + if (cssRemovalCheck) {{ + const toolTab = toolView.tabs[0]; + const toolChildren = toolTab.child.children; + const dimmedComponent = toolChildren[1]; + dimmedComponent.css_classes = []; + }}; + try {{ + const [bandwidthX, bandwidthY] = marginal2d.update( + rvDataX, + hdiProbabilityX, + rvDataY, + hdiProbabilityY, + bwFactor, + rvNameX, + rvNameY, + figures, + sources, + tooltips, + widgets, + glyphs, + ); + }} catch (error) {{ + {self.tool_js} + const [bandwidthX, bandwidthY] = marginal2d.update( + rvDataX, + hdiProbabilityX, + rvDataY, + hdiProbabilityY, + bwFactor, + rvNameX, + rvNameY, + figures, + sources, + tooltips, + widgets, + glyphs, + ); + }} + figures.xy.x_range = figures.x.x_range; + figures.xy.y_range = figures.x.y_range; + """ + + # Each widget requires the following dictionary for the CustomJS method. Notice + # that the callback_js object above uses the names defined as keys in the below + # object with values defined by the Python objects. + callback_arguments = { + "data": self.data, + "widgets": widgets, + "sources": sources, + "figures": figures, + "tooltips": tooltips, + "toolView": tool_view, + "widgets": widgets, + "glyphs": glyphs, + } + + # Each widget requires slightly different JS, except for the sliders. + rv_select_x_js = f""" + widgets.bw_factor_slider.value = 1.0; + widgets.hdi_slider_x.value = 89; + widgets.hdi_slider_y.value = 89; + {callback_js}; + """ + rv_select_x_callback = CustomJS(args=callback_arguments, code=rv_select_x_js) + rv_select_y_js = f""" + widgets.bw_factor_slider.value = 1.0; + widgets.hdi_slider_x.value = 89; + widgets.hdi_slider_y.value = 89; + {callback_js}; + """ + rv_select_y_callback = CustomJS(args=callback_arguments, code=rv_select_y_js) + slider_js = f""" + {callback_js}; + """ + slider_callback = CustomJS(args=callback_arguments, code=slider_js) + + # Tell Python to use the JavaScript. + widgets["rv_select_x"].js_on_change("value", rv_select_x_callback) + widgets["rv_select_y"].js_on_change("value", rv_select_y_callback) + widgets["bw_factor_slider"].js_on_change("value", slider_callback) + widgets["hdi_slider_x"].js_on_change("value", slider_callback) + widgets["hdi_slider_y"].js_on_change("value", slider_callback) + + return tool_view diff --git a/src/beanmachine/ppl/diagnostics/tools/marginal2d/typing.py b/src/beanmachine/ppl/diagnostics/tools/marginal2d/typing.py new file mode 100644 index 0000000000..6ce3c13b94 --- /dev/null +++ b/src/beanmachine/ppl/diagnostics/tools/marginal2d/typing.py @@ -0,0 +1,204 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +"""Marginal 2D diagnostic tool types for a Bean Machine model.""" +from __future__ import annotations + +from typing import Any, Dict, List, Union + +from beanmachine.ppl.diagnostics.tools import TypedDict +from bokeh.models.annotations import Band +from bokeh.models.glyphs import Circle, Image, Line +from bokeh.models.sources import ColumnDataSource +from bokeh.models.tools import HoverTool +from bokeh.models.widgets.inputs import Select +from bokeh.models.widgets.markups import Div +from bokeh.models.widgets.sliders import Slider +from bokeh.plotting import Figure + + +# NOTE: These are the types pyre gives us when using `reveal_type(...)` on the outputs +# of the methods. +Data = Dict[ + str, + Union[ + Dict[ + str, + Union[ + Dict[str, List[Any]], + Dict[str, Dict[str, List[Any]]], + Dict[str, Union[List[Any], float]], + ], + ], + Dict[ + str, + Union[Dict[str, List[Any]], Dict[str, Dict[str, Dict[str, List[Any]]]]], + ], + Dict[str, Union[Dict[str, List[Any]], Dict[str, Union[List[Any], float]]]], + ], +] +Sources = Dict[ + str, + Union[ + Dict[str, Union[Dict[str, Dict[str, ColumnDataSource]], ColumnDataSource]], + Dict[str, Union[Dict[str, ColumnDataSource], ColumnDataSource]], + Dict[str, ColumnDataSource], + ], +] +Figures = Dict[str, Any] +Glyphs = Dict[ + str, + Union[ + Dict[ + str, + Union[ + Dict[str, Dict[str, Dict[str, Line]]], + Dict[str, Circle], + ], + ], + Dict[str, Union[Dict[str, Circle], Dict[str, Line]]], + ], +] +Annotations = Dict[str, Union[Dict[str, Band], Band]] +Tooltips = Dict[ + str, + Union[ + Dict[str, Union[Dict[str, Dict[str, HoverTool]], HoverTool]], + Dict[str, HoverTool], + ], +] +Widgets = Dict[str, Union[Div, Select, Slider]] + + +# NOTE: TypedDict objects are for reference only. Due to the way pyre accesses keys in +# dictionaries, and how NumPy casts arrays when using tolist(), we are unable to +# use them, but they provide semantic information for the different types. + + +class XSource(TypedDict): # pyre-ignore + distribution: ColumnDataSource + hdi: ColumnDataSource + stats: ColumnDataSource + + +class YHDISource(TypedDict): # pyre-ignore + lower: ColumnDataSource + upper: ColumnDataSource + + +class YSource(TypedDict): # pyre-ignore + distribution: ColumnDataSource + hdi: YHDISource + stats: ColumnDataSource + + +class XYHDIBoundsSource(TypedDict): # pyre-ignore + lower: ColumnDataSource + upper: ColumnDataSource + + +class XYHDISource(TypedDict): # pyre-ignore + x: XYHDIBoundsSource + y: XYHDIBoundsSource + + +class XYSource(TypedDict): # pyre-ignore + distribution: ColumnDataSource + hdi: XYHDISource + stats: ColumnDataSource + + +class _Sources(TypedDict): # pyre-ignore + x: XSource + y: YSource + xy: XYSource + + +class _Figures(TypedDict): # pyre-ignore + x: Figure + y: Figure + xy: Figure + + +class LineGlyph(TypedDict): # pyre-ignore + glyph: Line + hover_glyph: Line + + +class CircleGlyph(TypedDict): # pyre-ignore + glyph: Circle + hover_glyph: Circle + + +class XorYGlyphs(TypedDict): # pyre-ignore + distribution: LineGlyph + stats: CircleGlyph + + +class LowerOrUpperHDIGlyphs(TypedDict): # pyre-ignore + lower: LineGlyph + upper: LineGlyph + + +class XYHDIGlyphs(TypedDict): # pyre-ignore + x: LowerOrUpperHDIGlyphs + y: LowerOrUpperHDIGlyphs + + +class XYGlyphs(TypedDict): # pyre-ignore + distribution: Image + hdi: XYHDIGlyphs + stats: CircleGlyph + + +class _Glyphs(TypedDict): # pyre-ignore + x: XorYGlyphs + y: XorYGlyphs + xy: XYGlyphs + + +class YAnnotations(TypedDict): # pyre-ignore + lower: Band + upper: Band + + +class _Annotations(TypedDict): # pyre-ignore + x: Band + y: YAnnotations + + +class XorYTooltips(TypedDict): # pyre-ignore + distribution: HoverTool + stats: HoverTool + + +class LowerOrUpperTooltips(TypedDict): # pyre-ignore + lower: HoverTool + upper: HoverTool + + +class XYHDITooltips(TypedDict): # pyre-ignore + x: LowerOrUpperTooltips + y: LowerOrUpperTooltips + + +class XYTooltips(TypedDict): # pyre-ignore + hdi: XYHDITooltips + stats: HoverTool + + +class _Tooltips(TypedDict): # pyre-ignore + x: XorYTooltips + y: XorYTooltips + xy: XYTooltips + + +class _Widgets(TypedDict): # pyre-ignore + rv_select_x: Select + rv_select_y: Select + hdi_slider_x: Slider + hdi_slider_y: Slider + bw_div_x: Div + bw_div_y: Div diff --git a/src/beanmachine/ppl/diagnostics/tools/marginal2d/utils.py b/src/beanmachine/ppl/diagnostics/tools/marginal2d/utils.py new file mode 100644 index 0000000000..2a19d0c29c --- /dev/null +++ b/src/beanmachine/ppl/diagnostics/tools/marginal2d/utils.py @@ -0,0 +1,776 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +"""Methods used to generate the diagnostic tool.""" +from __future__ import annotations + +from typing import List + +import numpy as np +from beanmachine.ppl.diagnostics.tools.marginal2d import typing +from beanmachine.ppl.diagnostics.tools.utils.plotting_utils import ( + choose_palette, + create_toolbar, + filter_renderers, +) +from bokeh.models.annotations import Band +from bokeh.models.glyphs import Circle, Line +from bokeh.models.layouts import Column, GridBox, Row +from bokeh.models.sources import ColumnDataSource +from bokeh.models.tools import HoverTool +from bokeh.models.widgets.inputs import Select +from bokeh.models.widgets.markups import Div +from bokeh.models.widgets.panels import Panel, Tabs +from bokeh.models.widgets.sliders import Slider +from bokeh.plotting import figure + + +MARGINAL1D_PLOT_WIDTH = 500 +MARGINAL1D_PLOT_HEIGHT = 100 +MARGINAL2D_PLOT_WIDTH = MARGINAL1D_PLOT_WIDTH +MARGINAL2D_PLOT_HEIGHT = MARGINAL2D_PLOT_WIDTH +# Define what the empty data object looks like in order to make the browser handle all +# computations. +EMPTY_DATA = { + "x": { + "distribution": {"x": [], "y": [], "bandwidth": np.NaN}, + "hdi": {"base": [], "lower": [], "upper": []}, + "stats": {"x": [], "y": [], "text": []}, + "labels": { + "x": [], + "y": [], + "text": [], + "text_align": [], + "x_offset": [], + "y_offset": [], + }, + }, + "y": { + "distribution": {"x": [], "y": [], "bandwidth": np.NaN}, + "hdi": { + "lower": {"base": [], "lower": [], "upper": []}, + "upper": {"base": [], "lower": [], "upper": []}, + }, + "stats": {"x": [], "y": [], "text": []}, + "labels": { + "x": [], + "y": [], + "text": [], + "text_align": [], + "x_offset": [], + "y_offset": [], + }, + }, + "xy": { + "distribution": {"x": [], "y": []}, + "hdi": { + "x": {"lower": {"x": [], "y": []}, "upper": {"x": [], "y": []}}, + "y": {"lower": {"x": [], "y": []}, "upper": {"x": [], "y": []}}, + }, + "stats": {"x": [], "y": [], "text": []}, + "labels": { + "x": [], + "y": [], + "text": [], + "text_align": [], + "x_offset": [], + "y_offset": [], + }, + }, +} + + +def create_sources() -> typing.Sources: + """ + Create Bokeh sources that will be bound to glyphs. + + Returns: + typing.Sources: A dictionary of Bokeh ColumnDataSource objects. + """ + output = { + "x": { + "distribution": ColumnDataSource({"x": [], "y": []}), + "hdi": ColumnDataSource({"base": [], "lower": [], "upper": []}), + "stats": ColumnDataSource({"x": [], "y": [], "text": []}), + }, + "y": { + "distribution": ColumnDataSource({"x": [], "y": []}), + "hdi": { + "lower": ColumnDataSource({"base": [], "lower": [], "upper": []}), + "upper": ColumnDataSource({"base": [], "lower": [], "upper": []}), + }, + "stats": ColumnDataSource({"x": [], "y": [], "text": []}), + }, + "xy": { + "distribution": ColumnDataSource({"x": [], "y": []}), + "hdi": { + "x": { + "lower": ColumnDataSource({"x": [], "y": []}), + "upper": ColumnDataSource({"x": [], "y": []}), + }, + "y": { + "lower": ColumnDataSource({"x": [], "y": []}), + "upper": ColumnDataSource({"x": [], "y": []}), + }, + }, + "stats": ColumnDataSource({"x": [], "y": [], "text": []}), + }, + } + return output + + +def create_figures(rv_name_x: str, rv_name_y: str) -> typing.Figures: + """ + Create the Bokeh figures used for the tool. + + Args: + rv_name_x (str): The name of the random variable data in the x-direction. + rv_name_y (str): The name of the random variable data in the y-direction. + + Returns: + typing.Figures: A dictionary of Bokeh Figure objects. + """ + # x figure + x = figure( + outline_line_color=None, + min_border=None, + width=MARGINAL1D_PLOT_WIDTH, + height=MARGINAL1D_PLOT_HEIGHT, + name="x", + y_axis_label=rv_name_y, + ) + # NOTE: The extra steps we are taking for customizing both the x and y figures stem + # from the fact that the plots will shift to fit in their allotted space if no + # axis exists. In order to prevent these visual shifts, we keep the axes + # around and manipulate them so you cannot see them. Unfortunately we must + # keep the major tick labels around and color them white, otherwise the + # problem will persist. + x.grid.visible = False + x.xaxis.visible = False + x.x_range.range_padding = 0 + x.y_range.range_padding = 0 + x.yaxis.axis_label_text_color = None + x.yaxis.axis_line_color = None + x.yaxis.major_label_text_color = "white" + x.yaxis.major_tick_line_color = None + x.yaxis.minor_tick_line_color = None + + # y figure + y = figure( + outline_line_color=None, + min_border=None, + width=MARGINAL1D_PLOT_HEIGHT, + height=MARGINAL1D_PLOT_WIDTH, + name="y", + x_axis_label=rv_name_x, + ) + y.grid.visible = False + y.yaxis.visible = False + y.x_range.range_padding = 0 + y.y_range.range_padding = 0 + y.xaxis.axis_label_text_color = None + y.xaxis.axis_line_color = None + y.xaxis.major_label_text_color = "white" + y.xaxis.major_tick_line_color = None + y.xaxis.minor_tick_line_color = None + + # xy figure + xy = figure( + outline_line_color="black", + min_border=None, + width=MARGINAL2D_PLOT_WIDTH, + height=MARGINAL2D_PLOT_HEIGHT, + x_axis_label=rv_name_x, + y_axis_label=rv_name_y, + x_range=x.x_range, + y_range=y.y_range, + name="xy", + ) + xy.grid.visible = False + xy.x_range.range_padding = 0 + xy.y_range.range_padding = 0 + + output = {"x": x, "y": y, "xy": xy} + return output + + +def create_glyphs() -> typing.Glyphs: + """ + Create the glyphs used for the figures of the tool. + + Returns: + typing.Glyphs: A dictionary of Bokeh Glyphs objects. + """ + palette = choose_palette(4) + glyph_color = palette[0] + hover_glyph_color = palette[1] + mean_color = palette[3] + output = { + "x": { + "distribution": { + "glyph": Line( + x="x", + y="y", + line_alpha=0.7, + line_color=glyph_color, + line_width=2.0, + name="xDistribution", + ), + "hover_glyph": Line( + x="x", + y="y", + line_alpha=1.0, + line_color=hover_glyph_color, + line_width=2.0, + ), + }, + "stats": { + "glyph": Circle( + x="x", + y="y", + size=10, + fill_alpha=1.0, + fill_color=glyph_color, + line_color="white", + name="xStats", + ), + "hover_glyph": Circle( + x="x", + y="y", + size=10, + fill_alpha=1.0, + fill_color=hover_glyph_color, + line_color="black", + ), + }, + }, + "y": { + "distribution": { + "glyph": Line( + x="x", + y="y", + line_alpha=0.7, + line_color=glyph_color, + line_width=2.0, + name="yDistribution", + ), + "hover_glyph": Line( + x="x", + y="y", + line_alpha=1.0, + line_color=hover_glyph_color, + line_width=2.0, + ), + }, + "stats": { + "glyph": Circle( + x="x", + y="y", + size=10, + fill_alpha=1.0, + fill_color=glyph_color, + line_color="white", + name="yStats", + ), + "hover_glyph": Circle( + x="x", + y="y", + size=10, + fill_alpha=1.0, + fill_color=hover_glyph_color, + line_color="black", + ), + }, + }, + "xy": { + "distribution": { + "glyph": Circle( + x="x", + y="y", + size=5, + fill_alpha=0.4, + line_color="white", + fill_color=glyph_color, + name="xyDistribution", + ), + "hover_glyph": Circle( + x="x", + y="y", + size=5, + fill_alpha=1.0, + line_color="black", + fill_color=hover_glyph_color, + ), + }, + "hdi": { + "x": { + "lower": { + "glyph": Line( + x="x", + y="y", + line_alpha=0.7, + line_color="black", + line_width=2.0, + name="xyLowerXHDI", + ), + "hover_glyph": Line( + x="x", + y="y", + line_alpha=1.0, + line_color="black", + line_width=2.0, + ), + }, + "upper": { + "glyph": Line( + x="x", + y="y", + line_alpha=0.7, + line_color="black", + line_width=2.0, + name="xyUpperXHDI", + ), + "hover_glyph": Line( + x="x", + y="y", + line_alpha=1.0, + line_color="black", + line_width=2.0, + ), + }, + }, + "y": { + "lower": { + "glyph": Line( + x="x", + y="y", + line_alpha=0.7, + line_color="black", + line_width=2.0, + name="xyLowerYHDI", + ), + "hover_glyph": Line( + x="x", + y="y", + line_alpha=1.0, + line_color="black", + line_width=2.0, + ), + }, + "upper": { + "glyph": Line( + x="x", + y="y", + line_alpha=0.7, + line_color="black", + line_width=2.0, + name="xyUpperYHDI", + ), + "hover_glyph": Line( + x="x", + y="y", + line_alpha=1.0, + line_color="black", + line_width=2.0, + ), + }, + }, + }, + "stats": { + "glyph": Circle( + x="x", + y="y", + size=20, + fill_alpha=0.5, + fill_color=mean_color, + line_color="white", + name="xyStats", + ), + "hover_glyph": Circle( + x="x", + y="y", + size=20, + fill_alpha=1.0, + fill_color=mean_color, + line_color="black", + ), + }, + }, + } + return output + + +def add_glyphs( + figures: typing.Figures, + glyphs: typing.Glyphs, + sources: typing.Sources, +) -> None: + """ + Bind source data to glyphs and add the glyphs to the given figures. + + Args: + figures (typing.Figures): A dictionary of Bokeh Figure objects. + glyphs (typing.Glyphs): A dictionary of Bokeh Glyphs objects. + sources (typing.Sources): A dictionary of Bokeh ColumnDataSource objects. + + Returns + None: Adds data bound glyphs to the given figures directly. + """ + # x figure + figures["x"].add_glyph( + source_or_glyph=sources["x"]["distribution"], + glyph=glyphs["x"]["distribution"]["glyph"], + hover_glyph=glyphs["x"]["distribution"]["hover_glyph"], + name="xDistribution", + ) + figures["x"].add_glyph( + source_or_glyph=sources["x"]["stats"], + glyph=glyphs["x"]["stats"]["glyph"], + hover_glyph=glyphs["x"]["stats"]["hover_glyph"], + name="xStats", + ) + + # y figure + figures["y"].add_glyph( + source_or_glyph=sources["y"]["distribution"], + glyph=glyphs["y"]["distribution"]["glyph"], + hover_glyph=glyphs["y"]["distribution"]["hover_glyph"], + name="yDistribution", + ) + figures["y"].add_glyph( + source_or_glyph=sources["y"]["stats"], + glyph=glyphs["y"]["stats"]["glyph"], + hover_glyph=glyphs["y"]["stats"]["hover_glyph"], + name="yStats", + ) + + # xy figure + figures["xy"].add_glyph( + source_or_glyph=sources["xy"]["distribution"], + glyph=glyphs["xy"]["distribution"]["glyph"], + hover_glyph=glyphs["xy"]["distribution"]["hover_glyph"], + name="xyDistribution", + ) + figures["xy"].add_glyph( + source_or_glyph=sources["xy"]["hdi"]["x"]["lower"], + glyph=glyphs["xy"]["hdi"]["x"]["lower"]["glyph"], + hover_glyph=glyphs["xy"]["hdi"]["x"]["lower"]["hover_glyph"], + name="xyHDIXLower", + ) + figures["xy"].add_glyph( + source_or_glyph=sources["xy"]["hdi"]["x"]["upper"], + glyph=glyphs["xy"]["hdi"]["x"]["upper"]["glyph"], + hover_glyph=glyphs["xy"]["hdi"]["x"]["upper"]["hover_glyph"], + name="xyHDIXUpper", + ) + figures["xy"].add_glyph( + source_or_glyph=sources["xy"]["hdi"]["y"]["lower"], + glyph=glyphs["xy"]["hdi"]["y"]["lower"]["glyph"], + hover_glyph=glyphs["xy"]["hdi"]["y"]["lower"]["hover_glyph"], + name="xyHDIYLower", + ) + figures["xy"].add_glyph( + source_or_glyph=sources["xy"]["hdi"]["y"]["upper"], + glyph=glyphs["xy"]["hdi"]["y"]["upper"]["glyph"], + hover_glyph=glyphs["xy"]["hdi"]["y"]["upper"]["hover_glyph"], + name="xyHDIYUpper", + ) + figures["xy"].add_glyph( + source_or_glyph=sources["xy"]["stats"], + glyph=glyphs["xy"]["stats"]["glyph"], + hover_glyph=glyphs["xy"]["stats"]["hover_glyph"], + name="xyStats", + ) + + +def create_annotations(sources: typing.Sources) -> typing.Annotations: + """ + Create any annotations for the figures of the tool. + + Args: + sources (typing.Sources): A dictionary of Bokeh ColumnDataSource objects. + + Returns: + typing.Annotations: A dictionary of Bokeh Annotation objects. + """ + palette = choose_palette(1) + color = palette[0] + output = { + "x": Band( + base="base", + lower="lower", + upper="upper", + source=sources["x"]["hdi"], + level="underlay", + fill_color=color, + fill_alpha=0.2, + line_color=None, + name="xHDI", + ), + "y": { + "lower": Band( + base="base", + lower="lower", + upper="upper", + source=sources["y"]["hdi"]["lower"], + level="underlay", + fill_color=color, + fill_alpha=0.2, + line_color=None, + name="yLowerHDI", + ), + "upper": Band( + base="base", + lower="lower", + upper="upper", + source=sources["y"]["hdi"]["upper"], + level="underlay", + fill_color=color, + fill_alpha=0.2, + line_color=None, + name="yUpperHDI", + ), + }, + } + return output + + +def add_annotations(figures: typing.Figures, annotations: typing.Annotations) -> None: + """ + Add the given annotations to the given figures of the tool. + + Args: + figures (typing.Figures): A dictionary of Bokeh Figure objects. + annotations (typing.Annotations): A dictionary of Bokeh Annotation objects. + + Returns: + None: Adds annotations directly to the given figures. + """ + figures["x"].add_layout(annotations["x"]) + figures["y"].add_layout(annotations["y"]["lower"]) + figures["y"].add_layout(annotations["y"]["upper"]) + + +def create_tooltips( + rv_name_x: str, + rv_name_y: str, + figures: typing.Figures, +) -> typing.Tooltips: + """ + Create hover tools for the glyphs used in the figures of the tool. + + Args: + rv_name_x (str): The name of the random variable data in the x-direction. + rv_name_y (str): The name of the random variable data in the y-direction. + figures (typing.Figures): A dictionary of Bokeh Figure objects. + + Returns: + typing.Tooltips: A dictionary of Bokeh HoverTools objects. + """ + x_dist = filter_renderers(figure=figures["x"], search="xDistribution") + x_stats = filter_renderers(figure=figures["x"], search="xStats") + y_dist = filter_renderers(figure=figures["y"], search="yDistribution") + y_stats = filter_renderers(figure=figures["y"], search="yStats") + xy_dist = filter_renderers(figure=figures["xy"], search="xyDistribution") + xy_lower_x_hdi = filter_renderers(figure=figures["xy"], search="xyLowerXHDI") + xy_upper_x_hdi = filter_renderers(figure=figures["xy"], search="xyUpperXHDI") + xy_lower_y_hdi = filter_renderers(figure=figures["xy"], search="xyLowerYHDI") + xy_upper_y_hdi = filter_renderers(figure=figures["xy"], search="xyUpperYHDI") + xy_stats = filter_renderers(figure=figures["xy"], search="xyStats") + output = { + "x": { + "distribution": HoverTool(renderers=x_dist, tooltips=[(rv_name_x, "@x")]), + "stats": HoverTool(renderers=x_stats, tooltips=[("", "@text")]), + }, + "y": { + "distribution": HoverTool(renderers=y_dist, tooltips=[(rv_name_y, "@y")]), + "stats": HoverTool(renderers=y_stats, tooltips=[("", "@text")]), + }, + "xy": { + "distribution": HoverTool( + renderers=xy_dist, + tooltips=[(rv_name_x, "@x"), (rv_name_y, "@y")], + ), + "hdi": { + "x": { + "lower": HoverTool( + renderers=xy_lower_x_hdi, + tooltips=[(rv_name_x, "@x")], + ), + "upper": HoverTool( + renderers=xy_upper_x_hdi, + tooltips=[(rv_name_x, "@x")], + ), + }, + "y": { + "lower": HoverTool( + renderers=xy_lower_y_hdi, + tooltips=[(rv_name_y, "@y")], + ), + "upper": HoverTool( + renderers=xy_upper_y_hdi, + tooltips=[(rv_name_y, "@y")], + ), + }, + }, + "stats": HoverTool( + renderers=xy_stats, + tooltips=[(rv_name_x, "@x"), (rv_name_y, "@y")], + ), + }, + } + return output + + +def add_tooltips(figures: typing.Figures, tooltips: typing.Tooltips) -> None: + """ + Add the given tools to the figures. + + Args: + figures (typing.Figures): A dictionary of Bokeh Figure objects. + tooltips (typing.Tooltips): A dictionary of Bokeh HoverTools objects. + + Returns: + None: Adds the tooltips directly to the given figures. + """ + figures["x"].add_tools(tooltips["x"]["distribution"]) + figures["x"].add_tools(tooltips["x"]["stats"]) + figures["y"].add_tools(tooltips["y"]["distribution"]) + figures["y"].add_tools(tooltips["y"]["stats"]) + figures["xy"].add_tools(tooltips["xy"]["distribution"]) + figures["xy"].add_tools(tooltips["xy"]["stats"]) + figures["xy"].add_tools(tooltips["xy"]["hdi"]["x"]["lower"]) + figures["xy"].add_tools(tooltips["xy"]["hdi"]["x"]["upper"]) + figures["xy"].add_tools(tooltips["xy"]["hdi"]["y"]["lower"]) + figures["xy"].add_tools(tooltips["xy"]["hdi"]["y"]["upper"]) + + +def create_widgets( + rv_name_x: str, + rv_name_y: str, + rv_names: List[str], + bw_factor: float, + bandwidth_x: float, + bandwidth_y: float, +) -> typing.Widgets: + """ + Create the widgets used in the tool. + + Args: + rv_name_x (str): The name of the random variable along the x-axis. + rv_name_y (str): The name of the random variable along the y-axis. + rv_names (List[str]): A list of all available random variable names. + bw_factor (float): Multiplicative factor used when calculating the kernel + density estimate. + bandwidth_x (float): The bandwidth used to calculate the KDE along the x-axis. + bandwidth_y (float): The bandwidth used to calculate the KDE along the y-axis. + + Returns: + typing.Widgets: A dictionary of Bokeh widget objects. + """ + output = { + "rv_select_x": Select(value=rv_name_x, options=rv_names, title="Query (x)"), + "rv_select_y": Select(value=rv_name_y, options=rv_names, title="Query (y)"), + "bw_factor_slider": Slider( + title="Bandwidth factor", + start=0.01, + end=2.00, + value=1.00, + step=0.01, + ), + "hdi_slider_x": Slider(start=1, end=99, step=1, value=89, title="HDI (x)"), + "hdi_slider_y": Slider(start=1, end=99, step=1, value=89, title="HDI (y)"), + "bw_div_x": Div(text=f"Bandwidth {rv_name_x}: {bw_factor * bandwidth_x}"), + "bw_div_y": Div(text=f"Bandwidth {rv_name_y}: {bw_factor * bandwidth_y}"), + } + return output + + +def help_page() -> Div: + """ + Help tab for the tool. + + Returns: + Div: Bokeh Div widget containing the help tab information. + """ + text = """ +

+ Joint plot +

+

+ A joint plot shows univariate marginals along the x and y axes. The + central figure shows the bivariate marginal of both random variables. +

+ """ + output = Div( + text=text, + disable_math=False, + min_width=MARGINAL1D_PLOT_WIDTH + MARGINAL2D_PLOT_WIDTH, + ) + return output + + +def create_figure_grid(figures: typing.Figures) -> Row: + """Layout the given figures in a grid, and make one toolbar. + + Parameters + ---------- + figures : typing.Figures + A dictionary of Bokeh Figure objects. + + Returns + ------- + Row + A Bokeh layout object. + """ + toolbar = create_toolbar(list(figures.values())) + grid_box = GridBox( + children=[ + [figures["x"], 0, 0], + [figures["xy"], 1, 0], + [figures["y"], 1, 1], + ], + ) + return Row(children=[grid_box, toolbar]) + + +def create_view(figures: typing.Figures, widgets: typing.Widgets) -> Tabs: + """ + Create the tool view. + + Args: + figures (typing.Figures): A dictionary of Bokeh Figure objects. + widgets (typing.Widgets): A dictionary of Bokeh widget objects. + + Returns: + Tabs: Bokeh Tabs objects. + """ + help_panel = Panel(child=help_page(), title="Help", name="helpPanel") + figure_grid = create_figure_grid(figures) + tool_panel = Panel( + child=Column( + children=[ + Row(children=[widgets["rv_select_x"], widgets["rv_select_y"]]), + Row( + children=[ + figure_grid, + Column( + children=[ + widgets["bw_factor_slider"], + widgets["hdi_slider_x"], + widgets["hdi_slider_y"], + widgets["bw_div_x"], + widgets["bw_div_y"], + ] + ), + ], + css_classes=["bm-tool-loading", "arcs"], + ), + ], + ), + title="Marginal 2D", + name="toolPanel", + ) + return Tabs(tabs=[tool_panel, help_panel]) diff --git a/src/beanmachine/ppl/diagnostics/tools/utils/model_serializers.py b/src/beanmachine/ppl/diagnostics/tools/utils/model_serializers.py index 27f1d1f8be..2059e33669 100644 --- a/src/beanmachine/ppl/diagnostics/tools/utils/model_serializers.py +++ b/src/beanmachine/ppl/diagnostics/tools/utils/model_serializers.py @@ -12,8 +12,10 @@ def serialize_bm(samples: MonteCarloSamples) -> Dict[str, List[List[float]]]: """ Convert Bean Machine models to a JSON serializable object. + Args: samples (MonteCarloSamples): Output of a model from Bean Machine. + Returns Dict[str, List[List[float]]]: The JSON serializable object for use in the diagnostics tools. diff --git a/src/beanmachine/ppl/diagnostics/tools/viz.py b/src/beanmachine/ppl/diagnostics/tools/viz.py index c149ba9d7e..33cf1899ca 100644 --- a/src/beanmachine/ppl/diagnostics/tools/viz.py +++ b/src/beanmachine/ppl/diagnostics/tools/viz.py @@ -59,6 +59,18 @@ def marginal1d(self: DiagnosticsTools) -> None: Marginal1d(self.mcs).show() + @_requires_dev_packages + def marginal2d(self: DiagnosticsTools) -> None: + """ + Marginal 2D diagnostic tool for a Bean Machine model. + + Returns: + None: Displays the tool directly in a Jupyter notebook. + """ + from beanmachine.ppl.diagnostics.tools.marginal2d.tool import Marginal2d + + Marginal2d(self.mcs).show() + @_requires_dev_packages def trace(self: DiagnosticsTools) -> None: """ From b21f2e58f0026c5fce01f5159256ff65fd770ef1 Mon Sep 17 00:00:00 2001 From: Andy Maloney Date: Thu, 3 Nov 2022 19:51:05 -0500 Subject: [PATCH 2/2] Revert yarn.lock file --- .../ppl/diagnostics/tools/js/yarn.lock | 172 +++++++++--------- 1 file changed, 83 insertions(+), 89 deletions(-) diff --git a/src/beanmachine/ppl/diagnostics/tools/js/yarn.lock b/src/beanmachine/ppl/diagnostics/tools/js/yarn.lock index 216e2b2dad..7f512032f9 100644 --- a/src/beanmachine/ppl/diagnostics/tools/js/yarn.lock +++ b/src/beanmachine/ppl/diagnostics/tools/js/yarn.lock @@ -3,19 +3,19 @@ "@babel/runtime-corejs3@^7.10.2": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.20.1.tgz#d0775a49bb5fba77e42cbb7276c9955c7b05af8d" - integrity sha512-CGulbEDcg/ND1Im7fUNRZdGXmX2MTWVVZacQi/6DiKE5HNwZ3aVTm5PV4lO8HHz0B2h8WQyvKKjbX5XgTtydsg== + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.19.4.tgz#870dbfd9685b3dad5aeb2d00841bb8b6192e3095" + integrity sha512-HzjQ8+dzdx7dmZy4DQ8KV8aHi/74AjEbBGTFutBmg/pd3dY5/q1sfuOGPTFGEytlQhWoeVXqcK5BwMgIkRkNDQ== dependencies: core-js-pure "^3.25.1" - regenerator-runtime "^0.13.10" + regenerator-runtime "^0.13.4" "@babel/runtime@^7.10.2", "@babel/runtime@^7.18.9", "@babel/runtime@^7.9.2": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9" - integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg== + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" + integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== dependencies: - regenerator-runtime "^0.13.10" + regenerator-runtime "^0.13.4" "@bokeh/bokehjs@^2.4.3": version "2.4.3" @@ -78,14 +78,14 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.11.6": - version "0.11.7" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.7.tgz#38aec044c6c828f6ed51d5d7ae3d9b9faf6dbb0f" - integrity sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw== +"@humanwhocodes/config-array@^0.10.5": + version "0.10.7" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.7.tgz#6d53769fd0c222767e6452e8ebda825c22e9f0dc" + integrity sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" - minimatch "^3.0.5" + minimatch "^3.0.4" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" @@ -130,9 +130,9 @@ integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + version "0.3.16" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.16.tgz#a7982f16c18cae02be36274365433e5b49d7b23f" + integrity sha512-LCQ+NeThyJ4k1W2d+vIKdxuSt9R3pQSZ4P92m7EakaYuXcVWbHuT5bjNcqLd4Rdgi6xYWYDvBJZJLZSLanjDcA== dependencies: "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" @@ -150,7 +150,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": +"@nodelib/fs.walk@^1.2.3": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -167,9 +167,9 @@ "@types/estree" "*" "@types/eslint@*": - version "8.4.9" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.9.tgz#f7371980148697f4b582b086630319b55324b5aa" - integrity sha512-jFCSo4wJzlHQLCpceUhUnXdrPuCNOjGFMQ8Eg6JXxlz3QaCKOb7eGi2cephQdM4XTYsNej69P9JDJ1zqNIbncQ== + version "8.4.6" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.6.tgz#7976f054c1bccfcf514bff0564c0c41df5c08207" + integrity sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -202,9 +202,9 @@ integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/node@*": - version "18.11.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4" - integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg== + version "18.8.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.8.5.tgz#6a31f820c1077c3f8ce44f9e203e68a176e8f59e" + integrity sha512-Bq7G3AErwe5A/Zki5fdD3O6+0zDChhg671NfPjtIcbtzDNZTv4NPKMRFr7gtYPG7y+B8uTiNK4Ngd9T0FTar6Q== "@types/sizzle@*": version "2.3.3" @@ -219,47 +219,47 @@ "@types/jquery" "*" "@typescript-eslint/parser@^5.30.5": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.42.0.tgz#be0ffbe279e1320e3d15e2ef0ad19262f59e9240" - integrity sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA== + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.40.0.tgz#432bddc1fe9154945660f67c1ba6d44de5014840" + integrity sha512-Ah5gqyX2ySkiuYeOIDg7ap51/b63QgWZA7w6AHtFrag7aH0lRQPbLzUjk0c9o5/KZ6JRkTTDKShL4AUrQa6/hw== dependencies: - "@typescript-eslint/scope-manager" "5.42.0" - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/typescript-estree" "5.42.0" + "@typescript-eslint/scope-manager" "5.40.0" + "@typescript-eslint/types" "5.40.0" + "@typescript-eslint/typescript-estree" "5.40.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.42.0.tgz#e1f2bb26d3b2a508421ee2e3ceea5396b192f5ef" - integrity sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow== +"@typescript-eslint/scope-manager@5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.40.0.tgz#d6ea782c8e3a2371ba3ea31458dcbdc934668fc4" + integrity sha512-d3nPmjUeZtEWRvyReMI4I1MwPGC63E8pDoHy0BnrYjnJgilBD3hv7XOiETKLY/zTwI7kCnBDf2vWTRUVpYw0Uw== dependencies: - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/visitor-keys" "5.42.0" + "@typescript-eslint/types" "5.40.0" + "@typescript-eslint/visitor-keys" "5.40.0" -"@typescript-eslint/types@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.42.0.tgz#5aeff9b5eced48f27d5b8139339bf1ef805bad7a" - integrity sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw== +"@typescript-eslint/types@5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.40.0.tgz#8de07e118a10b8f63c99e174a3860f75608c822e" + integrity sha512-V1KdQRTXsYpf1Y1fXCeZ+uhjW48Niiw0VGt4V8yzuaDTU8Z1Xl7yQDyQNqyAFcVhpYXIVCEuxSIWTsLDpHgTbw== -"@typescript-eslint/typescript-estree@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.0.tgz#2592d24bb5f89bf54a63384ff3494870f95b3fd8" - integrity sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg== +"@typescript-eslint/typescript-estree@5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.40.0.tgz#e305e6a5d65226efa5471ee0f12e0ffaab6d3075" + integrity sha512-b0GYlDj8TLTOqwX7EGbw2gL5EXS2CPEWhF9nGJiGmEcmlpNBjyHsTwbqpyIEPVpl6br4UcBOYlcI2FJVtJkYhg== dependencies: - "@typescript-eslint/types" "5.42.0" - "@typescript-eslint/visitor-keys" "5.42.0" + "@typescript-eslint/types" "5.40.0" + "@typescript-eslint/visitor-keys" "5.40.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.42.0": - version "5.42.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.0.tgz#ee8d62d486f41cfe646632fab790fbf0c1db5bb0" - integrity sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg== +"@typescript-eslint/visitor-keys@5.40.0": + version "5.40.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.40.0.tgz#dd2d38097f68e0d2e1e06cb9f73c0173aca54b68" + integrity sha512-ijJ+6yig+x9XplEpG2K6FUdJeQGGj/15U3S56W9IqXKJqleuD7zJ2AX/miLezwxpd7ZxDAqO87zWufKg+RPZyQ== dependencies: - "@typescript-eslint/types" "5.42.0" + "@typescript-eslint/types" "5.40.0" eslint-visitor-keys "^3.3.0" "@webassemblyjs/ast@1.11.1": @@ -421,9 +421,9 @@ acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: - version "8.8.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" - integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== ajv-keywords@^3.5.2: version "3.5.2" @@ -507,9 +507,9 @@ ast-types-flow@^0.0.7: integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== axe-core@^4.4.3: - version "4.5.1" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.5.1.tgz#04d561c11b6d76d096d34e9d14ba2c294fb20cdc" - integrity sha512-1exVbW0X1O/HSr/WMwnaweyqcWOgZgLiVxdLG34pvSQk4NlYQr9OUy0JLwuhFfuVNQzzqgH57eYzkFBCb3bIsQ== + version "4.4.3" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f" + integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w== axobject-query@^2.2.0: version "2.2.0" @@ -565,9 +565,9 @@ callsites@^3.0.0: integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== caniuse-lite@^1.0.30001400: - version "1.0.30001429" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz#70cdae959096756a85713b36dd9cb82e62325639" - integrity sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg== + version "1.0.30001418" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz#5f459215192a024c99e3e3a53aac310fc7cf24e6" + integrity sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg== chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" @@ -643,9 +643,9 @@ confusing-browser-globals@^1.0.10: integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== core-js-pure@^3.25.1: - version "3.26.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.26.0.tgz#7ad8a5dd7d910756f3124374b50026e23265ca9a" - integrity sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA== + version "3.25.5" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.25.5.tgz#79716ba54240c6aa9ceba6eee08cf79471ba184d" + integrity sha512-oml3M22pHM+igfWHDfdLVq2ShWmjM2V4L+dQEBs0DWVIqEm9WHCwGAlZ6BmyBQGy5sFrJmcx+856D9lVKyGWYg== cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" @@ -735,9 +735,9 @@ doctrine@^3.0.0: esutils "^2.0.2" electron-to-chromium@^1.4.251: - version "1.4.284" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" - integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== + version "1.4.279" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.279.tgz#84267fec806a8b1c5a1daebf726c4e296e5bcdf9" + integrity sha512-xs7vEuSZ84+JsHSTFqqG0TE3i8EAivHomRQZhhcRvsmnjsh5C2KdhwNKf4ZRYtzq75wojpFyqb62m32Oam57wA== emoji-regex@^9.2.2: version "9.2.2" @@ -1024,14 +1024,13 @@ eslint-visitor-keys@^3.3.0: integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== eslint@^8.19.0: - version "8.26.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.26.0.tgz#2bcc8836e6c424c4ac26a5674a70d44d84f2181d" - integrity sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg== + version "8.25.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.25.0.tgz#00eb962f50962165d0c4ee3327708315eaa8058b" + integrity sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A== dependencies: "@eslint/eslintrc" "^1.3.3" - "@humanwhocodes/config-array" "^0.11.6" + "@humanwhocodes/config-array" "^0.10.5" "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -1047,14 +1046,14 @@ eslint@^8.19.0: fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" find-up "^5.0.0" - glob-parent "^6.0.2" + glob-parent "^6.0.1" globals "^13.15.0" + globby "^11.1.0" grapheme-splitter "^1.0.4" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - is-path-inside "^3.0.3" js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" @@ -1290,7 +1289,7 @@ glob-parent@^5.1.2: dependencies: is-glob "^4.0.1" -glob-parent@^6.0.2: +glob-parent@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== @@ -1458,9 +1457,9 @@ is-callable@^1.1.4, is-callable@^1.2.7: integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== is-core-module@^2.8.1, is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + version "2.10.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== dependencies: has "^1.0.3" @@ -1500,11 +1499,6 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -1739,7 +1733,7 @@ mime-types@^2.1.27: dependencies: mime-db "1.52.0" -minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -2015,10 +2009,10 @@ redux@^4.1.2: dependencies: "@babel/runtime" "^7.9.2" -regenerator-runtime@^0.13.10: - version "0.13.10" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" - integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: version "1.4.3" @@ -2332,9 +2326,9 @@ tslib@^1.10.0, tslib@^1.8.1: integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.3.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" - integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== tsutils@^3.21.0: version "3.21.0"