Mercurial > pokemon-types
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 |