feat: cleanup

This commit is contained in:
NunoSempere 2022-06-19 19:25:55 -04:00
parent 01c5fbcc19
commit b3c70f1f56
64 changed files with 2390 additions and 212 deletions

8
packages/README.md Normal file
View File

@ -0,0 +1,8 @@
[](./webpage-refactor/public/example-graph.png)
This repository contains the code for the website [utility-function-extractor.quantifieduncertainty.org/](https://utility-function-extractor.quantifieduncertainty.org/) and tools to analyze utility comparisons. For details, see the relevant folders.
- [Webpage repository](to do: add links)
- [Tools repository](to do: add link)
[](./webpage-refactor/public/example-table.png)

View File

@ -0,0 +1 @@
Deprecated; see webpage-refactor instead.

View File

Before

Width:  |  Height:  |  Size: 289 KiB

After

Width:  |  Height:  |  Size: 289 KiB

View File

Before

Width:  |  Height:  |  Size: 294 KiB

After

Width:  |  Height:  |  Size: 294 KiB

View File

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 282 KiB

View File

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 266 KiB

View File

Before

Width:  |  Height:  |  Size: 294 KiB

After

Width:  |  Height:  |  Size: 294 KiB

View File

@ -354,7 +354,7 @@ export function CreateTable({ tableRows }) {
} }
return ( return (
<div className="w-full"> <div className="w-full">
<table className="w-full"> <table className="table-auto">
<thead> <thead>
<tr> <tr>
<th>Id</th> <th>Id</th>

View File

@ -0,0 +1,337 @@
import axios from "axios";
let data = [
{
source: "Thinking Fast and Slow",
target: "The Mathematical Theory of Communication",
distance: 99999.99999999999,
reasoning:
"TFS: Not original work. 1 million buyers, 0.3m readers, 0.03m did anything with it long-term. Debiasing doesn't work that much. But maybe some general sanity effects.\nMTC: foundational to half of good fields. Direct applications key to telecoms and internet, AI. Is very natural and probably invented anyway, but let's ignore replaceability. ",
},
{
source: "The Global Priorities Institute's Research Agenda",
target: "Superintelligence",
distance: 100,
reasoning:
"GPI: good stuff but v v hard questions read by ~200.\nSuperintelligence: Maybe 50,000 copies? associated interview tour transformed the field.",
},
{
source: "Thinking Fast and Slow",
target: "The Global Priorities Institute's Research Agenda",
distance: "100",
reasoning: "",
},
{
source: "The Global Priorities Institute's Research Agenda",
target: "The Mathematical Theory of Communication",
distance: 1000,
reasoning: "",
},
{
source: "Superintelligence",
target: "The Mathematical Theory of Communication",
distance: 10,
reasoning: "",
},
{
source: "Categorizing Variants of Goodhart's Law",
target: "The Vulnerable World Hypothesis",
distance: 10,
reasoning: "",
},
{
source: "Shallow evaluations of longtermist organizations",
target: "The motivated reasoning critique of effective altruism",
distance: 10,
reasoning: "",
},
{
source: "Shallow evaluations of longtermist organizations",
target: "Categorizing Variants of Goodhart's Law",
distance: 100,
reasoning: "",
},
{
source: "The motivated reasoning critique of effective altruism",
target: "Categorizing Variants of Goodhart's Law",
distance: 10,
reasoning: "",
},
{
source: "Shallow evaluations of longtermist organizations",
target: "Thinking Fast and Slow",
distance: 10,
reasoning: "",
},
{
source: "Thinking Fast and Slow",
target: "The motivated reasoning critique of effective altruism",
distance: 1,
reasoning: "",
},
{
source: "The motivated reasoning critique of effective altruism",
target: "The Global Priorities Institute's Research Agenda",
distance: 1000,
reasoning: "",
},
{
source: "Categorizing Variants of Goodhart's Law",
target: "The Global Priorities Institute's Research Agenda",
distance: 100,
reasoning: "",
},
{
source: "The Vulnerable World Hypothesis",
target: "The Global Priorities Institute's Research Agenda",
distance: 10,
reasoning: "",
},
{
source: "Reversals in Psychology",
target: "A Model of Patient Spending and Movement Building",
distance: 10,
reasoning:
"RiP: Cleaning up 10,000 people's brains a bit.\nMPS: far more important topic",
},
{
source: "Database of orgs relevant to longtermist/x-risk work",
target:
"What are some low-information priors that you find practically useful for thinking about the world?",
distance: "5",
reasoning:
"\nDatabase just saves a bit of time, maybe 100 counterfactual applications",
},
{
source: "Reversals in Psychology",
target: "Database of orgs relevant to longtermist/x-risk work",
distance: 1,
reasoning: "",
},
{
source: "Database of orgs relevant to longtermist/x-risk work",
target: "A Model of Patient Spending and Movement Building",
distance: 10,
reasoning: "",
},
{
source:
"What are some low-information priors that you find practically useful for thinking about the world?",
target: "A Model of Patient Spending and Movement Building",
distance: 2,
reasoning: "",
},
{
source: "Center for Election Science EA Wiki stub",
target:
"Extinguishing or preventing coal seam fires is a potential cause area",
distance: 1000,
reasoning: "",
},
{
source: "A comment on setting up a charity",
target: "Center for Election Science EA Wiki stub",
distance: 10,
reasoning: "",
},
{
source: "A comment on setting up a charity",
target: "Reversals in Psychology",
distance: 200,
reasoning: "",
},
{
source: "Center for Election Science EA Wiki stub",
target: "Reversals in Psychology",
distance: 20,
reasoning: "",
},
{
source: "Reversals in Psychology",
target:
"Extinguishing or preventing coal seam fires is a potential cause area",
distance: "50",
reasoning: "",
},
{
source: "Database of orgs relevant to longtermist/x-risk work",
target:
"Extinguishing or preventing coal seam fires is a potential cause area",
distance: "50",
reasoning: "",
},
{
source:
"What are some low-information priors that you find practically useful for thinking about the world?",
target:
"Extinguishing or preventing coal seam fires is a potential cause area",
distance: "10",
reasoning: "",
},
{
source: "A Model of Patient Spending and Movement Building",
target:
"Extinguishing or preventing coal seam fires is a potential cause area",
distance: 1,
reasoning: "",
},
{
source: "A comment on setting up a charity",
target: "Shallow evaluations of longtermist organizations",
distance: 1000,
reasoning: "",
},
{
source: "Center for Election Science EA Wiki stub",
target: "Shallow evaluations of longtermist organizations",
distance: 100,
reasoning: "",
},
{
source: "Reversals in Psychology",
target: "Shallow evaluations of longtermist organizations",
distance: 100,
reasoning: "",
},
{
source: "Database of orgs relevant to longtermist/x-risk work",
target: "Shallow evaluations of longtermist organizations",
distance: 100,
reasoning: "",
},
];
async function pushToMongoManually() {
let response = await axios.post(
"http://metaforecast-twitter-bot.herokuapp.com/utility-function-extractor",
{
data: data,
}
);
console.log(response);
}
pushToMongoManually();

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

View File

@ -4,13 +4,37 @@ 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.
Then, it cleverly aggregates them, by taking 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. ![](./public/example-prompt.png)
Then, it cleverly aggregates them, on the one hand by producing a graphical representation:
![](./public/example-graph.png)
and on the other hand doing some fast and clever mean aggregation [^1]:
![](./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". 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".
## **If you want to use the utility function extractor for a project, we are happy to add a page for your project, like `utility-function-extractor.quantifieduncertainty.org/your-project`**.
## Object structure ## Built with
- [Nextjs](https://nextjs.org/)
- [Netlify](https://github.com/netlify/netlify-plugin-nextjs/#readme)
- [React](https://reactjs.org/)
- [Squiggle](https://www.squiggle-language.com/)
- [Utility tools](to do: add links)
## Usage
Navigate to [utility-function-extractor.quantifieduncertainty.org/](https://utility-function-extractor.quantifieduncertainty.org/), and start comparing objects.
You can change the list of objects to be compared by clicking on "advanced options".
After comparing objects for a while, you will get a table and a graph with results. You can also use the [utility tools](to do: add link) package to process these results, for which you will need the json of comparisons, which can be found in "Advanced options" -> "Load comparisons"
## Notes
The core structure is json array of objects. Only the "name" attribute is required. If there is a "url", it is displayed nicely. The core structure is json array of objects. Only the "name" attribute is required. If there is a "url", it is displayed nicely.
@ -29,9 +53,35 @@ The core structure is json array of objects. Only the "name" attribute is requir
] ]
``` ```
## Netlify The core structure for links is as follows:
https://github.com/netlify/netlify-plugin-nextjs/#readme ```
[
{
"source": "Peter Parker",
"target": "Spiderman",
"squiggleString": "1 to 100",
"distance": 26.639800977355474
},
{
"source": "Spiderman",
"target": "Doctor Octopus",
"squiggleString": "20 to 2000",
"distance": 6.76997149080232
},
]
```
A previous version of this webpage had a more complicated structure, but it has since been simplified.
## Contributions and help
We welcome PR requests.
## License
Distributed under the MIT License. See LICENSE.txt for more information.
## To do ## To do
@ -40,12 +90,16 @@ https://github.com/netlify/netlify-plugin-nextjs/#readme
- [x] Push to github - [x] Push to github
- [x] Push to netlify - [x] Push to netlify
- [x] Don't allow further comparisons after completion - [x] Don't allow further comparisons after completion
- [ ] Paths table - [x] Paths table
- [ ] Add paths table - [x] Add paths table
- [ ] warn that the paths table is approximate. - [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 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. - However, I think it does make it more user to other users.
- [ ] 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
- [ ] Change README. - [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

@ -39,11 +39,12 @@ export function AdvancedOptions({
]; ];
return ( return (
<div> <div className="">
<br /> <br />
{/* Show advanced options*/} {/* Show advanced options*/}
<button <button
className="text-gray-500 text-sm" key={"advancedOptionsButton-top"}
className="text-gray-500 text-sm "
onClick={() => changeShowAdvanceOptions(!showAdvancedOptions)} onClick={() => changeShowAdvanceOptions(!showAdvancedOptions)}
> >
Advanced options Advanced options
@ -56,7 +57,7 @@ export function AdvancedOptions({
<button <button
className={effectButtonStyle} className={effectButtonStyle}
onClick={() => buttonToggles[i]()} onClick={() => buttonToggles[i]()}
id={`advancedOptionsButton-${i}`} key={`advancedOptionsButton-${i}`}
> >
{buttonName} {buttonName}
</button> </button>

View File

@ -1,4 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Separator } from "../separator.js";
// import JSONInput from "react-json-editor-ajrm/index"; // import JSONInput from "react-json-editor-ajrm/index";
// import locale from "react-json-editor-ajrm/locale/en"; // import locale from "react-json-editor-ajrm/locale/en";
@ -75,6 +76,7 @@ export function ComparisonsChanger({
onSubmit={handleSubmitInner} onSubmit={handleSubmitInner}
className={`inline ${show ? "" : "hidden"}`} className={`inline ${show ? "" : "hidden"}`}
> >
<Separator />
<h3 className="text-lg mt-8">Load comparisons</h3> <h3 className="text-lg mt-8">Load comparisons</h3>
<p>These can be edited, which will override your current comparisons.</p> <p>These can be edited, which will override your current comparisons.</p>
<br /> <br />

View File

@ -1,4 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Separator } from "../separator.js";
export function DataSetChanger({ onChangeOfDataset, show, listOfElements }) { export function DataSetChanger({ onChangeOfDataset, show, listOfElements }) {
/*let [value, setValue] = useState(`[ /*let [value, setValue] = useState(`[
@ -54,6 +55,7 @@ export function DataSetChanger({ onChangeOfDataset, show, listOfElements }) {
}; };
return ( return (
<div className={`${show ? "" : "hidden"} `}> <div className={`${show ? "" : "hidden"} `}>
<Separator />
<form onSubmit={handleSubmitInner} className="inline mt-0"> <form onSubmit={handleSubmitInner} className="inline mt-0">
<h3 className="text-lg mt-8 mb-4">Change dataset</h3> <h3 className="text-lg mt-8 mb-4">Change dataset</h3>
<textarea <textarea

View File

@ -1,10 +1,12 @@
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";
// googlecode // googlecode
export function ShowComparisons({ links, show }) { export function ShowComparisons({ links, show }) {
return ( return (
<div className={`text-left ${show ? "" : "hidden"}`}> <div className={`text-left ${show ? "" : "hidden"}`}>
<Separator />
<h3 className="text-lg mt-8">Comparisons</h3> <h3 className="text-lg mt-8">Comparisons</h3>
<CopyBlock <CopyBlock
text={JSON.stringify(links, null, 4)} text={JSON.stringify(links, null, 4)}

View File

@ -2,11 +2,13 @@ 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 spread from "cytoscape-spread";
import { import {
resolveToNumIfPossible, resolveToNumIfPossible,
getSquiggleSparkline, getSquiggleSparkline,
} from "../../lib/squiggle.js"; } from "../../lib/squiggle.js";
import { truncateValueForDisplay } from "../../lib/truncateNums.js"; import { truncateValueForDisplay } from "../../lib/truncateNums.js";
import { cutOffLongNames } from "../../lib/stringManipulations.js";
// import dagre from "cytoscape-dagre"; // import dagre from "cytoscape-dagre";
// import cola from "cytoscape-cola"; // import cola from "cytoscape-cola";
@ -38,32 +40,30 @@ const getEdgeLabel = async (squiggleString) => {
}; };
const getColors = (n) => { const getColors = (n) => {
let colors = colormap({ let colors;
colormap: "viridis", if (n >= 9) {
nshades: n, colors = colormap({
format: "hex", colormap: "viridis",
alpha: 1, nshades: n,
}); format: "hex",
return colors; alpha: 1,
}; });
const cutOffLongNames = (string) => {
let maxLength = 40;
let result;
if (string.length < maxLength) {
result = string;
} else { } else {
result = string.slice(0, maxLength - 4); colors = colormap({
result = result + "..."; colormap: "greys", // hot,
nshades: n,
format: "hex",
alpha: 1,
});
} }
return result; return colors;
}; };
export function Graph({ export function Graph({
listOfElements, listOfElements,
links, links,
isListOrdered, isListOrdered,
mergeSortOrder, listAfterMergeSort,
}) { }) {
const containerRef = useRef("hello-world"); const containerRef = useRef("hello-world");
const [visibility, setVisibility] = useState(""); /// useState("invisible"); const [visibility, setVisibility] = useState(""); /// useState("invisible");
@ -72,14 +72,14 @@ export function Graph({
listOfElements, listOfElements,
links, links,
isListOrdered, isListOrdered,
mergeSortOrder, listAfterMergeSort,
}) => { }) => {
//setVisibility("invisible"); //setVisibility("invisible");
let layoutName = "circle"; // let layoutName = "circle"; //
// cytoscape.use(circle); // spread, circle, // cytoscape.use(circle); // spread, circle,
let listOfElementsForGraph = isListOrdered let listOfElementsForGraph = isListOrdered
? mergeSortOrder ? listAfterMergeSort
: listOfElements; : listOfElements;
let colors = new Array(listOfElements.length); let colors = new Array(listOfElements.length);
@ -92,10 +92,11 @@ export function Graph({
data: { data: {
id: cutOffLongNames(element.name), id: cutOffLongNames(element.name),
color: colors[i] || "darkgreen", color: colors[i] || "darkgreen",
labelColor: labelColor: isListOrdered
isListOrdered && i >= listOfElementsForGraph.length - 2 ? i >= listOfElementsForGraph.length - 2
? "black" ? "black"
: "white", : "white"
: "white",
}, },
}; };
}); });
@ -195,22 +196,38 @@ export function Graph({
userPanningEnabled: false, userPanningEnabled: false,
}; };
cytoscape(config); cytoscape(config);
// setTimeout(() => setVisibility(""), 700); //setTimeout(() => setVisibility(""), 700);
}; };
useEffect(async () => { useEffect(async () => {
await callEffect({ listOfElements, links, isListOrdered, mergeSortOrder }); await callEffect({
listOfElements,
links,
isListOrdered,
listAfterMergeSort,
});
// console.log(JSON.stringify(config, null, 10)); // console.log(JSON.stringify(config, null, 10));
}, [listOfElements, links, isListOrdered]); }, [listOfElements, links, isListOrdered, listAfterMergeSort]);
return ( return (
<div> <div className="">
<div className={visibility}> <div className={visibility + "grid grid-cols-1 place-items-center "}>
<div ref={containerRef} style={{ height: "900px", width: "1000px" }} /> <div
ref={containerRef}
style={{
height: "900px", // isListOrdered ? "900px" : "500px",
width: "900px", // isListOrdered ? "900px" : "500px",
}}
/>
</div> </div>
<button <button
className={effectButtonStyle} className={effectButtonStyle}
onClick={() => onClick={() =>
callEffect({ listOfElements, links, isListOrdered, mergeSortOrder }) callEffect({
listOfElements,
links,
isListOrdered,
listAfterMergeSort,
})
} }
> >
{"Redraw graph"} {"Redraw graph"}

View File

@ -1,89 +0,0 @@
import { SimpleReactCytoscape } from "simple-react-cytoscape";
import { Core } from "cytoscape";
import { useCallback, useState } from "react";
/*
const elements = [
// list of graph elements to start with
{
// node a
data: { id: "a" },
},
{
// node b
data: { id: "b" },
},
{
// node c
data: { id: "c" },
},
{
// edge ab
data: { id: "ab", source: "a", target: "b" },
},
{
// edge ab
data: { id: "ac", source: "a", target: "c" },
},
];
*/
const style = [
// the stylesheet for the graph
{
selector: "node",
style: {
"background-color": "#666",
label: "data(id)",
},
},
{
selector: "edge",
style: {
width: 3,
"line-color": "#ccc",
"target-arrow-color": "#ccc",
"target-arrow-shape": "triangle",
"curve-style": "bezier",
},
},
];
export function Graph({ listOfElements, links }) {
const [myCy, setMyCy] = useState();
const cyCallback = useCallback(
(cy) => {
setMyCy(cy);
},
[listOfElements, links]
);
let nodeElements = listOfElements.map((element) => {
return { data: { id: element.name } };
});
let linkElements = links.map((link, i) => {
return {
data: {
id: `link-i`,
source: link.source,
target: link.target,
label: link.distance,
},
};
});
let elements = [...nodeElements, ...linkElements];
let options = {
elements,
style,
};
return (
<div className="App">
<p>"a"</p>
<SimpleReactCytoscape options={options} cyCallback={cyCallback} />
<h3>JSON representation</h3>
{/*<p>{myCy && JSON.stringify(myCy.json())}</p>*/}
</div>
);
}

View File

@ -1,30 +0,0 @@
import React, { useEffect, useMemo, useState } from "react";
import cytoscape from "cytoscape";
export function Graph({ listOfElements, links, options, getCy }) {
const [cy, setCy] = useState();
const id = useRef("cytoscape-id");
useEffect(() => {
if (!cy) {
let container = null;
try {
container = document.getElementById(id) || undefined;
} catch (e) {
// Might be running Headless (the unit test are headless)
container = undefined;
}
const newCy = cytoscape({
...options,
container,
});
setCy(newCy);
// If a callback was supplied we can now return the value
if (getCy) {
getCy(newCy);
}
}
}, [cy, getCy, id, options]);
return <div id={id} className="simple-react-cytoscape" />;
}

View File

@ -5,6 +5,8 @@ import { Title } from "./title.js";
import { ProgressIndicator } from "./progressIndicator.js"; import { ProgressIndicator } from "./progressIndicator.js";
import { DisplayElementForComparison } from "./displayElementForComparison.js"; import { DisplayElementForComparison } from "./displayElementForComparison.js";
import { ComparisonActuator } from "./comparisonActuator.js"; import { ComparisonActuator } from "./comparisonActuator.js";
import { Separator } from "./separator.js";
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";
@ -26,7 +28,7 @@ export function Homepage({ listOfElementsInit }) {
// is list ordered? // is list ordered?
const [isListOrdered, setIsListOrdered] = useState(false); const [isListOrdered, setIsListOrdered] = useState(false);
const [mergeSortOrder, setMergeSortOrder] = useState([]); const [listAfterMergeSort, setListAfterMergeSort] = useState([]);
// list of comparisons // list of comparisons
const [links, setLinks] = useState([]); const [links, setLinks] = useState([]);
@ -48,6 +50,7 @@ export function Homepage({ listOfElementsInit }) {
setPairCurrentlyBeingCompared([newListOfElements[0], newListOfElements[1]]); setPairCurrentlyBeingCompared([newListOfElements[0], newListOfElements[1]]);
setNumStepsNow(0); setNumStepsNow(0);
setIsListOrdered(false); setIsListOrdered(false);
setListAfterMergeSort([]);
}; };
// process next step // process next step
@ -62,9 +65,9 @@ export function Homepage({ listOfElementsInit }) {
setIsListOrdered(false); setIsListOrdered(false);
setPairCurrentlyBeingCompared(newPairToCompare); setPairCurrentlyBeingCompared(newPairToCompare);
} else { } else {
setIsListOrdered(true); setListAfterMergeSort(mergeSortOutput.orderedList);
setMergeSortOrder(mergeSortOutput.orderedList);
pushToMongo({ mergeSortOutput, links }); pushToMongo({ mergeSortOutput, links });
setIsListOrdered(true); // good if it's at the end.
// alert(JSON.stringify(mergeSortOutput, null, 4)); // alert(JSON.stringify(mergeSortOutput, null, 4));
// chooseNextPairToCompareRandomly({ listOfElements }); // chooseNextPairToCompareRandomly({ listOfElements });
// return 1; // return 1;
@ -118,6 +121,7 @@ export function Homepage({ listOfElementsInit }) {
numStepsNow={numStepsNow} numStepsNow={numStepsNow}
numElements={listOfElements.length} numElements={listOfElements.length}
/> />
{/* Comparisons section */} {/* Comparisons section */}
<div className={"" /*isListOrdered ? "hidden" : ""*/}> <div className={"" /*isListOrdered ? "hidden" : ""*/}>
<div className="flex justify-evenly mt-10"> <div className="flex justify-evenly mt-10">
@ -137,26 +141,32 @@ export function Homepage({ listOfElementsInit }) {
></DisplayElementForComparison> ></DisplayElementForComparison>
</div> </div>
</div> </div>
{/* <Graph />
{/* <Results table /> */}
<ResultsTable
isListOrdered={isListOrdered}
listAfterMergeSort={listAfterMergeSort}
links={links}
/>
*/} {/* <Graph /> */}
<Separator />
<Graph <Graph
listOfElements={listOfElements} listOfElements={listOfElements}
links={links} links={links}
isListOrdered={isListOrdered} isListOrdered={isListOrdered}
mergeSortOrder={mergeSortOrder} listAfterMergeSort={listAfterMergeSort}
/> />
{/* Advanced options section */} {/* Advanced options section */}
<div> <Separator />
<AdvancedOptions <AdvancedOptions
links={links} links={links}
setLinks={setLinks} setLinks={setLinks}
listOfElements={listOfElements} listOfElements={listOfElements}
moveToNextStep={moveToNextStep} moveToNextStep={moveToNextStep}
onChangeOfDataset={onChangeOfDataset} onChangeOfDataset={onChangeOfDataset}
/> />
</div>
</div> </div>
); );
} }

View File

@ -0,0 +1,166 @@
import React, { useState, useEffect } from "react";
import { findDistances, aggregatePaths } from "utility-tools";
import { Separator } from "./separator.js";
import { truncateValueForDisplay } from "../lib/truncateNums.js";
import { cutOffLongNames } from "../lib/stringManipulations.js";
async function fullResultsTable({ listAfterMergeSort, links }) {
console.log("listAfterMergeSort", listAfterMergeSort);
console.log(links);
let pathsArray = await findDistances({
orderedList: listAfterMergeSort,
links: links,
});
// console.log(pathsArray);
let aggregatedPaths = await aggregatePaths({
pathsArray: pathsArray,
orderedList: listAfterMergeSort,
aggregationType: "mean",
// VERBOSE: false,
});
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;
if (array.length > 10) {
newArray = array.slice(0, 9);
formatForDisplay = newArray.map((d) => truncateValueForDisplay(d));
formatForDisplay[9] = "...";
} else {
newArray = array;
formatForDisplay = newArray.map((d) => truncateValueForDisplay(d));
}
let result = JSON.stringify(formatForDisplay, null, 2).replaceAll(`"`, "");
return result;
}
function getRow(row, i) {
return (
<tr
className="border-b dark:bg-gray-800 dark:border-gray-700 odd:bg-white even:bg-gray-50 odd:dark:bg-gray-800 even:dark:bg-gray-700"
key={`row-display-${i}`}
>
<td className="px-6 py-4 pt-7">{i}</td>
<td className="px-6 py-4">{cutOffLongNames(row.name)}</td>
<td className="text-center px-6 py-4">
{abridgeArrayAndDisplay(row.arrayMeans)}
</td>
<td className="text-center px-6 py-4">
{truncateValueForDisplay(row.aggregatedMeans)}
</td>
<td className="text-center px-6 py-4">
{truncateValueForDisplay(getCoefficientOfVariation(row.arrayMeans))}
</td>
</tr>
);
}
function reactTableContents(tableContents) {
// alert(JSON.stringify(tableContents));
// return "Hello";
return tableContents.map((row, i) => getRow(row, i));
}
export function ResultsTable({ isListOrdered, listAfterMergeSort, links }) {
const [isTableComputed, setIsTableComputed] = useState(false);
const [tableContents, setTableContents] = useState([]);
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 (
<div>
{!(isListOrdered && isTableComputed) ? (
""
) : (
<div>
<Separator />
<div className="relative overflow-x-auto shadow-md sm:rounded-lg mt-10">
<table className="w-full text-sm text-left text-gray-800 dark:text-gray-400">
<thead className=" text-xs text-gray-700 bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" className="px-6 py-3">
Position
</th>
<th scope="col" className="px-6 py-3">
Element
</th>
<th scope="col" className="text-center px-6 py-3">
Possible relative values
</th>
<th scope="col" className="text-center px-6 py-3">
Aggregated Means*
</th>
<th scope="col" className="text-center px-6 py-3">
Coefficient of variation
</th>
</tr>
</thead>
<tbody>{reactTableContents(tableContents)}</tbody>
</table>
</div>
</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
</p>
</div>
</div>
);
}

View File

@ -0,0 +1,9 @@
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>
);
}

View File

@ -0,0 +1,11 @@
export const cutOffLongNames = (string) => {
let maxLength = 40;
let result;
if (string.length < maxLength) {
result = string;
} else {
result = string.slice(0, maxLength - 4);
result = result + "...";
}
return result;
};

View File

@ -27,7 +27,7 @@
"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": "^0.2.2" "utility-tools": "^0.2.5"
}, },
"devDependencies": { "devDependencies": {
"@netlify/plugin-nextjs": "^4.2.1", "@netlify/plugin-nextjs": "^4.2.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

View File

@ -4048,6 +4048,14 @@ utility-tools@^0.2.2:
dependencies: dependencies:
"@quri/squiggle-lang" "^0.2.11" "@quri/squiggle-lang" "^0.2.11"
utility-tools@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/utility-tools/-/utility-tools-0.2.5.tgz#73ee06c88d74fc5a7bf87882caf93abed28600c2"
integrity sha512-sXT3RiOdjgO0DeYr5G3CPYrrXR2+8+d+GBGN6VELYXSFWfzBNUN1DA/alyYyuDaXRI+iBQequpuPy9rsHvsXGA==
dependencies:
"@quri/squiggle-lang" "^0.2.11"
utility-tools "^0.2.2"
vfile-message@^2.0.0: vfile-message@^2.0.0:
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a"

View File

@ -1,33 +0,0 @@
## About
This repository creates a react webpage that allows to extract a utility function from possibly inconsistent binary comparisons. 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. Then, it cleverly aggregates them, by taking 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!
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".
## Object structure
The core structure is json array of objects. Only the "name" attribute is required. If there is a "url", it is displayed nicely.
```
[
{
"name": "Peter Parker",
"someOptionalKey": "...",
"anotherMoreOptionalKey": "...",
},
{
"name": "Spiderman",
"someOptionalKey": "...",
"anotherMoreOptionalKey": "..."
}
]
```
## Netlify
https://github.com/netlify/netlify-plugin-nextjs/#readme
## To do
- [ ] Extract merge, findPath and aggregatePath functionality into different repos
- [ ] Add functionality like names, etc.