diff --git a/purescript/factorio-throughput/.gitignore b/purescript/factorio-throughput/.gitignore index ba2464c..1349821 100644 --- a/purescript/factorio-throughput/.gitignore +++ b/purescript/factorio-throughput/.gitignore @@ -9,3 +9,8 @@ /.psa* /.spago dist +*.aux +*.fls +*.fdb_* +*.synctex* +*.log 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/src/Throughput.purs b/purescript/factorio-throughput/src/Throughput.purs index 1f03ea7..a9fe5ad 100644 --- a/purescript/factorio-throughput/src/Throughput.purs +++ b/purescript/factorio-throughput/src/Throughput.purs @@ -6,7 +6,7 @@ import Data.Array (length, mapWithIndex) import Data.Array as Array import Data.Either (Either) import Data.Foldable (foldMap, for_, minimum) -import Data.FoldableWithIndex (forWithIndex_) +import Data.FoldableWithIndex (foldlWithIndex, forWithIndex_) import Data.Generic.Rep (class Generic) import Data.HashMap (HashMap) import Data.HashMap as HashMap @@ -16,12 +16,11 @@ import Data.Int (toNumber) import Data.Lens (Lens') import Data.Lens.Record (prop) import Data.List (List(..), (:)) -import Data.List as 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, uncurry) +import Data.Tuple (Tuple(..), fst, snd, uncurry) import Data.Tuple.Nested (type (/\), (/\)) import Functorio.Lens (modifyAt) import Math (sin) @@ -70,8 +69,8 @@ blueBelt :: BeltConfig blueBelt = { speed: 45.0, delay: 4.0/8.0 } -- | Example factory -myFactory :: Factory -myFactory = Map.fromArray machines +myFactory1 :: Factory +myFactory1 = Map.fromArray machines where machines = mapWithIndex Tuple [ Provider [0, 1] $ startsAtZero $ \t -> 40.0 + 10.0 * sin t @@ -82,6 +81,19 @@ myFactory = Map.fromArray machines , 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 @@ -190,7 +202,7 @@ tryFindBoundImpl (targetId /\ targetSide) = do evalExpr expr <*> pure time BiRelationship id raw | Just relationship <- focusBiRelationship (targetId /\ targetSide) raw -> do - f <- once id fail $ tryFindBoundImpl relationship.p2 + f <- once id fail $ tryFindValueImpl $ fst relationship.p2 f (relationship.p1top2 time) _ -> fail # runReader constraints @@ -225,29 +237,27 @@ collectConstraintsImpl at = case _ of Provider for amount -> do forWithIndex_ for \index id -> do let limit ports time - = outputs ports time - # Array.findMap (\(id' /\ f) -> if id == id' then Just (f time) else Nothing) - # unsafePartial fromJust -- TODO: error handling + = 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 :: Array PortData -> Number -> Array (PortId /\ RealFunction) - outputs ports time - = outputsImpl (length ports) (List.fromFoldable sorted) amount + 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 - # Array.zipWith (_.id >>> Tuple) sorted where - sorted :: Array PortData - sorted = Array.sortWith (_.maxOutput >>> (#) time) ports - - outputsImpl :: Int -> List PortData -> RealFunction -> List RealFunction - outputsImpl 1 (head:Nil) remaining = pure \time -> min (head.maxOutput time) (remaining time) - outputsImpl n (head:tail) remaining = current:(outputsImpl (n - 1) tail $ remaining - current) - where - current time - | head.maxOutput time >= (remaining time) / (toNumber n) = (remaining time) / (toNumber n) - | otherwise = head.maxOutput time - outputsImpl _ _ _ = Nil - + count = length ports Consumer for -> do constrain $ Limit (Literal infinity) Output for Belt { input, output, config } -> do 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