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_: 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`) ### Aggregate paths (`aggregatePaths`)
Given a list of path, aggregate them to finally produce an estimate of the relative utility of each element. 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", "name": "utility-tools",
"version": "1.0.5", "version": "1.0.0",
"description": "Process the json produced by utility-function-extractor.quantifieduncertainty.org", "description": "Process the json produced by utility-function-extractor.quantifieduncertainty.org",
"scripts": { "scripts": {
"start": "node --max-old-space-size=8192 src/index.js", "start": "node --max-old-space-size=8192 src/index.js",
@ -11,6 +11,7 @@
"author": "Nuño Sempere", "author": "Nuño Sempere",
"license": "MIT", "license": "MIT",
"dependencies": { "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(); console.group();
print("Number of paths: ", multipliedDistributions.length); print("Number of paths: ", multipliedDistributions.length);
// print(multipliedDistributions.slice(0, 10));
let squiggleCode = `aggregatePath = mx(${multipliedDistributions let squiggleCode = `aggregatePath = mx(${multipliedDistributions
.filter((distributions) => distributions != undefined) .filter((distributions) => distributions != undefined)
// .slice(0, 600)
.join(", ")})`; .join(", ")})`;
// Start measuring time // Start measuring time
@ -52,7 +54,6 @@ export async function aggregatePathsThroughMixtureOfDistributions({
4 4
)}` )}`
); );
// Stop measuring time // Stop measuring time
let end = Date.now(); let end = Date.now();
print(`${(end - start) / 1000} seconds needed for processing`); print(`${(end - start) / 1000} seconds needed for processing`);
@ -76,6 +77,7 @@ export const avg = (arr) => sum(arr) / arr.length;
export const geomMean = (arr) => { export const geomMean = (arr) => {
let n = arr.length; let n = arr.length;
let logavg = sum(arr.map((x) => Math.log(x))); // works for low numbers much better let logavg = sum(arr.map((x) => Math.log(x))); // works for low numbers much better
// console.log(logavg);
let result = Math.exp(logavg / n); let result = Math.exp(logavg / n);
return result; return result;
}; };
@ -84,7 +86,6 @@ export function aggregatePathsThroughMixtureOfMeans({
pathsArray, pathsArray,
orderedList, orderedList,
VERBOSE, VERBOSE,
DONT_EXCLUDE_INFINITIES_AND_NANS,
}) { }) {
let print = (x) => { let print = (x) => {
if (VERBOSE) { if (VERBOSE) {
@ -94,37 +95,26 @@ export function aggregatePathsThroughMixtureOfMeans({
let result = pathsArray.map((paths, i) => { let result = pathsArray.map((paths, i) => {
print(orderedList[i].name); print(orderedList[i].name);
let expectedRelativeValues = paths.map( let expectedRelativeValues = paths
(path) => path.expectedRelativeValue .map((path) => path.expectedRelativeValue)
); .filter((x) => x != undefined);
let hasPositive = expectedRelativeValues.filter((x) => x > 0);
let expectedRelativeValuesFiltered = expectedRelativeValues; let hasNegative = expectedRelativeValues.filter((x) => x < 0);
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; let answer;
if (hasPositive.length != 0 && hasNegative.length != 0) { if (hasPositive.length != 0 && hasNegative.length != 0) {
answer = avg(expectedRelativeValuesFiltered); answer = avg(expectedRelativeValues);
} else { } else {
if (hasNegative.length == 0) { if (hasNegative.length == 0) {
answer = geomMean(expectedRelativeValuesFiltered); answer = geomMean(expectedRelativeValues);
} else { } else {
let arrayAsPositive = expectedRelativeValuesFiltered.map((x) => -x); let arrayAsPositive = expectedRelativeValues.map((x) => -x);
answer = -geomMean(arrayAsPositive); answer = -geomMean(arrayAsPositive);
} }
} }
return { return {
name: orderedList[i].name, name: orderedList[i].name,
aggregatedMeans: answer, aggregatedMeans: answer,
arrayMeans: expectedRelativeValuesFiltered, arrayMeans: expectedRelativeValues,
allPositive: hasNegative.length == 0, allPositive: hasNegative.length == 0,
}; };
}); });

View File

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

View File

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

View File

@ -7,6 +7,7 @@ function isFirstElementGreater(links, element1, element2) {
(link.source == element2.name && link.target == element1.name) (link.source == element2.name && link.target == element1.name)
); );
if (relevantComparisons.length == 0) { if (relevantComparisons.length == 0) {
// console.log(element1, "vs", element2);
let answer = { let answer = {
foundAnswer: false, foundAnswer: false,
error: errorMsg, error: errorMsg,
@ -14,6 +15,7 @@ function isFirstElementGreater(links, element1, element2) {
return answer; return answer;
} else { } else {
const firstLink = relevantComparisons[0]; const firstLink = relevantComparisons[0];
// console.log(firstLink);
const firstElementFirst = const firstElementFirst =
firstLink.source == element1.name && firstLink.target == element2.name firstLink.source == element1.name && firstLink.target == element2.name
? true ? true
@ -32,11 +34,16 @@ function isFirstElementGreater(links, element1, element2) {
} }
function merge(links, left, right) { function merge(links, left, right) {
let sortedArr = []; let sortedArr = []; // the sorted elements will go here
while (left.length && right.length) { while (left.length && right.length) {
// insert the biggest element to the sortedArr // insert the biggest element to the sortedArr
let getComparisonAnswer = isFirstElementGreater(links, left[0], right[0]); let getComparisonAnswer = isFirstElementGreater(links, left[0], right[0]);
if (getComparisonAnswer.foundAnswer == false) { if (getComparisonAnswer.foundAnswer == false) {
// console.log("Error@:");
// console.group();
// console.log({ left, right });
// console.groupEnd();
let result = { let result = {
finishedMerge: false, finishedMerge: false,
uncomparedElements: [left[0], right[0]], uncomparedElements: [left[0], right[0]],
@ -46,8 +53,6 @@ function merge(links, left, right) {
} else if (getComparisonAnswer.foundAnswer == true) { } else if (getComparisonAnswer.foundAnswer == true) {
if (getComparisonAnswer.isFirstElementFirst == true) { if (getComparisonAnswer.isFirstElementFirst == true) {
// left[0] > right[0] // 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()); sortedArr.push(right.shift());
} else { } else {
sortedArr.push(left.shift()); sortedArr.push(left.shift());
@ -65,6 +70,7 @@ function merge(links, left, right) {
} }
export function mergeSortInner({ recursiveInput, links }) { export function mergeSortInner({ recursiveInput, links }) {
// console.log({ l: list.length });
if (recursiveInput.bottleneckedByComparison == true) { if (recursiveInput.bottleneckedByComparison == true) {
let result = { let result = {
recursiveInput: { recursiveInput: {
@ -91,7 +97,7 @@ export function mergeSortInner({ recursiveInput, links }) {
} }
const left = recursiveInput.list.slice(0, half); // the first half of the list 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({ let orderedFirstHalfAnswer = mergeSortInner({
recursiveInput: { list: left, bottleneckedByComparison: false }, recursiveInput: { list: left, bottleneckedByComparison: false },
links, links,
@ -158,7 +164,7 @@ export function mergeSort({ list, links }) {
return result; return result;
} }
/* /*
// otherwise, test all other permutations: // otherwise
let permutation = list.slice(); let permutation = list.slice();
var length = list.length; var length = list.length;
// let result = [list.slice()]; // 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. 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"> ![](./public/example-prompt.png)
<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: Then, it cleverly aggregates them, on the one hand by producing a graphical representation:
<p align="center"> ![](./public/example-graph.png)
<img width="50%" height="50%" src="./public/example-graph.png">
</p>
and on the other hand doing some fast and clever mean aggregation [^1]: and on the other hand doing some fast and clever mean aggregation [^1]:
<p align="center"> ![](./public/example-table.png)
<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". 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", "source": "Spiderman",
"target": "Jonah Jameson", "target": "Doctor Octopus",
"squiggleString": "20 to 2000", "squiggleString": "20 to 2000",
"distance": 6.76997149080232 "distance": 6.76997149080232
}, },
@ -99,13 +93,13 @@ Distributed under the MIT License. See LICENSE.txt for more information.
- [x] Paths table - [x] Paths table
- [x] Add paths table - [x] Add paths table
- [x] warn that the paths table is approximate. - [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 - However, 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. - However, I think it does make it more user to other users.
- [x] Change README.
- [ ] Add functionality like names, etc. - [ ] Add functionality like names, etc.
- I also don't feel like doing this - I also don't feel like doing this
- [ ] Look back at Amazon thing which has been running - [ ] Look back at Amazon thing which has been running
- [ ] Simplify Graph and DynamicSquiggleChart components - [x] Change README.
- [ ] Add squiggle component to initial comparison?
## 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. [^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 ( return (
<div className=""> <div className="">
<br />
{/* Show advanced options*/} {/* Show advanced options*/}
<button <button
key={"advancedOptionsButton-top"} key={"advancedOptionsButton-top"}
@ -48,6 +49,7 @@ export function AdvancedOptions({
> >
Advanced options Advanced options
</button> </button>
<br />
{/* Toggle buttons */} {/* Toggle buttons */}
<div className={showAdvancedOptions ? "" : "hidden"}> <div className={showAdvancedOptions ? "" : "hidden"}>
{buttonNames.map((buttonName, i) => { {buttonNames.map((buttonName, i) => {
@ -62,8 +64,7 @@ export function AdvancedOptions({
); );
})} })}
{/* Element: Show comparisons */} {/* Element: Show comparisons */}
{/* <ShowComparisons links={links} show={showComparisons} /> */} <ShowComparisons links={links} show={showComparisons} />
{/* Element: Change comparisons */} {/* Element: Change comparisons */}
<ComparisonsChanger <ComparisonsChanger
setLinks={setLinks} setLinks={setLinks}
@ -72,8 +73,6 @@ export function AdvancedOptions({
moveToNextStep={moveToNextStep} moveToNextStep={moveToNextStep}
links={links} links={links}
/> />
{/* Element: Dataset changer */}
<DataSetChanger <DataSetChanger
onChangeOfDataset={onChangeOfDataset} onChangeOfDataset={onChangeOfDataset}
show={showChangeDataset} show={showChangeDataset}

View File

@ -1,6 +1,9 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Separator } from "../separator.js"; 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) => { const checkLinksAreOk = (links, listOfElements) => {
let linkSourceNames = links.map((link) => link.source.name); let linkSourceNames = links.map((link) => link.source.name);
let linkTargetNames = links.map((link) => link.target.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)); setValue(JSON.stringify(links, null, 4));
// console.log(JSON.stringify(config, null, 10));
}, [links]); }, [links]);
return ( return (
@ -80,9 +84,30 @@ export function ComparisonsChanger({
value={value} value={value}
onChange={handleTextChange} onChange={handleTextChange}
rows={4 + JSON.stringify(links, null, 4).split("\n").length} 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 " 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 /> <br />
<button <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" 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) => { let handleSubmitInner = (event) => {
clearTimeout(displayingDoneMessageTimer); clearTimeout(displayingDoneMessageTimer);
event.preventDefault(); event.preventDefault();
//console.log(event)
console.log("value@handleSubmitInner@DataSetChanger"); console.log("value@handleSubmitInner@DataSetChanger");
//console.log(typeof(value));
console.log(value); console.log(value);
try { try {
let newData = JSON.parse(value); let newData = JSON.parse(value);
//console.log(typeof(newData))
//console.log(newData)
if (!newData.length || newData.length < 2) { if (!newData.length || newData.length < 2) {
throw Error("Not enough objects"); throw Error("Not enough objects");
} }
@ -60,7 +64,7 @@ export function DataSetChanger({ onChangeOfDataset, show, listOfElements }) {
rows={ rows={
1.2 * JSON.stringify(listOfElements, null, 4).split("\n").length 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 " className="text-left text-gray-600 bg-white rounded text-normal p-10 border-0 shadow outline-none focus:outline-none focus:ring "
/> />
<br /> <br />

View File

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

View File

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

View File

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

View File

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

View File

@ -2,8 +2,8 @@ import React from "react";
export function Separator() { export function Separator() {
return ( return (
<div className="py-4"> <div class="py-4">
<div className="w-full border-t border-4 border-gray-300 mt-10"></div> <div class="w-full border-t border-4 border-gray-300 mt-10"></div>
</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); console.log(response);
} }
} }
// pushToMongo()

View File

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

View File

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

View File

@ -1,6 +1,6 @@
/* Notes */ /* 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 // Most of the time, I'll want to edit that instead
/* Imports */ /* Imports */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 KiB

File diff suppressed because it is too large Load Diff