KEMBAR78
Genetic Programming in Python | ODP
Genetic Algorithms and Genetic Programming in Python
 
Karl Sims
What are Genetic Algorithms and Genetic Programs?
Search algorithms based on the mechanics of natural selection and natural genetics
 
 
John Holland, University of Michigan
The Circle of Life
Find a better path
Minimize connection length
Best set of indexes
Artificial Intelligence
Locomotion
Art
They seem to do well when... There is a huge solution space
The solution space is dynamic, non-linear, or otherwise complex
The solution space is poorly understood
Domain knowledge is scarce or difficult to encode
No mathematical analysis is available
Fitness, payoff, or suitability of a solution can be determined
Traditional search methods fail
What are the “mechanics” of natural selection and natural genetics?
The Circle of Life
Genotype and Phenotype “ blueprint”, chromosomes “ Physical manifestation”
Pyevolve Built-In Genotypes One-Dimensional Binary String
Two-Dimensional Binary String
One-Dimensional List
Two-Dimensional List
Tree
Genetic Program (Tree specific to GP)
You can roll your own!
1-D Binary String from pyevolve import G1DBinaryString genome = G1DBinaryString.G1DBinaryString(16)
2-D Binary String from pyevolve import G2DBinaryString genome = G1DBinaryString.G2DBinaryString(12, 18)
1-D and 2-D Lists from pyevolve import G1DList, G2DList genome = G1DList.G1DList(140) genome = G2DList.G2DList(8, 5) “ Alleles” are the object types the list is made of... by default integers You can pass in a function to construct the list with whatever object you want genome.setParams(rangeMin=-5.12, rangemax=5.13) genome.initializator.set(Initializators.G1DListInitializatorReal)
Tree and Genetic Program from pyevolve import GTree genome = GTree.GTree() gp_genome = GTree.GTreeGP()
Tree and Genetic Program from pyevolve import GTree genome = GTree.GTree() gp_genome = GTree.GTreeGP()
Now that we have our genome, how is it expressed, and how do we know how “good” it is?
The Circle of Life
Fitness
Find the global minima of Schaffer F6
The genotype is the (x,y) point, eg. (3.2,-4.7)
Phenotype is the value that results from putting in the (x,y) point into the function
Fitness is how close that resultant value is to the global minimum value (in this case, zero)
The Fitness Function def schafferF6(genome): x2y2 = genome[0]**2 + genome[1]**2 t1 = math.sin(math.sqrt(x2y2)); t2 = 1.0 + 0.001*(x2y2); score = 0.5 + (t1**2 - 0.5)/(t2*t2) return score genome = G1DList.G1DList(2) genome.setParams(rangemin=-100.0, rangemax=100.0,  bestrawscore=0.0000, rounddecimal=4) genome.initializator.set(Initializators.G1DListInitializatorReal) genome.evaluator.set(schafferF6)
The Petri Dish
The GA Engine from pyevolve import GSimpleGA …  create genome … ga = GSimpleGA.GSimpleGA(genome, seed=123) ga.setMinimax(Consts.minimaxType["minimize"]) ga.evolve(freq_stats=1000) print ga.bestIndividual() Output: Gen. 0 (0.00%): Max/Min/Avg Fitness(Raw) [0.60(0.82)/0.37(0.09)/0.50(0.50)] Gen. 1000 (12.50%): Max/Min/Avg Fitness(Raw) [0.30(0.97)/0.23(0.01)/0.25(0.25)] Gen. 2000 (25.00%): Max/Min/Avg Fitness(Raw) [0.21(0.99)/0.17(0.01)/0.18(0.18)] Gen. 3000 (37.50%): Max/Min/Avg Fitness(Raw) [0.26(0.99)/0.21(0.00)/0.22(0.22)] Evolution stopped by Termination Criteria function ! Gen. 3203 (40.04%): Max/Min/Avg Fitness(Raw) [0.30(0.99)/0.23(0.00)/0.25(0.25)] Total time elapsed: 14.357 seconds. - GenomeBase Score:  0.000005 Fitness:  0.232880 - G1DList List size:  2 List:  [0.0020881039453384299, 0.00043589670629584631]
The Circle of Life
Selection
Pyevolve Built-In Selection Operators Rank Selection Choose the best (default) Uniform Selection Choose at random Tournament Selection Choose best from a random subset of population Roulette Wheel Selection More fit more likely to be chosen
Setting the selector from pyevolve import Selectors ga = GSimpleGA.GSimpleGA(genome) ga.selector.set(Selectors.GRouletteWheel) ga.selector is a “FunctionSlot.”  It can accept any number of functions that will be used in order.  ga.evaluator is another.
The Circle of Life
Crossover
Take two (or more) individuals and combine them in some way to create children
Single Point Crossover
Single Point Crossover
Pyevolve Built-In Crossover Operators 1D Binary String Single Point Crossover, Two Point Crossover, Uniform Crossover 1D List Single Point Crossover, Two Point Crossover, Uniform Crossover, OX Crossover, Edge Recombination Crossover, Cut and Crossfill Crossover, Real SBX Crossover 2D List Uniform Crossover, Single Vertical Point Crossover, Single Horizontal Point Crossover 2D Binary String Uniform Crossover, Single Vertical Point Crossover, Single Horizontal Point Crossover Tree Single Point Crossover, Strict Single Point Crossover GP Tree Single Point Crossover
Mutation
Binary Mutation
Tree Mutation
Pyevolve Built-In Mutation Operators 1D Binary String Swap Mutator, Flip Mutator 2D Binary String Swap Mutator, Flip Mutator 1D List Swap Mutator, Integer Range Mutator, Real Range Mutator, Integer Gaussian Mutator, Real Gaussian Mutator, Integer Binary Mutator, Allele Mutator, Simple Inversion Mutator 2D List Swap Mutator, Integer Gaussian Mutator, Real Gaussian Mutator, Allele Mutator, Integer Range Mutator Tree Swap Mutator, Integer Range Mutator, Real Range Mutator, Integer Gaussian Mutator, Real Gaussian Mutator GP Tree Operation Mutator, Subtree mutator
Genetic Programs are basically just a type of Genetic Algorithm
John Koza, University of Michigan, Stanford
Plug for Python and Entrepreneurship Karl Sims A founder of GenArts
Visual Effects plug-ins for film and video John Koza Cofounder of Scientific Games Corp.
Scientific Games now a near $1B company
65% of all scatch-off tickets created
A genetic program is just a tree with nodes
 
 
Simple Operator Node Examples Addition, Subtraction, Multiplication, Division Two inputs Cosine, Sine, Absolute Value One input Max, Min, Average Two inputs (or more) If Greater Than, If Less Than Four inputs: if A is greater than B then C else D
Terminal Node Examples Ephemeral Constants Generated at runtime Specified Constants Defined by you Variables Value determined when evaluated
Putting it all together: Creating a better unskilled forecast
Defining The Problem Columbus Next-Day High Temperatures From February 2005 to June 2010 Average Absolute Error Today's High: 5.74 degrees
Climate Average: 7.67 degrees
Yesterday's High: 8.01 degrees
Last Year's High: 10.87 degrees Can we beat that?
Create The Genome genome = GTree.GTreeGP() genome.setParams(max_depth=4, method="ramped") genome.evaluator.set(eval_func)
Create The Genome genome = GTree.GTreeGP() genome.setParams(max_depth=4, method="ramped") genome.evaluator.set(eval_func)
Create The Genome genome = GTree.GTreeGP() genome.setParams(max_depth=4, method="ramped") genome.evaluator.set(eval_func)
Initialize The Engine ga = GSimpleGA.GSimpleGA(genome) ga.setParams(gp_terminals = ['per', 'perlast', 'clm'], gp_function_prefix = "gp") ga.setMinimax(Consts.minimaxType["minimize"]) ga.setGenerations(50) ga.setCrossoverRate(1.0) ga.setMutationRate(0.25) ga.setPopulationSize(800)
Initialize The Engine ga = GSimpleGA.GSimpleGA(genome) ga.setParams(gp_terminals = ['per', 'perlast', 'clm'], gp_function_prefix = "gp") ga.setMinimax(Consts.minimaxType["minimize"]) ga.setGenerations(50) ga.setCrossoverRate(1.0) ga.setMutationRate(0.25) ga.setPopulationSize(800)
Define The Fitness Function def eval_func(chromosome): code_comp = chromosome.getCompiledCode() error = 0.0 count = 0.0 for day,per,peryest,perlast,clm,actual in data: forecast = eval(code_comp) error += abs(forecast-actual) count += 1 return error/count
Define The Fitness Function def eval_func(chromosome): code_comp = chromosome.getCompiledCode() error = 0.0 count = 0.0 for day,per,peryest,perlast,clm,actual in data: forecast = eval(code_comp) error += abs(forecast-actual) count += 1 return error/count
Define The Operators @GTree.gpdec(representation="+", color="red") def gp_add(a, b): return a+b @GTree.gpdec(representation="-", color="red") def gp_sub(a, b): return a-b @GTree.gpdec(representation="avg", color="green") def gp_avg(a, b): return (a+b)/2.0 @GTree.gpdec(representation="if_gt", color="blue") def gp_gt(a, b, c, d): if a>b: return c else: return d
Define The Operators @GTree.gpdec(representation="+", color="red") def gp_add(a, b): return a+b @GTree.gpdec(representation="-", color="red") def gp_sub(a, b): return a-b @GTree.gpdec(representation="avg", color="green") def gp_avg(a, b): return (a+b)/2.0 @GTree.gpdec(representation="if_gt", color="blue") def gp_gt(a, b, c, d): if a>b: return c else: return d
Run ga = GSimpleGA.GSimpleGA(genome) ga.setParams(gp_terminals = ['per','perlast','clm'], gp_function_prefix = "gp") ga.setMinimax(Consts.minimaxType["minimize"]) ga.setGenerations(50) ga.setCrossoverRate(1.0) ga.setMutationRate(0.25) ga.setPopulationSize(800) ga.evolve(freq_stats=10) best = ga.bestIndividual() print best
The Shiny Stuff
Interactive Mode ga.setInteractiveGeneration(50) >>> it.plotHistPopScore(population) >>> it.plotPopScore(population) >>> popScores = it.getPopScores(population)
GP Tree Graphs Gtree.GTreeGP.writePopulationDot(gp, filename, "png", 0, 1) best = ga.bestIndividual() best.writeDotImage("best.png")
GP Tree Graphs Gtree.GTreeGP.writePopulationDot(gp, filename, "png", 0, 1) best = ga.bestIndividual() best.writeDotImage("best.png")
Callbacks def step_callback(gp_engine): gen = gp_engine.getCurrentGeneration() if gen % 10 == 0: filename = "best_{gen}.png".format(gen=gen)  GTree.GTreeGP.writePopulationDot(gp_engine,  filename, "png", 0, 1) ga.stepCallback.set(step_callback)
Callbacks def step_callback(gp_engine): gen = gp_engine.getCurrentGeneration() if gen % 10 == 0: filename = "best_{gen}.png".format(gen=gen)  GTree.GTreeGP.writePopulationDot(gp_engine,  filename, "png", 0, 1) ga.stepCallback.set(step_callback)
Database Adapters csv_adapter = DBAdapters.DBFileCSV(identify="run1",  filename="stats.csv") ga.setDBAdapter(csv_adapter) ga.evolve(freq_stats=10) print ga.getStatistics() - Statistics Minimum raw score  = 7.35 Fitness average  = 16.43 Minimum fitness  = 16.32 Raw scores variance  = 1352.22 Standard deviation of raw scores  = 36.77 Average of raw scores  = 16.43 Maximum fitness  = 19.72 Maximum raw score  = 295.57
Real-Time Plots adapter = DBAdapters.DBVPythonGraph(identify="run_01",  frequency = 1) ga.setDBAdapter(adapter)

