comparison org/lpsolve.org @ 11:4ea23241ff5b

cleaned up prose
author Robert McIntyre <rlm@mit.edu>
date Wed, 02 Nov 2011 10:28:50 -0700
parents eedd6897197d
children 89462678a932
comparison
equal deleted inserted replaced
10:eedd6897197d 11:4ea23241ff5b
1 #+title: Discovering Effective Pok\eacute{}mon Types Using Linear Optimization 1 #+title: Discovering Effective Pok\eacute{}mon Types Using Linear Optimization
2 #+author: Robert McIntyre & Dylan Holmes 2 #+author: Robert McIntyre & Dylan Holmes
3 #+EMAIL: rlm@mit.edu 3 #+EMAIL: rlm@mit.edu
4 #+description: Using Lpsolve to func effective pokemon types in clojure.
5 #+keywords: Pokemon, clojure, linear optimization, lp_spolve, LpSolve
4 #+SETUPFILE: ../../aurellem/org/setup.org 6 #+SETUPFILE: ../../aurellem/org/setup.org
5 #+INCLUDE: ../../aurellem/org/level-0.org 7 #+INCLUDE: ../../aurellem/org/level-0.org
6 8
7 9
8 10
9 * Introduction 11 * Introduction
10 In the [[./types.org][previous post]], we used the best-first search algorithm to 12 This post continues the [[./types.org][previous one]] about pok\eacute{}mon types.
11 locate the most effective Pok\eacute{}mon type 13 Pok\eacute{}mon is a game in which adorable creatures battle each
12 combinations. Afterwards, we realized that we could transform this 14 other using fantastic attacks. It was made into a several gameboy
13 search problem into a /linear optimization problem/. This conversion 15 games that all share the same battle system. Every pok\eacute{}mon in
14 offeres several advantages: first, search algorithms are comparatively 16 the gameboy game has one or two /types/, such as Ground, Fire, Water,
15 slow, whereas linear optimization algorithms are extremely fast; 17 etc. Every pok\eacute{}mon attack has exactly one type. Certain
16 second, it is difficult to determine whether a search problem has any 18 defending types are weak or strong to other attacking types. For
17 solution, whereas it is straightforward to determine whether a linear 19 example, Water attacks are strong against Fire pok\eacute{}mon, while
18 optimization problem has any solution; finally, because systems of 20 Electric attacks are weak against Ground Pok\eacute{}mon. In the
19 linear equations are so common, many programming languages have linear 21 games, attacks can be either twice as effective as normal (Water
20 equation solvers written for them. 22 vs. Fire), neutrally effective (Normal vs. Normal), half as effective
21 23 (Fire vs. Water), or not effective at all (Electric vs. Ground). We
22 In this article, we will 24 represent these strengths and weakessness as the numbers 2, 1,
25 $\frac{1}{2}$, and 0, and call them the /susceptance/ of one type to
26 another.
27
28 If a pokemon has two types, then the strengths and weakness of each
29 type are /multiplied/ together. Thus Electric (2x weak to Ground)
30 combined with Flying (immune to Ground (0x)) is immune to Ground.
31 Fire (2x weak to Water) combined with Water (1/2x resistant to Water)
32 is neutral to Water. If both types are resistant to another type, then
33 the combination is doubly-resistant (1/4x) to that type. If both types
34 are weak to a certain type then the combination is double-weak (4x) to
35 that type.
36
37 In the [[./types.org][previous post]], we used the best-first search algorithm to find
38 the most effective Pok\eacute{}mon type combinations. Afterwards, we
39 realized that we could transform this search problem into a /linear
40 optimization problem/. This conversion offeres several advantages:
41 first, search algorithms are comparatively slow, whereas linear
42 optimization algorithms are extremely fast; second, it is difficult to
43 determine whether a search problem has any solution, whereas it is
44 straightforward to determine whether a linear optimization problem has
45 any solution; finally, because systems of linear equations are so
46 common, many programming languages have linear equation solvers
47 written for them.
48
49 In this article, we will:
23 - Solve a simple linear optimization problem in C :: We demonstrate 50 - Solve a simple linear optimization problem in C :: We demonstrate
24 how to use the linear programming C library, =lp_solve=, to 51 how to use the linear programming C library, =lp_solve=, to
25 solve a simple linear 52 solve a simple linear optimization problem.
26 optimization problem.
27 - Incorporate a C library into Clojure :: We will show how we gave 53 - Incorporate a C library into Clojure :: We will show how we gave
28 Clojure access to the linear programming C library, =lp_solve=. 54 Clojure access to the linear programming C library, =lp_solve=.
29 - Find effective Pokemon types using linear programming :: Building 55 - Find effective Pokemon types using linear programming :: Building
30 on our earlier code, (...) 56 on our earlier code, we answer some questions that were
31 - Present our results :: 57 impossible to answer using best-first search.
58 - Present our results :: We found some cool examples and learned a lot
59 about the pok\eacute{}mon type system as a whole.
32 60
33 #which can be represented and solved as a system of linear equations.
34
35 * COMMENT
36 This post continues the [[./types.org][previous one]] about pok\eacute{}mon types.
37 Pok\eacute{}mon is a game in which adorable creatures battle each
38 other using fantastic attacks. It was made into a several gameboy
39 games that all share the same battle system. Every pok\eacute{}mon in the
40 gameboy game has one or two /types/, such as Ground, Fire, Water,
41 etc. Every pok\eacute{}mon attack has exactly one type. Certain defending
42 types are weak or strong to other attacking types. For example, Water
43 attacks are strong against Fire pok\eacute{}mon, while Electric attacks are
44 weak against Ground Pok\eacute{}mon. In the games, attacks can be either
45 twice as effective as normal (Water vs. Fire), neutrally effective
46 (Normal vs. Normal), half as effective (Fire vs. Water), or not
47 effective at all (Electric vs. Ground). Thus the range of defense
48 values for a particular type is the set 0, 1/2, 1, 2. These are
49 referred to in the game as being immune, resistant, neutral, and
50 weak, respectively. I call them the /susceptance/ of one type to another.
51
52 If a pokemon has two types, then the strengths and weakness of each
53 type are /multiplied/ together. Thus Electric (2x weak to Ground)
54 combined with Flying (immune to Ground (0x)) is immune to
55 Ground. Fire (2x weak to Water) combined with Water (1/2x resistant
56 to Water) is neutral to Water. If both types are resistant to another
57 type, then the combination is doubly-resistant (1/4x) to that type. If
58 both types are weak to a certain type then the combination is
59 double-weak (4x) to that type.
60 61
61 ** Immortal Types 62 ** Immortal Types
62 63
63 In the game, pok\eacute{}mon can have either one type, or two types. If this 64 In the game, pok\eacute{}mon can have either one type or two types. If
64 restriction is lifted, is there any combination of types that is 65 this restriction is lifted, is there any combination of types that is
65 resistant to all types? I call such a combination an /Immortal Type/, 66 resistant to all types? I call such a combination an /Immortal Type/,
66 since if that type's pattern was repeated over and over again towards 67 since if that type's pattern was repeated over and over again towards
67 infinity, the resulting type would be immune to all attack types. 68 infinity, the resulting type would be immune to all attack types.
68 69
69 * Linear Programming 70 * Linear Programming
70 71
71 ** Terminology
72 Linear programming is the process of finding an optimal solution to a 72 Linear programming is the process of finding an optimal solution to a
73 linear equation of several variables which are constrained by some linear 73 linear equation of several variables which are constrained by some linear
74 inequalities. 74 inequalities.
75
76 In linear programming terminology, the function to be extremized is
77 the /objective function/.
78
79 ** COMMENT
80 First, we'll give a small example of a linear optimization problem,
81 and show how it can be solved with Clojure and =lp_solve=. Then,
82 we'll show how finding immortal pok\eacute{}mon types can be converted
83 into a linear problem suitable for solving in the same way.
84 75
85 ** The Farmer's Problem 76 ** The Farmer's Problem
86 77
87 Let's solve the Farmer's Problem, an example linear programming problem 78 Let's solve the Farmer's Problem, an example linear programming problem
88 borrowed from http://lpsolve.sourceforge.net/5.5/formulate.htm. 79 borrowed from http://lpsolve.sourceforge.net/5.5/formulate.htm.
159 150
160 /* he can't use more acres than he owns */ 151 /* he can't use more acres than he owns */
161 +wheat +barley <= 75; 152 +wheat +barley <= 75;
162 #+end_src 153 #+end_src
163 154
164
165 #This is a set of linear equations ideal for solving using a program like
166 #=lp_solve=. In Linear Algebra terms we are maximizing the linear function
167
168 #\(\text{profit} = 143\text{ wheat} + 60\text{ barley}\), subject to the constraints
169
170 #Ax <= b,
171
172 #where A is [120 210 110 30 1 1], x is [wheat barley] and b is [15000
173 #4000 75].
174
175 Running the =lp_solve= program on =farmer.lp= yields the following output. 155 Running the =lp_solve= program on =farmer.lp= yields the following output.
176 156
177 #+begin_src sh :exports both :results scalar 157 #+begin_src sh :exports both :results scalar
178 ~/roBin/lpsolve/lp_solve ~/aurellem/src/pokemon/farmer.lp 158 lp_solve ~/proj/pokemon-types/lp/farmer.lp
179 #+end_src 159 #+end_src
180 160
181 #+results: 161 #+results:
182 : 162 :
183 : Value of objective function: 6315.62500000 163 : Value of objective function: 6315.62500000
188 168
189 This shows that the farmer can maximize his profit by planting 21.875 169 This shows that the farmer can maximize his profit by planting 21.875
190 of the available acres with wheat and the remaining 53.125 acres with 170 of the available acres with wheat and the remaining 53.125 acres with
191 barley; by doing so, he will make $6315.62(5) in profit. 171 barley; by doing so, he will make $6315.62(5) in profit.
192 172
193
194 #The farmer can make a profit of $6315.62 by planting 21.875 acres of
195 #his land with wheat and the other 53.125 acres of his land with barley.
196
197 * Incorporating =lp_solve= into Clojure 173 * Incorporating =lp_solve= into Clojure
198 174
199 There is a Java API available which enables Java programs to use Lp 175 There is a [[http://lpsolve.sourceforge.net/5.5/Java/README.html][Java API]] written by Juergen Ebert which enables Java
200 Solve. Although Clojure can use this Java API directly, the 176 programs to use =lp_solve=. Although Clojure can use this Java API
201 interaction between Java, C, and Clojure is clumsy: 177 directly, the interaction between Java, C, and Clojure is clumsy:
202
203
204 The Java API for LP Solve makes it possible to use Lp Solve algorithms
205 within Java. Although Clojure can use this Java API directly,
206
207 178
208 ** The Farmer's Problem in Clojure 179 ** The Farmer's Problem in Clojure
180
209 We are going to solve the same problem involving wheat and barley, 181 We are going to solve the same problem involving wheat and barley,
210 that we did above, but this time using clojure and the lpsolve API. 182 that we did above, but this time using clojure and the =lp_solve= API.
211
212 #Because we ultimately intend to use Lp Solve to find optimal Pokemon type combinations.
213 # we want to solve linear optimization problems within Clojure, the language
214
215 ** Setup
216 =lp_solve= is a crufty =C= program which already comes with a JNI
217 interface written by Juergen Ebert. It's API is not
218 particularly friendly from a functional/immutable perspective, but
219 with a little work, we can build something that works great with
220 clojure.
221 183
222 #+srcname: intro 184 #+srcname: intro
223 #+begin_src clojure :results silent 185 #+begin_src clojure :results silent
224 (ns pokemon.lpsolve 186 (ns pokemon.lpsolve
225 (:use rlm.ns-rlm)) 187 (:use [clojure.contrib def set [seq :only [indexed]] pprint])
226 (rlm.ns-rlm/ns-clone rlm.light-base) 188 (:import lpsolve.LpSolve)
227 (use 'clojure.set) 189 (:require pokemon.types)
228 (import 'lpsolve.LpSolve) 190 (:require incanter.core))
229 (use '[clojure [pprint :only [pprint]]]) 191 #+end_src
230 #+end_src 192
231 193 The =lp_solve= Java interface is available from the same site as
232 The LpSolve Java interface is available from the same site as 194 =lp_solve= itself, http://lpsolve.sourceforge.net/ Using it is the
233 =lp_solve= itself, http://lpsolve.sourceforge.net/ 195 same as many other =C= programs. There are excellent instructions to
234 Using it is the same as many other =C= 196 get set up. The short version is that you must call Java with
235 programs. There are excellent instructions to get set
236 up. The short version is that you must call Java with
237 =-Djava.library.path=/path/to/lpsolve/libraries= and also add the 197 =-Djava.library.path=/path/to/lpsolve/libraries= and also add the
238 libraries to your export =LD_LIBRARY_PATH= if you are using Linux. For 198 libraries to your export =LD_LIBRARY_PATH= if you are using Linux. For
239 example, in my =.bashrc= file, I have the line 199 example, in my =.bashrc= file, I have the line
240 =LD_LIBRARY_PATH=$HOME/roBin/lpsolve:$LD_LIBRARY_PATH=. 200 =LD_LIBRARY_PATH=$HOME/roBin/lpsolve:$LD_LIBRARY_PATH=. If everything
241 If everything is set-up correctly, 201 is set-up correctly,
242 202
243 #+srcname: body 203 #+srcname: body
244 #+begin_src clojure :results verbatim :exports both 204 #+begin_src clojure :results verbatim :exports both
245 (import 'lpsolve.LpSolve) 205 (import 'lpsolve.LpSolve)
246 #+end_src 206 #+end_src
285 245
286 ;; immutable output from lp-solve 246 ;; immutable output from lp-solve
287 (declare solve get-results) 247 (declare solve get-results)
288 #+end_src 248 #+end_src
289 249
250
290 *** Memory Management 251 *** Memory Management
291 252
292 Every instance of =LpSolve= must be manually garbage collected via a 253 Every instance of =LpSolve= must be manually garbage collected via a
293 call to =deleteLP=. I use a non-hygienic macro similar to =with-open= 254 call to =deleteLP=. I use a non-hygienic macro similar to =with-open=
294 to ensure that =deleteLP= is always called. 255 to ensure that =deleteLP= is always called.
337 (let [target (java.io.File/createTempFile "lps" ".lp")] 298 (let [target (java.io.File/createTempFile "lps" ".lp")]
338 (.writeLp lps (.getPath target)) 299 (.writeLp lps (.getPath target))
339 (slurp target))) 300 (slurp target)))
340 301
341 (defn results 302 (defn results
342 "given an LpSolve object, solves the object and returns a map of the 303 "Given an LpSolve object, solves the object and returns a map of the
343 essential values which compose the solution." 304 essential values which compose the solution."
344 [#^LpSolve lps] 305 [#^LpSolve lps]
345 (locking lps 306 (locking lps
346 (let [status (solve lps) 307 (let [status (solve lps)
347 number-of-variables (.getNcolumns lps) 308 number-of-variables (.getNcolumns lps)
348 optimal-values (double-array number-of-variables) 309 optimal-values (double-array number-of-variables)
349 optimal-values (do (.getVariables lps optimal-values) 310 optimal-values (do (.getVariables lps optimal-values)
350 (seq optimal-values)) 311 (seq optimal-values))
351 variable-names 312 variable-names
352 (doall ;; the doall is necessary since the lps object might 313 (doall
353 ;; soon be deleted 314 ;; The doall is necessary since the lps object might
315 ;; soon be deleted.
354 (map 316 (map
355 #(.getColName lps (inc %)) 317 #(.getColName lps (inc %))
356 (range number-of-variables))) 318 (range number-of-variables)))
357 model (model lps)] 319 model (model lps)]
358 (LpSolution. 320 (LpSolution.
554 516
555 Notice that both the inputs to =better-farmer-example= and the results 517 Notice that both the inputs to =better-farmer-example= and the results
556 are immutable. 518 are immutable.
557 519
558 * Using LpSolve to find Immortal Types 520 * Using LpSolve to find Immortal Types
559 ** Converting the Pokemon problem into a linear form 521 ** Converting the Pok\eacute{}mon problem into a linear form
560 How can the original question about pok\eacute{}mon types be converted 522 How can the original question about pok\eacute{}mon types be converted
561 into a linear problem? 523 into a linear problem?
562 524
563 Pokemon types can be considered to be vectors of numbers representing 525 Pokemon types can be considered to be vectors of numbers representing
564 their susceptances to various attacking types, so Water might look 526 their susceptances to various attacking types, so Water might look
565 something like this. 527 something like this.
566 528
619 581
620 #+srcname: pokemon-lp 582 #+srcname: pokemon-lp
621 #+begin_src clojure :results silent 583 #+begin_src clojure :results silent
622 (in-ns 'pokemon.lpsolve) 584 (in-ns 'pokemon.lpsolve)
623 585
624 (require 'pokemon.types)
625 (require 'incanter.core)
626
627 (defn log-clamp-matrix [matrix] 586 (defn log-clamp-matrix [matrix]
628 ;; we have to clamp the Infinities to a more reasonable negative 587 ;; we have to clamp the Infinities to a more reasonable negative
629 ;; value because lp_solve does not play well with infinities in its 588 ;; value because lp_solve does not play well with infinities in its
630 ;; constraint matrix. 589 ;; constraint matrix.
631 (map (fn [row] (map #(if (= Double/NEGATIVE_INFINITY %) -1e3 %) row)) 590 (map (fn [row] (map #(if (= Double/NEGATIVE_INFINITY %) -1e3 %) row))
649 (defn all-weak [] 608 (defn all-weak []
650 (doall (map (constantly 1) (pokemon.types/type-names)))) 609 (doall (map (constantly 1) (pokemon.types/type-names))))
651 610
652 (defn all-neutral [] 611 (defn all-neutral []
653 (doall (map (constantly 0) (pokemon.types/type-names)))) 612 (doall (map (constantly 0) (pokemon.types/type-names))))
654
655 613
656 ;; objective functions 614 ;; objective functions
657 (defn number-of-types [] 615 (defn number-of-types []
658 (doall (map (constantly 1) (pokemon.types/type-names)))) 616 (doall (map (constantly 1) (pokemon.types/type-names))))
659 617
708 results. Otherwise, returns the error." 666 results. Otherwise, returns the error."
709 [results] 667 [results]
710 (if (not (= (:status results) "OPTIMAL")) 668 (if (not (= (:status results) "OPTIMAL"))
711 (:status results) 669 (:status results)
712 (:solution results))) 670 (:solution results)))
713
714 #+end_src 671 #+end_src
715 672
716 With this, we are finally able to get some results. 673 With this, we are finally able to get some results.
717 674
718 ** Results 675 ** Results
952 ":grass" 0.0, 909 ":grass" 0.0,
953 ":water" 0.0, 910 ":water" 0.0,
954 ":rock" 0.0} 911 ":rock" 0.0}
955 #+end_example 912 #+end_example
956 913
957 The best attacking type combination is strangely Dragon from the 914 The best attacking type combination is Dragon from the original games.
958 original games. It is neutral against all the original types except 915 It is neutral against all the original types except for Dragon, which
959 for Dragon, which it is strong against. There is no way to make an 916 it is strong against. There is no way to make an attacking type that
960 attacking type that is strong against every type, or even one that is 917 is strong against every type, or even one that is strong or neutral
961 strong or neutral against every type, in the new games. 918 against every type, in the new games.
962 919
963 920
964 *** Weakest Attack/Defense Combinations 921 *** Weakest Attack/Defense Combinations
965 922
966 #+begin_src clojure :results output :exports both 923 #+begin_src clojure :results output :exports both
1036 types. This would probably have been impossible to discover using 993 types. This would probably have been impossible to discover using
1037 best-first search, since it involves such an intricate type 994 best-first search, since it involves such an intricate type
1038 combination. 995 combination.
1039 996
1040 It's so interesting that it takes 20 types to make an attack type that 997 It's so interesting that it takes 20 types to make an attack type that
1041 is weak to all types that the combination merits further investigation. 998 is weak to all types that the combination merits further
999 investigation.
1042 1000
1043 Unfortunately, all of the tools that we've written so far are focused 1001 Unfortunately, all of the tools that we've written so far are focused
1044 on defense type combinations. However, it is possible to make every 1002 on defense type combinations. However, it is possible to make every
1045 tool attack-oriented via a simple macro. 1003 tool attack-oriented via a simple macro.
1046 1004
1343 immune to some types, such as Flying, which is immune to Ground. A 1301 immune to some types, such as Flying, which is immune to Ground. A
1344 perfectly weak type could not use any of these types. 1302 perfectly weak type could not use any of these types.
1345 1303
1346 * Summary 1304 * Summary
1347 1305
1348 Overall, the pok\eacute{}mon type system is slanted more towards defense 1306 Overall, the pok\eacute{}mon type system is slanted more towards
1349 rather than offense. While it is possible to create superior 1307 defense rather than offense. While it is possible to create superior
1350 defensive types and exceptionally weak attack types, it is not possible to 1308 defensive types and exceptionally weak attack types, it is not
1351 create exceptionally weak defensive types or very powerful attack 1309 possible to create exceptionally weak defensive types or very powerful
1352 types. 1310 attack types.
1353 1311
1354 Using the =lp_solve= library was more complicated than the best-first 1312 Using the =lp_solve= library was more complicated than the best-first
1355 search, but yielded results quickly and efficiently. Expressing the 1313 search, but yielded results quickly and efficiently. Expressing the
1356 problem in a linear form does have its drawbacks, however --- it's 1314 problem in a linear form does have its drawbacks, however --- it's
1357 hard to ask questions such as "what is the best 3-type defensive combo 1315 hard to ask questions such as "what is the best 3-type defensive combo
1376 <<results>> 1334 <<results>>
1377 <<attack-oriented>> 1335 <<attack-oriented>>
1378 #+end_src 1336 #+end_src
1379 1337
1380 1338
1381 * COMMENT Stuff to do. 1339
1382 ** TODO fix namespaces to not use rlm.light-base
1383