diff --git a/purescript/README.md b/purescript/README.md index 4605251..a852565 100644 --- a/purescript/README.md +++ b/purescript/README.md @@ -1,27 +1,28 @@ # Purescript -| Name | Description | -| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| [abilities](./abilities/) | Typeclass-dictionary abuse enabling the creation of a barebones effect system | -| [bug](./bug/) | Unknown reason of existence | -| [canopy](./canopy/) | Unfinished attempt at writing a [Diplomacy]() adjudecation engine | -| [compose](./compose/) | Overload do-notation for function composition and existential types | -| [ecs](./ecs/) | Purescript-wrapper for [thi.ng/ecs](thi.ng/ecs) | -| [existentials-blog](./existentials-blog) | Perhaps supposed to turn into a blog about existentials? | -| [existentials](./existentials) | Experiment regarding the Church-encoding of existential types | -| [free](./free/) | Experiments regarding free monads and interpreting algebras | -| [gadts](./gadts) | Experiment regarding ways to encode GADTs in Purescript | -| [kombinators](./kombinators) | Attempt at generating factorio combinator networks programmatically | -| [lambda-calculus](./lambda-calculus) | Lambda calculus evaluator | -| [lunarline](./lunarline) | Attempt at optimizing a functional language using compile-time partial evaluation | -| [lune](./lune) | Failed effect-system project | -| [maps](./maps) | Attempt at implementing maps with membership proofs | -| [proofs](./proofs) | Attempt at expressing mathematical proofs using Purescript's effect system | -| [purebird](./purebird) | Flappy-bird game | -| [purpleflow](./purpleflow) | Unfinished dependently-typed programming language | -| [slice](./slice) | Basic benchmarks and a `Slice` type | -| [sprint](./sprint) | Failled effect-system based on typelevel lists | -| [strategy](./strategy) | Unfinished attempt at implementing a mixed strategy nash equilibrium solver | -| [streams](./streams) | Playing with `purescript-pipes` | -| [thumbor](./thumbor) | Dropped attempt at writing purescript bindings for [thumbor-ts](https://github.com/Brettm12345/thumbor-ts) | -| [typelevel](./typelevel) | Typelevel naturals, vectors, sum-types, orderings and lambda-calculus evaluation and a value-level bounded-type GADT | +| Name | Description | +| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| [abilities](./abilities/) | Typeclass-dictionary abuse enabling the creation of a barebones effect system | +| [bug](./bug/) | Unknown reason of existence | +| [canopy](./canopy/) | Unfinished attempt at writing a [Diplomacy]() adjudecation engine | +| [compose](./compose/) | Overload do-notation for function composition and existential types | +| [ecs](./ecs/) | Purescript-wrapper for [thi.ng/ecs](thi.ng/ecs) | +| [existentials-blog](./existentials-blog) | Perhaps supposed to turn into a blog about existentials? | +| [existentials](./existentials) | Experiment regarding the Church-encoding of existential types | +| [free](./free/) | Experiments regarding free monads and interpreting algebras | +| [factorio-throughput](./factorio-throughput/) | Experiments with computing throughput of factorio systems | +| [gadts](./gadts) | Experiment regarding ways to encode GADTs in Purescript | +| [kombinators](./kombinators) | Attempt at generating factorio combinator networks programmatically | +| [lambda-calculus](./lambda-calculus) | Lambda calculus evaluator | +| [lunarline](./lunarline) | Attempt at optimizing a functional language using compile-time partial evaluation | +| [lune](./lune) | Failed effect-system project | +| [maps](./maps) | Attempt at implementing maps with membership proofs | +| [proofs](./proofs) | Attempt at expressing mathematical proofs using Purescript's effect system | +| [purebird](./purebird) | Flappy-bird game | +| [purpleflow](./purpleflow) | Unfinished dependently-typed programming language | +| [slice](./slice) | Basic benchmarks and a `Slice` type | +| [sprint](./sprint) | Failled effect-system based on typelevel lists | +| [strategy](./strategy) | Unfinished attempt at implementing a mixed strategy nash equilibrium solver | +| [streams](./streams) | Playing with `purescript-pipes` | +| [thumbor](./thumbor) | Dropped attempt at writing purescript bindings for [thumbor-ts](https://github.com/Brettm12345/thumbor-ts) | +| [typelevel](./typelevel) | Typelevel naturals, vectors, sum-types, orderings and lambda-calculus evaluation and a value-level bounded-type GADT | diff --git a/purescript/factorio-throughput/.gitignore b/purescript/factorio-throughput/.gitignore new file mode 100644 index 0000000..1349821 --- /dev/null +++ b/purescript/factorio-throughput/.gitignore @@ -0,0 +1,16 @@ +/bower_components/ +/node_modules/ +/.pulp-cache/ +/output/ +/generated-docs/ +/.psc-package/ +/.psc* +/.purs* +/.psa* +/.spago +dist +*.aux +*.fls +*.fdb_* +*.synctex* +*.log diff --git a/purescript/factorio-throughput/build.js b/purescript/factorio-throughput/build.js new file mode 100644 index 0000000..c64fa04 --- /dev/null +++ b/purescript/factorio-throughput/build.js @@ -0,0 +1,22 @@ +const esbuild = require("esbuild"); +const PurescriptPlugin = require("esbuild-plugin-purescript"); +const path = require("path"); + +const isProd = process.env.NODE_ENV === "production"; + +esbuild + .build({ + entryPoints: ["src/index.js"], + bundle: true, + outdir: "dist", + watch: !isProd, + plugins: [ + PurescriptPlugin({ + output: isProd ? path.resolve(__dirname, "dce-output") : undefined, + }), + ], + define: { + global: "window", + }, + }) + .catch((_e) => process.exit(1)); diff --git a/purescript/factorio-throughput/idea/example.pdf b/purescript/factorio-throughput/idea/example.pdf new file mode 100644 index 0000000..17cd036 Binary files /dev/null and b/purescript/factorio-throughput/idea/example.pdf differ diff --git a/purescript/factorio-throughput/idea/example.tex b/purescript/factorio-throughput/idea/example.tex new file mode 100644 index 0000000..bc199bd --- /dev/null +++ b/purescript/factorio-throughput/idea/example.tex @@ -0,0 +1,94 @@ +\documentclass[a4paper, 12pt]{article} + +\newcommand{\bold}{\textbf} + +\usepackage[english]{babel} +\usepackage{amsmath} +\usepackage{tikz} +\usepackage{indentfirst} + +\begin{document} + +\newcommand{\q2}{\quad\quad} + +\title{\Large{\bold{Moontorio}}} +\author{Matei Adriel} +\date {} + +\maketitle + +\section{Example 1} + +Solve the following factory: + +\vspace*{20pt} +\begin{figure}[h] +\centering +\begin{tikzpicture}[shorten >=1pt, auto, node distance={30mm}, + main/.style = {draw, rectangle}] + +\node[main] (3) {$consumer_1$}; +\node[main] (1) [above left of=3] {$provider_1$}; +\node[main] (2) [below left of=3] {$provider_2$}; +\node[main] (4) [right of=2] {$consumer_2$}; + +\draw[->] (1) edge node{$p_1$} (3); +\draw[->] (2) edge node{$p_2$} (3); +\draw[->] (2) edge node{$p_3$} (4); + +\end{tikzpicture} +\caption{Factory} +\label{fig:Factory} +\end{figure} + +Generating the constraints: +\begin{figure}[h] +\centering + +\begin{equation} + p_1(t) < provider_1(t) +\end{equation} + +\begin{equation} + p_3(t) < consumer_2(t) +\end{equation} + +\begin{equation} +\begin{split} + \begin{cases} + \begin{cases} + p_2(t) &< \displaystyle\frac{consumer_1(t)}{2}\\ + p_1(t) &< consumer_1(t) - p_2(t) + \end{cases} + \ ,&\;\mbox{if } p_1(t) \geq p_2(t)\\\\ + \begin{cases} + p_1(t) &< \displaystyle\frac{consumer_1(t)}{2}\\ + p_2(t) &< consumer_1(t) - p_1(t) + \end{cases} + \ ,&\;\mbox{if } p_1(t) < p_2(t) + \end{cases} +\end{split} +\end{equation} + +\begin{equation} +\begin{split} + \begin{cases} + \begin{cases} + p_3(t) &< \displaystyle\frac{provider_2(t)}{2}\\ + p_2(t) &< provider_2(t) - p_3(t) + \end{cases} + \ ,&\;\mbox{if } p_2(t) \geq p_3(t)\\\\ + \begin{cases} + p_2(t) &< \displaystyle\frac{provider_2(t)}{2}\\ + p_3(t) &< provider_2(t) - p_3(t) + \end{cases} + \ ,&\;\mbox{if } p_2(t) < p_3(t) + \end{cases} +\end{split} +\end{equation} + +\caption{Constraints} +\label{fig:Constraints} +\end{figure} + +\end{document} diff --git a/purescript/factorio-throughput/idea/hmm.pdf b/purescript/factorio-throughput/idea/hmm.pdf new file mode 100644 index 0000000..69f6bf8 Binary files /dev/null and b/purescript/factorio-throughput/idea/hmm.pdf differ diff --git a/purescript/factorio-throughput/idea/hmm.tex b/purescript/factorio-throughput/idea/hmm.tex new file mode 100644 index 0000000..2eeb8d2 --- /dev/null +++ b/purescript/factorio-throughput/idea/hmm.tex @@ -0,0 +1,77 @@ +\documentclass[a4paper, 12pt]{article} + +\newcommand{\bold}{\textbf} + +\usepackage[english]{babel} +\usepackage{amsmath} +\usepackage{tikz} +\usepackage{indentfirst} + +\begin{document} + +\newcommand{\q2}{\quad\quad} + +\title{\Large{\bold{Moontorio}}} +\author{Matei Adriel} +\date {} + +\maketitle + +\section{Describing a factory} + +A factory is made out of machines. A machine is either a provider, a belt or a consumer. Machines are connected by ports. + +\begin{figure}[h] + +\begin{equation} +\begin{split} +Machines\ A,\ B,\ C\ &::=\; belt\ p_i\ p_o \\ + &\quad|\quad provider\ p_1,\ p_2,\ ...\ p_n \\ + &\quad|\quad consumer\ p_1,\ p_2,\ ...\ p_n +\end{split} +\end{equation} +\caption{Machines} +\label{Machines} +\end{figure} + +We can represent the factory as a directed graph, with the machines being the nodes and the ports being the edges: + +\vspace*{20pt} +\begin{figure}[h] +\centering +\begin{tikzpicture}[shorten >=1pt, auto, node distance={50mm}, + main/.style = {draw, rectangle}] + +\node[main] (1) {$provider_1$}; +\node[main] (2) [right of=1] {$belt_1$}; +\node[main] (3) [right of=2] {$consumer_1$}; + +\draw[->] (1) edge node{$p_1$} (2); +\draw[->] (2) edge node{$p_2$} (3); + +\end{tikzpicture} +\caption{Example of a simple factory} +\label{SimpleFactory} +\end{figure} + +\section{Constraints} +The first step of the factory solving process is the constraint generation. +We currently use 3 different types of constraints (Figure \ref{Constraints}). +Let's take them one step at a time. The first two constrains ( + $p_k(t) <_{\Leftarrow} f(t)$ and $p_k(t) <_{\Rightarrow} f(t)$ +) are pretty similar, both limiting the flow through a port. + +\begin{figure}[ht] + +\begin{equation} +\begin{split} +Constraints\quad C_k\ &::=\; p_k(t) <_{\Leftarrow} f(t) \\ + &\quad|\quad p_k(t) <_{\Rightarrow} f(t) \\ + &\quad|\quad p_1(t) = p_2(f(t)) +\end{split} +\end{equation} +\caption{Constraints} +\label{Constraints} +\end{figure} + +\end{document} diff --git a/purescript/factorio-throughput/package.json b/purescript/factorio-throughput/package.json new file mode 100644 index 0000000..3f7309f --- /dev/null +++ b/purescript/factorio-throughput/package.json @@ -0,0 +1,13 @@ +{ + "name": "moontorio", + "scripts": { + "build": "node build.js" + }, + "dependencies": { + "calculess": "^1.0.2", + "esbuild": "^0.11.4", + "esbuild-plugin-purescript": "^1.0.0", + "events": "^3.3.0", + "function-plot": "^1.22.7" + } +} diff --git a/purescript/factorio-throughput/packages.dhall b/purescript/factorio-throughput/packages.dhall new file mode 100644 index 0000000..665be90 --- /dev/null +++ b/purescript/factorio-throughput/packages.dhall @@ -0,0 +1,27 @@ +let upstream = + https://github.com/purescript/package-sets/releases/download/psc-0.14.0-20210324/packages.dhall sha256:b4564d575da6aed1c042ca7936da97c8b7a29473b63f4515f09bb95fae8dddab + +let additions = + { debugged = + { dependencies = + [ "prelude" + , "console" + , "ordered-collections" + , "either" + , "tuples" + , "lists" + , "strings" + , "arrays" + , "bifunctors" + , "record" + , "effect" + , "datetime" + , "enums" + , "unordered-collections" + ] + , repo = "https://github.com/hdgarrood/purescript-debugged" + , version = "master" + } + } + +in upstream // additions diff --git a/purescript/factorio-throughput/pnpm-lock.yaml b/purescript/factorio-throughput/pnpm-lock.yaml new file mode 100644 index 0000000..780fc52 --- /dev/null +++ b/purescript/factorio-throughput/pnpm-lock.yaml @@ -0,0 +1,228 @@ +dependencies: + calculess: 1.0.2 + esbuild: 0.11.4 + esbuild-plugin-purescript: 1.0.0 + events: 3.3.0 + function-plot: 1.22.7 +lockfileVersion: 5.1 +packages: + /@types/assert/1.5.4: + dev: false + resolution: + integrity: sha512-CaFVW21Ulu0J9sUaEWJjwmhkDkeoxa4fniVSERzZC13sU9v8NNM2lMlkfZZv60j47D+qDt0Lyo8skVP3CTXUdA== + /built-in-math-eval/0.3.0: + dependencies: + math-codegen: 0.3.5 + dev: false + resolution: + integrity: sha1-JA3CHLOJQ5WIxhxGDrAHZJfvxBw= + /calculess/1.0.2: + dev: false + resolution: + integrity: sha512-9VCZJhW9fp7JmMgn6aTZ0U8NhcbJRFdD3vPlaYWMKIVIH1z4F/ZgDZKqx28fABjn4HaM4VT1ZoQzsONvI2LrCA== + /clamp/1.0.1: + dev: false + resolution: + integrity: sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ= + /d3-array/2.12.1: + dependencies: + internmap: 1.0.1 + dev: false + resolution: + integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== + /d3-axis/2.1.0: + dev: false + resolution: + integrity: sha512-z/G2TQMyuf0X3qP+Mh+2PimoJD41VOCjViJzT0BHeL/+JQAofkiWZbWxlwFGb1N8EN+Cl/CW+MUKbVzr1689Cw== + /d3-color/2.0.0: + dev: false + resolution: + integrity: sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ== + /d3-dispatch/2.0.0: + dev: false + resolution: + integrity: sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA== + /d3-drag/2.0.0: + dependencies: + d3-dispatch: 2.0.0 + d3-selection: 2.0.0 + dev: false + resolution: + integrity: sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w== + /d3-ease/2.0.0: + dev: false + resolution: + integrity: sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ== + /d3-format/2.0.0: + dev: false + resolution: + integrity: sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA== + /d3-interpolate/2.0.1: + dependencies: + d3-color: 2.0.0 + dev: false + resolution: + integrity: sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ== + /d3-path/2.0.0: + dev: false + resolution: + integrity: sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA== + /d3-scale/3.2.4: + dependencies: + d3-array: 2.12.1 + d3-format: 2.0.0 + d3-interpolate: 2.0.1 + d3-time: 2.0.0 + d3-time-format: 3.0.0 + dev: false + resolution: + integrity: sha512-PG6gtpbPCFqKbvdBEswQcJcTzHC8VEd/XzezF5e68KlkT4/ggELw/nR1tv863jY6ufKTvDlzCMZvhe06codbbA== + /d3-selection/2.0.0: + dev: false + resolution: + integrity: sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA== + /d3-shape/2.1.0: + dependencies: + d3-path: 2.0.0 + dev: false + resolution: + integrity: sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA== + /d3-time-format/3.0.0: + dependencies: + d3-time: 2.0.0 + dev: false + resolution: + integrity: sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag== + /d3-time/2.0.0: + dev: false + resolution: + integrity: sha512-2mvhstTFcMvwStWd9Tj3e6CEqtOivtD8AUiHT8ido/xmzrI9ijrUUihZ6nHuf/vsScRBonagOdj0Vv+SEL5G3Q== + /d3-timer/2.0.0: + dev: false + resolution: + integrity: sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA== + /d3-transition/2.0.0_d3-selection@2.0.0: + dependencies: + d3-color: 2.0.0 + d3-dispatch: 2.0.0 + d3-ease: 2.0.0 + d3-interpolate: 2.0.1 + d3-selection: 2.0.0 + d3-timer: 2.0.0 + dev: false + peerDependencies: + d3-selection: '2' + resolution: + integrity: sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog== + /d3-zoom/2.0.0: + dependencies: + d3-dispatch: 2.0.0 + d3-drag: 2.0.0 + d3-interpolate: 2.0.1 + d3-selection: 2.0.0 + d3-transition: 2.0.0_d3-selection@2.0.0 + dev: false + resolution: + integrity: sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw== + /double-bits/1.1.1: + dev: false + resolution: + integrity: sha1-WKu6RUlNpND6Nrc60RoobJGEscY= + /esbuild-plugin-purescript/1.0.0: + dev: false + resolution: + integrity: sha512-WdSdRtpm5AoRyDj3vEnmLsMsTY9kl3znRUTQe5ilbtcK/JIJv3Nz6d8lsEA079vmWgjc3JtR7Ii1y1omoz/wLg== + /esbuild/0.11.4: + dev: false + hasBin: true + requiresBuild: true + resolution: + integrity: sha512-qWGlOOTwyTn4f846LoR47Mif4Aek4rY9ChdXN7q7G15HpDYq3pxwnPFWe2os/jOq8naFh2Z+FqWfkq8ZP6kATw== + /events/3.3.0: + dev: false + engines: + node: '>=0.8.x' + resolution: + integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + /extend/3.0.2: + dev: false + resolution: + integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + /function-plot/1.22.7: + dependencies: + built-in-math-eval: 0.3.0 + clamp: 1.0.1 + d3-axis: 2.1.0 + d3-color: 2.0.0 + d3-format: 2.0.0 + d3-interpolate: 2.0.1 + d3-scale: 3.2.4 + d3-selection: 2.0.0 + d3-shape: 2.1.0 + d3-zoom: 2.0.0 + interval-arithmetic-eval: 0.4.7 + dev: false + resolution: + integrity: sha512-rB6FeVqvgNECmt5PhIvFFEOyEjM9AWLIpMkj9Nzbzq9f81Irgn3ZrXAuv5+qnuzM99jPL7ZM4kK3+ImiKXcSHA== + /internmap/1.0.1: + dev: false + resolution: + integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== + /interval-arithmetic-eval/0.4.7: + dependencies: + interval-arithmetic: 1.0.6 + math-codegen: 0.3.5 + dev: false + resolution: + integrity: sha512-ClK+N4efbsgjlZR8h0qd0LQbyzUzJ9IkrjmTnD5MVb4Ytebd0lesoVP4AxLclcsEI+nIieskQ8cepHIWUPaRhQ== + /interval-arithmetic/1.0.6: + dependencies: + '@types/assert': 1.5.4 + is-safe-integer: 2.0.0 + nextafter: 1.0.0 + typedarray: 0.0.6 + dev: false + resolution: + integrity: sha512-eVotDGYPNiEaJ63oa4CeEHgOczZJ3gNHqG5wfVQ2o8sN2CEczQyR82Sjey/Bp36x8e7PtBcBvitcMnw6VUpjgQ== + /is-safe-integer/2.0.0: + dependencies: + max-safe-integer: 1.0.1 + deprecated: This package is no longer relevant as ES2015 support is widespread. + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha512-eDaA39/1+3SNtYTRP28lRYOHMwiB1gfqXQaXcf/+f4mLwKgm8TTDkwJldsdtbgrK1R5CoDbf6AQ0KqP7BKoGtQ== + /math-codegen/0.3.5: + dependencies: + extend: 3.0.2 + mr-parser: 0.2.1 + dev: false + resolution: + integrity: sha1-R5nuRnfe0Ud2bQA8ykt4ee3UDMo= + /max-safe-integer/1.0.1: + dev: false + engines: + node: '>=0.10.0' + resolution: + integrity: sha1-84BgvixWPYwC5tSK85Ei/YO29BA= + /mr-parser/0.2.1: + dev: false + resolution: + integrity: sha1-hhi5ukF+KOn0OaQcaVtVTq/u2Sc= + /nextafter/1.0.0: + dependencies: + double-bits: 1.1.1 + dev: false + resolution: + integrity: sha1-t9d7U1MQ4+CX5gJauwqQNHfsGjo= + /typedarray/0.0.6: + dev: false + resolution: + integrity: sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +specifiers: + calculess: ^1.0.2 + esbuild: ^0.11.4 + esbuild-plugin-purescript: ^1.0.0 + events: ^3.3.0 + function-plot: ^1.22.7 diff --git a/purescript/factorio-throughput/spago.dhall b/purescript/factorio-throughput/spago.dhall new file mode 100644 index 0000000..e94d276 --- /dev/null +++ b/purescript/factorio-throughput/spago.dhall @@ -0,0 +1,24 @@ +{- +Welcome to a Spago project! +You can edit this file as you like. +-} +{ name = "my-project" +, dependencies = + [ "console" + , "debug" + , "effect" + , "filterable" + , "profunctor-lenses" + , "psci-support" + , "quickcheck" + , "quickcheck-laws" + , "run" + , "spec" + , "spec-discovery" + , "spec-quickcheck" + , "strings" + , "unordered-collections" + ] +, packages = ./packages.dhall +, sources = [ "src/**/*.purs", "test/**/*.purs" ] +} diff --git a/purescript/factorio-throughput/src/Lens.purs b/purescript/factorio-throughput/src/Lens.purs new file mode 100644 index 0000000..7c32d7b --- /dev/null +++ b/purescript/factorio-throughput/src/Lens.purs @@ -0,0 +1,62 @@ +module Functorio.Lens where + +import Prelude + +import Data.HashMap (HashMap) +import Data.HashMap as H +import Data.HashSet (HashSet) +import Data.HashSet as S +import Data.Hashable (class Hashable) +import Data.Lens (AGetter, Fold, Iso', Lens', Setter, iso, lens, over, preview, set, view) +import Data.Maybe (Maybe(..), maybe') +import Data.Maybe.First (First) +import Run (Run) +import Run.Reader (READER, ask) +import Run.State (STATE, get, modify) + +---------- Missing instances +atHashMap :: forall k v. Hashable k => k -> Lens' (HashMap k v) (Maybe v) +atHashMap k = + lens (H.lookup k) \m -> + maybe' (\_ -> H.delete k m) \v -> H.insert k v m + +-- | At implementation for hash sets +atHashSetRaw :: forall v. Hashable v => v -> Lens' (HashSet v) (Maybe Unit) +atHashSetRaw x = lens get (flip update) + where + get xs = + if S.member x xs + then Just unit + else Nothing + update Nothing = S.delete x + update (Just _) = S.insert x + +-- | Boolean implementation for AT on hash sets +atHashSet :: forall v. Hashable v => v -> Lens' (HashSet v) Boolean +atHashSet v = atHashSetRaw v <<< maybeUnitToBoolean + +-- | Helper fro implementing atHashSet' +maybeUnitToBoolean :: Iso' (Maybe Unit) Boolean +maybeUnitToBoolean = iso to from + where + from true = Just unit + from false = Nothing + + to Nothing = false + to _ = true + +--------- Helpers for monadic state +getAt :: forall s t a b r. AGetter s t a b -> Run (STATE s r) a +getAt optic = view optic <$> get + +getPreview :: forall r s t a b. Fold (First a) s t a b -> Run (STATE s r) (Maybe a) +getPreview optic = preview optic <$> get + +setAt :: forall s a b r. Setter s s a b -> b -> Run (STATE s r) Unit +setAt optic value = set optic value # modify + +modifyAt :: forall s a b r. Setter s s a b -> (a -> b) -> Run (STATE s r) Unit +modifyAt optic f = over optic f # modify + +askAt :: forall s t a b r. AGetter s t a b -> Run (READER s r) a +askAt optic = ask <#> view optic \ No newline at end of file diff --git a/purescript/factorio-throughput/src/Main.purs b/purescript/factorio-throughput/src/Main.purs new file mode 100644 index 0000000..0f2e3fd --- /dev/null +++ b/purescript/factorio-throughput/src/Main.purs @@ -0,0 +1,35 @@ +module Main where + +import Prelude + +import Data.Either (Either(..)) +import Data.Tuple (Tuple(..)) +import Data.Tuple.Nested ((/\)) +import Effect (Effect) +import Effect.Console (log) +import Moontorio.Render (RenderFn, renderFactory) +import RealFunction (PortSide(..), RealFunction, SolveM, collectConstraints, myFactory, runSolveM, tryFindBoundSolveM) + +p :: SolveM (Array RealFunction) +p = do + collectConstraints + a <- tryFindBoundSolveM (0 /\ Input) + b <- tryFindBoundSolveM (0 /\ Output) + c <- tryFindBoundSolveM (1 /\ Input) + d <- tryFindBoundSolveM (1 /\ Output) + e <- tryFindBoundSolveM (2 /\ Input) + f <- tryFindBoundSolveM (2 /\ Output) + g <- tryFindBoundSolveM (4 /\ Input) + h <- tryFindBoundSolveM (4 /\ Output) + pure [a, b, c, d, e, f, g, h] + +main :: RenderFn -> Effect Unit +main render = do + -- for_ (HashMap.toArrayBy Tuple myFactory) \(Tuple key value) -> log $ show key <> ": " <> show value + + case runSolveM myFactory p of + Left err -> log err + Right (Tuple s f) -> do + renderFactory render myFactory s.constraints + -- log $ joinWith "\n" $ show <$> s.constraints + -- logShow $ f <*> pure 0.0 \ No newline at end of file diff --git a/purescript/factorio-throughput/src/Render.purs b/purescript/factorio-throughput/src/Render.purs new file mode 100644 index 0000000..ab7391b --- /dev/null +++ b/purescript/factorio-throughput/src/Render.purs @@ -0,0 +1,18 @@ +module Moontorio.Render where + +import Prelude + +import Data.Foldable (for_) +import Data.Tuple.Nested ((/\)) +import Effect (Effect) +import RealFunction (Constraints, Factory, PortSide(..), RealFunction, factoryPorts, tryFindBoundPure, tryFindValuePure) + +type RenderFn = String -> Array RealFunction -> Effect Unit + +renderFactory :: RenderFn -> Factory -> Constraints -> Effect Unit +renderFactory render factory constraints = for_ (factoryPorts factory) \portId -> do + let inputMax = tryFindBoundPure (portId /\ Input) constraints + let outputMax = tryFindBoundPure (portId /\ Output) constraints + let actual = tryFindValuePure portId constraints + + render ("Port " <> show portId) [inputMax, outputMax, actual] \ No newline at end of file diff --git a/purescript/factorio-throughput/src/Run/Fail.purs b/purescript/factorio-throughput/src/Run/Fail.purs new file mode 100644 index 0000000..d94aeaa --- /dev/null +++ b/purescript/factorio-throughput/src/Run/Fail.purs @@ -0,0 +1,12 @@ +module Run.Fail.Extra where + +import Prelude + +import Data.Compactable (class Compactable, compact) +import Data.Traversable (class Traversable, traverse) +import Run (Run) +import Run.Except (FAIL, runFail) + +-- | `Compact` / `MapMaybe` usnig the `Fail` ability +traverseFail :: forall r t a b. Compactable t => Traversable t => (a -> Run (FAIL r) b) -> t a -> Run r (t b) +traverseFail f = traverse (f >>> runFail) >>> map compact \ No newline at end of file diff --git a/purescript/factorio-throughput/src/Run/Id.purs b/purescript/factorio-throughput/src/Run/Id.purs new file mode 100644 index 0000000..962de55 --- /dev/null +++ b/purescript/factorio-throughput/src/Run/Id.purs @@ -0,0 +1,33 @@ +module Run.Supply where + +import Prelude + +import Data.Tuple (Tuple(..)) +import Run (Run, Step(..), lift, on, runAccumPure) +import Type.Proxy (Proxy(..)) + +-- | Monad providing an infinite supply of values of a particular type. +-- | Example use cases: generating unique ids. +data SupplyF s a = Supply (s -> a) + +type SUPPLY s r = ( supply :: SupplyF s | r ) + +generate :: forall r s. Run (SUPPLY s r) s +generate = lift _supply (Supply identity) + +-- | Elimininate the supply monad using a function generating the next value +runSupply :: forall r s a. (s -> s) -> s -> Run (SUPPLY s r) a -> Run r a +runSupply next + = runAccumPure + (next >>> \current -> on _supply (Loop <<< handleSupply current) Done) + (\s a -> a) + where + handleSupply :: forall i. s -> SupplyF s i -> Tuple s i + handleSupply current (Supply continue) = Tuple current (continue current) + +---------- Typeclass instances +derive instance functorSupply :: Functor (SupplyF s) + +--------- SProxies +_supply :: Proxy "supply" +_supply = Proxy \ No newline at end of file diff --git a/purescript/factorio-throughput/src/Run/Reader.purs b/purescript/factorio-throughput/src/Run/Reader.purs new file mode 100644 index 0000000..b2836c2 --- /dev/null +++ b/purescript/factorio-throughput/src/Run/Reader.purs @@ -0,0 +1,18 @@ +module Run.Reader.Extra where + +import Prelude + +import Data.Lens (AGetter) +import Functorio.Lens (getAt) +import Run (Run) +import Run.Reader (READER, runReader) +import Run.State (STATE, get) +import Type.Row (type (+)) + +-- | Use state from the environemtn to eliminate a reader monad. +fromState :: forall r s a. Run (STATE s + READER s r) a -> Run (STATE s r) a +fromState m = get >>= flip runReader m + +-- | Focus on some state in the environemtn to eliminate a reader monad. +fromState' :: forall s t a b r x. AGetter s t a b -> Run (STATE s + READER a r) x -> Run (STATE s r) x +fromState' optic m = getAt optic >>= flip runReader m \ No newline at end of file diff --git a/purescript/factorio-throughput/src/Run/Visited.purs b/purescript/factorio-throughput/src/Run/Visited.purs new file mode 100644 index 0000000..97322b0 --- /dev/null +++ b/purescript/factorio-throughput/src/Run/Visited.purs @@ -0,0 +1,35 @@ +-- | Allows the programmer to limit a monad to only run once (using a key) +module Visited (VISITED, runVisited, once) where + +import Prelude + +import Data.HashSet (HashSet) +import Data.HashSet as HashSet +import Data.Hashable (class Hashable) +import Run (Run) +import Run.State (State, evalStateAt, getAt, modifyAt) +import Type.Proxy (Proxy(..)) + +-- | Monad keeping track of all the runned monad' keys +type VISITED a r = ( visited :: State (HashSet a) | r ) + +-- | Eliminate the Visited effect +runVisited :: forall d a r. Hashable d => Run (VISITED d r) a -> Run r a +runVisited = evalStateAt _visited mempty + +-- | Mark a key as visited +visit :: forall a r. Hashable a => a -> Run (VISITED a r) Unit +visit e = modifyAt _visited $ HashSet.insert e + +-- | Condition a monad to only run once. +-- | The first argument is a key, +-- | and the second is a default value to use when the monad has already run. +once :: forall d a r. Hashable d => d -> Run (VISITED d r) a -> Run (VISITED d r) a -> Run (VISITED d r) a +once at default m = do + visited <- getAt _visited + if HashSet.member at visited + then default + else visit at *> m + +_visited :: Proxy "visited" +_visited = Proxy \ No newline at end of file diff --git a/purescript/factorio-throughput/src/Throughput.purs b/purescript/factorio-throughput/src/Throughput.purs new file mode 100644 index 0000000..a9fe5ad --- /dev/null +++ b/purescript/factorio-throughput/src/Throughput.purs @@ -0,0 +1,308 @@ +module RealFunction where + +import Prelude + +import Data.Array (length, mapWithIndex) +import Data.Array as Array +import Data.Either (Either) +import Data.Foldable (foldMap, for_, minimum) +import Data.FoldableWithIndex (foldlWithIndex, forWithIndex_) +import Data.Generic.Rep (class Generic) +import Data.HashMap (HashMap) +import Data.HashMap as HashMap +import Data.HashMap as Map +import Data.HashSet as HashSet +import Data.Int (toNumber) +import Data.Lens (Lens') +import Data.Lens.Record (prop) +import Data.List (List(..), (:)) +import Data.Maybe (Maybe(..), fromJust, fromMaybe) +import Data.Number (infinity) +import Data.Show.Generic (genericShow) +import Data.Traversable (for) +import Data.Tuple (Tuple(..), fst, snd, uncurry) +import Data.Tuple.Nested (type (/\), (/\)) +import Functorio.Lens (modifyAt) +import Math (sin) +import Partial.Unsafe (unsafeCrashWith, unsafePartial) +import Run (Run, extract) +import Run.Except (EXCEPT, fail, runExcept) +import Run.Fail.Extra (traverseFail) +import Run.Reader (READER, ask, runReader) +import Run.Reader.Extra (fromState') +import Run.State (STATE, runState) +import Run.Supply (SUPPLY, generate, runSupply) +import Type.Proxy (Proxy(..)) +import Type.Row (type (+)) +import Visited (VISITED, once, runVisited) + +type RealFunction = Number -> Number +type BeltConfig = + { speed :: Number + , delay :: Number } + +type ChestConfig = + { maxContent :: Number + , delay :: Number } + +type PortId = Int +type MachineId = Int + +data PortSide = Input | Output + +data Machine + = Belt { input :: PortId, output :: PortId, config :: BeltConfig } + | Chest { inputs :: Array PortId, outputs :: Array PortId, config :: ChestConfig } + | Provider (Array PortId) RealFunction + | Consumer PortId + +type Factory = HashMap MachineId Machine + +---------- Some configs +yellowBelt :: BeltConfig +yellowBelt = { speed: 15.0, delay: 4.0/3.0 } + +redBelt :: BeltConfig +redBelt = { speed: 30.0, delay: 4.0/6.0 } + +blueBelt :: BeltConfig +blueBelt = { speed: 45.0, delay: 4.0/8.0 } + +-- | Example factory +myFactory1 :: Factory +myFactory1 = Map.fromArray machines + where + machines = mapWithIndex Tuple + [ Provider [0, 1] $ startsAtZero $ \t -> 40.0 + 10.0 * sin t + , Belt { input: 0, output: 3, config: yellowBelt } + , Belt { input: 1, output: 4, config: redBelt } + -- , Belt { input: 2, output: 5, config: blueBelt } + , Consumer 3 + , Consumer 4 + ] + +myFactory :: Factory +myFactory = Map.fromArray machines + where + machines = mapWithIndex Tuple + [ Provider [0, 1, 2] $ startsAtZero $ \t -> 80.0 + , Belt { input: 0, output: 3, config: yellowBelt } + , Belt { input: 1, output: 4, config: redBelt } + , Belt { input: 2, output: 5, config: blueBelt } + , Consumer 3 + , Consumer 4 + , Consumer 5 + ] + +---------- Helpers for real functions +type Endomorphism a = a -> a + +startsAtZero :: Endomorphism RealFunction +startsAtZero f x | x >= 0.0 = f x + | otherwise = 0.0 + +---------- Monad for factory solving +type PortData = + { id :: PortId + , maxInput :: Number -> Number + , maxOutput :: Number -> Number } + +data ConstraintExpression + = PortDependent (Array PortId) (Array PortData -> RealFunction) + | Function RealFunction + | Literal Number + +type BiRelationship = + { p1top2 :: RealFunction + , p2top1 :: RealFunction + , p1 :: PortId /\ PortSide + , p2 :: PortId /\ PortSide } + +type BiRelationshipId = Int + +data ThroughputConstraint + = Limit ConstraintExpression PortSide PortId + | BiRelationship BiRelationshipId BiRelationship + +type Constraints = Array ThroughputConstraint + +type SolveState = + { constraints :: Constraints } + +type SolveM = Run + ( EXCEPT String + + STATE SolveState + + READER Factory + + SUPPLY Int + + () ) + +runSolveM :: forall a. Factory -> SolveM a -> Either String (Tuple SolveState a) +runSolveM factory = runReader factory >>> runState mempty >>> runExcept >>> runSupply ((+) 1) 0 >>> extract + +focusBiRelationship :: PortId /\ PortSide -> BiRelationship -> Maybe BiRelationship +focusBiRelationship place relationship | relationship.p1 == place = Just relationship + | relationship.p2 == place = Just $ flipBiRelationship relationship + | otherwise = Nothing + +focusBiRelationshipWithoutSide :: PortId -> BiRelationship -> Maybe BiRelationship +focusBiRelationshipWithoutSide id relationship | fst relationship.p1 == id = Just relationship + | fst relationship.p2 == id = Just $ flipBiRelationship relationship + | otherwise = Nothing + +flipBiRelationship :: BiRelationship -> BiRelationship +flipBiRelationship { p1, p2, p1top2, p2top1 } = { p1: p2, p2: p1, p1top2: p2top1, p2top1: p1top2 } + +factoryPorts :: Factory -> HashSet.HashSet PortId +factoryPorts = foldMap case _ of + Belt { input, output } -> HashSet.fromArray [input, output] + Provider outputs _ -> HashSet.fromArray outputs + Chest { inputs, outputs } -> HashSet.fromArray (inputs <> outputs) + Consumer input -> HashSet.singleton input + +---------- System solving algorithm +constrain :: ThroughputConstraint -> SolveM Unit +constrain constraint = modifyAt _constraints $ push constraint + where + push = flip Array.snoc + +collectConstraints :: SolveM Unit +collectConstraints = do + factory <- ask + for_ (HashMap.toArrayBy (/\) $ factory) $ uncurry collectConstraintsImpl + +getPortData :: forall r. PortId -> Run (READER Constraints r) PortData +getPortData id = ado + maxInput <- tryFindBound $ id /\ Input + maxOutput <- tryFindBound $ id /\ Output + in { id, maxInput, maxOutput } + +evalExpr :: forall r. ConstraintExpression -> Run (READER Constraints r) RealFunction +evalExpr = case _ of + Literal a -> pure (const a) + Function f -> pure f + PortDependent portIds f -> for portIds getPortData <#> f + +tryFindBound :: forall r. PortId /\ PortSide -> Run (READER Constraints r) RealFunction +tryFindBound at = tryFindBoundImpl at <#> \f time -> extract $ runVisited $ f time + +tryFindBoundSolveM :: PortId /\ PortSide -> SolveM RealFunction +tryFindBoundSolveM at = fromState' _constraints $ tryFindBound at + +tryFindBoundPure :: PortId /\ PortSide -> Constraints -> RealFunction +tryFindBoundPure at constraints = extract $ runReader constraints $ tryFindBound at + +tryFindBoundImpl :: forall r k. + PortId /\ PortSide -> + Run (READER Constraints r) (Number -> Run (VISITED BiRelationshipId k) Number) +tryFindBoundImpl (targetId /\ targetSide) = do + constraints <- ask + pure \time -> constraints + # traverseFail case _ of + Limit expr side id | side == targetSide && id == targetId -> + evalExpr expr <*> pure time + BiRelationship id raw + | Just relationship <- focusBiRelationship (targetId /\ targetSide) raw -> do + f <- once id fail $ tryFindValueImpl $ fst relationship.p2 + f (relationship.p1top2 time) + _ -> fail + # runReader constraints + <#> minimum' + where + minimum' = minimum >>> fromMaybe 0.0 + +tryFindValue :: forall r. PortId -> Run (READER Constraints r) RealFunction +tryFindValue at = tryFindValueImpl at <#> \f time -> extract $ runVisited $ f time + +tryFindValueImpl :: forall r k. PortId -> Run (READER Constraints r) (Number -> Run (VISITED BiRelationshipId k) Number) +tryFindValueImpl targetId = do + constraints <- ask + pure \time -> constraints + # traverseFail case _ of + Limit expr _ id | id == targetId -> evalExpr expr <*> pure time + BiRelationship id raw + | Just relationship <- focusBiRelationshipWithoutSide targetId raw -> do + f <- once id fail $ tryFindValueImpl $ fst relationship.p2 + f (relationship.p1top2 time) + _ -> fail + # runReader constraints + <#> minimum' + where + minimum' = minimum >>> fromMaybe 0.0 + +tryFindValuePure :: PortId -> Constraints -> RealFunction +tryFindValuePure at constraints = extract $ runReader constraints $ tryFindValue at + +collectConstraintsImpl :: MachineId -> Machine -> SolveM Unit +collectConstraintsImpl at = case _ of + Provider for amount -> do + forWithIndex_ for \index id -> do + let limit ports time + = ports + # map (\port -> port.id /\ port.maxOutput time) + # outputs (amount time) + # Array.findMap (\(id' /\ f) -> if id == id' then Just f else Nothing) + # unsafePartial fromJust + constrain $ Limit (PortDependent for limit) Input id + where + outputs :: Number -> Array (PortId /\ Number) -> Array (PortId /\ Number) + outputs total ports + = ports + # Array.sortWith snd + # foldlWithIndex (\index (past /\ remaining) (id /\ value) -> do + let current + | lengthLeft <- remaining / toNumber (count - index), value >= lengthLeft = lengthLeft + | otherwise = value + ((id /\ current):past) /\ (remaining - current)) + (Nil /\ total) + # fst + # Array.fromFoldable + where + count = length ports + Consumer for -> do + constrain $ Limit (Literal infinity) Output for + Belt { input, output, config } -> do + biId <- generate + + constrain $ BiRelationship biId + { p1: input /\ Output + , p2: output /\ Input + , p1top2: (+) config.delay + , p2top1: (+) (-config.delay) } + + constrain $ Limit (Literal config.speed) Output input + constrain $ Limit (Literal config.speed) Input output + + _ -> unsafeCrashWith "unimplemented" + +---------- Lenses +_constraints :: Lens' SolveState (Array ThroughputConstraint) +_constraints = prop (Proxy :: _ "constraints") + +---------- Typeclass instances +derive instance genericMachine :: Generic Machine _ +derive instance genericPortSide :: Generic PortSide _ +derive instance eqPortSide :: Eq PortSide + +instance showMachine :: Show Machine where + show = case _ of + Provider for _ -> "Provider<" <> show for <> ">" + Consumer for -> "Consumer<" <> show for <> ">" + Belt { config, input, output } -> "Belt<" <> show input <> " -> " <> show output <> ", " <> show config <> ">" + Chest { inputs, outputs, config } -> "Chest<" <> show inputs <> " -> " <> show outputs <> ", " <> show config <> ">" + +instance showConstraint :: Show ThroughputConstraint where + show = case _ of + Limit f side id -> show f <> " !> " <> showPort (id /\ side) + BiRelationship _ { p1, p2 } -> showPort p1 <> " <-> " <> showPort p2 + where + showPort (p /\ side) = "?" <> show p <> case side of + Input -> "<-" + Output -> "<-" + +instance showConstraintExpression :: Show ConstraintExpression where + show (Literal i) = show i + show (Function f) = "" + show (PortDependent ids f) = "(" <> show ids <> " -> )" + +instance showPortSide :: Show PortSide where + show = genericShow \ No newline at end of file diff --git a/purescript/factorio-throughput/src/Utils/Ord.purs b/purescript/factorio-throughput/src/Utils/Ord.purs new file mode 100644 index 0000000..02bdc90 --- /dev/null +++ b/purescript/factorio-throughput/src/Utils/Ord.purs @@ -0,0 +1,33 @@ +module Moontorio.Ord.Extra (Side, left, right, OrderedArray, binarySearch) where + +import Prelude + +import Data.Array (length, unsafeIndex) +import Data.Maybe (Maybe(..)) +import Partial.Unsafe (unsafePartial) + +type OrderedArray = Array +newtype Side = Side Boolean + +left :: Side +left = Side false + +right :: Side +right = Side true + +binarySearch :: forall a. (Int -> a -> Side) -> OrderedArray a -> Maybe Int +binarySearch f arr = unsafePartial $ findImpl 0 (length arr) + where + findImpl :: Partial => _ + findImpl start length | length == 0 = Nothing + | length == 1 = Just start + | otherwise = do + let middle = start + length / 2 + let element = unsafeIndex arr middle + if f middle element == left then + findImpl start (middle - start) + else + findImpl middle (length + start - middle) + +---------- Typeclass instances +derive instance eqSide :: Eq Side \ No newline at end of file diff --git a/purescript/factorio-throughput/src/index.html b/purescript/factorio-throughput/src/index.html new file mode 100644 index 0000000..44dacf4 --- /dev/null +++ b/purescript/factorio-throughput/src/index.html @@ -0,0 +1,20 @@ + + + + + + + Moontorio + + + + + + + + \ No newline at end of file diff --git a/purescript/factorio-throughput/src/index.js b/purescript/factorio-throughput/src/index.js new file mode 100644 index 0000000..5e19d4b --- /dev/null +++ b/purescript/factorio-throughput/src/index.js @@ -0,0 +1,38 @@ +import { main } from "Main.purs"; +import functionPlot from "function-plot"; + +let lastId = 0; +const root = document.body; + +const width = 400; +const height = 250; + +const render = (name) => (functions) => () => { + const currentId = ++lastId; + + console.log("Renering!!!"); + + const node = document.createElement("div"); + node.id = currentId; + node.title = name; + node.className = "graph"; + + root.appendChild(node); + + const functionData = functions.map((fn) => ({ + fn: (scope) => fn(scope.x), + graphType: "polyline", + })); + + functionPlot({ + target: node, + width, + height, + yAxis: { domain: [-5, 35] }, + xAxis: { domain: [-1, 5] }, + grid: true, + data: functionData, + }); +}; + +main(render)(); diff --git a/purescript/factorio-throughput/test/Main.purs b/purescript/factorio-throughput/test/Main.purs new file mode 100644 index 0000000..f91f98c --- /dev/null +++ b/purescript/factorio-throughput/test/Main.purs @@ -0,0 +1,11 @@ +module Test.Main where + +import Prelude + +import Effect (Effect) +import Effect.Class.Console (log) + +main :: Effect Unit +main = do + log "🍝" + log "You should add some tests."