view org/lpsolve.org @ 12:89462678a932

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