Compare commits

..

No commits in common. "master" and "develop" have entirely different histories.

27 changed files with 3660 additions and 1357 deletions

File diff suppressed because one or more lines are too long

View File

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

View File

@ -18,8 +18,10 @@ 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
@ -52,7 +54,6 @@ export async function aggregatePathsThroughMixtureOfDistributions({
4
)}`
);
// Stop measuring time
let end = Date.now();
print(`${(end - start) / 1000} seconds needed for processing`);
@ -76,6 +77,7 @@ 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;
};
@ -84,7 +86,6 @@ export function aggregatePathsThroughMixtureOfMeans({
pathsArray,
orderedList,
VERBOSE,
DONT_EXCLUDE_INFINITIES_AND_NANS,
}) {
let print = (x) => {
if (VERBOSE) {
@ -94,37 +95,26 @@ export function aggregatePathsThroughMixtureOfMeans({
let result = pathsArray.map((paths, i) => {
print(orderedList[i].name);
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 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 answer;
if (hasPositive.length != 0 && hasNegative.length != 0) {
answer = avg(expectedRelativeValuesFiltered);
answer = avg(expectedRelativeValues);
} else {
if (hasNegative.length == 0) {
answer = geomMean(expectedRelativeValuesFiltered);
answer = geomMean(expectedRelativeValues);
} else {
let arrayAsPositive = expectedRelativeValuesFiltered.map((x) => -x);
let arrayAsPositive = expectedRelativeValues.map((x) => -x);
answer = -geomMean(arrayAsPositive);
}
}
return {
name: orderedList[i].name,
aggregatedMeans: answer,
arrayMeans: expectedRelativeValuesFiltered,
arrayMeans: expectedRelativeValues,
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,6 +21,7 @@ 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();
@ -29,6 +30,7 @@ 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));
@ -37,14 +39,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, // optional arg
DONT_EXCLUDE_INFINITIES_AND_NANS: false, // optional arg
VERBOSE: false,
});
console.log(aggregatedPaths);
}

View File

@ -13,8 +13,6 @@ 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,
@ -112,11 +110,10 @@ async function findPaths({
link.target == targetElementName) ||
(link.source == targetElementName && link.target == sourceElementName)
) {
// We have found a direct path.
// 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,
@ -163,7 +160,11 @@ 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,
@ -206,6 +207,49 @@ 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({
@ -219,12 +263,15 @@ 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,
@ -256,6 +303,7 @@ 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,6 +7,7 @@ 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,
@ -14,6 +15,7 @@ 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
@ -32,11 +34,16 @@ function isFirstElementGreater(links, element1, element2) {
}
function merge(links, left, right) {
let sortedArr = [];
let sortedArr = []; // the sorted elements will go here
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]],
@ -46,8 +53,6 @@ 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());
@ -65,6 +70,7 @@ function merge(links, left, right) {
}
export function mergeSortInner({ recursiveInput, links }) {
// console.log({ l: list.length });
if (recursiveInput.bottleneckedByComparison == true) {
let result = {
recursiveInput: {
@ -91,7 +97,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, and that slice instead creates a new array.
const right = recursiveInput.list.slice(half, recursiveInput.list.length); // Note that splice is destructive.
let orderedFirstHalfAnswer = mergeSortInner({
recursiveInput: { list: left, bottleneckedByComparison: false },
links,
@ -158,7 +164,7 @@ export function mergeSort({ list, links }) {
return result;
}
/*
// otherwise, test all other permutations:
// otherwise
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,21 +4,15 @@ 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.
<p align="center">
<img width="50%" height="50%" src="./public/example-prompt.png">
</p>
![](./public/example-prompt.png)
Then, it cleverly aggregates them, on the one hand by producing a graphical representation:
<p align="center">
<img width="50%" height="50%" src="./public/example-graph.png">
</p>
![](./public/example-graph.png)
and on the other hand doing some fast and clever mean aggregation [^1]:
<p align="center">
<img width="50%" height="50%" src="./public/example-table.png">
</p>
![](./public/example-table.png)
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".
@ -71,7 +65,7 @@ The core structure for links is as follows:
},
{
"source": "Spiderman",
"target": "Jonah Jameson",
"target": "Doctor Octopus",
"squiggleString": "20 to 2000",
"distance": 6.76997149080232
},
@ -99,13 +93,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.
- 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.
- 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.
- [ ] Add functionality like names, etc.
- I also don't feel like doing this
- [ ] Look back at Amazon thing which has been running
- [ ] Simplify Graph and DynamicSquiggleChart components
- [ ] Add squiggle component to initial comparison?
- [x] Change README.
## Footnotes
[^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,6 +40,7 @@ export function AdvancedOptions({
return (
<div className="">
<br />
{/* Show advanced options*/}
<button
key={"advancedOptionsButton-top"}
@ -48,6 +49,7 @@ export function AdvancedOptions({
>
Advanced options
</button>
<br />
{/* Toggle buttons */}
<div className={showAdvancedOptions ? "" : "hidden"}>
{buttonNames.map((buttonName, i) => {
@ -62,8 +64,7 @@ export function AdvancedOptions({
);
})}
{/* Element: Show comparisons */}
{/* <ShowComparisons links={links} show={showComparisons} /> */}
<ShowComparisons links={links} show={showComparisons} />
{/* Element: Change comparisons */}
<ComparisonsChanger
setLinks={setLinks}
@ -72,8 +73,6 @@ export function AdvancedOptions({
moveToNextStep={moveToNextStep}
links={links}
/>
{/* Element: Dataset changer */}
<DataSetChanger
onChangeOfDataset={onChangeOfDataset}
show={showChangeDataset}

View File

@ -1,6 +1,9 @@
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);
@ -63,8 +66,9 @@ export function ComparisonsChanger({
}
};
useEffect(() => {
useEffect(async () => {
setValue(JSON.stringify(links, null, 4));
// console.log(JSON.stringify(config, null, 10));
}, [links]);
return (
@ -80,9 +84,30 @@ export function ComparisonsChanger({
value={value}
onChange={handleTextChange}
rows={4 + JSON.stringify(links, null, 4).split("\n").length}
cols={70}
cols={90}
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,10 +33,14 @@ 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");
}
@ -60,7 +64,7 @@ export function DataSetChanger({ onChangeOfDataset, show, listOfElements }) {
rows={
1.2 * JSON.stringify(listOfElements, null, 4).split("\n").length
}
cols={70}
cols={90}
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,6 +1,7 @@
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,4 +1,5 @@
import React, { useState } from "react";
// import { SubmitButton } from "./submitButton";
export function ComparisonActuator({
listOfElements,
@ -6,10 +7,8 @@ export function ComparisonActuator({
moveToNextStep,
isListOrdered,
}) {
const initialComparisonString = "";
const [comparisonString, setComparisonString] = useState(
initialComparisonString
);
const initialComparisonString = "x to y";
const [comparisonString, setComparisonString] = useState("x to y");
const onChangeComparisonString = async (event) => {
if (!isListOrdered) {
await setComparisonString(event.target.value);
@ -17,6 +16,7 @@ export function ComparisonActuator({
};
const onClickSubmitEvent = (event) => {
// console.log(event.target.value);
if (!isListOrdered) {
moveToNextStep({
listOfElements,
@ -36,7 +36,6 @@ 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

@ -1,74 +0,0 @@
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,16 +1,15 @@
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/squiggleCalculations.js";
} from "../../lib/squiggle.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";
@ -37,7 +36,7 @@ const getEdgeLabel = async (squiggleString) => {
//alert("▁▁▁▁▁▁▁▁▁▁▁");
}
return squiggleString + sparklineConcat;
return squiggleString + sparklineConcat; //sparkline;
};
const getColors = (n) => {
@ -51,7 +50,7 @@ const getColors = (n) => {
});
} else {
colors = colormap({
colormap: "greys", // other themes: hot, winter, etc.
colormap: "greys", // hot,
nshades: n,
format: "hex",
alpha: 1,
@ -68,9 +67,6 @@ 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,
@ -81,7 +77,7 @@ export function Graph({
//setVisibility("invisible");
let layoutName = "circle"; //
// cytoscape.use(spread); // necessary for non-default themes,
// cytoscape.use(circle); // spread, circle,
let listOfElementsForGraph = isListOrdered
? listAfterMergeSort
: listOfElements;
@ -112,7 +108,6 @@ export function Graph({
source: cutOffLongNames(link.source),
target: cutOffLongNames(link.target),
label: await getEdgeLabel(link.squiggleString),
squiggleString: link.squiggleString,
},
};
})
@ -127,6 +122,7 @@ export function Graph({
content: "data(id)",
"background-color": "data(color)",
"text-wrap": "wrap",
//"text-overflow-wrap": "anywhere",
"text-max-width": 70,
"z-index": 1,
},
@ -170,6 +166,8 @@ export function Graph({
"text-border-width": 0.5,
"text-border-opacity": 1,
"z-index": 3,
// "text-rotation": "autorotate"
},
},
];
@ -177,69 +175,48 @@ export function Graph({
const config = {
container: containerRef.current,
style: cytoscapeStylesheet,
elements: [...nodeElements, ...linkElements],
elements: [
/* Dummy data:
{ data: { id: "n1" } },
{ data: { id: "n2" } },
{ data: { id: "e1", source: "n1", target: "n2" } },
Real data:*/
...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,
};
let newCs = cytoscape(config);
setCs(newCs);
// setTimeout(() => setVisibility(""), 700);
// necessary for themes like spread, which have
// a confusing animation at the beginning
cytoscape(config);
//setTimeout(() => setVisibility(""), 700);
};
useEffect(() => {
callEffect({
useEffect(async () => {
await callEffect({
listOfElements,
links,
isListOrdered,
listAfterMergeSort,
});
}, [listOfElements, links, isListOrdered, listAfterMergeSort, selectedLink]);
// console.log(JSON.stringify(config, null, 10));
}, [listOfElements, links, isListOrdered, listAfterMergeSort]);
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="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 className="">
<div className={visibility + "grid grid-cols-1 place-items-center "}>
<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,11 +11,10 @@ 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/squiggleCalculations.js";
import { resolveToNumIfPossible } from "../lib/squiggle.js";
export function Homepage({ listOfElementsInit }) {
const SLICE = false;
/* Statefull elements */
// list of elements
@ -44,8 +43,6 @@ export function Homepage({ listOfElementsInit }) {
pairCurrentlyBeingComparedInit
);
/* Effects */
// dataset changer
const onChangeOfDataset = (newListOfElements) => {
setListOfElements(newListOfElements);
@ -70,11 +67,14 @@ export function Homepage({ listOfElementsInit }) {
} else {
setListAfterMergeSort(mergeSortOutput.orderedList);
pushToMongo({ mergeSortOutput, links });
setIsListOrdered(true); // should be at the end, because some useEffects are triggered by it.
setIsListOrdered(true); // good if it's at the end.
// alert(JSON.stringify(mergeSortOutput, null, 4));
// chooseNextPairToCompareRandomly({ listOfElements });
// return 1;
}
};
// Main mergesort step
// full
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,6 +94,8 @@ 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];
@ -101,13 +103,14 @@ 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 (
@ -120,10 +123,8 @@ export function Homepage({ listOfElementsInit }) {
/>
{/* Comparisons section */}
<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">
<div className={"" /*isListOrdered ? "hidden" : ""*/}>
<div className="flex justify-evenly mt-10">
<DisplayElementForComparison
element={pairCurrentlyBeingCompared[0]}
></DisplayElementForComparison>

View File

@ -5,7 +5,6 @@ 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);
@ -14,6 +13,7 @@ async function fullResultsTable({ listAfterMergeSort, links }) {
orderedList: listAfterMergeSort,
links: links,
});
// console.log(pathsArray);
let aggregatedPaths = await aggregatePaths({
pathsArray: pathsArray,
orderedList: listAfterMergeSort,
@ -23,6 +23,38 @@ 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;
@ -60,6 +92,8 @@ function getRow(row, i) {
}
function reactTableContents(tableContents) {
// alert(JSON.stringify(tableContents));
// return "Hello";
return tableContents.map((row, i) => getRow(row, i));
}
@ -67,22 +101,20 @@ export function ResultsTable({ isListOrdered, listAfterMergeSort, links }) {
const [isTableComputed, setIsTableComputed] = useState(false);
const [tableContents, setTableContents] = useState([]);
useEffect(() => {
let iAsync = async () => {
if (isListOrdered && listAfterMergeSort.length > 0) {
// 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);
setTableContents(tableContentsResult);
setIsTableComputed(true);
}
return () => console.log("cleanup");
};
iAsync();
useEffect(async () => {
// console.log(JSON.stringify(config, null, 10));
if (isListOrdered && listAfterMergeSort.length > 0) {
// both necessary because there is a small moment when list is ordered
// but listAfterMergeSort wasn't ready yet
let tableContentsResult = await fullResultsTable({
listAfterMergeSort,
links,
});
console.log(tableContentsResult);
// alert(JSON.stringify(tableContentsResult));
setTableContents(tableContentsResult);
setIsTableComputed(true);
}
}, [isListOrdered, listAfterMergeSort, links]);
return !(isListOrdered && isTableComputed) ? (
@ -121,18 +153,10 @@ 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 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
*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
</p>
</div>
</div>

View File

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

View File

@ -1,33 +0,0 @@
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,3 +13,4 @@ export async function pushToMongo(data) {
console.log(response);
}
}
// pushToMongo()

View File

@ -1,4 +1,5 @@
let topOutAt100AndValidate = (x) => {
// return 10;
if (x == x) {
return x > 99 ? 99 : x < 0 ? 2 : x;
} else {
@ -21,8 +22,6 @@ 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,7 +9,6 @@
},
"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",
@ -21,14 +20,14 @@
"cytoscape-spread": "^3.0.0",
"next": "latest",
"path": "^0.12.7",
"react": "^18.0.1",
"react": "^17.0.1",
"react-code-blocks": "^0.0.9-0",
"react-compound-slider": "^3.3.1",
"react-dom": "^18.0.1",
"react-dom": "17.0.1",
"react-markdown": "^6.0.2",
"remark-gfm": "^1.0.0",
"simple-react-cytoscape": "^1.0.4",
"utility-tools": "^1.0.4"
"utility-tools": "^0.2.5"
},
"devDependencies": {
"@netlify/plugin-nextjs": "^4.2.1",

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 KiB

File diff suppressed because it is too large Load Diff