Genetic Programming in Python

  • 1.
    Genetic Algorithms andGenetic Programming in Python
  • 2.
  • 3.
  • 4.
    What are GeneticAlgorithms and Genetic Programs?
  • 5.
    Search algorithms basedon the mechanics of natural selection and natural genetics
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
    Best set ofindexes
  • 13.
  • 14.
  • 15.
  • 16.
    They seem todo well when... There is a huge solution space
  • 17.
    The solution spaceis dynamic, non-linear, or otherwise complex
  • 18.
    The solution spaceis poorly understood
  • 19.
    Domain knowledge isscarce or difficult to encode
  • 20.
  • 21.
    Fitness, payoff, orsuitability of a solution can be determined
  • 22.
  • 23.
    What are the“mechanics” of natural selection and natural genetics?
  • 24.
  • 25.
    Genotype and Phenotype“ blueprint”, chromosomes “ Physical manifestation”
  • 26.
    Pyevolve Built-In GenotypesOne-Dimensional Binary String
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
    Genetic Program (Treespecific to GP)
  • 32.
    You can rollyour own!
  • 33.
    1-D Binary Stringfrom pyevolve import G1DBinaryString genome = G1DBinaryString.G1DBinaryString(16)
  • 34.
    2-D Binary Stringfrom pyevolve import G2DBinaryString genome = G1DBinaryString.G2DBinaryString(12, 18)
  • 35.
    1-D and 2-DLists from pyevolve import G1DList, G2DList genome = G1DList.G1DList(140) genome = G2DList.G2DList(8, 5) “ Alleles” are the object types the list is made of... by default integers You can pass in a function to construct the list with whatever object you want genome.setParams(rangeMin=-5.12, rangemax=5.13) genome.initializator.set(Initializators.G1DListInitializatorReal)
  • 36.
    Tree and GeneticProgram from pyevolve import GTree genome = GTree.GTree() gp_genome = GTree.GTreeGP()
  • 37.
    Tree and GeneticProgram from pyevolve import GTree genome = GTree.GTree() gp_genome = GTree.GTreeGP()
  • 38.
    Now that wehave our genome, how is it expressed, and how do we know how “good” it is?
  • 39.
  • 40.
  • 41.
    Find the globalminima of Schaffer F6
  • 42.
    The genotype isthe (x,y) point, eg. (3.2,-4.7)
  • 43.
    Phenotype is thevalue that results from putting in the (x,y) point into the function
  • 44.
    Fitness is howclose that resultant value is to the global minimum value (in this case, zero)
  • 45.
    The Fitness Functiondef schafferF6(genome): x2y2 = genome[0]**2 + genome[1]**2 t1 = math.sin(math.sqrt(x2y2)); t2 = 1.0 + 0.001*(x2y2); score = 0.5 + (t1**2 - 0.5)/(t2*t2) return score genome = G1DList.G1DList(2) genome.setParams(rangemin=-100.0, rangemax=100.0, bestrawscore=0.0000, rounddecimal=4) genome.initializator.set(Initializators.G1DListInitializatorReal) genome.evaluator.set(schafferF6)
  • 46.
  • 47.
    The GA Enginefrom pyevolve import GSimpleGA … create genome … ga = GSimpleGA.GSimpleGA(genome, seed=123) ga.setMinimax(Consts.minimaxType["minimize"]) ga.evolve(freq_stats=1000) print ga.bestIndividual() Output: Gen. 0 (0.00%): Max/Min/Avg Fitness(Raw) [0.60(0.82)/0.37(0.09)/0.50(0.50)] Gen. 1000 (12.50%): Max/Min/Avg Fitness(Raw) [0.30(0.97)/0.23(0.01)/0.25(0.25)] Gen. 2000 (25.00%): Max/Min/Avg Fitness(Raw) [0.21(0.99)/0.17(0.01)/0.18(0.18)] Gen. 3000 (37.50%): Max/Min/Avg Fitness(Raw) [0.26(0.99)/0.21(0.00)/0.22(0.22)] Evolution stopped by Termination Criteria function ! Gen. 3203 (40.04%): Max/Min/Avg Fitness(Raw) [0.30(0.99)/0.23(0.00)/0.25(0.25)] Total time elapsed: 14.357 seconds. - GenomeBase Score: 0.000005 Fitness: 0.232880 - G1DList List size: 2 List: [0.0020881039453384299, 0.00043589670629584631]
  • 48.
  • 49.
  • 50.
    Pyevolve Built-In SelectionOperators Rank Selection Choose the best (default) Uniform Selection Choose at random Tournament Selection Choose best from a random subset of population Roulette Wheel Selection More fit more likely to be chosen
  • 51.
    Setting the selectorfrom pyevolve import Selectors ga = GSimpleGA.GSimpleGA(genome) ga.selector.set(Selectors.GRouletteWheel) ga.selector is a “FunctionSlot.” It can accept any number of functions that will be used in order. ga.evaluator is another.
  • 52.
  • 53.
  • 54.
    Take two (ormore) individuals and combine them in some way to create children
  • 55.
  • 56.
  • 57.
    Pyevolve Built-In CrossoverOperators 1D Binary String Single Point Crossover, Two Point Crossover, Uniform Crossover 1D List Single Point Crossover, Two Point Crossover, Uniform Crossover, OX Crossover, Edge Recombination Crossover, Cut and Crossfill Crossover, Real SBX Crossover 2D List Uniform Crossover, Single Vertical Point Crossover, Single Horizontal Point Crossover 2D Binary String Uniform Crossover, Single Vertical Point Crossover, Single Horizontal Point Crossover Tree Single Point Crossover, Strict Single Point Crossover GP Tree Single Point Crossover
  • 58.
  • 59.
  • 60.
  • 61.
    Pyevolve Built-In MutationOperators 1D Binary String Swap Mutator, Flip Mutator 2D Binary String Swap Mutator, Flip Mutator 1D List Swap Mutator, Integer Range Mutator, Real Range Mutator, Integer Gaussian Mutator, Real Gaussian Mutator, Integer Binary Mutator, Allele Mutator, Simple Inversion Mutator 2D List Swap Mutator, Integer Gaussian Mutator, Real Gaussian Mutator, Allele Mutator, Integer Range Mutator Tree Swap Mutator, Integer Range Mutator, Real Range Mutator, Integer Gaussian Mutator, Real Gaussian Mutator GP Tree Operation Mutator, Subtree mutator
  • 62.
    Genetic Programs arebasically just a type of Genetic Algorithm
  • 63.
    John Koza, Universityof Michigan, Stanford
  • 64.
    Plug for Pythonand Entrepreneurship Karl Sims A founder of GenArts
  • 65.
    Visual Effects plug-insfor film and video John Koza Cofounder of Scientific Games Corp.
  • 66.
    Scientific Games nowa near $1B company
  • 67.
    65% of allscatch-off tickets created
  • 68.
    A genetic programis just a tree with nodes
  • 69.
  • 70.
  • 71.
    Simple Operator NodeExamples Addition, Subtraction, Multiplication, Division Two inputs Cosine, Sine, Absolute Value One input Max, Min, Average Two inputs (or more) If Greater Than, If Less Than Four inputs: if A is greater than B then C else D
  • 72.
    Terminal Node ExamplesEphemeral Constants Generated at runtime Specified Constants Defined by you Variables Value determined when evaluated
  • 73.
    Putting it alltogether: Creating a better unskilled forecast
  • 74.
    Defining The ProblemColumbus Next-Day High Temperatures From February 2005 to June 2010 Average Absolute Error Today's High: 5.74 degrees
  • 75.
  • 76.
  • 77.
    Last Year's High:10.87 degrees Can we beat that?
  • 78.
    Create The Genomegenome = GTree.GTreeGP() genome.setParams(max_depth=4, method="ramped") genome.evaluator.set(eval_func)
  • 79.
    Create The Genomegenome = GTree.GTreeGP() genome.setParams(max_depth=4, method="ramped") genome.evaluator.set(eval_func)
  • 80.
    Create The Genomegenome = GTree.GTreeGP() genome.setParams(max_depth=4, method="ramped") genome.evaluator.set(eval_func)
  • 81.
    Initialize The Enginega = GSimpleGA.GSimpleGA(genome) ga.setParams(gp_terminals = ['per', 'perlast', 'clm'], gp_function_prefix = "gp") ga.setMinimax(Consts.minimaxType["minimize"]) ga.setGenerations(50) ga.setCrossoverRate(1.0) ga.setMutationRate(0.25) ga.setPopulationSize(800)
  • 82.
    Initialize The Enginega = GSimpleGA.GSimpleGA(genome) ga.setParams(gp_terminals = ['per', 'perlast', 'clm'], gp_function_prefix = "gp") ga.setMinimax(Consts.minimaxType["minimize"]) ga.setGenerations(50) ga.setCrossoverRate(1.0) ga.setMutationRate(0.25) ga.setPopulationSize(800)
  • 83.
    Define The FitnessFunction def eval_func(chromosome): code_comp = chromosome.getCompiledCode() error = 0.0 count = 0.0 for day,per,peryest,perlast,clm,actual in data: forecast = eval(code_comp) error += abs(forecast-actual) count += 1 return error/count
  • 84.
    Define The FitnessFunction def eval_func(chromosome): code_comp = chromosome.getCompiledCode() error = 0.0 count = 0.0 for day,per,peryest,perlast,clm,actual in data: forecast = eval(code_comp) error += abs(forecast-actual) count += 1 return error/count
  • 85.
    Define The Operators@GTree.gpdec(representation="+", color="red") def gp_add(a, b): return a+b @GTree.gpdec(representation="-", color="red") def gp_sub(a, b): return a-b @GTree.gpdec(representation="avg", color="green") def gp_avg(a, b): return (a+b)/2.0 @GTree.gpdec(representation="if_gt", color="blue") def gp_gt(a, b, c, d): if a>b: return c else: return d
  • 86.
    Define The Operators@GTree.gpdec(representation="+", color="red") def gp_add(a, b): return a+b @GTree.gpdec(representation="-", color="red") def gp_sub(a, b): return a-b @GTree.gpdec(representation="avg", color="green") def gp_avg(a, b): return (a+b)/2.0 @GTree.gpdec(representation="if_gt", color="blue") def gp_gt(a, b, c, d): if a>b: return c else: return d
  • 87.
    Run ga =GSimpleGA.GSimpleGA(genome) ga.setParams(gp_terminals = ['per','perlast','clm'], gp_function_prefix = "gp") ga.setMinimax(Consts.minimaxType["minimize"]) ga.setGenerations(50) ga.setCrossoverRate(1.0) ga.setMutationRate(0.25) ga.setPopulationSize(800) ga.evolve(freq_stats=10) best = ga.bestIndividual() print best
  • 88.
  • 89.
    Interactive Mode ga.setInteractiveGeneration(50)>>> it.plotHistPopScore(population) >>> it.plotPopScore(population) >>> popScores = it.getPopScores(population)
  • 90.
    GP Tree GraphsGtree.GTreeGP.writePopulationDot(gp, filename, "png", 0, 1) best = ga.bestIndividual() best.writeDotImage("best.png")
  • 91.
    GP Tree GraphsGtree.GTreeGP.writePopulationDot(gp, filename, "png", 0, 1) best = ga.bestIndividual() best.writeDotImage("best.png")
  • 92.
    Callbacks def step_callback(gp_engine):gen = gp_engine.getCurrentGeneration() if gen % 10 == 0: filename = "best_{gen}.png".format(gen=gen) GTree.GTreeGP.writePopulationDot(gp_engine, filename, "png", 0, 1) ga.stepCallback.set(step_callback)
  • 93.
    Callbacks def step_callback(gp_engine):gen = gp_engine.getCurrentGeneration() if gen % 10 == 0: filename = "best_{gen}.png".format(gen=gen) GTree.GTreeGP.writePopulationDot(gp_engine, filename, "png", 0, 1) ga.stepCallback.set(step_callback)
  • 94.
    Database Adapters csv_adapter= DBAdapters.DBFileCSV(identify="run1", filename="stats.csv") ga.setDBAdapter(csv_adapter) ga.evolve(freq_stats=10) print ga.getStatistics() - Statistics Minimum raw score = 7.35 Fitness average = 16.43 Minimum fitness = 16.32 Raw scores variance = 1352.22 Standard deviation of raw scores = 36.77 Average of raw scores = 16.43 Maximum fitness = 19.72 Maximum raw score = 295.57
  • 95.
    Real-Time Plots adapter= DBAdapters.DBVPythonGraph(identify="run_01", frequency = 1) ga.setDBAdapter(adapter)
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
    Comparison Average absoluteerror: Our GP: 5.38 degrees
  • 104.
  • 105.
  • 106.
  • 107.
    Last Year's High:10.87 degrees Improved on persistence forecast (today's high will be tomorrow's high) by 0.36 degrees.
  • 108.
    Average skilled forecastis about 3 degrees error
  • 109.
  • 110.
    matplotlib, vpython (visual),pydot Introduction to Genetic Algorithms http://www.obitko.com/tutorials/genetic-algorithms/index.php Field Guide to Genetic Programming http://www.gp-field-guide.org.uk/
  • 111.
  • 112.
    = There isa supercomputer under your desk
  • 113.
    There is asupercomputer under your desk
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.

Editor's Notes

  • #2 Intellovations – internet applications for data analysis and display ForecastWatch and ForecastAdvisor Background – C/C++/Java Amateur scientist Wanted to answer the question “is there any difference between weather forecasts from different providers”
  • #96 Intellovations – internet applications for data analysis and display ForecastWatch and ForecastAdvisor Background – C/C++/Java Amateur scientist Wanted to answer the question “is there any difference between weather forecasts from different providers”