Mercurial > pokemon-types
diff org/lpsolve.org @ 0:e03d363ed9a9
initial committ for pokemon types report
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Sun, 16 Oct 2011 06:57:42 -0700 |
parents | |
children | 55bba4805393 |
line wrap: on
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/org/lpsolve.org Sun Oct 16 06:57:42 2011 -0700 1.3 @@ -0,0 +1,1387 @@ 1.4 +#+title: Discovering Effective Pokemon Types Using Linear Optimization 1.5 +#+author: Robert McIntyre & Dylan Holmes 1.6 +#+EMAIL: rlm@mit.edu 1.7 +#+MATHJAX: align:"left" mathml:t path:"../MathJax/MathJax.js" 1.8 +#+STYLE: <link rel="stylesheet" type="text/css" href="../css/aurellem.css" /> 1.9 +#+OPTIONS: H:3 num:t toc:t \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t 1.10 +#+SETUPFILE: ../templates/level-0.org 1.11 +#+INCLUDE: ../templates/level-0.org 1.12 + 1.13 + 1.14 + 1.15 +* Introduction 1.16 +In the [[./types.org][previous post]], we used the best-first search algorithm to 1.17 +locate the most effective Pok\eacute{}mon type 1.18 +combinations. Afterwards, we realized that we could transform this 1.19 +search problem into a /linear optimization problem/. This conversion 1.20 +offered several advantages: first, search algorithms are comparatively 1.21 +slow, whereas linear optimization algorithms are extremely fast; 1.22 +second, it is difficult to determine whether a search problem has any 1.23 +solution, whereas it is straightforward to determine whether a linear 1.24 +optimization problem has any solution; finally, because systems of 1.25 +linear equations are so common, many programming languages have linear 1.26 +equation solvers written for them. 1.27 + 1.28 +In this article, we will 1.29 + - Solve a simple linear optimization problem in C :: We demonstrate 1.30 + how to use the linear programming C library, =lp_solve=, to 1.31 + solve a simple linear 1.32 + optimization problem. 1.33 + - Incorporate a C library into Clojure :: We will show how we gave 1.34 + Clojure access to the linear programming C library, =lp_solve=. 1.35 + - Find effective Pokemon types using linear programming :: Building 1.36 + on our earlier code, (...) 1.37 +- Present our results :: (!) 1.38 + 1.39 +#which can be represented and solved as a system of linear equations. 1.40 + 1.41 +* COMMENT 1.42 +This post continues the [[./types.org][previous one]] about pok\eacute{}mon types. 1.43 +Pok\eacute{}mon is a game in which adorable creatures battle each 1.44 +other using fantastic attacks. It was made into a several gameboy 1.45 +games that all share the same battle system. Every pok\eacute{}mon in the 1.46 +gameboy game has one or two /types/, such as Ground, Fire, Water, 1.47 +etc. Every pok\eacute{}mon attack has exactly one type. Certain defending 1.48 +types are weak or strong to other attacking types. For example, Water 1.49 +attacks are strong against Fire pok\eacute{}mon, while Electric attacks are 1.50 +weak against Ground Pok\eacute{}mon. In the games, attacks can be either 1.51 +twice as effective as normal (Water vs. Fire), neutrally effective 1.52 +(Normal vs. Normal), half as effective (Fire vs. Water), or not 1.53 +effective at all (Electric vs. Ground). Thus the range of defense 1.54 +values for a particular type is the set 0, 1/2, 1, 2. These are 1.55 +referred to in the game as being immune, resistant, neutral, and 1.56 +weak, respectively. I call them the /susceptance/ of one type to another. 1.57 + 1.58 +If a pokemon has two types, then the strengths and weakness of each 1.59 +type are /multiplied/ together. Thus Electric (2x weak to Ground) 1.60 +combined with Flying (immune to Ground (0x)) is immune to 1.61 +Ground. Fire (2x weak to Water) combined with Water (1/2x resistant 1.62 +to Water) is neutral to Water. If both types are resistant to another 1.63 +type, then the combination is doubly-resistant (1/4x) to that type. If 1.64 +both types are weak to a certain type then the combination is 1.65 +double-weak (4x) to that type. 1.66 + 1.67 +** Immortal Types 1.68 + 1.69 +In the game, pok\eacute{}mon can have either one type, or two types. If this 1.70 +restriction is lifted, is there any combination of types that is 1.71 +resistant to all types? I call such a combination an /Immortal Type/, 1.72 +since if that type's pattern was repeated over and over again towards 1.73 +infinity, the resulting type would be immune to all attack types. 1.74 + 1.75 +* Linear Programming 1.76 + 1.77 +** Terminology 1.78 +Linear programming is the process of finding an optimal solution to a 1.79 +linear equation of several variables which are constrained by some linear 1.80 +inequalities. 1.81 + 1.82 +In linear programming terminology, the function to be extremized is 1.83 +the /objective function/. 1.84 + 1.85 +** COMMENT 1.86 +First, we'll give a small example of a linear optimization problem, 1.87 +and show how it can be solved with Clojure and =lp_solve=. Then, 1.88 +we'll show how finding immortal pok\eacute{}mon types can be converted 1.89 +into a linear problem suitable for solving in the same way. 1.90 + 1.91 +** The Farmer's Problem 1.92 + 1.93 +Let's solve the Farmer's Problem, a typical linear programming problem 1.94 +borrowed from http://lpsolve.sourceforge.net/5.5/formulate.htm. 1.95 + 1.96 + 1.97 +#+BEGIN_QUOTE 1.98 +*The Farmer's Problem:* Suppose a farmer has 75 acres on which to 1.99 +plant two crops: wheat and barley. To produce these crops, it costs 1.100 +the farmer (for seed, fertilizer, etc.) $120 per acre for the wheat 1.101 +and $210 per acre for the barley. The farmer has $15000 available for 1.102 +expenses. But after the harvest, the farmer must store the crops while 1.103 +awaiting favorable market conditions. The farmer has storage space 1.104 +for 4000 bushels. Each acre yields an average of 110 bushels of wheat 1.105 +or 30 bushels of barley. If the net profit per bushel of wheat (after 1.106 +all expenses have been subtracted) is $1.30 and for barley is $2.00, 1.107 +how should the farmer plant the 75 acres to maximize profit? 1.108 +#+END_QUOTE 1.109 + 1.110 +The Farmer's Problem is to maximize profit subject to constraints on 1.111 +available farmland, funds for expenses, and storage space. 1.112 + 1.113 +| | Wheat | Barley | Maximum total | 1.114 +|----------+----------------------+---------------------+--------------| 1.115 +| / | < | > | <> | 1.116 +| Farmland | \(w\) acres | \(b\) acres | 75 acres | 1.117 +| Expense | $120 per acre | $210 per acre | $15000 | 1.118 +| Storage | 110 bushels per acre | 30 bushels per acre | 4000 bushels | 1.119 +|----------+----------------------+---------------------+--------------| 1.120 +| Profit | $1.30 per bushel | $2.00 per bushel | | 1.121 + 1.122 +*** COMMENT 1.123 +can be represented as a linear optimization 1.124 + problem. In this form, it is a problem with two variables\mdash{}the number of 1.125 + acres of wheat, \(w\), and the number of acres of barley, \(b\). The 1.126 + aim is to maximize profit, which 1.127 + 1.128 + subject to three constraints: the farmer can't spend more money 1.129 +than he has, the farmer can't use more acres than he owns, and the harvest has 1.130 +to fit in his storage space. 1.131 + 1.132 +We can express these constraints succinctly using matrix 1.133 +notation. Denoting the number of acres of barley and wheat by \(b\) and \(w\), 1.134 +we want to maximize the expression \(143 w + 60 b\) subject to 1.135 + 1.136 +\( 1.137 +\begin{cases} 1.138 +120 w + 210 b & \leq & 1500\\ 1.139 +110 w + 30 b & \leq & 4000\\ 1.140 +1 w + 1 w & \leq & 75 1.141 +\end{cases} 1.142 +\) 1.143 + 1.144 +#\(\begin{bmatrix}120&210\\110&30\\1 & 1.145 +#1\end{bmatrix}\;\begin{bmatrix}w\\b\end{bmatrix} 1.146 +#\leq \begin{bmatrix}\$15000\\4000\text{ bushels}\\75\text{ acres}\end{bmatrix}\) 1.147 + 1.148 + 1.149 +** Solution using LP Solve 1.150 +#(LP solve is available at http://www.example.com.) 1.151 +In a new file, =farmer.lp=, we list the variables and constraints 1.152 +of our problem using LP Solve syntax. 1.153 + 1.154 +#+begin_src lpsolve :tangle farmer.lp 1.155 +/* Maximize Total Profit */ 1.156 +max: +143 wheat +60 barley; 1.157 + 1.158 + 1.159 +/* -------- Constraints --------*/ 1.160 + 1.161 +/* the farmer can't spend more money than he has */ 1.162 ++120 wheat +210 barley <= 15000; 1.163 + 1.164 +/* the harvest has to fit in his storage space */ 1.165 ++110 wheat +30 barley <= 4000; 1.166 + 1.167 +/* he can't use more acres than he owns */ 1.168 ++wheat +barley <= 75; 1.169 +#+end_src 1.170 + 1.171 + 1.172 +#This is a set of linear equations ideal for solving using a program like 1.173 +#=lp_solve=. In Linear Algebra terms we are maximizing the linear function 1.174 + 1.175 +#\(\text{profit} = 143\text{ wheat} + 60\text{ barley}\), subject to the constraints 1.176 + 1.177 +#Ax <= b, 1.178 + 1.179 +#where A is [120 210 110 30 1 1], x is [wheat barley] and b is [15000 1.180 +#4000 75]. 1.181 + 1.182 +Running the =lp_solve= program on =farmer.lp= yields the following output. 1.183 + 1.184 +#+begin_src sh :exports both :results scalar 1.185 +~/roBin/lpsolve/lp_solve ~/aurellem/src/pokemon/farmer.lp 1.186 +#+end_src 1.187 + 1.188 +#+results: 1.189 +: 1.190 +: Value of objective function: 6315.62500000 1.191 +: 1.192 +: Actual values of the variables: 1.193 +: wheat 21.875 1.194 +: barley 53.125 1.195 + 1.196 +This shows that the farmer can maximize his profit by planting 21.875 1.197 +of the available acres with wheat and the remaining 53.125 acres with 1.198 +barley; by doing so, he will make $6315.62(5) in profit. 1.199 + 1.200 + 1.201 +#The farmer can make a profit of $6315.62 by planting 21.875 acres of 1.202 +#his land with wheat and the other 53.125 acres of his land with barley. 1.203 + 1.204 +* Incorporating =lp_solve= into Clojure 1.205 + 1.206 +There is a Java API available which enables Java programs to use Lp 1.207 +Solve. Although Clojure can use this Java API directly, the 1.208 +interaction between Java, C, and Clojure is clumsy: 1.209 + 1.210 + 1.211 +The Java API for LP Solve makes it possible to use Lp Solve algorithms 1.212 +within Java. Although Clojure can use this Java API directly, 1.213 + 1.214 + 1.215 +** The Farmer's Problem in Clojure 1.216 +We are going to solve the same problem involving wheat and barley, 1.217 +that we did above, but this time using clojure and the lpsolve API. 1.218 + 1.219 +#Because we ultimately intend to use Lp Solve to find optimal Pokemon type combinations. 1.220 +# we want to solve linear optimization problems within Clojure, the language 1.221 + 1.222 +** Setup 1.223 +=lp_solve= is a crufty =C= program which already comes with a JNI 1.224 +interface written by Juergen Ebert. It's API is not 1.225 +particularly friendly from a functional/immutable perspective, but 1.226 +with a little work, we can build something that works great with 1.227 +clojure. 1.228 + 1.229 +#+srcname: intro 1.230 +#+begin_src clojure :results silent 1.231 +(ns pokemon.lpsolve 1.232 + (:use rlm.ns-rlm)) 1.233 +(rlm.ns-rlm/ns-clone rlm.light-base) 1.234 +(use 'clojure.set) 1.235 +(import 'lpsolve.LpSolve) 1.236 +(use '[clojure [pprint :only [pprint]]]) 1.237 +#+end_src 1.238 + 1.239 +The LpSolve Java interface is available from the same site as 1.240 +=lp_solve= itself, http://lpsolve.sourceforge.net/ 1.241 +Using it is the same as many other =C= 1.242 +programs. There are excellent instructions to get set 1.243 +up. The short version is that you must call Java with 1.244 +=-Djava.library.path=/path/to/lpsolve/libraries= and also add the 1.245 +libraries to your export =LD_LIBRARY_PATH= if you are using Linux. For 1.246 +example, in my =.bashrc= file, I have the line 1.247 +=LD_LIBRARY_PATH=$HOME/roBin/lpsolve:$LD_LIBRARY_PATH=. 1.248 +If everything is set-up correctly, 1.249 + 1.250 +#+srcname: body 1.251 +#+begin_src clojure :results verbatim :exports both 1.252 +(import 'lpsolve.LpSolve) 1.253 +#+end_src 1.254 + 1.255 +#+results: body 1.256 +: lpsolve.LpSolve 1.257 + 1.258 +should run with no problems. 1.259 + 1.260 +** Making a DSL to talk with LpSolve 1.261 +*** Problems 1.262 +Since we are using a =C= wrapper, we have to deal with manual memory 1.263 +management for the =C= structures which are wrapped by the =LpSolve= 1.264 +object. Memory leaks in =LpSolve= instances can crash the JVM, so it's 1.265 +very important to get it right. Also, the Java wrapper follows the 1.266 +=C= tradition closely and defines many =static final int= constants 1.267 +for the different states of the =LpSolve= instance instead of using Java 1.268 +enums. The calling convention for adding rows and columns to 1.269 +the constraint matrix is rather complicated and must be done column by 1.270 +column or row by row, which can be error prone. Finally, I'd like to 1.271 +gather all the important output information from the =LpSolve= instance 1.272 +into a final, immutable structure. 1.273 + 1.274 +In summary, the issues I'd like to address are: 1.275 + 1.276 +- reliable memory management 1.277 +- functional interface to =LpSolve= 1.278 +- intelligible, immutable output 1.279 + 1.280 +To deal with these issues I'll create four functions for interfacing 1.281 +with =LpSolve= 1.282 + 1.283 +#+srcname: declares 1.284 +#+begin_src clojure :results silent 1.285 +(in-ns 'pokemon.lpsolve) 1.286 + 1.287 +;; deal with automatic memory management for LpSolve instance. 1.288 +(declare linear-program) 1.289 + 1.290 +;; functional interface to LpSolve 1.291 +(declare lp-solve) 1.292 + 1.293 +;; immutable output from lp-solve 1.294 +(declare solve get-results) 1.295 +#+end_src 1.296 + 1.297 +*** Memory Management 1.298 + 1.299 +Every instance of =LpSolve= must be manually garbage collected via a 1.300 +call to =deleteLP=. I use a non-hygienic macro similar to =with-open= 1.301 +to ensure that =deleteLP= is always called. 1.302 + 1.303 +#+srcname: memory-management 1.304 +#+begin_src clojure :results silent 1.305 +(in-ns 'pokemon.lpsolve) 1.306 +(defmacro linear-program 1.307 + "solve a linear programming problem using LpSolve syntax. 1.308 + within the macro, the variable =lps= is bound to the LpSolve instance." 1.309 + [& statements] 1.310 + (list 'let '[lps (LpSolve/makeLp 0 0)] 1.311 + (concat '(try) 1.312 + statements 1.313 + ;; always free the =C= data structures. 1.314 + '((finally (.deleteLp lps)))))) 1.315 +#+end_src 1.316 + 1.317 + 1.318 +The macro captures the variable =lps= within its body, providing for a 1.319 +convenient way to access the object using any of the methods of the 1.320 +=LpSolve= API without having to worry about when to call 1.321 +=deleteLP=. 1.322 + 1.323 +*** Sensible Results 1.324 +The =linear-program= macro deletes the actual =lps= object once it is 1.325 +done working, so it's important to collect the important results and 1.326 +add return them in an immutable structure at the end. 1.327 + 1.328 +#+srcname: get-results 1.329 +#+begin_src clojure :results silent 1.330 +(in-ns 'pokemon.lpsolve) 1.331 + 1.332 +(defrecord LpSolution 1.333 + [objective-value 1.334 + optimal-values 1.335 + variable-names 1.336 + solution 1.337 + status 1.338 + model]) 1.339 + 1.340 +(defn model 1.341 + "Returns a textual representation of the problem suitable for 1.342 + direct input to the =lp_solve= program (lps format)" 1.343 + [#^LpSolve lps] 1.344 + (let [target (java.io.File/createTempFile "lps" ".lp")] 1.345 + (.writeLp lps (.getPath target)) 1.346 + (slurp target))) 1.347 + 1.348 +(defn results 1.349 + "given an LpSolve object, solves the object and returns a map of the 1.350 + essential values which compose the solution." 1.351 + [#^LpSolve lps] 1.352 + (locking lps 1.353 + (let [status (solve lps) 1.354 + number-of-variables (.getNcolumns lps) 1.355 + optimal-values (double-array number-of-variables) 1.356 + optimal-values (do (.getVariables lps optimal-values) 1.357 + (seq optimal-values)) 1.358 + variable-names 1.359 + (doall ;; the doall is necessary since the lps object might 1.360 + ;; soon be deleted 1.361 + (map 1.362 + #(.getColName lps (inc %)) 1.363 + (range number-of-variables))) 1.364 + model (model lps)] 1.365 + (LpSolution. 1.366 + (.getObjective lps) 1.367 + optimal-values 1.368 + variable-names 1.369 + (zipmap variable-names optimal-values) 1.370 + status 1.371 + model)))) 1.372 + 1.373 +#+end_src 1.374 + 1.375 +Here I've created an object called =LpSolution= which stores the 1.376 +important results from a session with =lp_solve=. Of note is the 1.377 +=model= function which returns the problem in a form that can be 1.378 +solved by other linear programming packages. 1.379 + 1.380 +*** Solution Status of an LpSolve Object 1.381 + 1.382 +#+srcname: solve 1.383 +#+begin_src clojure :results silent 1.384 +(in-ns 'pokemon.lpsolve) 1.385 + 1.386 +(defn static-integer? 1.387 + "does the field represent a static integer constant?" 1.388 + [#^java.lang.reflect.Field field] 1.389 + (and (java.lang.reflect.Modifier/isStatic (.getModifiers field)) 1.390 + (integer? (.get field nil)))) 1.391 + 1.392 +(defn integer-constants [class] 1.393 + (filter static-integer? (.getFields class))) 1.394 + 1.395 +(defn-memo constant-map 1.396 + "Takes a class and creates a map of the static constant integer 1.397 + fields with their names. This helps with C wrappers where they have 1.398 + just defined a bunch of integer constants instead of enums" 1.399 + [class] 1.400 + (let [integer-fields (integer-constants class)] 1.401 + (into (sorted-map) 1.402 + (zipmap (map #(.get % nil) integer-fields) 1.403 + (map #(.getName %) integer-fields))))) 1.404 + 1.405 +(defn solve 1.406 + "Solve an instance of LpSolve and return a string representing the 1.407 + status of the computation. Will only solve a particular LpSolve 1.408 + instance once." 1.409 + [#^LpSolve lps] 1.410 + ((constant-map LpSolve) 1.411 + (.solve lps))) 1.412 + 1.413 +#+end_src 1.414 + 1.415 +The =.solve= method of an =LpSolve= object only returns an integer code 1.416 +to specify the status of the computation. The =solve= method here 1.417 +uses reflection to look up the actual name of the status code and 1.418 +returns a more helpful status message that is also resistant to 1.419 +changes in the meanings of the code numbers. 1.420 + 1.421 +*** The Farmer Example in Clojure, Pass 1 1.422 + 1.423 +Now we can implement a nicer version of the examples from the 1.424 +[[http://lpsolve.sourceforge.net/][=lp\_solve= website]]. The following is a more or less 1.425 +line-by-line translation of the Java code from that example. 1.426 + 1.427 +#+srcname: farmer-example 1.428 +#+begin_src clojure :results silent 1.429 +(in-ns 'pokemon.lpsolve) 1.430 +(defn farmer-example [] 1.431 + (linear-program 1.432 + (results 1.433 + (doto lps 1.434 + ;; name the columns 1.435 + (.setColName 1 "wheat") 1.436 + (.setColName 2 "barley") 1.437 + (.setAddRowmode true) 1.438 + ;; row 1 : 120x + 210y <= 15000 1.439 + (.addConstraintex 2 1.440 + (double-array [120 210]) 1.441 + (int-array [1 2]) 1.442 + LpSolve/LE 1.443 + 15e3) 1.444 + ;; row 2 : 110x + 30y <= 4000 1.445 + (.addConstraintex 2 1.446 + (double-array [110 30]) 1.447 + (int-array [1 2]) 1.448 + LpSolve/LE 1.449 + 4e3) 1.450 + ;; ;; row 3 : x + y <= 75 1.451 + (.addConstraintex 2 1.452 + (double-array [1 1]) 1.453 + (int-array [1 2]) 1.454 + LpSolve/LE 1.455 + 75) 1.456 + (.setAddRowmode false) 1.457 + 1.458 + ;; add constraints 1.459 + (.setObjFnex 2 1.460 + (double-array [143 60]) 1.461 + (int-array [1 2])) 1.462 + 1.463 + ;; set this as a maximization problem 1.464 + (.setMaxim))))) 1.465 + 1.466 +#+end_src 1.467 + 1.468 +#+begin_src clojure :results output :exports both 1.469 +(clojure.pprint/pprint 1.470 + (:solution (pokemon.lpsolve/farmer-example))) 1.471 +#+end_src 1.472 + 1.473 +#+results: 1.474 +: {"barley" 53.12499999999999, "wheat" 21.875} 1.475 + 1.476 +And it works as expected! 1.477 + 1.478 +*** The Farmer Example in Clojure, Pass 2 1.479 +We don't have to worry about memory management anymore, and the farmer 1.480 +example is about half as long as the example from the =LpSolve= 1.481 +website, but we can still do better. Solving linear problems is all 1.482 +about the constraint matrix $A$ , the objective function $c$, and the 1.483 +right-hand-side $b$, plus whatever other options one cares to set for 1.484 +the particular instance of =lp_solve=. Why not make a version of 1.485 +=linear-program= that takes care of initialization? 1.486 + 1.487 + 1.488 + 1.489 +#+srcname: lp-solve 1.490 +#+begin_src clojure :results silent 1.491 +(in-ns 'pokemon.lpsolve) 1.492 +(defn initialize-lpsolve-row-oriented 1.493 + "fill in an lpsolve instance using a constraint matrix =A=, the 1.494 + objective function =c=, and the right-hand-side =b=" 1.495 + [#^LpSolve lps A b c] 1.496 + ;; set the name of the last column to _something_ 1.497 + ;; this appears to be necessary to ensure proper initialization. 1.498 + (.setColName lps (count c) (str "C" (count c))) 1.499 + 1.500 + ;; This is the recommended way to "fill-in" an lps instance from the 1.501 + ;; documentation. First, set row mode, then set the objective 1.502 + ;; function, then set each row of the problem, and then turn off row 1.503 + ;; mode. 1.504 + (.setAddRowmode lps true) 1.505 + (.setObjFnex lps (count c) 1.506 + (double-array c) 1.507 + (int-array (range 1 (inc (count c))))) 1.508 + (dorun 1.509 + (for [n (range (count A))] 1.510 + (let [row (nth A n) 1.511 + row-length (int (count row))] 1.512 + (.addConstraintex lps 1.513 + row-length 1.514 + (double-array row) 1.515 + (int-array (range 1 (inc row-length))) 1.516 + LpSolve/LE 1.517 + (double (nth b n)))))) 1.518 + (.setAddRowmode lps false) 1.519 + lps) 1.520 + 1.521 + 1.522 +(defmacro lp-solve 1.523 + "by default:, 1.524 + minimize (* c x), subject to (<= (* A x) b), 1.525 + using continuous variables. You may set any number of 1.526 + other options as in the LpSolve API." 1.527 + [A b c & lp-solve-forms] 1.528 + ;; assume that A is a vector of vectors 1.529 + (concat 1.530 + (list 'linear-program 1.531 + (list 'initialize-lpsolve-row-oriented 'lps A b c)) 1.532 + `~lp-solve-forms)) 1.533 + #+end_src 1.534 + 1.535 +Now, we can use a much more functional approach to solving the 1.536 +farmer's problem: 1.537 + 1.538 +#+srcname: better-farmer 1.539 +#+begin_src clojure :results silent 1.540 +(in-ns 'pokemon.lpsolve) 1.541 +(defn better-farmer-example [] 1.542 + (lp-solve [[120 210] 1.543 + [110 30] 1.544 + [1 1]] 1.545 + [15000 1.546 + 4000 1.547 + 75] 1.548 + [143 60] 1.549 + (.setColName lps 1 "wheat") 1.550 + (.setColName lps 2 "barley") 1.551 + (.setMaxim lps) 1.552 + (results lps))) 1.553 +#+end_src 1.554 + 1.555 +#+begin_src clojure :exports both :results verbatim 1.556 +(vec (:solution (pokemon.lpsolve/better-farmer-example))) 1.557 +#+end_src 1.558 + 1.559 +#+results: 1.560 +: [["barley" 53.12499999999999] ["wheat" 21.875]] 1.561 + 1.562 +Notice that both the inputs to =better-farmer-example= and the results 1.563 +are immutable. 1.564 + 1.565 +* Using LpSolve to find Immortal Types 1.566 +** Converting the Pokemon problem into a linear form 1.567 +How can the original question about pok\eacute{}mon types be converted 1.568 +into a linear problem? 1.569 + 1.570 +Pokemon types can be considered to be vectors of numbers representing 1.571 +their susceptances to various attacking types, so Water might look 1.572 +something like this. 1.573 + 1.574 +#+begin_src clojure :results scalar :exports both 1.575 +(:water (pokemon.types/defense-strengths)) 1.576 +#+end_src 1.577 + 1.578 +#+results: 1.579 +: [1 0.5 0.5 2 2 0.5 1 1 1 1 1 1 1 1 1 1 0.5] 1.580 + 1.581 +Where the numbers represent the susceptibility of Water to the 1.582 +attacking types in the following order: 1.583 + 1.584 +#+begin_src clojure :results output :exports both 1.585 +(clojure.pprint/pprint 1.586 + (pokemon.types/type-names)) 1.587 +#+end_src 1.588 + 1.589 +#+results: 1.590 +#+begin_example 1.591 +[:normal 1.592 + :fire 1.593 + :water 1.594 + :electric 1.595 + :grass 1.596 + :ice 1.597 + :fighting 1.598 + :poison 1.599 + :ground 1.600 + :flying 1.601 + :psychic 1.602 + :bug 1.603 + :rock 1.604 + :ghost 1.605 + :dragon 1.606 + :dark 1.607 + :steel] 1.608 +#+end_example 1.609 + 1.610 + 1.611 +So, for example, Water is /resistant/ (x0.5) against Fire, which is 1.612 +the second element in the list. 1.613 + 1.614 +To combine types, these sorts of vectors are multiplied together 1.615 +pair-wise to yield the resulting combination. 1.616 + 1.617 +Unfortunately, we need some way to add two type vectors together 1.618 +instead of multiplying them if we want to solve the problem with 1.619 +=lp_solve=. Taking the log of the vector does just the trick. 1.620 + 1.621 +If we make a matrix with each column being the log (base 2) of the 1.622 +susceptance of each type, then finding an immortal type corresponds to 1.623 +setting each constraint (the $b$ vector) to -1 (since log_2(1/2) = -1) 1.624 +and setting the constraint vector $c$ to all ones, which means that we 1.625 +want to find the immortal type which uses the least amount of types. 1.626 + 1.627 +#+srcname: pokemon-lp 1.628 +#+begin_src clojure :results silent 1.629 +(in-ns 'pokemon.lpsolve) 1.630 + 1.631 +(require 'pokemon.types) 1.632 +(require 'incanter.core) 1.633 + 1.634 +(defn log-clamp-matrix [matrix] 1.635 + ;; we have to clamp the Infinities to a more reasonable negative 1.636 + ;; value because lp_solve does not play well with infinities in its 1.637 + ;; constraint matrix. 1.638 + (map (fn [row] (map #(if (= Double/NEGATIVE_INFINITY %) -1e3 %) row)) 1.639 + (incanter.core/log2 1.640 + (incanter.core/trans 1.641 + matrix)))) 1.642 + 1.643 +;; constraint matrices 1.644 +(defn log-defense-matrix [] 1.645 + (log-clamp-matrix 1.646 + (doall (map (pokemon.types/defense-strengths) 1.647 + (pokemon.types/type-names))))) 1.648 + 1.649 +(defn log-attack-matrix [] 1.650 + (incanter.core/trans (log-defense-matrix))) 1.651 + 1.652 +;; target vectors 1.653 +(defn all-resistant [] 1.654 + (doall (map (constantly -1) (pokemon.types/type-names)))) 1.655 + 1.656 +(defn all-weak [] 1.657 + (doall (map (constantly 1) (pokemon.types/type-names)))) 1.658 + 1.659 +(defn all-neutral [] 1.660 + (doall (map (constantly 0) (pokemon.types/type-names)))) 1.661 + 1.662 + 1.663 +;; objective functions 1.664 +(defn number-of-types [] 1.665 + (doall (map (constantly 1) (pokemon.types/type-names)))) 1.666 + 1.667 +(defn set-constraints 1.668 + "sets all the constraints for an lpsolve instance to the given 1.669 + constraint. =constraint= here is one of the LpSolve constants such 1.670 + as LpSolve/EQ." 1.671 + [#^LpSolve lps constraint] 1.672 + (dorun (map (fn [index] (.setConstrType lps index constraint)) 1.673 + ;; ONE based indexing!!! 1.674 + (range 1 (inc (.getNrows lps)))))) 1.675 + 1.676 + 1.677 +(defn set-discrete 1.678 + "sets every variable in an lps problem to be a discrete rather than 1.679 + continuous variable" 1.680 + [#^LpSolve lps] 1.681 + (dorun (map (fn [index] (.setInt lps index true)) 1.682 + ;; ONE based indexing!!! 1.683 + (range 1 (inc (.getNcolumns lps)))))) 1.684 + 1.685 +(defn set-variable-names 1.686 + "sets the variable names of the problem given a vector of names" 1.687 + [#^LpSolve lps names] 1.688 + (dorun 1.689 + (map (fn [[index name]] 1.690 + (.setColName lps (inc index) (str name))) 1.691 + ;; ONE based indexing!!! 1.692 + (indexed names)))) 1.693 + 1.694 +(defn poke-solve 1.695 + ([poke-matrix target objective-function constraint min-num-types] 1.696 + ;; must have at least one type 1.697 + (let [poke-matrix 1.698 + (concat poke-matrix 1.699 + [(map (constantly 1) 1.700 + (range (count (first poke-matrix))))]) 1.701 + target (concat target [min-num-types])] 1.702 + (lp-solve poke-matrix target objective-function 1.703 + (set-constraints lps constraint) 1.704 + ;; must have more than min-num-types 1.705 + (.setConstrType lps (count target) LpSolve/GE) 1.706 + (set-discrete lps) 1.707 + (set-variable-names lps (pokemon.types/type-names)) 1.708 + (results lps)))) 1.709 + ([poke-matrix target objective-function constraint] 1.710 + ;; at least one type 1.711 + (poke-solve poke-matrix target objective-function constraint 1))) 1.712 + 1.713 +(defn solution 1.714 + "If the results of an lpsolve operation are feasible, returns the 1.715 + results. Otherwise, returns the error." 1.716 + [results] 1.717 + (if (not (= (:status results) "OPTIMAL")) 1.718 + (:status results) 1.719 + (:solution results))) 1.720 + 1.721 +#+end_src 1.722 + 1.723 +With this, we are finally able to get some results. 1.724 + 1.725 +** Results 1.726 +#+srcname: results 1.727 +#+begin_src clojure :results silent 1.728 +(in-ns 'pokemon.lpsolve) 1.729 + 1.730 +(defn best-defense-type 1.731 + "finds a type combination which is resistant to all attacks." 1.732 + [] 1.733 + (poke-solve 1.734 + (log-defense-matrix) (all-resistant) (number-of-types) LpSolve/LE)) 1.735 + 1.736 +(defn worst-attack-type 1.737 + "finds the attack type which is not-very-effective against all pure 1.738 + defending types (each single defending type is resistant to this 1.739 + attack combination" 1.740 + [] 1.741 + (poke-solve 1.742 + (log-attack-matrix) (all-resistant) (number-of-types) LpSolve/LE)) 1.743 + 1.744 +(defn worst-defense-type 1.745 + "finds a defending type that is weak to all single attacking types." 1.746 + [] 1.747 + (poke-solve 1.748 + (log-defense-matrix) (all-weak) (number-of-types) LpSolve/GE)) 1.749 + 1.750 +(defn best-attack-type 1.751 + "finds an attack type which is super effective against all single 1.752 +defending types" 1.753 + [] 1.754 + (poke-solve 1.755 + (log-attack-matrix) (all-weak) (number-of-types) LpSolve/GE)) 1.756 + 1.757 +(defn solid-defense-type 1.758 + "finds a defense type which is either neutral or resistant to all 1.759 + single attacking types" 1.760 + [] 1.761 + (poke-solve 1.762 + (log-defense-matrix) (all-neutral) (number-of-types) LpSolve/LE)) 1.763 + 1.764 +(defn solid-attack-type 1.765 + "finds an attack type which is either neutral or super-effective to 1.766 + all single attacking types." 1.767 + [] 1.768 + (poke-solve 1.769 + (log-attack-matrix) (all-neutral) (number-of-types) LpSolve/GE)) 1.770 + 1.771 +(defn weak-defense-type 1.772 + "finds a defense type which is either neutral or weak to all single 1.773 + attacking types" 1.774 + [] 1.775 + (poke-solve 1.776 + (log-defense-matrix) (all-neutral) (number-of-types) LpSolve/GE)) 1.777 + 1.778 +(defn neutral-defense-type 1.779 + "finds a defense type which is perfectly neutral to all attacking 1.780 + types." 1.781 + [] 1.782 + (poke-solve 1.783 + (log-defense-matrix) (all-neutral) (number-of-types) LpSolve/EQ)) 1.784 + 1.785 +#+end_src 1.786 + 1.787 +*** Strongest Attack/Defense Combinations 1.788 + 1.789 +#+begin_src clojure :results output :exports both 1.790 +(clojure.pprint/pprint 1.791 + (pokemon.lpsolve/solution (pokemon.lpsolve/best-defense-type))) 1.792 +#+end_src 1.793 + 1.794 +#+results: 1.795 +#+begin_example 1.796 +{":normal" 0.0, 1.797 + ":ground" 1.0, 1.798 + ":poison" 2.0, 1.799 + ":flying" 1.0, 1.800 + ":fighting" 0.0, 1.801 + ":dragon" 0.0, 1.802 + ":fire" 0.0, 1.803 + ":dark" 1.0, 1.804 + ":ice" 0.0, 1.805 + ":steel" 1.0, 1.806 + ":ghost" 0.0, 1.807 + ":electric" 0.0, 1.808 + ":bug" 0.0, 1.809 + ":psychic" 0.0, 1.810 + ":grass" 0.0, 1.811 + ":water" 2.0, 1.812 + ":rock" 0.0} 1.813 +#+end_example 1.814 + 1.815 +# #+results-old: 1.816 +# : [[":normal" 0.0] [":ground" 1.0] [":poison" 0.0] [":flying" 1.0] [":fighting" 0.0] [":dragon" 1.0] [":fire" 0.0] [":dark" 0.0] [":ice" 0.0] [":steel" 2.0] [":ghost" 1.0] [":electric" 0.0] [":bug" 0.0] [":psychic" 0.0] [":grass" 0.0] [":water" 2.0] [":rock" 0.0]] 1.817 + 1.818 + 1.819 +This is the immortal type combination we've been looking for. By 1.820 +combining Steel, Water, Poison, and three types which each have complete 1.821 +immunities to various other types, we've created a type that is resistant to 1.822 +all attacking types. 1.823 + 1.824 +#+begin_src clojure :results output :exports both 1.825 +(clojure.pprint/pprint 1.826 + (pokemon.types/susceptibility 1.827 + [:poison :poison :water :water :steel :ground :flying :dark])) 1.828 +#+end_src 1.829 + 1.830 +#+results: 1.831 +#+begin_example 1.832 +{:water 1/2, 1.833 + :psychic 0, 1.834 + :dragon 1/2, 1.835 + :fire 1/2, 1.836 + :ice 1/2, 1.837 + :grass 1/2, 1.838 + :ghost 1/4, 1.839 + :poison 0, 1.840 + :flying 1/2, 1.841 + :normal 1/2, 1.842 + :rock 1/2, 1.843 + :electric 0, 1.844 + :ground 0, 1.845 + :fighting 1/2, 1.846 + :dark 1/4, 1.847 + :steel 1/8, 1.848 + :bug 1/8} 1.849 +#+end_example 1.850 + 1.851 +# #+results-old: 1.852 +# : {:water 1/4, :psychic 1/4, :dragon 1/2, :fire 1/2, :ice 1/2, :grass 1/2, :ghost 1/2, :poison 0, :flying 1/4, :normal 0, :rock 1/4, :electric 0, :ground 0, :fighting 0, :dark 1/2, :steel 1/16, :bug 1/16} 1.853 + 1.854 + 1.855 +Cool! 1.856 + 1.857 +#+begin_src clojure :results output :exports both 1.858 +(clojure.pprint/pprint 1.859 + (pokemon.lpsolve/solution (pokemon.lpsolve/solid-defense-type))) 1.860 +#+end_src 1.861 + 1.862 +#+results: 1.863 +#+begin_example 1.864 +{":normal" 0.0, 1.865 + ":ground" 0.0, 1.866 + ":poison" 0.0, 1.867 + ":flying" 0.0, 1.868 + ":fighting" 0.0, 1.869 + ":dragon" 0.0, 1.870 + ":fire" 0.0, 1.871 + ":dark" 1.0, 1.872 + ":ice" 0.0, 1.873 + ":steel" 0.0, 1.874 + ":ghost" 1.0, 1.875 + ":electric" 0.0, 1.876 + ":bug" 0.0, 1.877 + ":psychic" 0.0, 1.878 + ":grass" 0.0, 1.879 + ":water" 0.0, 1.880 + ":rock" 0.0} 1.881 +#+end_example 1.882 + 1.883 +Dark and Ghost are the best dual-type combo, and are resistant or 1.884 +neutral to all types. 1.885 + 1.886 +#+begin_src clojure :results output :exports both 1.887 +(clojure.pprint/pprint 1.888 + (pokemon.types/old-school 1.889 + (pokemon.lpsolve/solution (pokemon.lpsolve/solid-defense-type)))) 1.890 +#+end_src 1.891 + 1.892 +#+results: 1.893 +#+begin_example 1.894 +{":normal" 0.0, 1.895 + ":ground" 0.0, 1.896 + ":poison" 0.0, 1.897 + ":flying" 0.0, 1.898 + ":fighting" 0.0, 1.899 + ":dragon" 0.0, 1.900 + ":fire" 0.0, 1.901 + ":ice" 0.0, 1.902 + ":ghost" 1.0, 1.903 + ":electric" 0.0, 1.904 + ":bug" 0.0, 1.905 + ":psychic" 1.0, 1.906 + ":grass" 0.0, 1.907 + ":water" 0.0, 1.908 + ":rock" 0.0} 1.909 +#+end_example 1.910 + 1.911 +Ghost and Psychic are a powerful dual type combo in the original games, 1.912 +due to a glitch which made Psychic immune to Ghost type attacks, even 1.913 +though the game claims that Ghost is strong to Psychic. 1.914 + 1.915 +#+begin_src clojure :results verbatim :exports both 1.916 +(pokemon.lpsolve/solution (pokemon.lpsolve/best-attack-type)) 1.917 +#+end_src 1.918 + 1.919 +#+results: 1.920 +: INFEASIBLE 1.921 + 1.922 +#+begin_src clojure :results verbatim :exports both 1.923 +(pokemon.lpsolve/solution (pokemon.lpsolve/solid-attack-type)) 1.924 +#+end_src 1.925 + 1.926 +#+results: 1.927 +: INFEASIBLE 1.928 + 1.929 + 1.930 +#+begin_src clojure :results verbatim :exports both 1.931 +(pokemon.types/old-school 1.932 + (pokemon.lpsolve/solution (pokemon.lpsolve/best-attack-type))) 1.933 +#+end_src 1.934 + 1.935 +#+results: 1.936 +: INFEASIBLE 1.937 + 1.938 + 1.939 +#+begin_src clojure :results output :exports both 1.940 +(clojure.pprint/pprint 1.941 + (pokemon.types/old-school 1.942 + (pokemon.lpsolve/solution (pokemon.lpsolve/solid-attack-type)))) 1.943 +#+end_src 1.944 + 1.945 +#+results: 1.946 +#+begin_example 1.947 +{":normal" 0.0, 1.948 + ":ground" 0.0, 1.949 + ":poison" 0.0, 1.950 + ":flying" 0.0, 1.951 + ":fighting" 0.0, 1.952 + ":dragon" 1.0, 1.953 + ":fire" 0.0, 1.954 + ":ice" 0.0, 1.955 + ":ghost" 0.0, 1.956 + ":electric" 0.0, 1.957 + ":bug" 0.0, 1.958 + ":psychic" 0.0, 1.959 + ":grass" 0.0, 1.960 + ":water" 0.0, 1.961 + ":rock" 0.0} 1.962 +#+end_example 1.963 + 1.964 +The best attacking type combination is strangely Dragon from the 1.965 +original games. It is neutral against all the original types except 1.966 +for Dragon, which it is strong against. There is no way to make an 1.967 +attacking type that is strong against every type, or even one that is 1.968 +strong or neutral against every type, in the new games. 1.969 + 1.970 + 1.971 +*** Weakest Attack/Defense Combinations 1.972 + 1.973 +#+begin_src clojure :results output :exports both 1.974 +(clojure.pprint/pprint 1.975 + (pokemon.types/old-school 1.976 + (pokemon.lpsolve/solution (pokemon.lpsolve/worst-attack-type)))) 1.977 +#+end_src 1.978 + 1.979 +#+results: 1.980 +#+begin_example 1.981 +{":normal" 5.0, 1.982 + ":ground" 0.0, 1.983 + ":poison" 0.0, 1.984 + ":flying" 0.0, 1.985 + ":fighting" 0.0, 1.986 + ":dragon" 0.0, 1.987 + ":fire" 1.0, 1.988 + ":ice" 2.0, 1.989 + ":ghost" 1.0, 1.990 + ":electric" 1.0, 1.991 + ":bug" 1.0, 1.992 + ":psychic" 0.0, 1.993 + ":grass" 3.0, 1.994 + ":water" 2.0, 1.995 + ":rock" 0.0} 1.996 +#+end_example 1.997 + 1.998 +# #+results-old: 1.999 +# : [[":normal" 5.0] [":ground" 1.0] [":poison" 0.0] [":flying" 0.0] [":fighting" 2.0] [":dragon" 0.0] [":fire" 0.0] [":ice" 4.0] [":ghost" 1.0] [":electric" 4.0] [":bug" 0.0] [":psychic" 0.0] [":grass" 0.0] [":water" 1.0] [":rock" 1.0]] 1.1000 + 1.1001 +#+begin_src clojure :results output :exports both 1.1002 +(clojure.pprint/pprint 1.1003 + (pokemon.lpsolve/solution (pokemon.lpsolve/worst-attack-type))) 1.1004 +#+end_src 1.1005 + 1.1006 +#+results: 1.1007 +#+begin_example 1.1008 +{":normal" 4.0, 1.1009 + ":ground" 1.0, 1.1010 + ":poison" 1.0, 1.1011 + ":flying" 0.0, 1.1012 + ":fighting" 1.0, 1.1013 + ":dragon" 0.0, 1.1014 + ":fire" 0.0, 1.1015 + ":dark" 0.0, 1.1016 + ":ice" 4.0, 1.1017 + ":steel" 0.0, 1.1018 + ":ghost" 1.0, 1.1019 + ":electric" 3.0, 1.1020 + ":bug" 0.0, 1.1021 + ":psychic" 1.0, 1.1022 + ":grass" 1.0, 1.1023 + ":water" 1.0, 1.1024 + ":rock" 2.0} 1.1025 +#+end_example 1.1026 + 1.1027 +# #+results-old: 1.1028 +# : [[":normal" 4.0] [":ground" 1.0] [":poison" 1.0] [":flying" 0.0] [":fighting" 2.0] [":dragon" 0.0] [":fire" 0.0] [":dark" 0.0] [":ice" 5.0] [":steel" 0.0] [":ghost" 1.0] [":electric" 5.0] [":bug" 0.0] [":psychic" 1.0] [":grass" 0.0] [":water" 1.0] [":rock" 2.0]] 1.1029 + 1.1030 + 1.1031 +This is an extremely interesting type combination, in that it uses 1.1032 +quite a few types. 1.1033 + 1.1034 +#+begin_src clojure :results verbatim :exports both 1.1035 +(reduce + (vals (:solution (pokemon.lpsolve/worst-attack-type)))) 1.1036 +#+end_src 1.1037 + 1.1038 +#+results: 1.1039 +: 20.0 1.1040 + 1.1041 +20 types is the /minimum/ number of types before the attacking 1.1042 +combination is not-very-effective or worse against all defending 1.1043 +types. This would probably have been impossible to discover using 1.1044 +best-first search, since it involves such an intricate type 1.1045 +combination. 1.1046 + 1.1047 +It's so interesting that it takes 20 types to make an attack type that 1.1048 +is weak to all types that the combination merits further investigation. 1.1049 + 1.1050 +Unfortunately, all of the tools that we've written so far are focused 1.1051 +on defense type combinations. However, it is possible to make every 1.1052 +tool attack-oriented via a simple macro. 1.1053 + 1.1054 +#+srcname: attack-oriented 1.1055 +#+begin_src clojure :results silent 1.1056 +(in-ns 'pokemon.lpsolve) 1.1057 + 1.1058 +(defmacro attack-mode [& forms] 1.1059 + `(let [attack-strengths# pokemon.types/attack-strengths 1.1060 + defense-strengths# pokemon.types/defense-strengths] 1.1061 + (binding [pokemon.types/attack-strengths 1.1062 + defense-strengths# 1.1063 + pokemon.types/defense-strengths 1.1064 + attack-strengths#] 1.1065 + ~@forms))) 1.1066 +#+end_src 1.1067 + 1.1068 +Now all the tools from =pokemon.types= will work for attack 1.1069 +combinations. 1.1070 + 1.1071 +#+begin_src clojure :results output :exports both 1.1072 +(clojure.pprint/pprint 1.1073 + (pokemon.types/susceptibility [:water])) 1.1074 +#+end_src 1.1075 + 1.1076 +#+results: 1.1077 +#+begin_example 1.1078 +{:water 1/2, 1.1079 + :psychic 1, 1.1080 + :dragon 1, 1.1081 + :fire 1/2, 1.1082 + :ice 1/2, 1.1083 + :grass 2, 1.1084 + :ghost 1, 1.1085 + :poison 1, 1.1086 + :flying 1, 1.1087 + :normal 1, 1.1088 + :rock 1, 1.1089 + :electric 2, 1.1090 + :ground 1, 1.1091 + :fighting 1, 1.1092 + :dark 1, 1.1093 + :steel 1/2, 1.1094 + :bug 1} 1.1095 +#+end_example 1.1096 + 1.1097 + 1.1098 +#+begin_src clojure :results output :exports both 1.1099 +(clojure.pprint/pprint 1.1100 + (pokemon.lpsolve/attack-mode 1.1101 + (pokemon.types/susceptibility [:water]))) 1.1102 +#+end_src 1.1103 + 1.1104 +#+results: 1.1105 +#+begin_example 1.1106 +{:water 1/2, 1.1107 + :psychic 1, 1.1108 + :dragon 1/2, 1.1109 + :fire 2, 1.1110 + :ice 1, 1.1111 + :grass 1/2, 1.1112 + :ghost 1, 1.1113 + :poison 1, 1.1114 + :flying 1, 1.1115 + :normal 1, 1.1116 + :rock 2, 1.1117 + :electric 1, 1.1118 + :ground 2, 1.1119 + :fighting 1, 1.1120 + :dark 1, 1.1121 + :steel 1, 1.1122 + :bug 1} 1.1123 +#+end_example 1.1124 + 1.1125 +Now =pokemon.types/susceptibility= reports the /attack-type/ 1.1126 +combination's effectiveness against other types. 1.1127 + 1.1128 +The 20 type combo achieves its goal in a very clever way. 1.1129 + 1.1130 +First, it weakens its effectiveness to other types at the expense of 1.1131 +making it very strong against flying. 1.1132 + 1.1133 +#+begin_src clojure :results output :exports both 1.1134 +(clojure.pprint/pprint 1.1135 + (pokemon.lpsolve/attack-mode 1.1136 + (pokemon.types/susceptibility 1.1137 + [:normal :normal :normal :normal 1.1138 + :ice :ice :ice :ice 1.1139 + :electric :electric :electric 1.1140 + :rock :rock]))) 1.1141 +#+end_src 1.1142 + 1.1143 +#+results: 1.1144 +#+begin_example 1.1145 +{:water 1/2, 1.1146 + :psychic 1, 1.1147 + :dragon 2, 1.1148 + :fire 1/4, 1.1149 + :ice 1/4, 1.1150 + :grass 2, 1.1151 + :ghost 0, 1.1152 + :poison 1, 1.1153 + :flying 512, 1.1154 + :normal 1, 1.1155 + :rock 1/16, 1.1156 + :electric 1/8, 1.1157 + :ground 0, 1.1158 + :fighting 1/4, 1.1159 + :dark 1, 1.1160 + :steel 1/1024, 1.1161 + :bug 4} 1.1162 +#+end_example 1.1163 + 1.1164 +Then, it removes it's strengths against Flying, Normal, and Fighting 1.1165 +by adding Ghost and Ground. 1.1166 + 1.1167 +#+begin_src clojure :results output :exports both 1.1168 +(clojure.pprint/pprint 1.1169 + (pokemon.lpsolve/attack-mode 1.1170 + (pokemon.types/susceptibility 1.1171 + [:normal :normal :normal :normal 1.1172 + :ice :ice :ice :ice 1.1173 + :electric :electric :electric 1.1174 + :rock :rock 1.1175 + ;; Spot resistances 1.1176 + :ghost :ground]))) 1.1177 +#+end_src 1.1178 + 1.1179 +#+results: 1.1180 +#+begin_example 1.1181 +{:water 1/2, 1.1182 + :psychic 2, 1.1183 + :dragon 2, 1.1184 + :fire 1/2, 1.1185 + :ice 1/4, 1.1186 + :grass 1, 1.1187 + :ghost 0, 1.1188 + :poison 2, 1.1189 + :flying 0, 1.1190 + :normal 0, 1.1191 + :rock 1/8, 1.1192 + :electric 1/4, 1.1193 + :ground 0, 1.1194 + :fighting 1/4, 1.1195 + :dark 1/2, 1.1196 + :steel 1/1024, 1.1197 + :bug 2} 1.1198 +#+end_example 1.1199 + 1.1200 +Adding the pair Psychic and Fighting takes care of its strength 1.1201 +against Psychic and makes it ineffective against Dark, which is immune 1.1202 +to Psychic. 1.1203 + 1.1204 +Adding the pair Grass and Poison makes takes care of its strength 1.1205 +against poison and makes it ineffective against Steel, which is immune 1.1206 +to poison. 1.1207 + 1.1208 +#+begin_src clojure :results output :exports both 1.1209 +(clojure.pprint/pprint 1.1210 + (pokemon.lpsolve/attack-mode 1.1211 + (pokemon.types/susceptibility 1.1212 + [;; setup 1.1213 + :normal :normal :normal :normal 1.1214 + :ice :ice :ice :ice 1.1215 + :electric :electric :electric 1.1216 + :rock :rock 1.1217 + ;; Spot resistances 1.1218 + :ghost :ground 1.1219 + ;; Pair resistances 1.1220 + :psychic :fighting 1.1221 + :grass :poison]))) 1.1222 +#+end_src 1.1223 + 1.1224 +#+results: 1.1225 +#+begin_example 1.1226 +{:water 1, 1.1227 + :psychic 1/2, 1.1228 + :dragon 1, 1.1229 + :fire 1/4, 1.1230 + :ice 1/2, 1.1231 + :grass 1, 1.1232 + :ghost 0, 1.1233 + :poison 1/2, 1.1234 + :flying 0, 1.1235 + :normal 0, 1.1236 + :rock 1/4, 1.1237 + :electric 1/4, 1.1238 + :ground 0, 1.1239 + :fighting 1/2, 1.1240 + :dark 0, 1.1241 + :steel 0, 1.1242 + :bug 1/2} 1.1243 +#+end_example 1.1244 + 1.1245 +Can you see the final step? 1.1246 + 1.1247 +It's adding the Water type, which is weak against Water and Dragon and 1.1248 +strong against Rock and Fire. 1.1249 + 1.1250 +#+begin_src clojure :results output :exports both 1.1251 +(clojure.pprint/pprint 1.1252 + (pokemon.lpsolve/attack-mode 1.1253 + (pokemon.types/susceptibility 1.1254 + [;; setup 1.1255 + :normal :normal :normal :normal 1.1256 + :ice :ice :ice :ice 1.1257 + :electric :electric :electric 1.1258 + :rock :rock 1.1259 + ;; Spot resistances 1.1260 + :ghost :ground 1.1261 + ;; Pair resistances 1.1262 + :psychic :fighting 1.1263 + :grass :poison 1.1264 + ;; completion 1.1265 + :water]))) 1.1266 +#+end_src 1.1267 + 1.1268 +#+results: 1.1269 +#+begin_example 1.1270 +{:water 1/2, 1.1271 + :psychic 1/2, 1.1272 + :dragon 1/2, 1.1273 + :fire 1/2, 1.1274 + :ice 1/2, 1.1275 + :grass 1/2, 1.1276 + :ghost 0, 1.1277 + :poison 1/2, 1.1278 + :flying 0, 1.1279 + :normal 0, 1.1280 + :rock 1/2, 1.1281 + :electric 1/4, 1.1282 + :ground 0, 1.1283 + :fighting 1/2, 1.1284 + :dark 0, 1.1285 + :steel 0, 1.1286 + :bug 1/2} 1.1287 +#+end_example 1.1288 + 1.1289 +Which makes a particularly beautiful combination which is ineffective 1.1290 +against all defending types. 1.1291 + 1.1292 + 1.1293 +# #+begin_src clojure :results scalar :exports both 1.1294 +# (with-out-str (clojure.contrib.pprint/pprint (seq (attack-mode (pokemon.types/susceptibility [:normal :normal :normal :normal :ice :ice :ice :ice :electric :electric :electric :rock :rock :ground :ghost :psychic :fighting :grass :poison]))))) 1.1295 +# #+end_src 1.1296 + 1.1297 +# #+results: 1.1298 +# | [:water 1] | [:psychic 1/2] | [:dragon 1] | [:fire 1/4] | [:ice 1/2] | [:grass 1] | [:ghost 0] | [:poison 1/2] | [:flying 0] | [:normal 0] | [:rock 1/4] | [:electric 1/4] | [:ground 0] | [:fighting 1/2] | [:dark 0] | [:steel 0] | [:bug 1/2] | 1.1299 + 1.1300 + 1.1301 +Is there anything else that's interesting? 1.1302 + 1.1303 +#+begin_src clojure :exports both 1.1304 +(pokemon.lpsolve/solution (pokemon.lpsolve/worst-defense-type)) 1.1305 +#+end_src 1.1306 + 1.1307 +#+results: 1.1308 +: INFEASIBLE 1.1309 + 1.1310 +#+begin_src clojure :exports both 1.1311 +(pokemon.types/old-school 1.1312 + (pokemon.lpsolve/solution (pokemon.lpsolve/worst-defense-type))) 1.1313 +#+end_src 1.1314 + 1.1315 +#+results: 1.1316 +: INFEASIBLE 1.1317 + 1.1318 +#+begin_src clojure :exports both 1.1319 +(pokemon.lpsolve/solution (pokemon.lpsolve/weak-defense-type)) 1.1320 +#+end_src 1.1321 + 1.1322 +#+results: 1.1323 +: INFEASIBLE 1.1324 + 1.1325 +#+begin_src clojure :exports both 1.1326 +(pokemon.types/old-school 1.1327 + (pokemon.lpsolve/solution (pokemon.lpsolve/weak-defense-type))) 1.1328 +#+end_src 1.1329 + 1.1330 +#+results: 1.1331 +: INFEASIBLE 1.1332 + 1.1333 +#+begin_src clojure :exports both 1.1334 +(pokemon.lpsolve/solution (pokemon.lpsolve/neutral-defense-type)) 1.1335 +#+end_src 1.1336 + 1.1337 +#+results: 1.1338 +: INFEASIBLE 1.1339 + 1.1340 +#+begin_src clojure :exports both 1.1341 +(pokemon.types/old-school 1.1342 + (pokemon.lpsolve/solution (pokemon.lpsolve/neutral-defense-type))) 1.1343 +#+end_src 1.1344 + 1.1345 +#+results: 1.1346 +: INFEASIBLE 1.1347 + 1.1348 +There is no way to produce a defense-type that is weak to all types. 1.1349 +This is probably because there are many types that are completely 1.1350 +immune to some types, such as Flying, which is immune to Ground. A 1.1351 +perfectly weak type could not use any of these types. 1.1352 + 1.1353 +* Summary 1.1354 + 1.1355 +Overall, the pok\eacute{}mon type system is slanted more towards defense 1.1356 +rather than offense. While it is possible to create superior 1.1357 +defensive types and exceptionally weak attack types, it is not possible to 1.1358 +create exceptionally weak defensive types or very powerful attack 1.1359 +types. 1.1360 + 1.1361 +Using the =lp_solve= library was more complicated than the best-first 1.1362 +search, but yielded results quickly and efficiently. Expressing the 1.1363 +problem in a linear form does have its drawbacks, however --- it's 1.1364 +hard to ask questions such as "what is the best 3-type defensive combo 1.1365 +in terms of susceptibility?", since susceptibility is not a linear 1.1366 +function of a combo's types. It is also hard to get all the solutions 1.1367 +to a particular problem, such as all the pokemon type combinations of 1.1368 +length 8 which are immortal defense types. 1.1369 + 1.1370 + 1.1371 +* COMMENT main-program 1.1372 +#+begin_src clojure :tangle ../src/pokemon/lpsolve.clj :noweb yes :exports none 1.1373 +<<intro>> 1.1374 +<<body>> 1.1375 +<<declares>> 1.1376 +<<memory-management>> 1.1377 +<<get-results>> 1.1378 +<<solve>> 1.1379 +<<farmer-example>> 1.1380 +<<lp-solve>> 1.1381 +<<better-farmer>> 1.1382 +<<pokemon-lp>> 1.1383 +<<results>> 1.1384 +<<attack-oriented>> 1.1385 +#+end_src 1.1386 + 1.1387 + 1.1388 +* COMMENT Stuff to do. 1.1389 +** TODO fix namespaces to not use rlm.light-base 1.1390 +