annotate org/lpsolve.org @ 10:eedd6897197d

fixed spelling errors for pokemon.types
author Robert McIntyre <rlm@mit.edu>
date Wed, 02 Nov 2011 08:02:11 -0700
parents a227fe337e83
children 4ea23241ff5b
rev   line source
rlm@10 1 #+title: Discovering Effective Pok\eacute{}mon Types Using Linear Optimization
rlm@0 2 #+author: Robert McIntyre & Dylan Holmes
rlm@0 3 #+EMAIL: rlm@mit.edu
rlm@4 4 #+SETUPFILE: ../../aurellem/org/setup.org
rlm@2 5 #+INCLUDE: ../../aurellem/org/level-0.org
rlm@4 6
rlm@0 7
rlm@0 8
rlm@0 9 * Introduction
rlm@0 10 In the [[./types.org][previous post]], we used the best-first search algorithm to
rlm@0 11 locate the most effective Pok\eacute{}mon type
rlm@0 12 combinations. Afterwards, we realized that we could transform this
rlm@0 13 search problem into a /linear optimization problem/. This conversion
rlm@10 14 offeres several advantages: first, search algorithms are comparatively
rlm@0 15 slow, whereas linear optimization algorithms are extremely fast;
rlm@0 16 second, it is difficult to determine whether a search problem has any
rlm@0 17 solution, whereas it is straightforward to determine whether a linear
rlm@0 18 optimization problem has any solution; finally, because systems of
rlm@0 19 linear equations are so common, many programming languages have linear
rlm@0 20 equation solvers written for them.
rlm@0 21
rlm@0 22 In this article, we will
rlm@0 23 - Solve a simple linear optimization problem in C :: We demonstrate
rlm@0 24 how to use the linear programming C library, =lp_solve=, to
rlm@0 25 solve a simple linear
rlm@0 26 optimization problem.
rlm@0 27 - Incorporate a C library into Clojure :: We will show how we gave
rlm@0 28 Clojure access to the linear programming C library, =lp_solve=.
rlm@0 29 - Find effective Pokemon types using linear programming :: Building
rlm@0 30 on our earlier code, (...)
rlm@10 31 - Present our results ::
rlm@0 32
rlm@0 33 #which can be represented and solved as a system of linear equations.
rlm@0 34
rlm@0 35 * COMMENT
rlm@0 36 This post continues the [[./types.org][previous one]] about pok\eacute{}mon types.
rlm@0 37 Pok\eacute{}mon is a game in which adorable creatures battle each
rlm@0 38 other using fantastic attacks. It was made into a several gameboy
rlm@0 39 games that all share the same battle system. Every pok\eacute{}mon in the
rlm@0 40 gameboy game has one or two /types/, such as Ground, Fire, Water,
rlm@0 41 etc. Every pok\eacute{}mon attack has exactly one type. Certain defending
rlm@0 42 types are weak or strong to other attacking types. For example, Water
rlm@0 43 attacks are strong against Fire pok\eacute{}mon, while Electric attacks are
rlm@0 44 weak against Ground Pok\eacute{}mon. In the games, attacks can be either
rlm@0 45 twice as effective as normal (Water vs. Fire), neutrally effective
rlm@0 46 (Normal vs. Normal), half as effective (Fire vs. Water), or not
rlm@0 47 effective at all (Electric vs. Ground). Thus the range of defense
rlm@0 48 values for a particular type is the set 0, 1/2, 1, 2. These are
rlm@0 49 referred to in the game as being immune, resistant, neutral, and
rlm@0 50 weak, respectively. I call them the /susceptance/ of one type to another.
rlm@0 51
rlm@0 52 If a pokemon has two types, then the strengths and weakness of each
rlm@0 53 type are /multiplied/ together. Thus Electric (2x weak to Ground)
rlm@0 54 combined with Flying (immune to Ground (0x)) is immune to
rlm@0 55 Ground. Fire (2x weak to Water) combined with Water (1/2x resistant
rlm@0 56 to Water) is neutral to Water. If both types are resistant to another
rlm@0 57 type, then the combination is doubly-resistant (1/4x) to that type. If
rlm@0 58 both types are weak to a certain type then the combination is
rlm@0 59 double-weak (4x) to that type.
rlm@0 60
rlm@0 61 ** Immortal Types
rlm@0 62
rlm@0 63 In the game, pok\eacute{}mon can have either one type, or two types. If this
rlm@0 64 restriction is lifted, is there any combination of types that is
rlm@0 65 resistant to all types? I call such a combination an /Immortal Type/,
rlm@0 66 since if that type's pattern was repeated over and over again towards
rlm@0 67 infinity, the resulting type would be immune to all attack types.
rlm@0 68
rlm@0 69 * Linear Programming
rlm@0 70
rlm@0 71 ** Terminology
rlm@0 72 Linear programming is the process of finding an optimal solution to a
rlm@0 73 linear equation of several variables which are constrained by some linear
rlm@0 74 inequalities.
rlm@0 75
rlm@0 76 In linear programming terminology, the function to be extremized is
rlm@0 77 the /objective function/.
rlm@0 78
rlm@0 79 ** COMMENT
rlm@0 80 First, we'll give a small example of a linear optimization problem,
rlm@0 81 and show how it can be solved with Clojure and =lp_solve=. Then,
rlm@0 82 we'll show how finding immortal pok\eacute{}mon types can be converted
rlm@0 83 into a linear problem suitable for solving in the same way.
rlm@0 84
rlm@0 85 ** The Farmer's Problem
rlm@0 86
rlm@10 87 Let's solve the Farmer's Problem, an example linear programming problem
rlm@0 88 borrowed from http://lpsolve.sourceforge.net/5.5/formulate.htm.
rlm@0 89
rlm@0 90
rlm@0 91 #+BEGIN_QUOTE
rlm@0 92 *The Farmer's Problem:* Suppose a farmer has 75 acres on which to
rlm@0 93 plant two crops: wheat and barley. To produce these crops, it costs
rlm@0 94 the farmer (for seed, fertilizer, etc.) $120 per acre for the wheat
rlm@0 95 and $210 per acre for the barley. The farmer has $15000 available for
rlm@0 96 expenses. But after the harvest, the farmer must store the crops while
rlm@0 97 awaiting favorable market conditions. The farmer has storage space
rlm@0 98 for 4000 bushels. Each acre yields an average of 110 bushels of wheat
rlm@0 99 or 30 bushels of barley. If the net profit per bushel of wheat (after
rlm@0 100 all expenses have been subtracted) is $1.30 and for barley is $2.00,
rlm@0 101 how should the farmer plant the 75 acres to maximize profit?
rlm@0 102 #+END_QUOTE
rlm@0 103
rlm@0 104 The Farmer's Problem is to maximize profit subject to constraints on
rlm@0 105 available farmland, funds for expenses, and storage space.
rlm@0 106
rlm@0 107 | | Wheat | Barley | Maximum total |
rlm@0 108 |----------+----------------------+---------------------+--------------|
rlm@0 109 | / | < | > | <> |
rlm@0 110 | Farmland | \(w\) acres | \(b\) acres | 75 acres |
rlm@0 111 | Expense | $120 per acre | $210 per acre | $15000 |
rlm@0 112 | Storage | 110 bushels per acre | 30 bushels per acre | 4000 bushels |
rlm@0 113 |----------+----------------------+---------------------+--------------|
rlm@0 114 | Profit | $1.30 per bushel | $2.00 per bushel | |
rlm@0 115
rlm@0 116 *** COMMENT
rlm@0 117 can be represented as a linear optimization
rlm@0 118 problem. In this form, it is a problem with two variables\mdash{}the number of
rlm@0 119 acres of wheat, \(w\), and the number of acres of barley, \(b\). The
rlm@0 120 aim is to maximize profit, which
rlm@0 121
rlm@0 122 subject to three constraints: the farmer can't spend more money
rlm@0 123 than he has, the farmer can't use more acres than he owns, and the harvest has
rlm@0 124 to fit in his storage space.
rlm@0 125
rlm@0 126 We can express these constraints succinctly using matrix
rlm@0 127 notation. Denoting the number of acres of barley and wheat by \(b\) and \(w\),
rlm@0 128 we want to maximize the expression \(143 w + 60 b\) subject to
rlm@0 129
rlm@0 130 \(
rlm@0 131 \begin{cases}
rlm@0 132 120 w + 210 b & \leq & 1500\\
rlm@0 133 110 w + 30 b & \leq & 4000\\
rlm@0 134 1 w + 1 w & \leq & 75
rlm@0 135 \end{cases}
rlm@0 136 \)
rlm@0 137
rlm@0 138 #\(\begin{bmatrix}120&210\\110&30\\1 &
rlm@0 139 #1\end{bmatrix}\;\begin{bmatrix}w\\b\end{bmatrix}
rlm@0 140 #\leq \begin{bmatrix}\$15000\\4000\text{ bushels}\\75\text{ acres}\end{bmatrix}\)
rlm@0 141
rlm@0 142 ** Solution using LP Solve
rlm@0 143 #(LP solve is available at http://www.example.com.)
rlm@0 144 In a new file, =farmer.lp=, we list the variables and constraints
rlm@0 145 of our problem using LP Solve syntax.
rlm@0 146
rlm@3 147 #+begin_src lpsolve :tangle ../lp/farmer.lp
rlm@0 148 /* Maximize Total Profit */
rlm@0 149 max: +143 wheat +60 barley;
rlm@0 150
rlm@0 151
rlm@0 152 /* -------- Constraints --------*/
rlm@0 153
rlm@0 154 /* the farmer can't spend more money than he has */
rlm@0 155 +120 wheat +210 barley <= 15000;
rlm@0 156
rlm@0 157 /* the harvest has to fit in his storage space */
rlm@0 158 +110 wheat +30 barley <= 4000;
rlm@0 159
rlm@0 160 /* he can't use more acres than he owns */
rlm@0 161 +wheat +barley <= 75;
rlm@0 162 #+end_src
rlm@0 163
rlm@0 164
rlm@0 165 #This is a set of linear equations ideal for solving using a program like
rlm@0 166 #=lp_solve=. In Linear Algebra terms we are maximizing the linear function
rlm@0 167
rlm@0 168 #\(\text{profit} = 143\text{ wheat} + 60\text{ barley}\), subject to the constraints
rlm@0 169
rlm@0 170 #Ax <= b,
rlm@0 171
rlm@0 172 #where A is [120 210 110 30 1 1], x is [wheat barley] and b is [15000
rlm@0 173 #4000 75].
rlm@0 174
rlm@0 175 Running the =lp_solve= program on =farmer.lp= yields the following output.
rlm@0 176
rlm@0 177 #+begin_src sh :exports both :results scalar
rlm@0 178 ~/roBin/lpsolve/lp_solve ~/aurellem/src/pokemon/farmer.lp
rlm@0 179 #+end_src
rlm@0 180
rlm@0 181 #+results:
rlm@0 182 :
rlm@0 183 : Value of objective function: 6315.62500000
rlm@0 184 :
rlm@0 185 : Actual values of the variables:
rlm@0 186 : wheat 21.875
rlm@0 187 : barley 53.125
rlm@0 188
rlm@0 189 This shows that the farmer can maximize his profit by planting 21.875
rlm@0 190 of the available acres with wheat and the remaining 53.125 acres with
rlm@0 191 barley; by doing so, he will make $6315.62(5) in profit.
rlm@0 192
rlm@0 193
rlm@0 194 #The farmer can make a profit of $6315.62 by planting 21.875 acres of
rlm@0 195 #his land with wheat and the other 53.125 acres of his land with barley.
rlm@0 196
rlm@0 197 * Incorporating =lp_solve= into Clojure
rlm@0 198
rlm@0 199 There is a Java API available which enables Java programs to use Lp
rlm@0 200 Solve. Although Clojure can use this Java API directly, the
rlm@0 201 interaction between Java, C, and Clojure is clumsy:
rlm@0 202
rlm@0 203
rlm@0 204 The Java API for LP Solve makes it possible to use Lp Solve algorithms
rlm@0 205 within Java. Although Clojure can use this Java API directly,
rlm@0 206
rlm@0 207
rlm@0 208 ** The Farmer's Problem in Clojure
rlm@0 209 We are going to solve the same problem involving wheat and barley,
rlm@0 210 that we did above, but this time using clojure and the lpsolve API.
rlm@0 211
rlm@0 212 #Because we ultimately intend to use Lp Solve to find optimal Pokemon type combinations.
rlm@0 213 # we want to solve linear optimization problems within Clojure, the language
rlm@0 214
rlm@0 215 ** Setup
rlm@0 216 =lp_solve= is a crufty =C= program which already comes with a JNI
rlm@0 217 interface written by Juergen Ebert. It's API is not
rlm@0 218 particularly friendly from a functional/immutable perspective, but
rlm@0 219 with a little work, we can build something that works great with
rlm@0 220 clojure.
rlm@0 221
rlm@0 222 #+srcname: intro
rlm@0 223 #+begin_src clojure :results silent
rlm@0 224 (ns pokemon.lpsolve
rlm@0 225 (:use rlm.ns-rlm))
rlm@0 226 (rlm.ns-rlm/ns-clone rlm.light-base)
rlm@0 227 (use 'clojure.set)
rlm@0 228 (import 'lpsolve.LpSolve)
rlm@0 229 (use '[clojure [pprint :only [pprint]]])
rlm@0 230 #+end_src
rlm@0 231
rlm@0 232 The LpSolve Java interface is available from the same site as
rlm@0 233 =lp_solve= itself, http://lpsolve.sourceforge.net/
rlm@0 234 Using it is the same as many other =C=
rlm@0 235 programs. There are excellent instructions to get set
rlm@0 236 up. The short version is that you must call Java with
rlm@0 237 =-Djava.library.path=/path/to/lpsolve/libraries= and also add the
rlm@0 238 libraries to your export =LD_LIBRARY_PATH= if you are using Linux. For
rlm@0 239 example, in my =.bashrc= file, I have the line
rlm@0 240 =LD_LIBRARY_PATH=$HOME/roBin/lpsolve:$LD_LIBRARY_PATH=.
rlm@0 241 If everything is set-up correctly,
rlm@0 242
rlm@0 243 #+srcname: body
rlm@0 244 #+begin_src clojure :results verbatim :exports both
rlm@0 245 (import 'lpsolve.LpSolve)
rlm@0 246 #+end_src
rlm@0 247
rlm@0 248 #+results: body
rlm@0 249 : lpsolve.LpSolve
rlm@0 250
rlm@0 251 should run with no problems.
rlm@0 252
rlm@0 253 ** Making a DSL to talk with LpSolve
rlm@0 254 *** Problems
rlm@0 255 Since we are using a =C= wrapper, we have to deal with manual memory
rlm@0 256 management for the =C= structures which are wrapped by the =LpSolve=
rlm@0 257 object. Memory leaks in =LpSolve= instances can crash the JVM, so it's
rlm@0 258 very important to get it right. Also, the Java wrapper follows the
rlm@0 259 =C= tradition closely and defines many =static final int= constants
rlm@0 260 for the different states of the =LpSolve= instance instead of using Java
rlm@0 261 enums. The calling convention for adding rows and columns to
rlm@0 262 the constraint matrix is rather complicated and must be done column by
rlm@0 263 column or row by row, which can be error prone. Finally, I'd like to
rlm@0 264 gather all the important output information from the =LpSolve= instance
rlm@0 265 into a final, immutable structure.
rlm@0 266
rlm@0 267 In summary, the issues I'd like to address are:
rlm@0 268
rlm@0 269 - reliable memory management
rlm@0 270 - functional interface to =LpSolve=
rlm@0 271 - intelligible, immutable output
rlm@0 272
rlm@0 273 To deal with these issues I'll create four functions for interfacing
rlm@0 274 with =LpSolve=
rlm@0 275
rlm@0 276 #+srcname: declares
rlm@0 277 #+begin_src clojure :results silent
rlm@0 278 (in-ns 'pokemon.lpsolve)
rlm@0 279
rlm@0 280 ;; deal with automatic memory management for LpSolve instance.
rlm@0 281 (declare linear-program)
rlm@0 282
rlm@0 283 ;; functional interface to LpSolve
rlm@0 284 (declare lp-solve)
rlm@0 285
rlm@0 286 ;; immutable output from lp-solve
rlm@0 287 (declare solve get-results)
rlm@0 288 #+end_src
rlm@0 289
rlm@0 290 *** Memory Management
rlm@0 291
rlm@0 292 Every instance of =LpSolve= must be manually garbage collected via a
rlm@0 293 call to =deleteLP=. I use a non-hygienic macro similar to =with-open=
rlm@0 294 to ensure that =deleteLP= is always called.
rlm@0 295
rlm@0 296 #+srcname: memory-management
rlm@0 297 #+begin_src clojure :results silent
rlm@0 298 (in-ns 'pokemon.lpsolve)
rlm@0 299 (defmacro linear-program
rlm@0 300 "solve a linear programming problem using LpSolve syntax.
rlm@0 301 within the macro, the variable =lps= is bound to the LpSolve instance."
rlm@0 302 [& statements]
rlm@0 303 (list 'let '[lps (LpSolve/makeLp 0 0)]
rlm@0 304 (concat '(try)
rlm@0 305 statements
rlm@0 306 ;; always free the =C= data structures.
rlm@0 307 '((finally (.deleteLp lps))))))
rlm@0 308 #+end_src
rlm@0 309
rlm@0 310
rlm@0 311 The macro captures the variable =lps= within its body, providing for a
rlm@0 312 convenient way to access the object using any of the methods of the
rlm@0 313 =LpSolve= API without having to worry about when to call
rlm@0 314 =deleteLP=.
rlm@0 315
rlm@0 316 *** Sensible Results
rlm@0 317 The =linear-program= macro deletes the actual =lps= object once it is
rlm@0 318 done working, so it's important to collect the important results and
rlm@0 319 add return them in an immutable structure at the end.
rlm@0 320
rlm@0 321 #+srcname: get-results
rlm@0 322 #+begin_src clojure :results silent
rlm@0 323 (in-ns 'pokemon.lpsolve)
rlm@0 324
rlm@0 325 (defrecord LpSolution
rlm@0 326 [objective-value
rlm@0 327 optimal-values
rlm@0 328 variable-names
rlm@0 329 solution
rlm@0 330 status
rlm@0 331 model])
rlm@0 332
rlm@0 333 (defn model
rlm@0 334 "Returns a textual representation of the problem suitable for
rlm@0 335 direct input to the =lp_solve= program (lps format)"
rlm@0 336 [#^LpSolve lps]
rlm@0 337 (let [target (java.io.File/createTempFile "lps" ".lp")]
rlm@0 338 (.writeLp lps (.getPath target))
rlm@0 339 (slurp target)))
rlm@0 340
rlm@0 341 (defn results
rlm@0 342 "given an LpSolve object, solves the object and returns a map of the
rlm@0 343 essential values which compose the solution."
rlm@0 344 [#^LpSolve lps]
rlm@0 345 (locking lps
rlm@0 346 (let [status (solve lps)
rlm@0 347 number-of-variables (.getNcolumns lps)
rlm@0 348 optimal-values (double-array number-of-variables)
rlm@0 349 optimal-values (do (.getVariables lps optimal-values)
rlm@0 350 (seq optimal-values))
rlm@0 351 variable-names
rlm@0 352 (doall ;; the doall is necessary since the lps object might
rlm@0 353 ;; soon be deleted
rlm@0 354 (map
rlm@0 355 #(.getColName lps (inc %))
rlm@0 356 (range number-of-variables)))
rlm@0 357 model (model lps)]
rlm@0 358 (LpSolution.
rlm@0 359 (.getObjective lps)
rlm@0 360 optimal-values
rlm@0 361 variable-names
rlm@0 362 (zipmap variable-names optimal-values)
rlm@0 363 status
rlm@0 364 model))))
rlm@0 365
rlm@0 366 #+end_src
rlm@0 367
rlm@0 368 Here I've created an object called =LpSolution= which stores the
rlm@0 369 important results from a session with =lp_solve=. Of note is the
rlm@0 370 =model= function which returns the problem in a form that can be
rlm@0 371 solved by other linear programming packages.
rlm@0 372
rlm@0 373 *** Solution Status of an LpSolve Object
rlm@0 374
rlm@0 375 #+srcname: solve
rlm@0 376 #+begin_src clojure :results silent
rlm@0 377 (in-ns 'pokemon.lpsolve)
rlm@0 378
rlm@0 379 (defn static-integer?
rlm@0 380 "does the field represent a static integer constant?"
rlm@0 381 [#^java.lang.reflect.Field field]
rlm@0 382 (and (java.lang.reflect.Modifier/isStatic (.getModifiers field))
rlm@0 383 (integer? (.get field nil))))
rlm@0 384
rlm@0 385 (defn integer-constants [class]
rlm@0 386 (filter static-integer? (.getFields class)))
rlm@0 387
rlm@0 388 (defn-memo constant-map
rlm@0 389 "Takes a class and creates a map of the static constant integer
rlm@0 390 fields with their names. This helps with C wrappers where they have
rlm@0 391 just defined a bunch of integer constants instead of enums"
rlm@0 392 [class]
rlm@0 393 (let [integer-fields (integer-constants class)]
rlm@0 394 (into (sorted-map)
rlm@0 395 (zipmap (map #(.get % nil) integer-fields)
rlm@0 396 (map #(.getName %) integer-fields)))))
rlm@0 397
rlm@0 398 (defn solve
rlm@0 399 "Solve an instance of LpSolve and return a string representing the
rlm@0 400 status of the computation. Will only solve a particular LpSolve
rlm@0 401 instance once."
rlm@0 402 [#^LpSolve lps]
rlm@0 403 ((constant-map LpSolve)
rlm@0 404 (.solve lps)))
rlm@0 405
rlm@0 406 #+end_src
rlm@0 407
rlm@0 408 The =.solve= method of an =LpSolve= object only returns an integer code
rlm@0 409 to specify the status of the computation. The =solve= method here
rlm@0 410 uses reflection to look up the actual name of the status code and
rlm@0 411 returns a more helpful status message that is also resistant to
rlm@0 412 changes in the meanings of the code numbers.
rlm@0 413
rlm@0 414 *** The Farmer Example in Clojure, Pass 1
rlm@0 415
rlm@0 416 Now we can implement a nicer version of the examples from the
rlm@0 417 [[http://lpsolve.sourceforge.net/][=lp\_solve= website]]. The following is a more or less
rlm@0 418 line-by-line translation of the Java code from that example.
rlm@0 419
rlm@0 420 #+srcname: farmer-example
rlm@0 421 #+begin_src clojure :results silent
rlm@0 422 (in-ns 'pokemon.lpsolve)
rlm@0 423 (defn farmer-example []
rlm@0 424 (linear-program
rlm@0 425 (results
rlm@0 426 (doto lps
rlm@0 427 ;; name the columns
rlm@0 428 (.setColName 1 "wheat")
rlm@0 429 (.setColName 2 "barley")
rlm@0 430 (.setAddRowmode true)
rlm@0 431 ;; row 1 : 120x + 210y <= 15000
rlm@0 432 (.addConstraintex 2
rlm@0 433 (double-array [120 210])
rlm@0 434 (int-array [1 2])
rlm@0 435 LpSolve/LE
rlm@0 436 15e3)
rlm@0 437 ;; row 2 : 110x + 30y <= 4000
rlm@0 438 (.addConstraintex 2
rlm@0 439 (double-array [110 30])
rlm@0 440 (int-array [1 2])
rlm@0 441 LpSolve/LE
rlm@0 442 4e3)
rlm@0 443 ;; ;; row 3 : x + y <= 75
rlm@0 444 (.addConstraintex 2
rlm@0 445 (double-array [1 1])
rlm@0 446 (int-array [1 2])
rlm@0 447 LpSolve/LE
rlm@0 448 75)
rlm@0 449 (.setAddRowmode false)
rlm@0 450
rlm@0 451 ;; add constraints
rlm@0 452 (.setObjFnex 2
rlm@0 453 (double-array [143 60])
rlm@0 454 (int-array [1 2]))
rlm@0 455
rlm@0 456 ;; set this as a maximization problem
rlm@0 457 (.setMaxim)))))
rlm@0 458
rlm@0 459 #+end_src
rlm@0 460
rlm@0 461 #+begin_src clojure :results output :exports both
rlm@0 462 (clojure.pprint/pprint
rlm@0 463 (:solution (pokemon.lpsolve/farmer-example)))
rlm@0 464 #+end_src
rlm@0 465
rlm@0 466 #+results:
rlm@0 467 : {"barley" 53.12499999999999, "wheat" 21.875}
rlm@0 468
rlm@0 469 And it works as expected!
rlm@0 470
rlm@0 471 *** The Farmer Example in Clojure, Pass 2
rlm@0 472 We don't have to worry about memory management anymore, and the farmer
rlm@0 473 example is about half as long as the example from the =LpSolve=
rlm@0 474 website, but we can still do better. Solving linear problems is all
rlm@0 475 about the constraint matrix $A$ , the objective function $c$, and the
rlm@0 476 right-hand-side $b$, plus whatever other options one cares to set for
rlm@0 477 the particular instance of =lp_solve=. Why not make a version of
rlm@0 478 =linear-program= that takes care of initialization?
rlm@0 479
rlm@0 480
rlm@0 481
rlm@0 482 #+srcname: lp-solve
rlm@0 483 #+begin_src clojure :results silent
rlm@0 484 (in-ns 'pokemon.lpsolve)
rlm@0 485 (defn initialize-lpsolve-row-oriented
rlm@0 486 "fill in an lpsolve instance using a constraint matrix =A=, the
rlm@0 487 objective function =c=, and the right-hand-side =b="
rlm@0 488 [#^LpSolve lps A b c]
rlm@0 489 ;; set the name of the last column to _something_
rlm@0 490 ;; this appears to be necessary to ensure proper initialization.
rlm@0 491 (.setColName lps (count c) (str "C" (count c)))
rlm@0 492
rlm@0 493 ;; This is the recommended way to "fill-in" an lps instance from the
rlm@0 494 ;; documentation. First, set row mode, then set the objective
rlm@0 495 ;; function, then set each row of the problem, and then turn off row
rlm@0 496 ;; mode.
rlm@0 497 (.setAddRowmode lps true)
rlm@0 498 (.setObjFnex lps (count c)
rlm@0 499 (double-array c)
rlm@0 500 (int-array (range 1 (inc (count c)))))
rlm@0 501 (dorun
rlm@0 502 (for [n (range (count A))]
rlm@0 503 (let [row (nth A n)
rlm@0 504 row-length (int (count row))]
rlm@0 505 (.addConstraintex lps
rlm@0 506 row-length
rlm@0 507 (double-array row)
rlm@0 508 (int-array (range 1 (inc row-length)))
rlm@0 509 LpSolve/LE
rlm@0 510 (double (nth b n))))))
rlm@0 511 (.setAddRowmode lps false)
rlm@0 512 lps)
rlm@0 513
rlm@0 514
rlm@0 515 (defmacro lp-solve
rlm@0 516 "by default:,
rlm@0 517 minimize (* c x), subject to (<= (* A x) b),
rlm@0 518 using continuous variables. You may set any number of
rlm@0 519 other options as in the LpSolve API."
rlm@0 520 [A b c & lp-solve-forms]
rlm@0 521 ;; assume that A is a vector of vectors
rlm@0 522 (concat
rlm@0 523 (list 'linear-program
rlm@0 524 (list 'initialize-lpsolve-row-oriented 'lps A b c))
rlm@0 525 `~lp-solve-forms))
rlm@0 526 #+end_src
rlm@0 527
rlm@0 528 Now, we can use a much more functional approach to solving the
rlm@0 529 farmer's problem:
rlm@0 530
rlm@0 531 #+srcname: better-farmer
rlm@0 532 #+begin_src clojure :results silent
rlm@0 533 (in-ns 'pokemon.lpsolve)
rlm@0 534 (defn better-farmer-example []
rlm@0 535 (lp-solve [[120 210]
rlm@0 536 [110 30]
rlm@0 537 [1 1]]
rlm@0 538 [15000
rlm@0 539 4000
rlm@0 540 75]
rlm@0 541 [143 60]
rlm@0 542 (.setColName lps 1 "wheat")
rlm@0 543 (.setColName lps 2 "barley")
rlm@0 544 (.setMaxim lps)
rlm@0 545 (results lps)))
rlm@0 546 #+end_src
rlm@0 547
rlm@0 548 #+begin_src clojure :exports both :results verbatim
rlm@0 549 (vec (:solution (pokemon.lpsolve/better-farmer-example)))
rlm@0 550 #+end_src
rlm@0 551
rlm@0 552 #+results:
rlm@0 553 : [["barley" 53.12499999999999] ["wheat" 21.875]]
rlm@0 554
rlm@0 555 Notice that both the inputs to =better-farmer-example= and the results
rlm@0 556 are immutable.
rlm@0 557
rlm@0 558 * Using LpSolve to find Immortal Types
rlm@0 559 ** Converting the Pokemon problem into a linear form
rlm@0 560 How can the original question about pok\eacute{}mon types be converted
rlm@0 561 into a linear problem?
rlm@0 562
rlm@0 563 Pokemon types can be considered to be vectors of numbers representing
rlm@0 564 their susceptances to various attacking types, so Water might look
rlm@0 565 something like this.
rlm@0 566
rlm@0 567 #+begin_src clojure :results scalar :exports both
rlm@0 568 (:water (pokemon.types/defense-strengths))
rlm@0 569 #+end_src
rlm@0 570
rlm@0 571 #+results:
rlm@0 572 : [1 0.5 0.5 2 2 0.5 1 1 1 1 1 1 1 1 1 1 0.5]
rlm@0 573
rlm@0 574 Where the numbers represent the susceptibility of Water to the
rlm@0 575 attacking types in the following order:
rlm@0 576
rlm@0 577 #+begin_src clojure :results output :exports both
rlm@0 578 (clojure.pprint/pprint
rlm@0 579 (pokemon.types/type-names))
rlm@0 580 #+end_src
rlm@0 581
rlm@0 582 #+results:
rlm@0 583 #+begin_example
rlm@0 584 [:normal
rlm@0 585 :fire
rlm@0 586 :water
rlm@0 587 :electric
rlm@0 588 :grass
rlm@0 589 :ice
rlm@0 590 :fighting
rlm@0 591 :poison
rlm@0 592 :ground
rlm@0 593 :flying
rlm@0 594 :psychic
rlm@0 595 :bug
rlm@0 596 :rock
rlm@0 597 :ghost
rlm@0 598 :dragon
rlm@0 599 :dark
rlm@0 600 :steel]
rlm@0 601 #+end_example
rlm@0 602
rlm@0 603
rlm@0 604 So, for example, Water is /resistant/ (x0.5) against Fire, which is
rlm@0 605 the second element in the list.
rlm@0 606
rlm@0 607 To combine types, these sorts of vectors are multiplied together
rlm@0 608 pair-wise to yield the resulting combination.
rlm@0 609
rlm@0 610 Unfortunately, we need some way to add two type vectors together
rlm@0 611 instead of multiplying them if we want to solve the problem with
rlm@0 612 =lp_solve=. Taking the log of the vector does just the trick.
rlm@0 613
rlm@0 614 If we make a matrix with each column being the log (base 2) of the
rlm@0 615 susceptance of each type, then finding an immortal type corresponds to
rlm@0 616 setting each constraint (the $b$ vector) to -1 (since log_2(1/2) = -1)
rlm@0 617 and setting the constraint vector $c$ to all ones, which means that we
rlm@0 618 want to find the immortal type which uses the least amount of types.
rlm@0 619
rlm@0 620 #+srcname: pokemon-lp
rlm@0 621 #+begin_src clojure :results silent
rlm@0 622 (in-ns 'pokemon.lpsolve)
rlm@0 623
rlm@0 624 (require 'pokemon.types)
rlm@0 625 (require 'incanter.core)
rlm@0 626
rlm@0 627 (defn log-clamp-matrix [matrix]
rlm@0 628 ;; we have to clamp the Infinities to a more reasonable negative
rlm@0 629 ;; value because lp_solve does not play well with infinities in its
rlm@0 630 ;; constraint matrix.
rlm@0 631 (map (fn [row] (map #(if (= Double/NEGATIVE_INFINITY %) -1e3 %) row))
rlm@0 632 (incanter.core/log2
rlm@0 633 (incanter.core/trans
rlm@0 634 matrix))))
rlm@0 635
rlm@0 636 ;; constraint matrices
rlm@0 637 (defn log-defense-matrix []
rlm@0 638 (log-clamp-matrix
rlm@0 639 (doall (map (pokemon.types/defense-strengths)
rlm@0 640 (pokemon.types/type-names)))))
rlm@0 641
rlm@0 642 (defn log-attack-matrix []
rlm@0 643 (incanter.core/trans (log-defense-matrix)))
rlm@0 644
rlm@0 645 ;; target vectors
rlm@0 646 (defn all-resistant []
rlm@0 647 (doall (map (constantly -1) (pokemon.types/type-names))))
rlm@0 648
rlm@0 649 (defn all-weak []
rlm@0 650 (doall (map (constantly 1) (pokemon.types/type-names))))
rlm@0 651
rlm@0 652 (defn all-neutral []
rlm@0 653 (doall (map (constantly 0) (pokemon.types/type-names))))
rlm@0 654
rlm@0 655
rlm@0 656 ;; objective functions
rlm@0 657 (defn number-of-types []
rlm@0 658 (doall (map (constantly 1) (pokemon.types/type-names))))
rlm@0 659
rlm@0 660 (defn set-constraints
rlm@0 661 "sets all the constraints for an lpsolve instance to the given
rlm@0 662 constraint. =constraint= here is one of the LpSolve constants such
rlm@0 663 as LpSolve/EQ."
rlm@0 664 [#^LpSolve lps constraint]
rlm@0 665 (dorun (map (fn [index] (.setConstrType lps index constraint))
rlm@0 666 ;; ONE based indexing!!!
rlm@0 667 (range 1 (inc (.getNrows lps))))))
rlm@0 668
rlm@0 669
rlm@0 670 (defn set-discrete
rlm@0 671 "sets every variable in an lps problem to be a discrete rather than
rlm@0 672 continuous variable"
rlm@0 673 [#^LpSolve lps]
rlm@0 674 (dorun (map (fn [index] (.setInt lps index true))
rlm@0 675 ;; ONE based indexing!!!
rlm@0 676 (range 1 (inc (.getNcolumns lps))))))
rlm@0 677
rlm@0 678 (defn set-variable-names
rlm@0 679 "sets the variable names of the problem given a vector of names"
rlm@0 680 [#^LpSolve lps names]
rlm@0 681 (dorun
rlm@0 682 (map (fn [[index name]]
rlm@0 683 (.setColName lps (inc index) (str name)))
rlm@0 684 ;; ONE based indexing!!!
rlm@0 685 (indexed names))))
rlm@0 686
rlm@0 687 (defn poke-solve
rlm@0 688 ([poke-matrix target objective-function constraint min-num-types]
rlm@0 689 ;; must have at least one type
rlm@0 690 (let [poke-matrix
rlm@0 691 (concat poke-matrix
rlm@0 692 [(map (constantly 1)
rlm@0 693 (range (count (first poke-matrix))))])
rlm@0 694 target (concat target [min-num-types])]
rlm@0 695 (lp-solve poke-matrix target objective-function
rlm@0 696 (set-constraints lps constraint)
rlm@0 697 ;; must have more than min-num-types
rlm@0 698 (.setConstrType lps (count target) LpSolve/GE)
rlm@0 699 (set-discrete lps)
rlm@0 700 (set-variable-names lps (pokemon.types/type-names))
rlm@0 701 (results lps))))
rlm@0 702 ([poke-matrix target objective-function constraint]
rlm@0 703 ;; at least one type
rlm@0 704 (poke-solve poke-matrix target objective-function constraint 1)))
rlm@0 705
rlm@0 706 (defn solution
rlm@0 707 "If the results of an lpsolve operation are feasible, returns the
rlm@0 708 results. Otherwise, returns the error."
rlm@0 709 [results]
rlm@0 710 (if (not (= (:status results) "OPTIMAL"))
rlm@0 711 (:status results)
rlm@0 712 (:solution results)))
rlm@0 713
rlm@0 714 #+end_src
rlm@0 715
rlm@0 716 With this, we are finally able to get some results.
rlm@0 717
rlm@0 718 ** Results
rlm@0 719 #+srcname: results
rlm@0 720 #+begin_src clojure :results silent
rlm@0 721 (in-ns 'pokemon.lpsolve)
rlm@0 722
rlm@0 723 (defn best-defense-type
rlm@0 724 "finds a type combination which is resistant to all attacks."
rlm@0 725 []
rlm@0 726 (poke-solve
rlm@0 727 (log-defense-matrix) (all-resistant) (number-of-types) LpSolve/LE))
rlm@0 728
rlm@0 729 (defn worst-attack-type
rlm@0 730 "finds the attack type which is not-very-effective against all pure
rlm@0 731 defending types (each single defending type is resistant to this
rlm@0 732 attack combination"
rlm@0 733 []
rlm@0 734 (poke-solve
rlm@0 735 (log-attack-matrix) (all-resistant) (number-of-types) LpSolve/LE))
rlm@0 736
rlm@0 737 (defn worst-defense-type
rlm@0 738 "finds a defending type that is weak to all single attacking types."
rlm@0 739 []
rlm@0 740 (poke-solve
rlm@0 741 (log-defense-matrix) (all-weak) (number-of-types) LpSolve/GE))
rlm@0 742
rlm@0 743 (defn best-attack-type
rlm@0 744 "finds an attack type which is super effective against all single
rlm@0 745 defending types"
rlm@0 746 []
rlm@0 747 (poke-solve
rlm@0 748 (log-attack-matrix) (all-weak) (number-of-types) LpSolve/GE))
rlm@0 749
rlm@0 750 (defn solid-defense-type
rlm@0 751 "finds a defense type which is either neutral or resistant to all
rlm@0 752 single attacking types"
rlm@0 753 []
rlm@0 754 (poke-solve
rlm@0 755 (log-defense-matrix) (all-neutral) (number-of-types) LpSolve/LE))
rlm@0 756
rlm@0 757 (defn solid-attack-type
rlm@0 758 "finds an attack type which is either neutral or super-effective to
rlm@0 759 all single attacking types."
rlm@0 760 []
rlm@0 761 (poke-solve
rlm@0 762 (log-attack-matrix) (all-neutral) (number-of-types) LpSolve/GE))
rlm@0 763
rlm@0 764 (defn weak-defense-type
rlm@0 765 "finds a defense type which is either neutral or weak to all single
rlm@0 766 attacking types"
rlm@0 767 []
rlm@0 768 (poke-solve
rlm@0 769 (log-defense-matrix) (all-neutral) (number-of-types) LpSolve/GE))
rlm@0 770
rlm@0 771 (defn neutral-defense-type
rlm@0 772 "finds a defense type which is perfectly neutral to all attacking
rlm@0 773 types."
rlm@0 774 []
rlm@0 775 (poke-solve
rlm@0 776 (log-defense-matrix) (all-neutral) (number-of-types) LpSolve/EQ))
rlm@0 777
rlm@0 778 #+end_src
rlm@0 779
rlm@0 780 *** Strongest Attack/Defense Combinations
rlm@0 781
rlm@0 782 #+begin_src clojure :results output :exports both
rlm@0 783 (clojure.pprint/pprint
rlm@0 784 (pokemon.lpsolve/solution (pokemon.lpsolve/best-defense-type)))
rlm@0 785 #+end_src
rlm@0 786
rlm@0 787 #+results:
rlm@0 788 #+begin_example
rlm@0 789 {":normal" 0.0,
rlm@0 790 ":ground" 1.0,
rlm@0 791 ":poison" 2.0,
rlm@0 792 ":flying" 1.0,
rlm@0 793 ":fighting" 0.0,
rlm@0 794 ":dragon" 0.0,
rlm@0 795 ":fire" 0.0,
rlm@0 796 ":dark" 1.0,
rlm@0 797 ":ice" 0.0,
rlm@0 798 ":steel" 1.0,
rlm@0 799 ":ghost" 0.0,
rlm@0 800 ":electric" 0.0,
rlm@0 801 ":bug" 0.0,
rlm@0 802 ":psychic" 0.0,
rlm@0 803 ":grass" 0.0,
rlm@0 804 ":water" 2.0,
rlm@0 805 ":rock" 0.0}
rlm@0 806 #+end_example
rlm@0 807
rlm@0 808 # #+results-old:
rlm@0 809 # : [[":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]]
rlm@0 810
rlm@0 811
rlm@0 812 This is the immortal type combination we've been looking for. By
rlm@0 813 combining Steel, Water, Poison, and three types which each have complete
rlm@0 814 immunities to various other types, we've created a type that is resistant to
rlm@0 815 all attacking types.
rlm@0 816
rlm@0 817 #+begin_src clojure :results output :exports both
rlm@0 818 (clojure.pprint/pprint
rlm@0 819 (pokemon.types/susceptibility
rlm@0 820 [:poison :poison :water :water :steel :ground :flying :dark]))
rlm@0 821 #+end_src
rlm@0 822
rlm@0 823 #+results:
rlm@0 824 #+begin_example
rlm@0 825 {:water 1/2,
rlm@0 826 :psychic 0,
rlm@0 827 :dragon 1/2,
rlm@0 828 :fire 1/2,
rlm@0 829 :ice 1/2,
rlm@0 830 :grass 1/2,
rlm@0 831 :ghost 1/4,
rlm@0 832 :poison 0,
rlm@0 833 :flying 1/2,
rlm@0 834 :normal 1/2,
rlm@0 835 :rock 1/2,
rlm@0 836 :electric 0,
rlm@0 837 :ground 0,
rlm@0 838 :fighting 1/2,
rlm@0 839 :dark 1/4,
rlm@0 840 :steel 1/8,
rlm@0 841 :bug 1/8}
rlm@0 842 #+end_example
rlm@0 843
rlm@0 844 # #+results-old:
rlm@0 845 # : {: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}
rlm@0 846
rlm@0 847
rlm@0 848 Cool!
rlm@0 849
rlm@0 850 #+begin_src clojure :results output :exports both
rlm@0 851 (clojure.pprint/pprint
rlm@0 852 (pokemon.lpsolve/solution (pokemon.lpsolve/solid-defense-type)))
rlm@0 853 #+end_src
rlm@0 854
rlm@0 855 #+results:
rlm@0 856 #+begin_example
rlm@0 857 {":normal" 0.0,
rlm@0 858 ":ground" 0.0,
rlm@0 859 ":poison" 0.0,
rlm@0 860 ":flying" 0.0,
rlm@0 861 ":fighting" 0.0,
rlm@0 862 ":dragon" 0.0,
rlm@0 863 ":fire" 0.0,
rlm@0 864 ":dark" 1.0,
rlm@0 865 ":ice" 0.0,
rlm@0 866 ":steel" 0.0,
rlm@0 867 ":ghost" 1.0,
rlm@0 868 ":electric" 0.0,
rlm@0 869 ":bug" 0.0,
rlm@0 870 ":psychic" 0.0,
rlm@0 871 ":grass" 0.0,
rlm@0 872 ":water" 0.0,
rlm@0 873 ":rock" 0.0}
rlm@0 874 #+end_example
rlm@0 875
rlm@0 876 Dark and Ghost are the best dual-type combo, and are resistant or
rlm@0 877 neutral to all types.
rlm@0 878
rlm@0 879 #+begin_src clojure :results output :exports both
rlm@0 880 (clojure.pprint/pprint
rlm@0 881 (pokemon.types/old-school
rlm@0 882 (pokemon.lpsolve/solution (pokemon.lpsolve/solid-defense-type))))
rlm@0 883 #+end_src
rlm@0 884
rlm@0 885 #+results:
rlm@0 886 #+begin_example
rlm@0 887 {":normal" 0.0,
rlm@0 888 ":ground" 0.0,
rlm@0 889 ":poison" 0.0,
rlm@0 890 ":flying" 0.0,
rlm@0 891 ":fighting" 0.0,
rlm@0 892 ":dragon" 0.0,
rlm@0 893 ":fire" 0.0,
rlm@0 894 ":ice" 0.0,
rlm@0 895 ":ghost" 1.0,
rlm@0 896 ":electric" 0.0,
rlm@0 897 ":bug" 0.0,
rlm@0 898 ":psychic" 1.0,
rlm@0 899 ":grass" 0.0,
rlm@0 900 ":water" 0.0,
rlm@0 901 ":rock" 0.0}
rlm@0 902 #+end_example
rlm@0 903
rlm@0 904 Ghost and Psychic are a powerful dual type combo in the original games,
rlm@0 905 due to a glitch which made Psychic immune to Ghost type attacks, even
rlm@0 906 though the game claims that Ghost is strong to Psychic.
rlm@0 907
rlm@0 908 #+begin_src clojure :results verbatim :exports both
rlm@0 909 (pokemon.lpsolve/solution (pokemon.lpsolve/best-attack-type))
rlm@0 910 #+end_src
rlm@0 911
rlm@0 912 #+results:
rlm@0 913 : INFEASIBLE
rlm@0 914
rlm@0 915 #+begin_src clojure :results verbatim :exports both
rlm@0 916 (pokemon.lpsolve/solution (pokemon.lpsolve/solid-attack-type))
rlm@0 917 #+end_src
rlm@0 918
rlm@0 919 #+results:
rlm@0 920 : INFEASIBLE
rlm@0 921
rlm@0 922
rlm@0 923 #+begin_src clojure :results verbatim :exports both
rlm@0 924 (pokemon.types/old-school
rlm@0 925 (pokemon.lpsolve/solution (pokemon.lpsolve/best-attack-type)))
rlm@0 926 #+end_src
rlm@0 927
rlm@0 928 #+results:
rlm@0 929 : INFEASIBLE
rlm@0 930
rlm@0 931
rlm@0 932 #+begin_src clojure :results output :exports both
rlm@0 933 (clojure.pprint/pprint
rlm@0 934 (pokemon.types/old-school
rlm@0 935 (pokemon.lpsolve/solution (pokemon.lpsolve/solid-attack-type))))
rlm@0 936 #+end_src
rlm@0 937
rlm@0 938 #+results:
rlm@0 939 #+begin_example
rlm@0 940 {":normal" 0.0,
rlm@0 941 ":ground" 0.0,
rlm@0 942 ":poison" 0.0,
rlm@0 943 ":flying" 0.0,
rlm@0 944 ":fighting" 0.0,
rlm@0 945 ":dragon" 1.0,
rlm@0 946 ":fire" 0.0,
rlm@0 947 ":ice" 0.0,
rlm@0 948 ":ghost" 0.0,
rlm@0 949 ":electric" 0.0,
rlm@0 950 ":bug" 0.0,
rlm@0 951 ":psychic" 0.0,
rlm@0 952 ":grass" 0.0,
rlm@0 953 ":water" 0.0,
rlm@0 954 ":rock" 0.0}
rlm@0 955 #+end_example
rlm@0 956
rlm@0 957 The best attacking type combination is strangely Dragon from the
rlm@0 958 original games. It is neutral against all the original types except
rlm@0 959 for Dragon, which it is strong against. There is no way to make an
rlm@0 960 attacking type that is strong against every type, or even one that is
rlm@0 961 strong or neutral against every type, in the new games.
rlm@0 962
rlm@0 963
rlm@0 964 *** Weakest Attack/Defense Combinations
rlm@0 965
rlm@0 966 #+begin_src clojure :results output :exports both
rlm@0 967 (clojure.pprint/pprint
rlm@0 968 (pokemon.types/old-school
rlm@0 969 (pokemon.lpsolve/solution (pokemon.lpsolve/worst-attack-type))))
rlm@0 970 #+end_src
rlm@0 971
rlm@0 972 #+results:
rlm@0 973 #+begin_example
rlm@0 974 {":normal" 5.0,
rlm@0 975 ":ground" 0.0,
rlm@0 976 ":poison" 0.0,
rlm@0 977 ":flying" 0.0,
rlm@0 978 ":fighting" 0.0,
rlm@0 979 ":dragon" 0.0,
rlm@0 980 ":fire" 1.0,
rlm@0 981 ":ice" 2.0,
rlm@0 982 ":ghost" 1.0,
rlm@0 983 ":electric" 1.0,
rlm@0 984 ":bug" 1.0,
rlm@0 985 ":psychic" 0.0,
rlm@0 986 ":grass" 3.0,
rlm@0 987 ":water" 2.0,
rlm@0 988 ":rock" 0.0}
rlm@0 989 #+end_example
rlm@0 990
rlm@0 991 # #+results-old:
rlm@0 992 # : [[":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]]
rlm@0 993
rlm@0 994 #+begin_src clojure :results output :exports both
rlm@0 995 (clojure.pprint/pprint
rlm@0 996 (pokemon.lpsolve/solution (pokemon.lpsolve/worst-attack-type)))
rlm@0 997 #+end_src
rlm@0 998
rlm@0 999 #+results:
rlm@0 1000 #+begin_example
rlm@0 1001 {":normal" 4.0,
rlm@0 1002 ":ground" 1.0,
rlm@0 1003 ":poison" 1.0,
rlm@0 1004 ":flying" 0.0,
rlm@0 1005 ":fighting" 1.0,
rlm@0 1006 ":dragon" 0.0,
rlm@0 1007 ":fire" 0.0,
rlm@0 1008 ":dark" 0.0,
rlm@0 1009 ":ice" 4.0,
rlm@0 1010 ":steel" 0.0,
rlm@0 1011 ":ghost" 1.0,
rlm@0 1012 ":electric" 3.0,
rlm@0 1013 ":bug" 0.0,
rlm@0 1014 ":psychic" 1.0,
rlm@0 1015 ":grass" 1.0,
rlm@0 1016 ":water" 1.0,
rlm@0 1017 ":rock" 2.0}
rlm@0 1018 #+end_example
rlm@0 1019
rlm@0 1020 # #+results-old:
rlm@0 1021 # : [[":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]]
rlm@0 1022
rlm@0 1023
rlm@0 1024 This is an extremely interesting type combination, in that it uses
rlm@0 1025 quite a few types.
rlm@0 1026
rlm@0 1027 #+begin_src clojure :results verbatim :exports both
rlm@0 1028 (reduce + (vals (:solution (pokemon.lpsolve/worst-attack-type))))
rlm@0 1029 #+end_src
rlm@0 1030
rlm@0 1031 #+results:
rlm@0 1032 : 20.0
rlm@0 1033
rlm@0 1034 20 types is the /minimum/ number of types before the attacking
rlm@0 1035 combination is not-very-effective or worse against all defending
rlm@0 1036 types. This would probably have been impossible to discover using
rlm@0 1037 best-first search, since it involves such an intricate type
rlm@0 1038 combination.
rlm@0 1039
rlm@0 1040 It's so interesting that it takes 20 types to make an attack type that
rlm@0 1041 is weak to all types that the combination merits further investigation.
rlm@0 1042
rlm@0 1043 Unfortunately, all of the tools that we've written so far are focused
rlm@0 1044 on defense type combinations. However, it is possible to make every
rlm@0 1045 tool attack-oriented via a simple macro.
rlm@0 1046
rlm@0 1047 #+srcname: attack-oriented
rlm@0 1048 #+begin_src clojure :results silent
rlm@0 1049 (in-ns 'pokemon.lpsolve)
rlm@0 1050
rlm@0 1051 (defmacro attack-mode [& forms]
rlm@0 1052 `(let [attack-strengths# pokemon.types/attack-strengths
rlm@0 1053 defense-strengths# pokemon.types/defense-strengths]
rlm@0 1054 (binding [pokemon.types/attack-strengths
rlm@0 1055 defense-strengths#
rlm@0 1056 pokemon.types/defense-strengths
rlm@0 1057 attack-strengths#]
rlm@0 1058 ~@forms)))
rlm@0 1059 #+end_src
rlm@0 1060
rlm@0 1061 Now all the tools from =pokemon.types= will work for attack
rlm@0 1062 combinations.
rlm@0 1063
rlm@0 1064 #+begin_src clojure :results output :exports both
rlm@0 1065 (clojure.pprint/pprint
rlm@0 1066 (pokemon.types/susceptibility [:water]))
rlm@0 1067 #+end_src
rlm@0 1068
rlm@0 1069 #+results:
rlm@0 1070 #+begin_example
rlm@0 1071 {:water 1/2,
rlm@0 1072 :psychic 1,
rlm@0 1073 :dragon 1,
rlm@0 1074 :fire 1/2,
rlm@0 1075 :ice 1/2,
rlm@0 1076 :grass 2,
rlm@0 1077 :ghost 1,
rlm@0 1078 :poison 1,
rlm@0 1079 :flying 1,
rlm@0 1080 :normal 1,
rlm@0 1081 :rock 1,
rlm@0 1082 :electric 2,
rlm@0 1083 :ground 1,
rlm@0 1084 :fighting 1,
rlm@0 1085 :dark 1,
rlm@0 1086 :steel 1/2,
rlm@0 1087 :bug 1}
rlm@0 1088 #+end_example
rlm@0 1089
rlm@0 1090
rlm@0 1091 #+begin_src clojure :results output :exports both
rlm@0 1092 (clojure.pprint/pprint
rlm@0 1093 (pokemon.lpsolve/attack-mode
rlm@0 1094 (pokemon.types/susceptibility [:water])))
rlm@0 1095 #+end_src
rlm@0 1096
rlm@0 1097 #+results:
rlm@0 1098 #+begin_example
rlm@0 1099 {:water 1/2,
rlm@0 1100 :psychic 1,
rlm@0 1101 :dragon 1/2,
rlm@0 1102 :fire 2,
rlm@0 1103 :ice 1,
rlm@0 1104 :grass 1/2,
rlm@0 1105 :ghost 1,
rlm@0 1106 :poison 1,
rlm@0 1107 :flying 1,
rlm@0 1108 :normal 1,
rlm@0 1109 :rock 2,
rlm@0 1110 :electric 1,
rlm@0 1111 :ground 2,
rlm@0 1112 :fighting 1,
rlm@0 1113 :dark 1,
rlm@0 1114 :steel 1,
rlm@0 1115 :bug 1}
rlm@0 1116 #+end_example
rlm@0 1117
rlm@0 1118 Now =pokemon.types/susceptibility= reports the /attack-type/
rlm@0 1119 combination's effectiveness against other types.
rlm@0 1120
rlm@0 1121 The 20 type combo achieves its goal in a very clever way.
rlm@0 1122
rlm@0 1123 First, it weakens its effectiveness to other types at the expense of
rlm@0 1124 making it very strong against flying.
rlm@0 1125
rlm@0 1126 #+begin_src clojure :results output :exports both
rlm@0 1127 (clojure.pprint/pprint
rlm@0 1128 (pokemon.lpsolve/attack-mode
rlm@0 1129 (pokemon.types/susceptibility
rlm@0 1130 [:normal :normal :normal :normal
rlm@0 1131 :ice :ice :ice :ice
rlm@0 1132 :electric :electric :electric
rlm@0 1133 :rock :rock])))
rlm@0 1134 #+end_src
rlm@0 1135
rlm@0 1136 #+results:
rlm@0 1137 #+begin_example
rlm@0 1138 {:water 1/2,
rlm@0 1139 :psychic 1,
rlm@0 1140 :dragon 2,
rlm@0 1141 :fire 1/4,
rlm@0 1142 :ice 1/4,
rlm@0 1143 :grass 2,
rlm@0 1144 :ghost 0,
rlm@0 1145 :poison 1,
rlm@0 1146 :flying 512,
rlm@0 1147 :normal 1,
rlm@0 1148 :rock 1/16,
rlm@0 1149 :electric 1/8,
rlm@0 1150 :ground 0,
rlm@0 1151 :fighting 1/4,
rlm@0 1152 :dark 1,
rlm@0 1153 :steel 1/1024,
rlm@0 1154 :bug 4}
rlm@0 1155 #+end_example
rlm@0 1156
rlm@0 1157 Then, it removes it's strengths against Flying, Normal, and Fighting
rlm@0 1158 by adding Ghost and Ground.
rlm@0 1159
rlm@0 1160 #+begin_src clojure :results output :exports both
rlm@0 1161 (clojure.pprint/pprint
rlm@0 1162 (pokemon.lpsolve/attack-mode
rlm@0 1163 (pokemon.types/susceptibility
rlm@0 1164 [:normal :normal :normal :normal
rlm@0 1165 :ice :ice :ice :ice
rlm@0 1166 :electric :electric :electric
rlm@0 1167 :rock :rock
rlm@0 1168 ;; Spot resistances
rlm@0 1169 :ghost :ground])))
rlm@0 1170 #+end_src
rlm@0 1171
rlm@0 1172 #+results:
rlm@0 1173 #+begin_example
rlm@0 1174 {:water 1/2,
rlm@0 1175 :psychic 2,
rlm@0 1176 :dragon 2,
rlm@0 1177 :fire 1/2,
rlm@0 1178 :ice 1/4,
rlm@0 1179 :grass 1,
rlm@0 1180 :ghost 0,
rlm@0 1181 :poison 2,
rlm@0 1182 :flying 0,
rlm@0 1183 :normal 0,
rlm@0 1184 :rock 1/8,
rlm@0 1185 :electric 1/4,
rlm@0 1186 :ground 0,
rlm@0 1187 :fighting 1/4,
rlm@0 1188 :dark 1/2,
rlm@0 1189 :steel 1/1024,
rlm@0 1190 :bug 2}
rlm@0 1191 #+end_example
rlm@0 1192
rlm@0 1193 Adding the pair Psychic and Fighting takes care of its strength
rlm@0 1194 against Psychic and makes it ineffective against Dark, which is immune
rlm@0 1195 to Psychic.
rlm@0 1196
rlm@0 1197 Adding the pair Grass and Poison makes takes care of its strength
rlm@0 1198 against poison and makes it ineffective against Steel, which is immune
rlm@0 1199 to poison.
rlm@0 1200
rlm@0 1201 #+begin_src clojure :results output :exports both
rlm@0 1202 (clojure.pprint/pprint
rlm@0 1203 (pokemon.lpsolve/attack-mode
rlm@0 1204 (pokemon.types/susceptibility
rlm@0 1205 [;; setup
rlm@0 1206 :normal :normal :normal :normal
rlm@0 1207 :ice :ice :ice :ice
rlm@0 1208 :electric :electric :electric
rlm@0 1209 :rock :rock
rlm@0 1210 ;; Spot resistances
rlm@0 1211 :ghost :ground
rlm@0 1212 ;; Pair resistances
rlm@0 1213 :psychic :fighting
rlm@0 1214 :grass :poison])))
rlm@0 1215 #+end_src
rlm@0 1216
rlm@0 1217 #+results:
rlm@0 1218 #+begin_example
rlm@0 1219 {:water 1,
rlm@0 1220 :psychic 1/2,
rlm@0 1221 :dragon 1,
rlm@0 1222 :fire 1/4,
rlm@0 1223 :ice 1/2,
rlm@0 1224 :grass 1,
rlm@0 1225 :ghost 0,
rlm@0 1226 :poison 1/2,
rlm@0 1227 :flying 0,
rlm@0 1228 :normal 0,
rlm@0 1229 :rock 1/4,
rlm@0 1230 :electric 1/4,
rlm@0 1231 :ground 0,
rlm@0 1232 :fighting 1/2,
rlm@0 1233 :dark 0,
rlm@0 1234 :steel 0,
rlm@0 1235 :bug 1/2}
rlm@0 1236 #+end_example
rlm@0 1237
rlm@0 1238 Can you see the final step?
rlm@0 1239
rlm@0 1240 It's adding the Water type, which is weak against Water and Dragon and
rlm@0 1241 strong against Rock and Fire.
rlm@0 1242
rlm@0 1243 #+begin_src clojure :results output :exports both
rlm@0 1244 (clojure.pprint/pprint
rlm@0 1245 (pokemon.lpsolve/attack-mode
rlm@0 1246 (pokemon.types/susceptibility
rlm@0 1247 [;; setup
rlm@0 1248 :normal :normal :normal :normal
rlm@0 1249 :ice :ice :ice :ice
rlm@0 1250 :electric :electric :electric
rlm@0 1251 :rock :rock
rlm@0 1252 ;; Spot resistances
rlm@0 1253 :ghost :ground
rlm@0 1254 ;; Pair resistances
rlm@0 1255 :psychic :fighting
rlm@0 1256 :grass :poison
rlm@0 1257 ;; completion
rlm@0 1258 :water])))
rlm@0 1259 #+end_src
rlm@0 1260
rlm@0 1261 #+results:
rlm@0 1262 #+begin_example
rlm@0 1263 {:water 1/2,
rlm@0 1264 :psychic 1/2,
rlm@0 1265 :dragon 1/2,
rlm@0 1266 :fire 1/2,
rlm@0 1267 :ice 1/2,
rlm@0 1268 :grass 1/2,
rlm@0 1269 :ghost 0,
rlm@0 1270 :poison 1/2,
rlm@0 1271 :flying 0,
rlm@0 1272 :normal 0,
rlm@0 1273 :rock 1/2,
rlm@0 1274 :electric 1/4,
rlm@0 1275 :ground 0,
rlm@0 1276 :fighting 1/2,
rlm@0 1277 :dark 0,
rlm@0 1278 :steel 0,
rlm@0 1279 :bug 1/2}
rlm@0 1280 #+end_example
rlm@0 1281
rlm@0 1282 Which makes a particularly beautiful combination which is ineffective
rlm@0 1283 against all defending types.
rlm@0 1284
rlm@0 1285
rlm@0 1286 # #+begin_src clojure :results scalar :exports both
rlm@0 1287 # (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])))))
rlm@0 1288 # #+end_src
rlm@0 1289
rlm@0 1290 # #+results:
rlm@0 1291 # | [: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] |
rlm@0 1292
rlm@0 1293
rlm@0 1294 Is there anything else that's interesting?
rlm@0 1295
rlm@0 1296 #+begin_src clojure :exports both
rlm@0 1297 (pokemon.lpsolve/solution (pokemon.lpsolve/worst-defense-type))
rlm@0 1298 #+end_src
rlm@0 1299
rlm@0 1300 #+results:
rlm@0 1301 : INFEASIBLE
rlm@0 1302
rlm@0 1303 #+begin_src clojure :exports both
rlm@0 1304 (pokemon.types/old-school
rlm@0 1305 (pokemon.lpsolve/solution (pokemon.lpsolve/worst-defense-type)))
rlm@0 1306 #+end_src
rlm@0 1307
rlm@0 1308 #+results:
rlm@0 1309 : INFEASIBLE
rlm@0 1310
rlm@0 1311 #+begin_src clojure :exports both
rlm@0 1312 (pokemon.lpsolve/solution (pokemon.lpsolve/weak-defense-type))
rlm@0 1313 #+end_src
rlm@0 1314
rlm@0 1315 #+results:
rlm@0 1316 : INFEASIBLE
rlm@0 1317
rlm@0 1318 #+begin_src clojure :exports both
rlm@0 1319 (pokemon.types/old-school
rlm@0 1320 (pokemon.lpsolve/solution (pokemon.lpsolve/weak-defense-type)))
rlm@0 1321 #+end_src
rlm@0 1322
rlm@0 1323 #+results:
rlm@0 1324 : INFEASIBLE
rlm@0 1325
rlm@0 1326 #+begin_src clojure :exports both
rlm@0 1327 (pokemon.lpsolve/solution (pokemon.lpsolve/neutral-defense-type))
rlm@0 1328 #+end_src
rlm@0 1329
rlm@0 1330 #+results:
rlm@0 1331 : INFEASIBLE
rlm@0 1332
rlm@0 1333 #+begin_src clojure :exports both
rlm@0 1334 (pokemon.types/old-school
rlm@0 1335 (pokemon.lpsolve/solution (pokemon.lpsolve/neutral-defense-type)))
rlm@0 1336 #+end_src
rlm@0 1337
rlm@0 1338 #+results:
rlm@0 1339 : INFEASIBLE
rlm@0 1340
rlm@0 1341 There is no way to produce a defense-type that is weak to all types.
rlm@0 1342 This is probably because there are many types that are completely
rlm@0 1343 immune to some types, such as Flying, which is immune to Ground. A
rlm@0 1344 perfectly weak type could not use any of these types.
rlm@0 1345
rlm@0 1346 * Summary
rlm@0 1347
rlm@0 1348 Overall, the pok\eacute{}mon type system is slanted more towards defense
rlm@0 1349 rather than offense. While it is possible to create superior
rlm@0 1350 defensive types and exceptionally weak attack types, it is not possible to
rlm@0 1351 create exceptionally weak defensive types or very powerful attack
rlm@0 1352 types.
rlm@0 1353
rlm@0 1354 Using the =lp_solve= library was more complicated than the best-first
rlm@0 1355 search, but yielded results quickly and efficiently. Expressing the
rlm@0 1356 problem in a linear form does have its drawbacks, however --- it's
rlm@0 1357 hard to ask questions such as "what is the best 3-type defensive combo
rlm@0 1358 in terms of susceptibility?", since susceptibility is not a linear
rlm@0 1359 function of a combo's types. It is also hard to get all the solutions
rlm@0 1360 to a particular problem, such as all the pokemon type combinations of
rlm@0 1361 length 8 which are immortal defense types.
rlm@0 1362
rlm@0 1363
rlm@0 1364 * COMMENT main-program
rlm@0 1365 #+begin_src clojure :tangle ../src/pokemon/lpsolve.clj :noweb yes :exports none
rlm@0 1366 <<intro>>
rlm@0 1367 <<body>>
rlm@0 1368 <<declares>>
rlm@0 1369 <<memory-management>>
rlm@0 1370 <<get-results>>
rlm@0 1371 <<solve>>
rlm@0 1372 <<farmer-example>>
rlm@0 1373 <<lp-solve>>
rlm@0 1374 <<better-farmer>>
rlm@0 1375 <<pokemon-lp>>
rlm@0 1376 <<results>>
rlm@0 1377 <<attack-oriented>>
rlm@0 1378 #+end_src
rlm@0 1379
rlm@0 1380
rlm@0 1381 * COMMENT Stuff to do.
rlm@0 1382 ** TODO fix namespaces to not use rlm.light-base
rlm@0 1383