Compare commits

..

18 Commits

27 changed files with 1357 additions and 3660 deletions

File diff suppressed because one or more lines are too long

View File

@ -40,6 +40,8 @@ Given an (ordered) list of elements and a list of utility comparisons, find all
_Note_: Some elements will have many more paths than others.
_Note_: The `findPaths.js` file has a few un-used functions which should make it easier to understand the code.
### Aggregate paths (`aggregatePaths`)
Given a list of path, aggregate them to finally produce an estimate of the relative utility of each element.

View File

@ -1,6 +1,6 @@
{
"name": "utility-tools",
"version": "1.0.0",
"version": "1.0.5",
"description": "Process the json produced by utility-function-extractor.quantifieduncertainty.org",
"scripts": {
"start": "node --max-old-space-size=8192 src/index.js",
@ -11,7 +11,6 @@
"author": "Nuño Sempere",
"license": "MIT",
"dependencies": {
"@quri/squiggle-lang": "^0.2.11",
"utility-tools": "^0.2.2"
"@quri/squiggle-lang": "^0.2.11"
}
}

View File

@ -18,10 +18,8 @@ export async function aggregatePathsThroughMixtureOfDistributions({
);
console.group();
print("Number of paths: ", multipliedDistributions.length);
// print(multipliedDistributions.slice(0, 10));
let squiggleCode = `aggregatePath = mx(${multipliedDistributions
.filter((distributions) => distributions != undefined)
// .slice(0, 600)
.join(", ")})`;
// Start measuring time
@ -54,6 +52,7 @@ export async function aggregatePathsThroughMixtureOfDistributions({
4
)}`
);
// Stop measuring time
let end = Date.now();
print(`${(end - start) / 1000} seconds needed for processing`);
@ -77,7 +76,6 @@ export const avg = (arr) => sum(arr) / arr.length;
export const geomMean = (arr) => {
let n = arr.length;
let logavg = sum(arr.map((x) => Math.log(x))); // works for low numbers much better
// console.log(logavg);
let result = Math.exp(logavg / n);
return result;
};
@ -86,6 +84,7 @@ export function aggregatePathsThroughMixtureOfMeans({
pathsArray,
orderedList,
VERBOSE,
DONT_EXCLUDE_INFINITIES_AND_NANS,
}) {
let print = (x) => {
if (VERBOSE) {
@ -95,26 +94,37 @@ export function aggregatePathsThroughMixtureOfMeans({
let result = pathsArray.map((paths, i) => {
print(orderedList[i].name);
let expectedRelativeValues = paths
.map((path) => path.expectedRelativeValue)
.filter((x) => x != undefined);
let hasPositive = expectedRelativeValues.filter((x) => x > 0);
let hasNegative = expectedRelativeValues.filter((x) => x < 0);
let expectedRelativeValues = paths.map(
(path) => path.expectedRelativeValue
);
let expectedRelativeValuesFiltered = expectedRelativeValues;
if (!DONT_EXCLUDE_INFINITIES_AND_NANS) {
expectedRelativeValuesFiltered = expectedRelativeValues
.filter((x) => x != undefined)
.filter((x) => !isNaN(x))
.filter((x) => isFinite(x))
.filter((x) => x != null);
}
let hasPositive = expectedRelativeValuesFiltered.filter((x) => x > 0);
let hasNegative = expectedRelativeValuesFiltered.filter((x) => x < 0);
let answer;
if (hasPositive.length != 0 && hasNegative.length != 0) {
answer = avg(expectedRelativeValues);
answer = avg(expectedRelativeValuesFiltered);
} else {
if (hasNegative.length == 0) {
answer = geomMean(expectedRelativeValues);
answer = geomMean(expectedRelativeValuesFiltered);
} else {
let arrayAsPositive = expectedRelativeValues.map((x) => -x);
let arrayAsPositive = expectedRelativeValuesFiltered.map((x) => -x);
answer = -geomMean(arrayAsPositive);
}
}
return {
name: orderedList[i].name,
aggregatedMeans: answer,
arrayMeans: expectedRelativeValues,
arrayMeans: expectedRelativeValuesFiltered,
allPositive: hasNegative.length == 0,
};
});

View File

@ -13,7 +13,7 @@ const outputFilePath = "./output/output.json";
// MAIN
async function main() {
// read file
// Read file
const inputLinksAsString = fs.readFileSync(inputLinksFilePath);
const inputListAsString = fs.readFileSync(inputListFilePath);
const links = JSON.parse(inputLinksAsString);
@ -21,7 +21,6 @@ async function main() {
// Merge sort
let mergeSortOutput = mergeSort({ list, links });
// console.log("Output: ");
if (mergeSortOutput.finishedOrderingList == false) {
console.log("Merge could not proceed");
console.group();
@ -30,7 +29,6 @@ async function main() {
console.groupEnd();
} else {
let orderedList = mergeSortOutput.orderedList;
// console.log(orderedList);
console.log("Sorted output: ");
console.group();
console.log(orderedList.map((x) => x.name));
@ -39,14 +37,14 @@ async function main() {
// find Paths
let paths = await findDistances({ orderedList, links });
// console.log(JSON.stringify(paths, null, 4));
// Aggregate paths.
let aggregatedPaths = await aggregatePaths({
pathsArray: paths,
orderedList,
aggregationType: "mean", // alternatively: aggregationType: "distribution"
VERBOSE: false,
VERBOSE: false, // optional arg
DONT_EXCLUDE_INFINITIES_AND_NANS: false, // optional arg
});
console.log(aggregatedPaths);
}

View File

@ -13,6 +13,8 @@ const findElementPosition = (name, nodes) => {
};
async function findPathsWithoutPrunning({
// DO NOT DELETE THIS UN-USED FUNCTION
// USEFUL FOR UNDERSTANDING AGAIN HOW THIS CODE WORKS AFTER A FEW MONTHS
sourceElementName,
targetElementName,
maxLengthOfPath,
@ -110,10 +112,11 @@ async function findPaths({
link.target == targetElementName) ||
(link.source == targetElementName && link.target == sourceElementName)
) {
// direct Path
// We have found a direct path.
let newPath = pathPlusLink(pathSoFar, link);
paths.push(newPath);
} else if (link.source == sourceElementName) {
// Recursively call find Paths
let newPaths = await findPaths({
pathSoFar: pathPlusLink(pathSoFar, link),
maxLengthOfPath: maxLengthOfPath - 1,
@ -160,11 +163,7 @@ async function findExpectedValuesAndDistributionsForElement({
// then orders them correctly in the for loop
// (by flipping the distance to 1/distance when necessary)
// and then gets the array of weights for the different paths.
/*
console.log(
`findDistance@findPaths.js from ${sourceElementPosition} to ${targetElementPosition}`
);
*/
let maxLengthOfPath = Math.abs(sourceElementPosition - targetElementPosition);
let paths = await findPaths({
sourceElementName,
@ -207,49 +206,6 @@ async function findExpectedValuesAndDistributionsForElement({
});
}
return processedPaths;
/*
let expectedValues = [];
for (let path of paths) {
let currentSource = sourceElementName;
let weight = 1;
for (let element of path) {
let distance = 0;
if (element.source == currentSource) {
distance = element.distance;
currentSource = element.target;
} else if (element.target == currentSource) {
distance = 1 / Number(element.distance);
currentSource = element.source;
}
weight = weight * distance;
}
expectedValues.push(weight);
}
let distributionalForm = [];
for (let path of paths) {
let currentSource = sourceElementName;
let multipliedDistributionsInPath = 1;
for (let element of path) {
let anotherDistributionInPath;
if (element.source == currentSource) {
distributionInPath = element.squiggleString;
currentSource = element.target;
} else if (element.target == currentSource) {
distance = `1 / (${element.squiggleString})`;
currentSource = element.source;
}
multipliedDistributionsInPath = `${multipliedDistributionsInPath} * (${anotherDistributionInPath})`;
}
distributionalForm.push(multipliedDistributionsInPath);
}
return {
expectedValues,
distributionalForm,
// paths,
};
*/
}
async function findDistancesFromAllElementsToReferencePoint({
@ -263,15 +219,12 @@ async function findDistancesFromAllElementsToReferencePoint({
/* Get or build reference element */
let midpoint = Math.round(nodes.length / 2);
referenceElement = referenceElement || nodes[midpoint];
// console.log(`referenceElement.position: ${referenceElement.position}`);
/* Get distances. */
let distancesArray = nodes.map((node) => {
if (node.name == referenceElement.name) {
return [1];
} else {
// console.log("node");
// console.log(node);
let expectedValuesAndDistributionsForElement =
findExpectedValuesAndDistributionsForElement({
sourceElementName: referenceElement.name,
@ -303,7 +256,6 @@ export async function findDistancesFromAllElementsToAllReferencePoints({
links,
referenceElement: node,
});
// alert(`distancesFromNode.length: ${distancesFromNode.length}`);
distancesForAllElements = distancesForAllElements.map((arr, i) => {
return !!arr && arr.length > 0
? [...arr, ...distancesFromNode[i]]

View File

@ -7,7 +7,6 @@ function isFirstElementGreater(links, element1, element2) {
(link.source == element2.name && link.target == element1.name)
);
if (relevantComparisons.length == 0) {
// console.log(element1, "vs", element2);
let answer = {
foundAnswer: false,
error: errorMsg,
@ -15,7 +14,6 @@ function isFirstElementGreater(links, element1, element2) {
return answer;
} else {
const firstLink = relevantComparisons[0];
// console.log(firstLink);
const firstElementFirst =
firstLink.source == element1.name && firstLink.target == element2.name
? true
@ -34,16 +32,11 @@ function isFirstElementGreater(links, element1, element2) {
}
function merge(links, left, right) {
let sortedArr = []; // the sorted elements will go here
let sortedArr = [];
while (left.length && right.length) {
// insert the biggest element to the sortedArr
let getComparisonAnswer = isFirstElementGreater(links, left[0], right[0]);
if (getComparisonAnswer.foundAnswer == false) {
// console.log("Error@:");
// console.group();
// console.log({ left, right });
// console.groupEnd();
let result = {
finishedMerge: false,
uncomparedElements: [left[0], right[0]],
@ -53,6 +46,8 @@ function merge(links, left, right) {
} else if (getComparisonAnswer.foundAnswer == true) {
if (getComparisonAnswer.isFirstElementFirst == true) {
// left[0] > right[0]
// note that we can order from smallest to largest or the reverse
// ; I forget which one this is.
sortedArr.push(right.shift());
} else {
sortedArr.push(left.shift());
@ -70,7 +65,6 @@ function merge(links, left, right) {
}
export function mergeSortInner({ recursiveInput, links }) {
// console.log({ l: list.length });
if (recursiveInput.bottleneckedByComparison == true) {
let result = {
recursiveInput: {
@ -97,7 +91,7 @@ export function mergeSortInner({ recursiveInput, links }) {
}
const left = recursiveInput.list.slice(0, half); // the first half of the list
const right = recursiveInput.list.slice(half, recursiveInput.list.length); // Note that splice is destructive.
const right = recursiveInput.list.slice(half, recursiveInput.list.length); // Note that splice is destructive, and that slice instead creates a new array.
let orderedFirstHalfAnswer = mergeSortInner({
recursiveInput: { list: left, bottleneckedByComparison: false },
links,
@ -164,7 +158,7 @@ export function mergeSort({ list, links }) {
return result;
}
/*
// otherwise
// otherwise, test all other permutations:
let permutation = list.slice();
var length = list.length;
// let result = [list.slice()];

File diff suppressed because one or more lines are too long

View File

@ -4,15 +4,21 @@ This repository creates a react webpage that allows to extract a utility functio
It presents the users with a series of elements to compare, using merge-sort in the background to cleverly minimize the number of choices needed.
![](./public/example-prompt.png)
<p align="center">
<img width="50%" height="50%" src="./public/example-prompt.png">
</p>
Then, it cleverly aggregates them, on the one hand by producing a graphical representation:
![](./public/example-graph.png)
<p align="center">
<img width="50%" height="50%" src="./public/example-graph.png">
</p>
and on the other hand doing some fast and clever mean aggregation [^1]:
![](./public/example-table.png)
<p align="center">
<img width="50%" height="50%" src="./public/example-table.png">
</p>
Initially, users could only input numbers, e.g., "A is `3` times better than B". But now, users can also input distributions, using the [squiggle](https://www.squiggle-language.com/) syntax, e.g., "A is `1 to 10` times better than B", or "A is `mm(normal(1, 10), uniform(0,100))` better than B".
@ -65,7 +71,7 @@ The core structure for links is as follows:
},
{
"source": "Spiderman",
"target": "Doctor Octopus",
"target": "Jonah Jameson",
"squiggleString": "20 to 2000",
"distance": 6.76997149080232
},
@ -93,13 +99,13 @@ Distributed under the MIT License. See LICENSE.txt for more information.
- [x] Paths table
- [x] Add paths table
- [x] warn that the paths table is approximate.
- However, I really don't feel like re-adding this after having worked out the distribution rather than the mean aggregation
- However, I think it does make it more user to other users.
- I really don't feel like re-adding this after having worked out the distribution rather than the mean aggregation
- On the other hand, I think it does make it more user to other users.
- [x] Change README.
- [ ] Add functionality like names, etc.
- I also don't feel like doing this
- [ ] Look back at Amazon thing which has been running
- [x] Change README.
## Footnotes
- [ ] Simplify Graph and DynamicSquiggleChart components
- [ ] Add squiggle component to initial comparison?
[^1]: The program takes each element as a reference point in turn, and computing the possible distances from that reference point to all other points, and taking the geometric mean of these distances. This produces a number representing the value of each element, such that the ratios between elements represent the user's preferences: a utility function. However, this isn't perfect; the principled approach woud be to aggregate the distributions rather than their means. But this principled approach is much more slowly. For the principled approach, see the `utility-tools` repository.

View File

@ -40,7 +40,6 @@ export function AdvancedOptions({
return (
<div className="">
<br />
{/* Show advanced options*/}
<button
key={"advancedOptionsButton-top"}
@ -49,7 +48,6 @@ export function AdvancedOptions({
>
Advanced options
</button>
<br />
{/* Toggle buttons */}
<div className={showAdvancedOptions ? "" : "hidden"}>
{buttonNames.map((buttonName, i) => {
@ -64,7 +62,8 @@ export function AdvancedOptions({
);
})}
{/* Element: Show comparisons */}
<ShowComparisons links={links} show={showComparisons} />
{/* <ShowComparisons links={links} show={showComparisons} /> */}
{/* Element: Change comparisons */}
<ComparisonsChanger
setLinks={setLinks}
@ -73,6 +72,8 @@ export function AdvancedOptions({
moveToNextStep={moveToNextStep}
links={links}
/>
{/* Element: Dataset changer */}
<DataSetChanger
onChangeOfDataset={onChangeOfDataset}
show={showChangeDataset}

View File

@ -1,9 +1,6 @@
import React, { useState, useEffect } from "react";
import { Separator } from "../separator.js";
// import JSONInput from "react-json-editor-ajrm/index";
// import locale from "react-json-editor-ajrm/locale/en";
const checkLinksAreOk = (links, listOfElements) => {
let linkSourceNames = links.map((link) => link.source.name);
let linkTargetNames = links.map((link) => link.target.name);
@ -66,9 +63,8 @@ export function ComparisonsChanger({
}
};
useEffect(async () => {
useEffect(() => {
setValue(JSON.stringify(links, null, 4));
// console.log(JSON.stringify(config, null, 10));
}, [links]);
return (
@ -84,30 +80,9 @@ export function ComparisonsChanger({
value={value}
onChange={handleTextChange}
rows={4 + JSON.stringify(links, null, 4).split("\n").length}
cols={90}
cols={70}
className="text-left text-gray-600 bg-white rounded text-normal p-10 border-0 shadow outline-none focus:outline-none focus:ring "
/>
{/* */}
{/*
<div className="flex text-left text-xl justify-around ">
<JSONInput
placeholder={value} // data to display
theme="dark_vscode_tribute" //"light_mitsuketa_tribute" //
locale={locale}
colors={{
string: "#DAA520", // overrides theme colors with whatever color value you want
}}
height="550px"
style={{
body: {
fontSize: "20px",
},
}}
onChange={onChangeForJsonEditor}
/>
</div>
*/}
<br />
<button
className="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded mt-5 p-10"

View File

@ -33,14 +33,10 @@ export function DataSetChanger({ onChangeOfDataset, show, listOfElements }) {
let handleSubmitInner = (event) => {
clearTimeout(displayingDoneMessageTimer);
event.preventDefault();
//console.log(event)
console.log("value@handleSubmitInner@DataSetChanger");
//console.log(typeof(value));
console.log(value);
try {
let newData = JSON.parse(value);
//console.log(typeof(newData))
//console.log(newData)
if (!newData.length || newData.length < 2) {
throw Error("Not enough objects");
}
@ -64,7 +60,7 @@ export function DataSetChanger({ onChangeOfDataset, show, listOfElements }) {
rows={
1.2 * JSON.stringify(listOfElements, null, 4).split("\n").length
}
cols={90}
cols={70}
className="text-left text-gray-600 bg-white rounded text-normal p-10 border-0 shadow outline-none focus:outline-none focus:ring "
/>
<br />

View File

@ -1,7 +1,6 @@
import React, { state } from "react";
import { CopyBlock, googlecode } from "react-code-blocks";
import { Separator } from "../separator.js";
// googlecode
export function ShowComparisons({ links, show }) {
return (

View File

@ -1,5 +1,4 @@
import React, { useState } from "react";
// import { SubmitButton } from "./submitButton";
export function ComparisonActuator({
listOfElements,
@ -7,8 +6,10 @@ export function ComparisonActuator({
moveToNextStep,
isListOrdered,
}) {
const initialComparisonString = "x to y";
const [comparisonString, setComparisonString] = useState("x to y");
const initialComparisonString = "";
const [comparisonString, setComparisonString] = useState(
initialComparisonString
);
const onChangeComparisonString = async (event) => {
if (!isListOrdered) {
await setComparisonString(event.target.value);
@ -16,7 +17,6 @@ export function ComparisonActuator({
};
const onClickSubmitEvent = (event) => {
// console.log(event.target.value);
if (!isListOrdered) {
moveToNextStep({
listOfElements,
@ -36,6 +36,7 @@ export function ComparisonActuator({
<br />
<input
disabled={isListOrdered ? true : false}
placeholder={"x to y"}
type="text"
className="text-center text-blueGray-600 bg-white rounded text-lg border-0 shadow outline-none focus:outline-none focus:ring w-8/12 h-10 m-2"
value={comparisonString}

View File

@ -0,0 +1,74 @@
import React from "react";
// import { SquiggleChart } from "@quri/squiggle-components";
import dynamic from "next/dynamic";
const SquiggleChart = dynamic(
() => import("@quri/squiggle-components").then((mod) => mod.SquiggleChart),
{
loading: () => <p>Loading...</p>,
ssr: false,
}
);
/*
const SquiggleChart = dynamic(
() => import("@quri/squiggle-components").then((mod) => mod.SquiggleChart),
{
suspense: true,
ssr: false,
}
);
*/
const effectButtonStyle =
"bg-transparent m-2 hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded mt-5";
export function DynamicSquiggleChart({ link, stopShowing }) {
if (link == null) {
return "";
} else {
let usefulLink = {
source: link.source,
target: link.target,
squiggleString: link.squiggleString,
};
return (
<div className="">
<textarea
value={JSON.stringify(usefulLink, null, 4)}
//onChange={handleChange}
disabled={true}
rows={JSON.stringify(usefulLink, null, 4).split("\n").length}
cols={37}
className="text-left text-gray-600 bg-white rounded text-normal p-6 border-0 shadow outline-none focus:outline-none focus:ring mb-4"
/>
<SquiggleChart
squiggleString={link.squiggleString}
width={445}
height={200}
showSummary={true}
showTypes={true}
/>
{/*
SquiggleChart props:
squiggleString?: string;
sampleCount?: number;
environment?: environment;
chartSettings?: FunctionChartSettings;
onChange?(expr: squiggleExpression): void;
width?: number;
height?: number;
bindings?: bindings;
jsImports?: jsImports;
showSummary?: boolean;
showTypes?: boolean;
showControls?: boolean;
*/}
<button className={effectButtonStyle} onClick={() => stopShowing()}>
Hide chart
</button>
</div>
);
}
}

View File

@ -1,15 +1,16 @@
import React, { useEffect, useState, useRef } from "react";
import colormap from "colormap";
import cytoscape from "cytoscape";
import spread from "cytoscape-spread";
import { DynamicSquiggleChart } from "../dynamicSquiggleChart.js";
import {
resolveToNumIfPossible,
getSquiggleSparkline,
} from "../../lib/squiggle.js";
} from "../../lib/squiggleCalculations.js";
import { truncateValueForDisplay } from "../../lib/truncateNums.js";
import { cutOffLongNames } from "../../lib/stringManipulations.js";
// import spread from "cytoscape-spread";
// import dagre from "cytoscape-dagre";
// import cola from "cytoscape-cola";
// import fcose from "cytoscape-fcose";
@ -36,7 +37,7 @@ const getEdgeLabel = async (squiggleString) => {
//alert("▁▁▁▁▁▁▁▁▁▁▁");
}
return squiggleString + sparklineConcat; //sparkline;
return squiggleString + sparklineConcat;
};
const getColors = (n) => {
@ -50,7 +51,7 @@ const getColors = (n) => {
});
} else {
colors = colormap({
colormap: "greys", // hot,
colormap: "greys", // other themes: hot, winter, etc.
nshades: n,
format: "hex",
alpha: 1,
@ -67,6 +68,9 @@ export function Graph({
}) {
const containerRef = useRef("hello-world");
const [visibility, setVisibility] = useState(""); /// useState("invisible");
const [cs, setCs] = useState(null); /// useState("invisible");
const [selectedLink, setSelectedLink] = useState(null);
const [selectedLinkTimeout, setSelectedLinkTimeout] = useState(null);
const callEffect = async ({
listOfElements,
@ -77,7 +81,7 @@ export function Graph({
//setVisibility("invisible");
let layoutName = "circle"; //
// cytoscape.use(circle); // spread, circle,
// cytoscape.use(spread); // necessary for non-default themes,
let listOfElementsForGraph = isListOrdered
? listAfterMergeSort
: listOfElements;
@ -108,6 +112,7 @@ export function Graph({
source: cutOffLongNames(link.source),
target: cutOffLongNames(link.target),
label: await getEdgeLabel(link.squiggleString),
squiggleString: link.squiggleString,
},
};
})
@ -122,7 +127,6 @@ export function Graph({
content: "data(id)",
"background-color": "data(color)",
"text-wrap": "wrap",
//"text-overflow-wrap": "anywhere",
"text-max-width": 70,
"z-index": 1,
},
@ -166,8 +170,6 @@ export function Graph({
"text-border-width": 0.5,
"text-border-opacity": 1,
"z-index": 3,
// "text-rotation": "autorotate"
},
},
];
@ -175,48 +177,69 @@ export function Graph({
const config = {
container: containerRef.current,
style: cytoscapeStylesheet,
elements: [
/* Dummy data:
{ data: { id: "n1" } },
{ data: { id: "n2" } },
{ data: { id: "e1", source: "n1", target: "n2" } },
Real data:*/
...nodeElements,
...linkElements,
],
elements: [...nodeElements, ...linkElements],
layout: {
name: layoutName, // circle, grid, dagre
minDist: 10,
//prelayout: false,
// animate: false, // whether to transition the node positions
// animationDuration: 250, // duration of animation in ms if enabled
// the cytoscape documentation is pretty good here.
},
userZoomingEnabled: false,
userPanningEnabled: false,
};
cytoscape(config);
//setTimeout(() => setVisibility(""), 700);
let newCs = cytoscape(config);
setCs(newCs);
// setTimeout(() => setVisibility(""), 700);
// necessary for themes like spread, which have
// a confusing animation at the beginning
};
useEffect(async () => {
await callEffect({
useEffect(() => {
callEffect({
listOfElements,
links,
isListOrdered,
listAfterMergeSort,
});
// console.log(JSON.stringify(config, null, 10));
}, [listOfElements, links, isListOrdered, listAfterMergeSort]);
}, [listOfElements, links, isListOrdered, listAfterMergeSort, selectedLink]);
useEffect(() => {
if (cs != null) {
clearTimeout(selectedLinkTimeout);
let newTimeout = setTimeout(() => {
cs.edges().on("mouseover", (event) => {
// on("click",
let edge = event.target;
// alert(JSON.stringify(edge.json()));
console.log(JSON.stringify(edge.json()));
setSelectedLink(JSON.parse(JSON.stringify(edge.json())).data);
});
}, 100);
setSelectedLinkTimeout(newTimeout);
}
}, [cs]);
return (
<div className="">
<div className={visibility + "grid grid-cols-1 place-items-center "}>
<div className="grid place-items-center">
<div
className={
visibility +
`grid grid-cols-${
selectedLink == null ? "1 " : "2"
} place-items-center place-self-center space-x-0 w-10/12 `
}
>
<div
ref={containerRef}
style={{
height: "900px", // isListOrdered ? "900px" : "500px",
width: "900px", // isListOrdered ? "900px" : "500px",
}}
className=""
/>
<DynamicSquiggleChart
link={selectedLink}
stopShowing={() => setSelectedLink(null)}
/>
</div>
<button

View File

@ -11,10 +11,11 @@ import { ResultsTable } from "./resultsTable.js";
import { AdvancedOptions } from "./advancedOptions/advancedOptions.js";
import { Graph } from "./graph/graph.js";
import { pushToMongo } from "../lib/pushToMongo.js";
import { resolveToNumIfPossible } from "../lib/squiggle.js";
import { resolveToNumIfPossible } from "../lib/squiggleCalculations.js";
export function Homepage({ listOfElementsInit }) {
const SLICE = false;
/* Statefull elements */
// list of elements
@ -43,6 +44,8 @@ export function Homepage({ listOfElementsInit }) {
pairCurrentlyBeingComparedInit
);
/* Effects */
// dataset changer
const onChangeOfDataset = (newListOfElements) => {
setListOfElements(newListOfElements);
@ -67,14 +70,11 @@ export function Homepage({ listOfElementsInit }) {
} else {
setListAfterMergeSort(mergeSortOutput.orderedList);
pushToMongo({ mergeSortOutput, links });
setIsListOrdered(true); // good if it's at the end.
// alert(JSON.stringify(mergeSortOutput, null, 4));
// chooseNextPairToCompareRandomly({ listOfElements });
// return 1;
setIsListOrdered(true); // should be at the end, because some useEffects are triggered by it.
}
};
// full
// Main mergesort step
const moveToNextStep = async ({
listOfElements,
pairCurrentlyBeingCompared,
@ -82,8 +82,8 @@ export function Homepage({ listOfElementsInit }) {
whileChangingStuff,
newLinksFromChangingStuff,
}) => {
// In the normal course of things:
if (!whileChangingStuff) {
// In the normal course of things:
let newLink = {
source: pairCurrentlyBeingCompared[0].name,
target: pairCurrentlyBeingCompared[1].name,
@ -94,8 +94,6 @@ export function Homepage({ listOfElementsInit }) {
if (numOption.asNum == false) {
alert(JSON.stringify(numOption.errorMsg));
} else if (numOption.asNum == true) {
// addLink({ ...newLink, distance: numOption.num }, links);
/// let newLinks = [...links, { ...newLink, distance: numOption.num }];
newLink = { ...newLink, distance: numOption.num };
addLink(newLink, links);
let newLinks = [...links, newLink];
@ -103,14 +101,13 @@ export function Homepage({ listOfElementsInit }) {
mergeSortStep({ list: listOfElements, links: newLinks });
}
} else {
// When changing comparisons:
mergeSortStep({
list: listOfElements,
links: newLinksFromChangingStuff,
});
setNumStepsNow(0); // almost no guarantees of how many left.
}
// When changing comparisons:
};
return (
@ -123,8 +120,10 @@ export function Homepage({ listOfElementsInit }) {
/>
{/* Comparisons section */}
<div className={"" /*isListOrdered ? "hidden" : ""*/}>
<div className="flex justify-evenly mt-10">
<div
className={"grid place-items-center" /*isListOrdered ? "hidden" : ""*/}
>
<div className="grid grid-rows-1 grid-cols-3 place-items-center w-6/11 mt-10">
<DisplayElementForComparison
element={pairCurrentlyBeingCompared[0]}
></DisplayElementForComparison>

View File

@ -5,6 +5,7 @@ import { Separator } from "./separator.js";
import { truncateValueForDisplay } from "../lib/truncateNums.js";
import { cutOffLongNames } from "../lib/stringManipulations.js";
import { getCoefficientOfVariation } from "../lib/coefficientOfVariation.js";
async function fullResultsTable({ listAfterMergeSort, links }) {
console.log("listAfterMergeSort", listAfterMergeSort);
@ -13,7 +14,6 @@ async function fullResultsTable({ listAfterMergeSort, links }) {
orderedList: listAfterMergeSort,
links: links,
});
// console.log(pathsArray);
let aggregatedPaths = await aggregatePaths({
pathsArray: pathsArray,
orderedList: listAfterMergeSort,
@ -23,38 +23,6 @@ async function fullResultsTable({ listAfterMergeSort, links }) {
return aggregatedPaths;
}
const sum = (arr) => arr.reduce((a, b) => a + b, 0);
function getStdev(arr) {
if (Array.isArray(arr) && arr.length > 0) {
const n = arr.length;
const mean = arr.reduce((a, b) => a + b) / n;
return Math.sqrt(
arr.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n
);
} else {
return 0;
}
}
export const geomMean = (arr) => {
let n = arr.length;
let logavg = sum(arr.map((x) => Math.log(x))); // works for low numbers much better
let result = Math.exp(logavg / n);
return result;
};
const getCoefficientOfVariation = (arr) => {
let nonPositiveNumbers = arr.filter((x) => x <= 0);
if (nonPositiveNumbers.length == 0) {
let gm = geomMean(arr);
let stdev = getStdev(arr);
return stdev / gm;
} else {
return getStdev(arr) / avg(arr);
}
};
function abridgeArrayAndDisplay(array) {
let newArray;
let formatForDisplay;
@ -92,8 +60,6 @@ function getRow(row, i) {
}
function reactTableContents(tableContents) {
// alert(JSON.stringify(tableContents));
// return "Hello";
return tableContents.map((row, i) => getRow(row, i));
}
@ -101,20 +67,22 @@ export function ResultsTable({ isListOrdered, listAfterMergeSort, links }) {
const [isTableComputed, setIsTableComputed] = useState(false);
const [tableContents, setTableContents] = useState([]);
useEffect(async () => {
// console.log(JSON.stringify(config, null, 10));
useEffect(() => {
let iAsync = async () => {
if (isListOrdered && listAfterMergeSort.length > 0) {
// both necessary because there is a small moment when list is ordered
// but listAfterMergeSort wasn't ready yet
// both comparisons aren't strictly necessary,
// but it bit me once, so I'm leaving it
let tableContentsResult = await fullResultsTable({
listAfterMergeSort,
links,
});
console.log(tableContentsResult);
// alert(JSON.stringify(tableContentsResult));
setTableContents(tableContentsResult);
setIsTableComputed(true);
}
return () => console.log("cleanup");
};
iAsync();
}, [isListOrdered, listAfterMergeSort, links]);
return !(isListOrdered && isTableComputed) ? (
@ -153,10 +121,18 @@ export function ResultsTable({ isListOrdered, listAfterMergeSort, links }) {
</div>
<div className="grid w-full place-items-center text-center ">
<p className="mt-8 max-w-5xl">
*This is the geometric mean if all elements are either all positive or
all negative, and the arithmetic mean otherwise. For a principled
aggregation which is able to produce meaningfull 90% confidence
intervals, see the utility-tools package in npm or github
*This is the geometric mean of all means of paths if all elements are
either all positive or all negative, and the arithmetic mean
otherwise. Paths with a non-numeric mean (e.g., resulting from
dividing by a mean of 0) are ignored. For a principled aggregation
which is able to produce meaningfull 90% confidence intervals, see the{" "}
<a
href="https://github.com/quantified-uncertainty/utility-function-extractor/tree/master/packages/utility-tools"
target="_blank"
>
utility-tools package
</a>{" "}
in npm or Github
</p>
</div>
</div>

View File

@ -2,8 +2,8 @@ import React from "react";
export function Separator() {
return (
<div class="py-4">
<div class="w-full border-t border-4 border-gray-300 mt-10"></div>
<div className="py-4">
<div className="w-full border-t border-4 border-gray-300 mt-10"></div>
</div>
);
}

View File

@ -0,0 +1,33 @@
const sum = (arr) => arr.reduce((a, b) => a + b, 0);
function getStdev(arr) {
if (Array.isArray(arr) && arr.length > 0) {
const n = arr.length;
const mean = arr.reduce((a, b) => a + b) / n;
return Math.sqrt(
arr.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n
);
} else {
return 0;
}
}
export const avg = (arr) => sum(arr) / arr.length;
export const geomMean = (arr) => {
let n = arr.length;
let logavg = sum(arr.map((x) => Math.log(x))); // works for low numbers much better, numerically
let result = Math.exp(logavg / n);
return result;
};
export const getCoefficientOfVariation = (arr) => {
let nonPositiveNumbers = arr.filter((x) => x <= 0);
if (nonPositiveNumbers.length == 0) {
let gm = geomMean(arr);
let stdev = getStdev(arr);
return stdev / gm;
} else {
return getStdev(arr) / avg(arr);
}
};

View File

@ -13,4 +13,3 @@ export async function pushToMongo(data) {
console.log(response);
}
}
// pushToMongo()

View File

@ -1,5 +1,4 @@
let topOutAt100AndValidate = (x) => {
// return 10;
if (x == x) {
return x > 99 ? 99 : x < 0 ? 2 : x;
} else {
@ -22,6 +21,8 @@ export const truncateValueForDisplay = (value) => {
candidateNumSignificantDigits
);
result = value.toFixed(numSignificantDigits);
} else if (value == 0) {
return 0;
} else if (-1 < value) {
let candidateNumSignificantDigits =
-Math.floor(Math.log(Math.abs(value)) / Math.log(10)) + 1;

View File

@ -9,6 +9,7 @@
},
"dependencies": {
"@nextui-org/react": "^1.0.0-beta.9",
"@quri/squiggle-components": "^0.2.22",
"@quri/squiggle-lang": "^0.2.11",
"axios": "^0.21.4",
"colormap": "^2.3.2",
@ -20,14 +21,14 @@
"cytoscape-spread": "^3.0.0",
"next": "latest",
"path": "^0.12.7",
"react": "^17.0.1",
"react": "^18.0.1",
"react-code-blocks": "^0.0.9-0",
"react-compound-slider": "^3.3.1",
"react-dom": "17.0.1",
"react-dom": "^18.0.1",
"react-markdown": "^6.0.2",
"remark-gfm": "^1.0.0",
"simple-react-cytoscape": "^1.0.4",
"utility-tools": "^0.2.5"
"utility-tools": "^1.0.4"
},
"devDependencies": {
"@netlify/plugin-nextjs": "^4.2.1",

View File

@ -1,6 +1,6 @@
/* Notes */
// This function is just a simple wrapper around lib/comparisonView.
// This function is just a simple wrapper around components/homepage.
// Most of the time, I'll want to edit that instead
/* Imports */

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

File diff suppressed because it is too large Load Diff