├── .gitignore ├── README.md ├── bitcoin └── dumbcoin │ └── dumbcoin.ipynb ├── keras ├── convmnist │ ├── keras_cnn_mnist.ipynb │ ├── keras_cnn_mnist_v1.ipynb │ ├── keras_conv_autoencoder_mnist.ipynb │ ├── keras_conv_autoencoder_mnist.old_api.ipynb │ └── keras_conv_autoencoder_mnist.tensorflow.ipynb ├── learning_cosine.ipynb └── visutils_model2dot.ipynb ├── misc_math ├── fft.ipynb └── homography_2d.ipynb ├── misc_ml ├── curse-of-dimensionality_1.png ├── curse-of-dimensionality_2.png └── curse_dimensionality.ipynb ├── perceptron ├── perceptron.html ├── perceptron.ipynb ├── perceptron_theano.html └── perceptron_theano.ipynb └── strava ├── analytics.ipynb └── strava_get_activities.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | /perceptron/.ipynb_checkpoints/perceptron_theano-checkpoint.ipynb 2 | /.ipynb_checkpoints/perceptron_theano-checkpoint.ipynb 3 | .DS_Store 4 | strava/activities.json/keras/convmnist/.ipynb_checkpoints/keras_cnn_mnist-checkpoint.ipynb 5 | /keras/convmnist/.ipynb_checkpoints/keras_convda_mnist_repr_10-checkpoint.ipynb 6 | /keras/convmnist/.ipynb_checkpoints/keras_cnn_mnist-checkpoint.ipynb 7 | /keras/convmnist/.ipynb_checkpoints/Untitled-checkpoint.ipynb 8 | /keras/convmnist/mnist_cnn_weights.hdf 9 | /.ipynb_checkpoints/curse_dimensionality-checkpoint.ipynb 10 | /misc_ml/.ipynb_checkpoints/curse_dimensionality-checkpoint.ipynb 11 | .ipynb_checkpoints/ 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | My ipython notebook playground 2 | ============================== 3 | 4 | Best viewed on [nbviewer](http://nbviewer.ipython.org/github/julienr/ipynb_playground/). 5 | 6 | Contents 7 | -------- 8 | 9 | - dumbcoin 10 | - [bitcoin/dumbcoin/dumbcoin.ipynb](bitcoin/dumbcoin/dumbcoin.ipynb) : A bitcoin-like educational blockchain implementation. 11 | 12 | - perceptron 13 | - [perceptron/perceptron.ipynb](perceptron/perceptron.ipynb) : A single unit perceptron implementation in python, with a nice video of the training if you run the notebook locally. 14 | - [perceptron/perceptron_theano.ipynb](perceptron/perceptron_theano.ipynb) : Perceptron but with Theano this time 15 | 16 | - keras (deep learning) 17 | - [keras/convmnist/keras_cnn_mnist.ipynb](keras/convmnist/keras_cnn_mnist.ipynb) : Keras' CNN example on MNIST with weights and convolutions visualization 18 | - [keras/convmnist/keras_conv_autoencoder_mnist.ipynb](keras/convmnist/keras_conv_autoencoder_mnist.ipynb) : Convolutional autoencoder on MNIST 19 | - [keras/learning_cosine.ipynb](keras/learning_cosine.ipynb): Learning a cosine with a simple NN using keras 20 | 21 | - strava API (GPS sport tracking service) 22 | - [strava/strava_get_activities.ipynb](strava/strava_get_activities.ipynb) : Getting your activities from strava 23 | - [strava/analytics.ipynb](strava/analytics.ipynb) : Loading them and doing some basics analytics 24 | 25 | - misc_ml (Some notes about machine learning) 26 | - [misc_ml/curse_dimensionality.ipynb](misc_ml/curse_dimensionality.ipynb) : Plots about the curse of dimensionality 27 | -------------------------------------------------------------------------------- /bitcoin/dumbcoin/dumbcoin.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Dumbcoin - An educational python implementation of a bitcoin-like blockchain" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Introduction " 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "This is an experimental notebook that implements most of the concepts of a bitcoin-like blockchain in python.\n", 22 | "It is **NOT secure** neither a real blockchain and you should **NOT** use this for anything else than educational purposes. \n", 23 | "\n", 24 | "Regardless of the financial benefits/drawbacks of bitcoin, the underlying technology is very interesting and the original paper is relatively easy to grasp. \n", 25 | "\n", 26 | "You might want to have a look at the [hacker news](https://news.ycombinator.com/item?id=15945490) discussion about this notebook." 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## References\n", 34 | "\n", 35 | "I used the [original bitcoin](https://bitcoin.org/bitcoin.pdf) paper as reference as well as [Michael Nielsen's bitcoin explanation](http://www.michaelnielsen.org/ddi/how-the-bitcoin-protocol-actually-works/). The [bitcoin wiki](https://en.bitcoin.it/wiki/Main_Page) is a great technical resource as well.\n", 36 | "\n", 37 | "\n", 38 | "## Table of contents\n", 39 | "\n", 40 | "1. [Hash function and mining](#mining)\n", 41 | "2. [Difficulty](#difficulty)\n", 42 | "3. [A wallet](#wallet)\n", 43 | "4. [Transactions](#transactions)\n", 44 | "5. [Blocks](#block)\n", 45 | "6. [Attacks](#attacks)\n", 46 | "7. [Majority attack](#majority_attack)\n", 47 | "8. [Differences with Bitcoin](#differences)" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 1, 53 | "metadata": { 54 | "collapsed": true 55 | }, 56 | "outputs": [], 57 | "source": [ 58 | "# Requires\n", 59 | "# - pycryptodome (pycrypto for 3.6)\n", 60 | "# - numpy /scipy / matplotlib\n", 61 | "# - pandas" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 2, 67 | "metadata": { 68 | "collapsed": true 69 | }, 70 | "outputs": [], 71 | "source": [ 72 | "import hashlib\n", 73 | "import random\n", 74 | "import string\n", 75 | "import json\n", 76 | "import binascii\n", 77 | "import numpy as np\n", 78 | "import pandas as pd\n", 79 | "import pylab as pl\n", 80 | "import logging\n", 81 | "%matplotlib inline" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "# Hash function and mining \n", 89 | "\n", 90 | "So we'll start a little backward and start with the miner implementation.\n", 91 | "For the example here, we'll use the SHA256 hash function because it's readily implemented in python. Note that bitcoin uses [two round of SHA256](https://en.bitcoin.it/wiki/Hashcash) instead of one.\n", 92 | "\n", 93 | "So our hash function will turn a string of arbitrary length into a fixed-length string of 64 hexadecimal characters. For example :" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 3, 99 | "metadata": { 100 | "collapsed": true 101 | }, 102 | "outputs": [], 103 | "source": [ 104 | "def sha256(message):\n", 105 | " return hashlib.sha256(message.encode('ascii')).hexdigest()" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "Now, the process of *mining* is : given an arbitrary string $x$, find a nonce such that $hash(x + nonce)$ produces a hash starting with a number of leading ones.\n", 113 | "\n", 114 | "For example here, we'll \"mine\" a nonce such that the hash of our message (\"hello bitcoin\") when concatenated with our nonce will have at least 2 leading ones." 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 4, 120 | "metadata": {}, 121 | "outputs": [ 122 | { 123 | "name": "stdout", 124 | "output_type": "stream", 125 | "text": [ 126 | "Found nonce = 32\n", 127 | "112c38d2fdb6ddaf32f371a390307ccc779cd92443b42c4b5c58fa548f63ed83\n" 128 | ] 129 | } 130 | ], 131 | "source": [ 132 | "message = 'hello bitcoin'\n", 133 | "for nonce in range(1000):\n", 134 | " digest = sha256(message + str(nonce))\n", 135 | " if digest.startswith('11'):\n", 136 | " print('Found nonce = %d' % nonce)\n", 137 | " break\n", 138 | "print(sha256(message + str(nonce)))" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "metadata": {}, 144 | "source": [ 145 | "The more you increase the number of leading ones you require, the harder it becomes (on average) to find a nonce. In bitcoin, this is called the mining difficulty. Note that bitcoin doesn't require a number of leading digits, but instead requires the hash to be below a certain value. But it's the same idea.\n", 146 | "\n", 147 | "So let's define two functions that we'll reuse later : one to hash a string and one to mine a nonce for a given string." 148 | ] 149 | }, 150 | { 151 | "cell_type": "code", 152 | "execution_count": 5, 153 | "metadata": { 154 | "collapsed": true 155 | }, 156 | "outputs": [], 157 | "source": [ 158 | "def dumb_hash(message):\n", 159 | " \"\"\"\n", 160 | " Returns an hexadecimal hash\n", 161 | " \"\"\"\n", 162 | " return sha256(message)\n", 163 | "\n", 164 | "\n", 165 | "def mine(message, difficulty=1):\n", 166 | " \"\"\"\n", 167 | " Given an input string, will return a nonce such that\n", 168 | " hash(string + nonce) starts with `difficulty` ones\n", 169 | " \n", 170 | " Returns: (nonce, niters)\n", 171 | " nonce: The found nonce\n", 172 | " niters: The number of iterations required to find the nonce\n", 173 | " \"\"\"\n", 174 | " assert difficulty >= 1, \"Difficulty of 0 is not possible\"\n", 175 | " i = 0\n", 176 | " prefix = '1' * difficulty\n", 177 | " while True:\n", 178 | " nonce = str(i)\n", 179 | " digest = dumb_hash(message + nonce)\n", 180 | " if digest.startswith(prefix):\n", 181 | " return nonce, i\n", 182 | " i += 1" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "With that in place, we can mine nonce of varied difficulty :" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 6, 195 | "metadata": {}, 196 | "outputs": [ 197 | { 198 | "name": "stdout", 199 | "output_type": "stream", 200 | "text": [ 201 | "Took 23 iterations\n", 202 | "Took 2272 iterations\n" 203 | ] 204 | } 205 | ], 206 | "source": [ 207 | "nonce, niters = mine('42', difficulty=1)\n", 208 | "print('Took %d iterations' % niters)\n", 209 | "\n", 210 | "nonce, niters = mine('42', difficulty=3)\n", 211 | "print('Took %d iterations' % niters)" 212 | ] 213 | }, 214 | { 215 | "cell_type": "markdown", 216 | "metadata": {}, 217 | "source": [ 218 | "As you can see in this example, the number of iterations required for a difficulty of 3 is much larger than for a difficulty of 1. Note though that you could get lucky and have a string where the first nonce (0 in our case) would yield the solution. So the difficulty controls the *average* number of tries. We can do a nice plot of that" 219 | ] 220 | }, 221 | { 222 | "cell_type": "markdown", 223 | "metadata": {}, 224 | "source": [ 225 | "## Plotting difficulty " 226 | ] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "metadata": {}, 231 | "source": [ 232 | "For each difficulty level, we'll mine a nonce for a variety of input strings (we'll use 50 in this example). We'll record the average number of iterations required at each difficulty level." 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 7, 238 | "metadata": { 239 | "collapsed": true 240 | }, 241 | "outputs": [], 242 | "source": [ 243 | "def random_string(length=10):\n", 244 | " return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length))\n", 245 | "\n", 246 | "strings = [random_string() for i in range(50)]" 247 | ] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": 8, 252 | "metadata": { 253 | "collapsed": true 254 | }, 255 | "outputs": [], 256 | "source": [ 257 | "levels = range(1, 5)\n", 258 | "# An array of results with a row for each difficulty and a column for each test string\n", 259 | "results = pd.DataFrame(index=strings, columns=levels, dtype=np.int)\n", 260 | "results.fillna(value=0)\n", 261 | "\n", 262 | "#results = np.zeros((N_LEVELS, len(strings)), dtype=np.int)\n", 263 | "for level in levels:\n", 264 | " for s in strings:\n", 265 | " _, niters = mine(s, difficulty=level)\n", 266 | " results[level][s] = niters" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": 9, 272 | "metadata": {}, 273 | "outputs": [ 274 | { 275 | "data": { 276 | "text/html": [ 277 | "
\n", 278 | "\n", 291 | "\n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | "
1234
Z6AR67BMX723423879156631
2U6TP42RIM4513162116671
Q4XZ1QL2MI055688332199
PNQTJ3GD8I4524346650681
VIH18FKQ4K91611917744720
\n", 339 | "
" 340 | ], 341 | "text/plain": [ 342 | " 1 2 3 4\n", 343 | "Z6AR67BMX7 23 423 879 156631\n", 344 | "2U6TP42RIM 4 513 1621 16671\n", 345 | "Q4XZ1QL2MI 0 55 688 332199\n", 346 | "PNQTJ3GD8I 4 524 3466 50681\n", 347 | "VIH18FKQ4K 9 161 19177 44720" 348 | ] 349 | }, 350 | "execution_count": 9, 351 | "metadata": {}, 352 | "output_type": "execute_result" 353 | } 354 | ], 355 | "source": [ 356 | "results.iloc[:5]" 357 | ] 358 | }, 359 | { 360 | "cell_type": "code", 361 | "execution_count": 10, 362 | "metadata": {}, 363 | "outputs": [ 364 | { 365 | "data": { 366 | "text/plain": [ 367 | "" 368 | ] 369 | }, 370 | "execution_count": 10, 371 | "metadata": {}, 372 | "output_type": "execute_result" 373 | }, 374 | { 375 | "data": { 376 | "image/png": "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\nAAAAAElFTkSuQmCC\n", 377 | "text/plain": [ 378 | "" 379 | ] 380 | }, 381 | "metadata": {}, 382 | "output_type": "display_data" 383 | } 384 | ], 385 | "source": [ 386 | "pl.figure(figsize=(10, 5))\n", 387 | "ax = pl.subplot(111)\n", 388 | "ax.set_title('Number of iterations to mine a nonce for various difficulty')\n", 389 | "results.plot.box(showfliers=False, ax=ax)\n", 390 | "ax.set_xlabel('Difficulty')\n", 391 | "ax.set_ylabel('Iterations')" 392 | ] 393 | }, 394 | { 395 | "cell_type": "markdown", 396 | "metadata": {}, 397 | "source": [ 398 | "# A wallet " 399 | ] 400 | }, 401 | { 402 | "cell_type": "markdown", 403 | "metadata": {}, 404 | "source": [ 405 | "In bitcoin a wallet is a private/public key pair. If you don't know what public/private key is, you should probably read about [RSA](https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29).\n", 406 | "\n", 407 | "The public key is used to receive transactions and the private key is used to spend money. By signing a transaction with our private key, anybody else can verify the signature using our public key.\n", 408 | "\n", 409 | "Note that in bitcoin, the wallet is a little bit more complicated. A wallet is a set of multiple private/public key pairs, and an address is not directly the public key. This ensures better privacy and security, but for dumbcoin, we'll use a single key and use the public key as the address." 410 | ] 411 | }, 412 | { 413 | "cell_type": "code", 414 | "execution_count": 11, 415 | "metadata": { 416 | "collapsed": true 417 | }, 418 | "outputs": [], 419 | "source": [ 420 | "import Crypto\n", 421 | "import Crypto.Random\n", 422 | "from Crypto.Hash import SHA\n", 423 | "from Crypto.PublicKey import RSA\n", 424 | "from Crypto.Signature import PKCS1_v1_5\n", 425 | "\n", 426 | "\n", 427 | "class Wallet(object):\n", 428 | " \"\"\"\n", 429 | " A wallet is a private/public key pair\n", 430 | " \"\"\"\n", 431 | " def __init__(self):\n", 432 | " random_gen = Crypto.Random.new().read\n", 433 | " self._private_key = RSA.generate(1024, random_gen)\n", 434 | " self._public_key = self._private_key.publickey()\n", 435 | " self._signer = PKCS1_v1_5.new(self._private_key)\n", 436 | " \n", 437 | " @property\n", 438 | " def address(self):\n", 439 | " \"\"\"We take a shortcut and say address is public key\"\"\"\n", 440 | " return binascii.hexlify(self._public_key.exportKey(format='DER')).decode('ascii')\n", 441 | " \n", 442 | " def sign(self, message):\n", 443 | " \"\"\"\n", 444 | " Sign a message with this wallet\n", 445 | " \"\"\"\n", 446 | " h = SHA.new(message.encode('utf8'))\n", 447 | " return binascii.hexlify(self._signer.sign(h)).decode('ascii')\n", 448 | " \n", 449 | " \n", 450 | "def verify_signature(wallet_address, message, signature):\n", 451 | " \"\"\"\n", 452 | " Check that the provided `signature` corresponds to `message`\n", 453 | " signed by the wallet at `wallet_address`\n", 454 | " \"\"\"\n", 455 | " pubkey = RSA.importKey(binascii.unhexlify(wallet_address))\n", 456 | " verifier = PKCS1_v1_5.new(pubkey)\n", 457 | " h = SHA.new(message.encode('utf8'))\n", 458 | " return verifier.verify(h, binascii.unhexlify(signature))\n", 459 | "\n", 460 | "\n", 461 | "# Check that the wallet signing functionality works\n", 462 | "w1 = Wallet()\n", 463 | "signature = w1.sign('foobar')\n", 464 | "assert verify_signature(w1.address, 'foobar', signature)\n", 465 | "assert not verify_signature(w1.address, 'rogue message', signature)" 466 | ] 467 | }, 468 | { 469 | "cell_type": "markdown", 470 | "metadata": {}, 471 | "source": [ 472 | "# Doing transactions " 473 | ] 474 | }, 475 | { 476 | "cell_type": "markdown", 477 | "metadata": {}, 478 | "source": [ 479 | "To exchange money between wallets, we'll use transactions. A transaction is composed of :\n", 480 | "- A spender, who will sign the transaction and who's spending his money\n", 481 | "- A number of inputs, which are other transactions' outputs. The recipient of all those should be the spender's wallet.\n", 482 | " Otherwise you could spend other people's money.\n", 483 | "- A number of outputs, each of which specify an amount of money and a recipient\n", 484 | "\n", 485 | "Transactions can also contain a \"transaction fee\" which is an incentive for the miner to include the transaction in a block (more on that later). The fee is just the difference between the total input amount and the total output amount.\n", 486 | "\n", 487 | "\n", 488 | "Since all transactions need a parent, we also need a root in our hierarchy. This will be the *GenesisTransaction*." 489 | ] 490 | }, 491 | { 492 | "cell_type": "code", 493 | "execution_count": 12, 494 | "metadata": { 495 | "collapsed": true 496 | }, 497 | "outputs": [], 498 | "source": [ 499 | "class TransactionInput(object):\n", 500 | " \"\"\"\n", 501 | " An input for a transaction. This points to an output of another transaction\n", 502 | " \"\"\"\n", 503 | " def __init__(self, transaction, output_index):\n", 504 | " self.transaction = transaction\n", 505 | " self.output_index = output_index\n", 506 | " assert 0 <= self.output_index < len(transaction.outputs)\n", 507 | " \n", 508 | " def to_dict(self):\n", 509 | " d = {\n", 510 | " 'transaction': self.transaction.hash(),\n", 511 | " 'output_index': self.output_index\n", 512 | " }\n", 513 | " return d\n", 514 | " \n", 515 | " @property\n", 516 | " def parent_output(self):\n", 517 | " return self.transaction.outputs[self.output_index]\n", 518 | " \n", 519 | "\n", 520 | "class TransactionOutput(object):\n", 521 | " \"\"\"\n", 522 | " An output for a transaction. This specifies an amount and a recipient (wallet)\n", 523 | " \"\"\"\n", 524 | " def __init__(self, recipient_address, amount):\n", 525 | " self.recipient = recipient_address\n", 526 | " self.amount = amount\n", 527 | " \n", 528 | " def to_dict(self):\n", 529 | " d = {\n", 530 | " 'recipient_address': self.recipient,\n", 531 | " 'amount': self.amount\n", 532 | " }\n", 533 | " return d\n", 534 | "\n", 535 | " \n", 536 | "def compute_fee(inputs, outputs):\n", 537 | " \"\"\"\n", 538 | " Compute the transaction fee by computing the difference between total input and total output\n", 539 | " \"\"\"\n", 540 | " total_in = sum(i.transaction.outputs[i.output_index].amount for i in inputs)\n", 541 | " total_out = sum(o.amount for o in outputs)\n", 542 | " assert total_out <= total_in, \"Invalid transaction with out(%f) > in(%f)\" % (total_out, total_in)\n", 543 | " return total_in - total_out\n", 544 | "\n", 545 | " \n", 546 | "class Transaction(object):\n", 547 | " def __init__(self, wallet, inputs, outputs):\n", 548 | " \"\"\"\n", 549 | " Create a transaction spending money from the provided wallet\n", 550 | " \"\"\"\n", 551 | " self.inputs = inputs\n", 552 | " self.outputs = outputs\n", 553 | " self.fee = compute_fee(inputs, outputs)\n", 554 | " self.signature = wallet.sign(json.dumps(self.to_dict(include_signature=False)))\n", 555 | " \n", 556 | " def to_dict(self, include_signature=True):\n", 557 | " d = {\n", 558 | " \"inputs\": list(map(TransactionInput.to_dict, self.inputs)),\n", 559 | " \"outputs\": list(map(TransactionOutput.to_dict, self.outputs)),\n", 560 | " \"fee\": self.fee\n", 561 | " }\n", 562 | " if include_signature:\n", 563 | " d[\"signature\"] = self.signature\n", 564 | " return d\n", 565 | " \n", 566 | " def hash(self):\n", 567 | " return dumb_hash(json.dumps(self.to_dict()))\n", 568 | " \n", 569 | " \n", 570 | "class GenesisTransaction(Transaction):\n", 571 | " \"\"\"\n", 572 | " This is the first transaction which is a special transaction\n", 573 | " with no input and 25 bitcoins output\n", 574 | " \"\"\"\n", 575 | " def __init__(self, recipient_address, amount=25):\n", 576 | " self.inputs = []\n", 577 | " self.outputs = [\n", 578 | " TransactionOutput(recipient_address, amount)\n", 579 | " ]\n", 580 | " self.fee = 0\n", 581 | " self.signature = 'genesis'\n", 582 | " \n", 583 | " def to_dict(self, include_signature=False):\n", 584 | " # TODO: Instead, should sign genesis transaction will well-known public key ?\n", 585 | " assert not include_signature, \"Cannot include signature of genesis transaction\"\n", 586 | " return super().to_dict(include_signature=False)" 587 | ] 588 | }, 589 | { 590 | "cell_type": "markdown", 591 | "metadata": {}, 592 | "source": [ 593 | "With the classes above, we can make transactions between alice and bob." 594 | ] 595 | }, 596 | { 597 | "cell_type": "code", 598 | "execution_count": 13, 599 | "metadata": { 600 | "collapsed": true 601 | }, 602 | "outputs": [], 603 | "source": [ 604 | "alice = Wallet()\n", 605 | "bob = Wallet()\n", 606 | "\n", 607 | "t1 = GenesisTransaction(alice.address)\n", 608 | "t2 = Transaction(\n", 609 | " alice,\n", 610 | " [TransactionInput(t1, 0)],\n", 611 | " [TransactionOutput(bob.address, 2.0), TransactionOutput(alice.address, 22.0)]\n", 612 | ")\n", 613 | "assert np.abs(t2.fee - 1.0) < 1e-5" 614 | ] 615 | }, 616 | { 617 | "cell_type": "markdown", 618 | "metadata": {}, 619 | "source": [ 620 | "In bitcoin, you never store how much you have in your wallet. Instead, you go through the whole chain of transactions to compute how much you have. Let's write a function to do that" 621 | ] 622 | }, 623 | { 624 | "cell_type": "code", 625 | "execution_count": 14, 626 | "metadata": { 627 | "collapsed": true 628 | }, 629 | "outputs": [], 630 | "source": [ 631 | "alice = Wallet()\n", 632 | "bob = Wallet()\n", 633 | "walter = Wallet()\n", 634 | "\n", 635 | "# This gives 25 coins to Alice\n", 636 | "t1 = GenesisTransaction(alice.address)\n", 637 | "\n", 638 | "# Of those 25, Alice will spend\n", 639 | "# Alice -- 5 --> Bob\n", 640 | "# -- 15 --> Alice\n", 641 | "# -- 5 --> Walter\n", 642 | "t2 = Transaction(\n", 643 | " alice,\n", 644 | " [TransactionInput(t1, 0)],\n", 645 | " [TransactionOutput(bob.address, 5.0), TransactionOutput(alice.address, 15.0), TransactionOutput(walter.address, 5.0)]\n", 646 | ")\n", 647 | "\n", 648 | "# Walter -- 5 --> Bob\n", 649 | "t3 = Transaction(\n", 650 | " walter,\n", 651 | " [TransactionInput(t2, 2)],\n", 652 | " [TransactionOutput(bob.address, 5.0)])\n", 653 | "\n", 654 | "# Bob -- 8 --> Walter\n", 655 | "# -- 1 --> Bob\n", 656 | "# 1 fee\n", 657 | "t4 = Transaction(\n", 658 | " bob,\n", 659 | " [TransactionInput(t2, 0), TransactionInput(t3, 0)],\n", 660 | " [TransactionOutput(walter.address, 8.0), TransactionOutput(bob.address, 1.0)]\n", 661 | ")\n", 662 | "\n", 663 | "transactions = [t1, t2, t3, t4]\n" 664 | ] 665 | }, 666 | { 667 | "cell_type": "code", 668 | "execution_count": 15, 669 | "metadata": {}, 670 | "outputs": [ 671 | { 672 | "name": "stdout", 673 | "output_type": "stream", 674 | "text": [ 675 | "Alice has 15.00 dumbcoins\n", 676 | "Bob has 1.00 dumbcoins\n", 677 | "Walter has 8.00 dumbcoins\n" 678 | ] 679 | } 680 | ], 681 | "source": [ 682 | "def compute_balance(wallet_address, transactions):\n", 683 | " \"\"\"\n", 684 | " Given an address and a list of transactions, computes the wallet balance of the address\n", 685 | " \"\"\"\n", 686 | " balance = 0\n", 687 | " for t in transactions:\n", 688 | " # Subtract all the money that the address sent out\n", 689 | " for txin in t.inputs:\n", 690 | " if txin.parent_output.recipient == wallet_address:\n", 691 | " balance -= txin.parent_output.amount\n", 692 | " # Add all the money received by the address\n", 693 | " for txout in t.outputs:\n", 694 | " if txout.recipient == wallet_address:\n", 695 | " balance += txout.amount\n", 696 | " return balance\n", 697 | "\n", 698 | "print(\"Alice has %.02f dumbcoins\" % compute_balance(alice.address, transactions))\n", 699 | "print(\"Bob has %.02f dumbcoins\" % compute_balance(bob.address, transactions))\n", 700 | "print(\"Walter has %.02f dumbcoins\" % compute_balance(walter.address, transactions))" 701 | ] 702 | }, 703 | { 704 | "cell_type": "markdown", 705 | "metadata": {}, 706 | "source": [ 707 | "We also want to be able to verify that a transaction is valid. This means :\n", 708 | "\n", 709 | "- You can only spend your money. This means checking that all inputs are owned by the transaction's owner\n", 710 | "- Ensure you don't spend more than you have. This is checked by the compute_fee function above" 711 | ] 712 | }, 713 | { 714 | "cell_type": "code", 715 | "execution_count": 16, 716 | "metadata": { 717 | "collapsed": true 718 | }, 719 | "outputs": [], 720 | "source": [ 721 | "def verify_transaction(transaction):\n", 722 | " \"\"\"\n", 723 | " Verify that the transaction is valid.\n", 724 | " We need to verify two things :\n", 725 | " - That all of the inputs of the transaction belong to the same wallet\n", 726 | " - That the transaction is signed by the owner of said wallet\n", 727 | " \"\"\"\n", 728 | " tx_message = json.dumps(transaction.to_dict(include_signature=False))\n", 729 | " if isinstance(transaction, GenesisTransaction):\n", 730 | " # TODO: We should probably be more careful about validating genesis transactions\n", 731 | " return True\n", 732 | " \n", 733 | " # Verify input transactions\n", 734 | " for tx in transaction.inputs:\n", 735 | " if not verify_transaction(tx.transaction):\n", 736 | " logging.error(\"Invalid parent transaction\")\n", 737 | " return False\n", 738 | " \n", 739 | " # Verify a single wallet owns all the inputs\n", 740 | " first_input_address = transaction.inputs[0].parent_output.recipient\n", 741 | " for txin in transaction.inputs[1:]:\n", 742 | " if txin.parent_output.recipient != first_input_address:\n", 743 | " logging.error(\n", 744 | " \"Transaction inputs belong to multiple wallets (%s and %s)\" %\n", 745 | " (txin.parent_output.recipient, first_input_address)\n", 746 | " )\n", 747 | " return False\n", 748 | " \n", 749 | " if not verify_signature(first_input_address, tx_message, transaction.signature):\n", 750 | " logging.error(\"Invalid transaction signature, trying to spend someone else's money ?\")\n", 751 | " return False\n", 752 | " \n", 753 | " # Call compute_fee here to trigger an assert if output sum is great than input sum. Without this,\n", 754 | " # a miner could put such an invalid transaction.\n", 755 | " compute_fee(transaction.inputs, transaction.outputs)\n", 756 | " \n", 757 | " return True\n", 758 | "\n", 759 | "\n", 760 | "t1 = GenesisTransaction(alice.address)\n", 761 | "# This is an invalid transaction because bob is trying to spend alice's money\n", 762 | "# (alice was the recipient of the input - t1)\n", 763 | "t2 = Transaction(\n", 764 | " bob,\n", 765 | " [TransactionInput(t1, 0)],\n", 766 | " [TransactionOutput(walter.address, 10.0)]\n", 767 | ")\n", 768 | "# This is valid, alice is spending her own money\n", 769 | "t3 = Transaction(\n", 770 | " alice,\n", 771 | " [TransactionInput(t1, 0)],\n", 772 | " [TransactionOutput(walter.address, 10.0)]\n", 773 | ")" 774 | ] 775 | }, 776 | { 777 | "cell_type": "code", 778 | "execution_count": 17, 779 | "metadata": {}, 780 | "outputs": [], 781 | "source": [ 782 | "assert verify_transaction(t1)" 783 | ] 784 | }, 785 | { 786 | "cell_type": "code", 787 | "execution_count": 18, 788 | "metadata": {}, 789 | "outputs": [ 790 | { 791 | "name": "stderr", 792 | "output_type": "stream", 793 | "text": [ 794 | "ERROR:root:Invalid transaction signature, trying to spend someone else's money ?\n" 795 | ] 796 | } 797 | ], 798 | "source": [ 799 | "assert not verify_transaction(t2)" 800 | ] 801 | }, 802 | { 803 | "cell_type": "code", 804 | "execution_count": 19, 805 | "metadata": { 806 | "collapsed": true 807 | }, 808 | "outputs": [], 809 | "source": [ 810 | "assert verify_transaction(t3)" 811 | ] 812 | }, 813 | { 814 | "cell_type": "markdown", 815 | "metadata": {}, 816 | "source": [ 817 | "# Putting transactions in blocks " 818 | ] 819 | }, 820 | { 821 | "cell_type": "markdown", 822 | "metadata": {}, 823 | "source": [ 824 | "Now that we have :\n", 825 | "\n", 826 | "- A way to define a wallet (as a private/public key pair)\n", 827 | "- A way to create transactions between wallets\n", 828 | "- A way to verify transactions (by checking the signature matches\n", 829 | "\n", 830 | "What remains is to group transactions into blocks and have miners mine blocks. Mining a block consists of two parts :\n", 831 | "- Verifying the transactions in the block\n", 832 | "- Finding a nonce such that the block's hash starts with a number of 0\n", 833 | "\n", 834 | "Also, mining generates money by the convention that the first transaction in a block is a GenesisTransaction that gives 25 coins to whatever address the miner chose.\n", 835 | "In the same way, the miner can add transactions to redirect the fees from the transactions in the block to whatever address it chooses." 836 | ] 837 | }, 838 | { 839 | "cell_type": "code", 840 | "execution_count": 20, 841 | "metadata": { 842 | "collapsed": true 843 | }, 844 | "outputs": [], 845 | "source": [ 846 | "BLOCK_INCENTIVE = 25 # The number of coins miners get for mining a block\n", 847 | "DIFFICULTY = 2\n", 848 | "\n", 849 | "\n", 850 | "def compute_total_fee(transactions):\n", 851 | " \"\"\"Return the total fee for the set of transactions\"\"\"\n", 852 | " return sum(t.fee for t in transactions)\n", 853 | "\n", 854 | "\n", 855 | "class Block(object):\n", 856 | " def __init__(self, transactions, ancestor, miner_address, skip_verif=False):\n", 857 | " \"\"\"\n", 858 | " Args:\n", 859 | " transactions: The list of transactions to include in the block\n", 860 | " ancestor: The previous block\n", 861 | " miner_address: The address of the miner's wallet. This is where the block\n", 862 | " incentive and the transactions fees will be deposited\n", 863 | " \"\"\"\n", 864 | " reward = compute_total_fee(transactions) + BLOCK_INCENTIVE\n", 865 | " self.transactions = [GenesisTransaction(miner_address, amount=reward)] + transactions\n", 866 | " self.ancestor = ancestor\n", 867 | " \n", 868 | " if not skip_verif:\n", 869 | " assert all(map(verify_transaction, transactions))\n", 870 | " \n", 871 | " json_block = json.dumps(self.to_dict(include_hash=False))\n", 872 | " self.nonce, _ = mine(json_block, DIFFICULTY)\n", 873 | " self.hash = dumb_hash(json_block + self.nonce)\n", 874 | " \n", 875 | " def fee(self):\n", 876 | " \"\"\"Return transaction fee for this block\"\"\"\n", 877 | " return compute_total_fee(self.transactions)\n", 878 | " \n", 879 | " def to_dict(self, include_hash=True):\n", 880 | " d = {\n", 881 | " \"transactions\": list(map(Transaction.to_dict, self.transactions)),\n", 882 | " \"previous_block\": self.ancestor.hash,\n", 883 | " }\n", 884 | " if include_hash:\n", 885 | " d[\"nonce\"] = self.nonce\n", 886 | " d[\"hash\"] = self.hash\n", 887 | " return d\n", 888 | " \n", 889 | " \n", 890 | "class GenesisBlock(Block):\n", 891 | " \"\"\"\n", 892 | " The genesis block is the first block in the chain.\n", 893 | " It is the only block with no ancestor\n", 894 | " \"\"\"\n", 895 | " def __init__(self, miner_address):\n", 896 | " super(GenesisBlock, self).__init__(transactions=[], ancestor=None, miner_address=miner_address)\n", 897 | "\n", 898 | " def to_dict(self, include_hash=True):\n", 899 | " d = {\n", 900 | " \"transactions\": [],\n", 901 | " \"genesis_block\": True,\n", 902 | " }\n", 903 | " if include_hash:\n", 904 | " d[\"nonce\"] = self.nonce\n", 905 | " d[\"hash\"] = self.hash\n", 906 | " return d" 907 | ] 908 | }, 909 | { 910 | "cell_type": "markdown", 911 | "metadata": {}, 912 | "source": [ 913 | "Similarly to how we verify transactions, we also need a way to verify blocks :" 914 | ] 915 | }, 916 | { 917 | "cell_type": "code", 918 | "execution_count": 21, 919 | "metadata": { 920 | "collapsed": true 921 | }, 922 | "outputs": [], 923 | "source": [ 924 | "def verify_block(block, genesis_block, used_outputs=None):\n", 925 | " \"\"\"\n", 926 | " Verifies that a block is valid :\n", 927 | " - Verifies the hash starts with the required amount of ones\n", 928 | " - Verifies that the same transaction output isn't used twice\n", 929 | " - Verifies all transactions are valid\n", 930 | " - Verifies the first transaction in the block is a genesis transaction with BLOCK_INCENTIVE + total_fee\n", 931 | " \n", 932 | " Args:\n", 933 | " block: The block to validate\n", 934 | " genesis_block: The genesis block (this needs to be shared by everybody. E.g. hardcoded somewhere)\n", 935 | " used_outputs: list of outputs used in transactions for all blocks above this one\n", 936 | " \"\"\"\n", 937 | " if used_outputs is None:\n", 938 | " used_outputs = set()\n", 939 | " \n", 940 | " # Verify hash\n", 941 | " prefix = '1' * DIFFICULTY\n", 942 | " if not block.hash.startswith(prefix):\n", 943 | " logging.error(\"Block hash (%s) doesn't start with prefix %s\" % (block.hash, prefix))\n", 944 | " return False\n", 945 | " if not all(map(verify_transaction, block.transactions)):\n", 946 | " return False\n", 947 | " \n", 948 | " # Verify that transactions in this block don't use already spent outputs\n", 949 | " #\n", 950 | " # Note that we could move this in verify_transaction, but this would require some passing the used_outputs\n", 951 | " # around more. So we do it here for simplicity\n", 952 | " for transaction in block.transactions:\n", 953 | " for i in transaction.inputs:\n", 954 | " if i.parent_output in used_outputs:\n", 955 | " logging.error(\"Transaction uses an already spent output : %s\" % json.dumps(i.parent_output.to_dict()))\n", 956 | " return False\n", 957 | " used_outputs.add(i.parent_output)\n", 958 | " \n", 959 | " # Verify ancestors up to the genesis block\n", 960 | " if not (block.hash == genesis_block.hash):\n", 961 | " if not verify_block(block.ancestor, genesis_block, used_outputs):\n", 962 | " logging.error(\"Failed to validate ancestor block\")\n", 963 | " return False\n", 964 | " \n", 965 | " # Verify the first transaction is the miner's reward\n", 966 | " tx0 = block.transactions[0]\n", 967 | " if not isinstance(tx0, GenesisTransaction):\n", 968 | " logging.error(\"Transaction 0 is not a GenesisTransaction\")\n", 969 | " return False\n", 970 | " if not len(tx0.outputs) == 1:\n", 971 | " logging.error(\"Transactions 0 doesn't have exactly 1 output\")\n", 972 | " return False\n", 973 | " reward = compute_total_fee(block.transactions[1:]) + BLOCK_INCENTIVE\n", 974 | " if not tx0.outputs[0].amount == reward:\n", 975 | " logging.error(\"Invalid amount in transaction 0 : %d, expected %d\" % (tx0.outputs[0].amount, reward))\n", 976 | " return False\n", 977 | " \n", 978 | " # Only the first transaction shall be a genesis\n", 979 | " for i, tx in enumerate(block.transactions):\n", 980 | " if i == 0:\n", 981 | " if not isinstance(tx, GenesisTransaction):\n", 982 | " logging.error(\"Non-genesis transaction at index 0\")\n", 983 | " return False \n", 984 | " elif isinstance(tx, GenesisTransaction):\n", 985 | " logging.error(\"GenesisTransaction (hash=%s) at index %d != 0\", tx.hash(), i)\n", 986 | " return False\n", 987 | " return True" 988 | ] 989 | }, 990 | { 991 | "cell_type": "code", 992 | "execution_count": 22, 993 | "metadata": {}, 994 | "outputs": [ 995 | { 996 | "name": "stdout", 997 | "output_type": "stream", 998 | "text": [ 999 | "genesis_block : 1162dce8ffec3acf13ce61109f121922eee8cceeea4784aa9d90dc6ec0e0fa92 with fee=0\n", 1000 | "block1 : 11af277c02c22a7e3c3a73102282ca5a0e01869b1d852527b6a842f0786ee8e3 with fee=0.0\n", 1001 | "block2 : 119e461d393b793478c7c7cb9fa6feb54fca35865a398d017e541027a78a2e9a with fee=1.0\n" 1002 | ] 1003 | } 1004 | ], 1005 | "source": [ 1006 | "alice = Wallet()\n", 1007 | "bob = Wallet()\n", 1008 | "walter = Wallet()\n", 1009 | "\n", 1010 | "genesis_block = GenesisBlock(miner_address=alice.address)\n", 1011 | "print(\"genesis_block : \" + genesis_block.hash + \" with fee=\" + str(genesis_block.fee()))\n", 1012 | "\n", 1013 | "t1 = genesis_block.transactions[0]\n", 1014 | "t2 = Transaction(\n", 1015 | " alice,\n", 1016 | " [TransactionInput(t1, 0)],\n", 1017 | " [TransactionOutput(bob.address, 5.0), TransactionOutput(alice.address, 15.0), TransactionOutput(walter.address, 5.0)]\n", 1018 | ")\n", 1019 | "t3 = Transaction(\n", 1020 | " walter,\n", 1021 | " [TransactionInput(t2, 2)],\n", 1022 | " [TransactionOutput(bob.address, 5.0)])\n", 1023 | "\n", 1024 | "t4 = Transaction(\n", 1025 | " bob,\n", 1026 | " [TransactionInput(t2, 0), TransactionInput(t3, 0)],\n", 1027 | " [TransactionOutput(walter.address, 8.0), TransactionOutput(bob.address, 1.0)]\n", 1028 | ")\n", 1029 | "\n", 1030 | "block1 = Block([t2], ancestor=genesis_block, miner_address=walter.address)\n", 1031 | "print(\"block1 : \" + block1.hash + \" with fee=\" + str(block1.fee()))\n", 1032 | "\n", 1033 | "block2 = Block([t3, t4], ancestor=block1, miner_address=walter.address)\n", 1034 | "print(\"block2 : \" + block2.hash + \" with fee=\" + str(block2.fee()))" 1035 | ] 1036 | }, 1037 | { 1038 | "cell_type": "code", 1039 | "execution_count": 23, 1040 | "metadata": {}, 1041 | "outputs": [ 1042 | { 1043 | "data": { 1044 | "text/plain": [ 1045 | "True" 1046 | ] 1047 | }, 1048 | "execution_count": 23, 1049 | "metadata": {}, 1050 | "output_type": "execute_result" 1051 | } 1052 | ], 1053 | "source": [ 1054 | "verify_block(block1, genesis_block)\n", 1055 | "verify_block(block2, genesis_block)" 1056 | ] 1057 | }, 1058 | { 1059 | "cell_type": "code", 1060 | "execution_count": 24, 1061 | "metadata": {}, 1062 | "outputs": [ 1063 | { 1064 | "name": "stdout", 1065 | "output_type": "stream", 1066 | "text": [ 1067 | "Alice has 15.00 dumbcoins\n", 1068 | "Bob has 1.00 dumbcoins\n", 1069 | "Walter has 59.00 dumbcoins\n" 1070 | ] 1071 | } 1072 | ], 1073 | "source": [ 1074 | "def collect_transactions(block, genesis_block):\n", 1075 | " \"\"\"Recursively collect transactions in `block` and all of its ancestors\"\"\"\n", 1076 | " # Important : COPY block.transactions\n", 1077 | " transactions = [] + block.transactions\n", 1078 | " if block.hash != genesis_block.hash:\n", 1079 | " transactions += collect_transactions(block.ancestor, genesis_block)\n", 1080 | " return transactions\n", 1081 | "\n", 1082 | "transactions = collect_transactions(block2, genesis_block)\n", 1083 | "\n", 1084 | "# Alice mined 25 (from the genesis block) and gave 5 to bob and 5 to walter\n", 1085 | "print(\"Alice has %.02f dumbcoins\" % compute_balance(alice.address, transactions))\n", 1086 | "# Bob received 5 from alice and 5 from walter, but then back 8 to walter with a transaction fee of 1\n", 1087 | "print(\"Bob has %.02f dumbcoins\" % compute_balance(bob.address, transactions))\n", 1088 | "# Walter mined 2 blocks (2 * 25), received 8 from bob and go a transaction fee of 1 on block2\n", 1089 | "print(\"Walter has %.02f dumbcoins\" % compute_balance(walter.address, transactions))" 1090 | ] 1091 | }, 1092 | { 1093 | "cell_type": "markdown", 1094 | "metadata": {}, 1095 | "source": [ 1096 | "# Some attacks " 1097 | ] 1098 | }, 1099 | { 1100 | "cell_type": "markdown", 1101 | "metadata": {}, 1102 | "source": [ 1103 | "Now, if our miner (walter) tries to spend bob's money, his block won't verify :" 1104 | ] 1105 | }, 1106 | { 1107 | "cell_type": "code", 1108 | "execution_count": 25, 1109 | "metadata": {}, 1110 | "outputs": [ 1111 | { 1112 | "name": "stderr", 1113 | "output_type": "stream", 1114 | "text": [ 1115 | "ERROR:root:Invalid transaction signature, trying to spend someone else's money ?\n" 1116 | ] 1117 | } 1118 | ], 1119 | "source": [ 1120 | "tx = Transaction(\n", 1121 | " walter,\n", 1122 | " [TransactionInput(t4, 1)],\n", 1123 | " [TransactionOutput(walter.address, 1.0)],\n", 1124 | ")\n", 1125 | "\n", 1126 | "# Note that we have to use skip_verif=True to not trigger the assert in Block's constructor\n", 1127 | "block = Block([tx], ancestor=block2, miner_address=walter.address, skip_verif=True)\n", 1128 | "assert not verify_block(block, genesis_block)" 1129 | ] 1130 | }, 1131 | { 1132 | "cell_type": "markdown", 1133 | "metadata": {}, 1134 | "source": [ 1135 | "Another possible attack would be for the miner to modify a transaction it include in its block. Again, this won't work" 1136 | ] 1137 | }, 1138 | { 1139 | "cell_type": "code", 1140 | "execution_count": 26, 1141 | "metadata": {}, 1142 | "outputs": [ 1143 | { 1144 | "name": "stderr", 1145 | "output_type": "stream", 1146 | "text": [ 1147 | "ERROR:root:Invalid transaction signature, trying to spend someone else's money ?\n" 1148 | ] 1149 | } 1150 | ], 1151 | "source": [ 1152 | "# This is a transaction signed by bob\n", 1153 | "tx = Transaction(\n", 1154 | " bob,\n", 1155 | " [TransactionInput(t2, 0), TransactionInput(t3, 0)],\n", 1156 | " [TransactionOutput(walter.address, 8.0), TransactionOutput(bob.address, 1.0)]\n", 1157 | ")\n", 1158 | "# And modified by the miner\n", 1159 | "tx.outputs[0].amount += 4\n", 1160 | "block = Block([t3, tx], ancestor=block1, miner_address=walter.address, skip_verif=True)\n", 1161 | "assert not verify_block(block, genesis_block)" 1162 | ] 1163 | }, 1164 | { 1165 | "cell_type": "markdown", 1166 | "metadata": {}, 1167 | "source": [ 1168 | "A signed transaction could also be duplicated, increasing the total coins in circulation." 1169 | ] 1170 | }, 1171 | { 1172 | "cell_type": "code", 1173 | "execution_count": 27, 1174 | "metadata": {}, 1175 | "outputs": [ 1176 | { 1177 | "name": "stderr", 1178 | "output_type": "stream", 1179 | "text": [ 1180 | "ERROR:root:Transaction uses an already spent output : {\"recipient_address\": \"30819f300d06092a864886f70d010101050003818d0030818902818100aec88246cc7e63c404c083b34ce153bdcf25749ec8d8764df14c0940ec795ce7522c9923e7aad577f60d346939e6c6aff45c432c6644cef675efb336fc24bdbb966d1448521537390b502c5677251e40c4a0d7b8ec9be732c21b9fe7bac7dc0f8551677645a6b89a8a39ac581fc2d440867f46994d1a52d1ddc1a22f67740f3f0203010001\", \"amount\": 5.0}\n" 1181 | ] 1182 | } 1183 | ], 1184 | "source": [ 1185 | "block = Block([t3, t3, t4], ancestor=block1, miner_address=bob.address, skip_verif=True)\n", 1186 | "assert not verify_block(block, genesis_block)" 1187 | ] 1188 | }, 1189 | { 1190 | "cell_type": "markdown", 1191 | "metadata": {}, 1192 | "source": [ 1193 | "# Majority attack " 1194 | ] 1195 | }, 1196 | { 1197 | "cell_type": "markdown", 1198 | "metadata": {}, 1199 | "source": [ 1200 | "The tools built up so-far ensure that the ledger is self-consistent: money is spent by addresses which own that money. However, a flaw remains: given that there isn't a central copy of the ledger, which is to be trusted ? The rule is that you trust the **longest chain**. Let's see what are the limits of this rule." 1201 | ] 1202 | }, 1203 | { 1204 | "cell_type": "code", 1205 | "execution_count": 28, 1206 | "metadata": { 1207 | "collapsed": true 1208 | }, 1209 | "outputs": [], 1210 | "source": [ 1211 | "# Let's start again with the same example transactions and blocks as above\n", 1212 | "alice = Wallet()\n", 1213 | "bob = Wallet()\n", 1214 | "walter = Wallet()\n", 1215 | "\n", 1216 | "genesis_block = GenesisBlock(miner_address=alice.address)\n", 1217 | "\n", 1218 | "t1 = genesis_block.transactions[0]\n", 1219 | "t2 = Transaction(\n", 1220 | " alice,\n", 1221 | " [TransactionInput(t1, 0)],\n", 1222 | " [TransactionOutput(bob.address, 5.0), TransactionOutput(alice.address, 15.0), TransactionOutput(walter.address, 5.0)]\n", 1223 | ")\n", 1224 | "t3 = Transaction(\n", 1225 | " walter,\n", 1226 | " [TransactionInput(t2, 2)],\n", 1227 | " [TransactionOutput(bob.address, 5.0)])\n", 1228 | "\n", 1229 | "t4 = Transaction(\n", 1230 | " bob,\n", 1231 | " [TransactionInput(t2, 0), TransactionInput(t3, 0)],\n", 1232 | " [TransactionOutput(walter.address, 8.0), TransactionOutput(bob.address, 1.0)]\n", 1233 | ")\n", 1234 | "\n", 1235 | "block1 = Block([t2], ancestor=genesis_block, miner_address=walter.address)\n", 1236 | "block2 = Block([t3, t4], ancestor=block1, miner_address=walter.address)" 1237 | ] 1238 | }, 1239 | { 1240 | "cell_type": "markdown", 1241 | "metadata": {}, 1242 | "source": [ 1243 | "So with t4 above, bob sent 8 coins to walter. Let's assume walter is a merchant and he sold bob a nice car in exchange of those 8 coins. Bob now has a car and walter has 8 coins." 1244 | ] 1245 | }, 1246 | { 1247 | "cell_type": "markdown", 1248 | "metadata": {}, 1249 | "source": [ 1250 | "Bob is feeling a bit poor and decides he'd rather not have spent those 8 coins in `block2`. He re-writes history a bit, putting a transaction to himself and mines his own block." 1251 | ] 1252 | }, 1253 | { 1254 | "cell_type": "code", 1255 | "execution_count": 29, 1256 | "metadata": {}, 1257 | "outputs": [ 1258 | { 1259 | "name": "stdout", 1260 | "output_type": "stream", 1261 | "text": [ 1262 | "True\n" 1263 | ] 1264 | } 1265 | ], 1266 | "source": [ 1267 | "t4_malicious = Transaction(\n", 1268 | " bob,\n", 1269 | " [TransactionInput(t2, 0), TransactionInput(t3, 0)],\n", 1270 | " [TransactionOutput(bob.address, 8.0), TransactionOutput(bob.address, 1.0)]\n", 1271 | ")\n", 1272 | "block2_malicious = Block([t3, t4_malicious], ancestor=block1, miner_address=bob.address)\n", 1273 | "print(verify_block(block2_malicious, genesis_block))" 1274 | ] 1275 | }, 1276 | { 1277 | "cell_type": "markdown", 1278 | "metadata": {}, 1279 | "source": [ 1280 | "The `block2_malicious` isn't yet accepted by the network as it hasn't surpassed the longest ledger (`block2`).\n", 1281 | "More transactions are made, to be mined in the next block. Bob happens to mine the new block with this last transaction." 1282 | ] 1283 | }, 1284 | { 1285 | "cell_type": "code", 1286 | "execution_count": 30, 1287 | "metadata": { 1288 | "collapsed": true 1289 | }, 1290 | "outputs": [], 1291 | "source": [ 1292 | "t6 = Transaction(\n", 1293 | " alice,\n", 1294 | " [TransactionInput(t2, 1)],\n", 1295 | " [TransactionOutput(walter.address, 5.0), TransactionOutput(alice.address, 10.0)]\n", 1296 | ")\n", 1297 | "\n", 1298 | "block3_malicious = Block([t6], ancestor=block2_malicious, miner_address=bob.address)" 1299 | ] 1300 | }, 1301 | { 1302 | "cell_type": "markdown", 1303 | "metadata": {}, 1304 | "source": [ 1305 | "Now the rule in Bitcoin is that everybody trust the longest chain. So let's check which one is the longest" 1306 | ] 1307 | }, 1308 | { 1309 | "cell_type": "code", 1310 | "execution_count": 31, 1311 | "metadata": {}, 1312 | "outputs": [ 1313 | { 1314 | "name": "stdout", 1315 | "output_type": "stream", 1316 | "text": [ 1317 | "Chain length for block2 : 3\n", 1318 | "Chain length for block3_malicious : 4 \n" 1319 | ] 1320 | } 1321 | ], 1322 | "source": [ 1323 | "def chain_length(block):\n", 1324 | " if block.ancestor is None:\n", 1325 | " return 1\n", 1326 | " else:\n", 1327 | " return 1 + chain_length(block.ancestor)\n", 1328 | "\n", 1329 | "print(\"Chain length for block2 : %d\" % chain_length(block2))\n", 1330 | "print(\"Chain length for block3_malicious : %d \" % chain_length(block3_malicious))" 1331 | ] 1332 | }, 1333 | { 1334 | "cell_type": "markdown", 1335 | "metadata": {}, 1336 | "source": [ 1337 | "The accepted ledger is now `block3_malicious`, as it is the longest ledger. Bob has successfully re-written the ledger. On the other hand, he still has the car that he got from walter in exchange of the 8 coins, it's just that the transaction where walter received 8 coins isn't considered the truth so walter won't have those 8 coins.\n", 1338 | "\n", 1339 | "This is a 51% or [Majority attack](https://en.bitcoin.it/wiki/Majority_attack). \n", 1340 | "\n", 1341 | "In practice, however, Walter had a head start on computing `block3 = Block([t6], ancestor=block2, miner_address=walter.address)`, and that `block3` would be accepted by the network. Bob would either have to be very lucky and find the correct nonce very quickly, or would have to keep mining blocks slightly faster than Walter to eventually create a longer ledger which incorporates his modified history.\n", 1342 | "\n", 1343 | "There are three interesting things we can learn from this example :\n", 1344 | "- Even with a majority of mining power, an attacker can only change one of his transaction. He can't create money\n", 1345 | " out of thin air (this would require invalid blocks) or spend other people's money (he wouldn't be able to sign\n", 1346 | " the transaction).\n", 1347 | "- An expensive proof of work is required to make sure it's costly to create valid blocks. If the proof of work was\n", 1348 | " trivial, the network would be spammed by concurrent chains and it would never reach a consensus.\n", 1349 | "- The \"trust the longest chain\" rule is the reason why you wait for a number of confirmations before considering\n", 1350 | " a transaction done. The number of confirmation of a transaction is how many blocks behind the last one a transaction\n", 1351 | " is. The more confirmations you wait for, the costlier it become for an attacker to change his transaction." 1352 | ] 1353 | }, 1354 | { 1355 | "cell_type": "markdown", 1356 | "metadata": {}, 1357 | "source": [ 1358 | "# Main differences with Bitcoin \n", 1359 | "\n", 1360 | "As explained above, the goal is not to have a conformant Bitcoin implementation and therefore many things are not implemented. The most important are :\n", 1361 | "\n", 1362 | "- This uses the wallet public key as its address. Bitcoin uses [more complicated addresses](https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses)\n", 1363 | "- Bitcoin uses ECDSA for signature, this uses RSA.\n", 1364 | "- Bitcoin uses two rounds of SHA256, this uses one\n", 1365 | "- Bitcoin transactions use a [scripting language](https://en.bitcoin.it/wiki/Script). Here we just have\n", 1366 | " simple transactions with one amount per output\n", 1367 | "- Bitcoin requires a way to broadcast blocks to all the nodes. The whole communication part is left out from this notebook.\n", 1368 | " See the [Client Node Discovery](https://en.bitcoin.it/wiki/Satoshi_Client_Node_Discovery) and [Protocol documentation](https://en.bitcoin.it/wiki/Protocol_documentation) pages on the bitcoin wiki\n", 1369 | "- Bitcoin uses a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree) to save disk space by pruning spent \n", 1370 | " transactions.\n", 1371 | " \n", 1372 | "Thanks to HN users *westurner*, *vinn124*, *ivan_ah* and *magnat* for helping me figure out the main differences.\n", 1373 | "\n", 1374 | "## Security issues\n", 1375 | "\n", 1376 | "In addition to the differences above, there are a number of security issues with this implementation (pointed out by HN user *magnat*) :\n", 1377 | "- Use of floating point arithmetic can lead to many problems (e.g. loss of precisions).\n", 1378 | "- The JSON serialization as implemented here is not reproducible across platforms. For example, two serialization of\n", 1379 | " the same transaction on two different platforms could produce two different JSON (line endings, keys ordering, spaces).\n", 1380 | "- The mine method implemented here can loop forever due to integer overflow." 1381 | ] 1382 | } 1383 | ], 1384 | "metadata": { 1385 | "kernelspec": { 1386 | "display_name": "Python 3", 1387 | "language": "python", 1388 | "name": "python3" 1389 | }, 1390 | "language_info": { 1391 | "codemirror_mode": { 1392 | "name": "ipython", 1393 | "version": 3 1394 | }, 1395 | "file_extension": ".py", 1396 | "mimetype": "text/x-python", 1397 | "name": "python", 1398 | "nbconvert_exporter": "python", 1399 | "pygments_lexer": "ipython3", 1400 | "version": "3.6.2" 1401 | } 1402 | }, 1403 | "nbformat": 4, 1404 | "nbformat_minor": 1 1405 | } 1406 | -------------------------------------------------------------------------------- /misc_math/homography_2d.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Solving 2D homography from 4 points correspondences" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Small experiment to solve a 2D homography turning any quadrilateral into another quadrilateral" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": { 21 | "collapsed": true 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "import numpy as np\n", 26 | "import numpy.linalg as la\n", 27 | "import pylab as pl\n", 28 | "%matplotlib inline\n", 29 | "np.set_printoptions(suppress=True, precision=5)" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 2, 35 | "metadata": { 36 | "collapsed": true 37 | }, 38 | "outputs": [], 39 | "source": [ 40 | "# Source and target quadrilaterals\n", 41 | "x = np.array([\n", 42 | " [2, 2],\n", 43 | " [2, 4],\n", 44 | " [4, 4],\n", 45 | " [4, 2]\n", 46 | "])\n", 47 | "\n", 48 | "xp = np.array([\n", 49 | " [2, 2],\n", 50 | " [2.5, 4],\n", 51 | " [3.5, 3.5],\n", 52 | " [4, 2]\n", 53 | "])" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 3, 59 | "metadata": {}, 60 | "outputs": [ 61 | { 62 | "data": { 63 | "text/plain": [ 64 | "" 65 | ] 66 | }, 67 | "execution_count": 3, 68 | "metadata": {}, 69 | "output_type": "execute_result" 70 | }, 71 | { 72 | "data": { 73 | "image/png": "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\n", 74 | "text/plain": [ 75 | "" 76 | ] 77 | }, 78 | "metadata": {}, 79 | "output_type": "display_data" 80 | } 81 | ], 82 | "source": [ 83 | "pl.figure(figsize=(10, 5))\n", 84 | "pl.subplot(121)\n", 85 | "pl.scatter(x[:,0], x[:,1], c='b')\n", 86 | "pl.subplot(122)\n", 87 | "pl.scatter(xp[:,0], xp[:,1], c='r')" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "## Solving for the homography\n", 95 | "\n", 96 | "See _Hartley & Zissermann \"Multiple view geometry in computer vision\"_, section 4.1 and equation 4.3 for reference.\n", 97 | "\n", 98 | "We want to solve for the homography $H$ such that\n", 99 | "$$x_i' = Hx_i$$\n", 100 | "Which can be rewritten as a cross product\n", 101 | "$$x_i' \\times Hx_i = 0$$\n", 102 | "\n", 103 | "With the $j-th$ row of H denoted as $h^{jT}$, then we have\n", 104 | "\n", 105 | "$$Hx_i = \\left( \\begin{array}{c} h^{1T}x_i \\\\ h^{2T}x_i \\\\ h^{3T}x_i\\end{array} \\right)$$\n", 106 | "\n", 107 | "So now the cross-product above can be rewritten (assuming $x_i'(k)$ denotes the k-th coordinate of $x_i'$)\n", 108 | "\n", 109 | "$$\n", 110 | "x_i' \\times Hx_i = \\left(\n", 111 | " \\begin{array}{c}\n", 112 | " x_i'(1) h^{3T}x_i - x_i'(2) h^{2T}x_i \\\\\n", 113 | " x_i'(2) h^{1T}x_i - x_i'(0) h^{3T}x_i \\\\\n", 114 | " x_i'(0) h^{2T}x_i - x_i'(1) h^{1T}x_i\n", 115 | " \\end{array} \\right)\n", 116 | "$$\n", 117 | "\n", 118 | "Since $h^{jT}x_i = x_i^T h^j$, we can rewrite as\n", 119 | "\n", 120 | "$$\n", 121 | "\\left(\n", 122 | "\\begin{array}{c c c}\n", 123 | "0^T & -x_i'(2)x_i^T & x_i'(1)x_i^T \\\\\n", 124 | "x_i'(2)x_i^T & 0^T & -x_i'(0)x_i^T \\\\\n", 125 | "-x_i'(1)x_i^T & x_i'(0)x_i^T & 0^T\n", 126 | "\\end{array}\n", 127 | "\\right)\n", 128 | "\\\n", 129 | "\\left(\n", 130 | "\\begin{array}{c}\n", 131 | "h^1 \\\\\n", 132 | "h^2 \\\\\n", 133 | "h^3\n", 134 | "\\end{array}\n", 135 | "\\right)\n", 136 | "\\\n", 137 | "=0\n", 138 | "$$\n", 139 | "\n", 140 | "Note that this is a 3x9 matrix multiplying a 9x1 vector containing the unknown (the row of H).\n", 141 | "\n", 142 | "The third row can be obtaine as a combination of first and second and therefore we can drop it, so we have two remaining equations per point\n", 143 | "$$\n", 144 | "\\left(\n", 145 | "\\begin{array}{c c c}\n", 146 | "0^T & -x_i'(2)x_i^T & x_i'(1)x_i^T \\\\\n", 147 | "x_i'(2)x_i^T & 0^T & -x_i'(0)x_i^T\n", 148 | "\\end{array}\n", 149 | "\\right)\n", 150 | "\\\n", 151 | "\\left(\n", 152 | "\\begin{array}{c}\n", 153 | "h^1 \\\\\n", 154 | "h^2 \\\\\n", 155 | "h^3\n", 156 | "\\end{array}\n", 157 | "\\right)\n", 158 | "\\\n", 159 | "=0\n", 160 | "$$\n", 161 | "\n", 162 | "Which we'll solve as an $Ah = 0$ problem" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 4, 168 | "metadata": {}, 169 | "outputs": [ 170 | { 171 | "name": "stdout", 172 | "output_type": "stream", 173 | "text": [ 174 | "[[-0.22664 -0.17434 0.62761]\n", 175 | " [-0.06973 -0.33124 0.62761]\n", 176 | " [-0.03487 -0.06102 0.1046 ]]\n" 177 | ] 178 | } 179 | ], 180 | "source": [ 181 | "def compute_A_for_point(x, xp):\n", 182 | " \"\"\"\n", 183 | " Computes two rows of A for a single point correspondence x and xp\n", 184 | " \"\"\"\n", 185 | " assert len(x) == len(xp) == 2\n", 186 | " # convert to homogeneous\n", 187 | " x = np.r_[x, 1]\n", 188 | " xp = np.r_[xp, 1]\n", 189 | " \n", 190 | " return np.array([\n", 191 | " np.r_[np.zeros(3), -xp[2] * x , xp[1] * x],\n", 192 | " np.r_[xp[2] * x , np.zeros(3), -xp[0] * x]\n", 193 | " ])\n", 194 | "\n", 195 | "def compute_homography(x, xp):\n", 196 | " \"\"\"\n", 197 | " Given two arrays of corresponding points as 4x2 arrays, compute the\n", 198 | " homography H such that xp = Hx\n", 199 | " \"\"\"\n", 200 | " A = np.concatenate([compute_A_for_point(*pair) for pair in zip(x, xp)], axis=0)\n", 201 | " U, s, V = la.svd(A)\n", 202 | " # Solution is the last row of V (corresponding to the smallest eigenvalue)\n", 203 | " H = np.reshape(V[-1], (3, 3))\n", 204 | " return H\n", 205 | " \n", 206 | "compute_A_for_point(x[0], xp[0])\n", 207 | "H = compute_homography(x, xp)\n", 208 | "\n", 209 | "print(H)" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": 5, 215 | "metadata": {}, 216 | "outputs": [ 217 | { 218 | "name": "stdout", 219 | "output_type": "stream", 220 | "text": [ 221 | "-- 0\n", 222 | "x = [2 2]\n", 223 | "xp = [ 2. 2.]\n", 224 | "Hx = [ 2. 2.]\n", 225 | "-- 1\n", 226 | "x = [2 4]\n", 227 | "xp = [ 2.5 4. ]\n", 228 | "Hx = [ 2.5 4. ]\n", 229 | "-- 2\n", 230 | "x = [4 4]\n", 231 | "xp = [ 3.5 3.5]\n", 232 | "Hx = [ 3.5 3.5]\n", 233 | "-- 3\n", 234 | "x = [4 2]\n", 235 | "xp = [ 4. 2.]\n", 236 | "Hx = [ 4. 2.]\n" 237 | ] 238 | } 239 | ], 240 | "source": [ 241 | "def hdot(M, p):\n", 242 | " \"\"\"Convenience function to apply a 3x3 transform to a 2D point\"\"\"\n", 243 | " pp = M.dot(np.r_[p, 1]) # to homogeneous\n", 244 | " return pp[:-1] / pp[-1] # back to inhomogeneous\n", 245 | "\n", 246 | "for i in range(4):\n", 247 | " print(\"-- %d\" % i)\n", 248 | " print(\"x = %s\" % x[i])\n", 249 | " print(\"xp = %s\" % xp[i])\n", 250 | " print(\"Hx = %s\" % (hdot(H, x[i])))\n", 251 | " assert np.allclose(hdot(H, x[i]), xp[i])" 252 | ] 253 | } 254 | ], 255 | "metadata": { 256 | "kernelspec": { 257 | "display_name": "Python 3", 258 | "language": "python", 259 | "name": "python3" 260 | }, 261 | "language_info": { 262 | "codemirror_mode": { 263 | "name": "ipython", 264 | "version": 3 265 | }, 266 | "file_extension": ".py", 267 | "mimetype": "text/x-python", 268 | "name": "python", 269 | "nbconvert_exporter": "python", 270 | "pygments_lexer": "ipython3", 271 | "version": "3.6.2" 272 | } 273 | }, 274 | "nbformat": 4, 275 | "nbformat_minor": 2 276 | } 277 | -------------------------------------------------------------------------------- /misc_ml/curse-of-dimensionality_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/julienr/ipynb_playground/265ee3b540c49de6ac3f0921c2f88703feaccb20/misc_ml/curse-of-dimensionality_1.png -------------------------------------------------------------------------------- /misc_ml/curse-of-dimensionality_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/julienr/ipynb_playground/265ee3b540c49de6ac3f0921c2f88703feaccb20/misc_ml/curse-of-dimensionality_2.png -------------------------------------------------------------------------------- /misc_ml/curse_dimensionality.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Curse of dimensionality" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Based on this article : http://www.edupristine.com/blog/curse-dimensionality" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": { 21 | "collapsed": false 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "import numpy as np\n", 26 | "import pylab as pl\n", 27 | "%matplotlib inline" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "## Sampling" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | "This the sampling example of the original article.\n", 42 | "\n", 43 | "\n", 44 | "[Original image](http://content.edupristine.com/images/blogs/curse-of-dimensionality_1.png)" 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "metadata": {}, 50 | "source": [ 51 | "Let's say we want to sample a target percentage ($p$) of our points.\n", 52 | "If our points are in $n$ dimensions, we have to sample $x$ percent of the range along each dimension to\n", 53 | "achieve overall $p$ sampling rate.\n", 54 | "\n", 55 | "$$x^n = p$$\n", 56 | "\n", 57 | "So if we want to find $x$ for a given dimension\n", 58 | "\n", 59 | "$$x = p^\\frac{1}{n}$$\n", 60 | "\n", 61 | "Let's plot this" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 2, 67 | "metadata": { 68 | "collapsed": false 69 | }, 70 | "outputs": [], 71 | "source": [ 72 | "# we want to sample 1% of our points\n", 73 | "p = 0.01\n", 74 | "n = np.arange(1, 50)\n", 75 | "\n", 76 | "x = p**(1.0 / n)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 3, 82 | "metadata": { 83 | "collapsed": false 84 | }, 85 | "outputs": [ 86 | { 87 | "data": { 88 | "text/plain": [ 89 | "" 90 | ] 91 | }, 92 | "execution_count": 3, 93 | "metadata": {}, 94 | "output_type": "execute_result" 95 | }, 96 | { 97 | "data": { 98 | "image/png": "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\n", 99 | "text/plain": [ 100 | "" 101 | ] 102 | }, 103 | "metadata": {}, 104 | "output_type": "display_data" 105 | } 106 | ], 107 | "source": [ 108 | "pl.title('Percentage of range to sample along each axis\\nto achieve 1% overall sampling rate')\n", 109 | "pl.plot(n, x)\n", 110 | "pl.xlabel('dimensionality')\n", 111 | "pl.ylabel('percentage of range')" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "metadata": {}, 117 | "source": [ 118 | "## Distance" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "This is the distance example. \n", 126 | "\n", 127 | "\n", 128 | "[Original image](http://content.edupristine.com/images/blogs/curse-of-dimensionality_2.png)\n", 129 | "\n", 130 | "(Image linked from the article)\n", 131 | "\n", 132 | "Given a square (or a cube or hypercube), we want to find what volume of the hypercube is covered by the biggest possible hypersphere. Since many algorithms rely on euclidean distance to define nearness, this gives an idea of how many points will fall in our sphere if we choose the biggest possible radius. This should be high for our algorithms to work as intended.\n", 133 | "\n", 134 | "For example, if you're in 2D and you have points in the ranges [0, 10], [0, 10], you would except that setting a distance threshold of 10 will include most of the points (obviously missing the points in the corner). If we increase the dimensionality, the volume of the \"corners\" will increase proportionally to our sphere's volume.\n", 135 | "\n", 136 | "The volume of an hypersphere of dimension n can be [computed recursively](https://en.wikipedia.org/wiki/Volume_of_an_n-ball#Recursions) :\n", 137 | "\n", 138 | "$$V_2(R) = \\pi R^2$$\n", 139 | "\n", 140 | "$$V_3(R) = \\frac{4}{3} \\pi R^3$$\n", 141 | "\n", 142 | "$$V_n(R) = \\frac{2\\pi R^2}{n} V_{n-2}(R)$$" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 4, 148 | "metadata": { 149 | "collapsed": false 150 | }, 151 | "outputs": [], 152 | "source": [ 153 | "# volume of n-spheres of radius R\n", 154 | "dims = np.arange(15)\n", 155 | "R = 1.0\n", 156 | "S_n = [1,\n", 157 | " 2 * R,\n", 158 | " np.pi * R**2,\n", 159 | " 4.0/3.0 * np.pi * R**3]\n", 160 | "for n in dims[4:]:\n", 161 | " S_n.append((2 * np.pi * R**2 / float(n)) * S_n[n - 2])\n", 162 | "S_n = np.array(S_n)" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 5, 168 | "metadata": { 169 | "collapsed": false 170 | }, 171 | "outputs": [], 172 | "source": [ 173 | "# volume of hypercubes of edge length 2*R\n", 174 | "C_n = np.array([(2*R)**n for n in dims])" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 6, 180 | "metadata": { 181 | "collapsed": false 182 | }, 183 | "outputs": [ 184 | { 185 | "data": { 186 | "text/plain": [ 187 | "" 188 | ] 189 | }, 190 | "execution_count": 6, 191 | "metadata": {}, 192 | "output_type": "execute_result" 193 | }, 194 | { 195 | "data": { 196 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtcAAADhCAYAAAADMpnEAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XlYlGX3wPEvu4p77iuYGybLIOKSC2qplbstWpoL7pl7\npbao1Vtvv8pELfVVFM3CJbdS00wlrRRUwN3MBVfccQFxAeb3x/0MDDisCg8D53Ndc83Ms815ZiY7\nczjPfYMQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEEIIIYQQQgghhBAF\nih9wTu8gdNQddf53AM88fN03gM15+HomzwL/os63Sxa2DwI+0R63BI7lTlg5thHomwevkwTU0h7P\nAT7Ig9cUQgghhE42AdMsLO8KRAO2GezrR+FOrk8CnXP5NVxQyVlGn0Ne2Qq8nY3tFwEf51Is1sQ8\nuTbnR+H+70cIkU354X8EQojMBQF9LCzvCyxFJQbiUTZADeBIHr6e3nJyvvkhbiGEEEKIPFMUuIn6\ns71JGSAecAecgBnABe32DeCobedH6spb2gpdECltAX7AeeAd4ApwEegGvAgcB64DE832tdGenwCu\nAcu1uCwpDazXjnsD+AWoara+P6rKfBs4BbyeznF8gV1AjBbfLMDBwnZOQKx2vrGoVgnI2vmPAy5r\nx+9vtm1R4GsgCvV57ACKAGe1497R4m+q7bfTbN/mwB5tvzCgmdm6EFT1+E9t/83AU+mcP8Bg7Xyu\nA+uAytryk0AicFc7jqX3xQCEa+uXAcFpzt/8uxIFTAAOaOcWCFQEfgVuAVtQn6tJU+Bv1GcTCbTO\n4jkWQf1IvKbtGwaUN9vPX3tsg2rXiEJ9PouBkto6F9Rn8CZwBrgKTDZ7/cy+N+bfiyDtPSmG+m8s\nkZTPtjLq/S1rtq836ntthxBCCCGsxv+A+WbPh6KSJFBJy99AOe32Fyl/6vcj4+R6UZptH6ISGDtg\nECrh+QFwBhqgEoua2vajtdetgkpU5gI/phN/WVT/cxGgOLACWKOtc0Yla3W05xW117LEG5Uo2Wpx\nHNHiSE/a883K+U9Fnf8LQBxQSlv/LbANlWDZopJJRy2OtG0h/UlJrsuikro3tG16oX5gmH6IhKCS\n5dqo92c78Hk659MWlTh6aa89E/jDbP1pbRtLHFGJ52jt/HoCD0j/u3Ia9fmWR33Gl1HfOU/Uj5et\nwEfatlVR35WO2vPntOemBDqjcxwK/Kwtt0H9ACihrdsODNQeD9SO4YL6zqwClmjrXFCfwTwtNg/g\nHlBPW5/Z98b8e2H+nWjNo20hG4BhZs+/AQIQQgghhFV5FpWgmSrSf5GSHJwgJakBaI9KjCBrybV5\n5fIuKW0CJbTtG5ttv5eUC+WOkjqRq4xK1rLScuaFSjBBJUoxQA9UdTg7xgCrM1ifleQ67fmbx3+Z\nlKTsLuovBWm5kHFy3RfYnWafv4F+2uPtpK6yDkdVhy0JBP5r9twZ9Z7X0J5nlFy3Qv1lw1xGP8RO\nA73Nnv+E+oFhMpKUH0jvkZLommxCVZIh43McoMVh6b01T663kjqprUvK980F9RlUMVsfCrxm4Zjw\n6PcmbXKdXjUf7Zh/ao/tUNc9+KTzOkKIQkZ6roWwHn+hKoHdgadRCa+pSlwFVZE0OUvqJCM7rgNG\n7XG8dn/ZbH08qvIMqgK4BpUYx6CqgQmoynNaxVBVxShUlfoPVEXYBlUdfg2VOF1EtY/Us3AMUAnV\nelRCcwv4Dxm3UGTXdVL3sN9FnW85VGX1ZA6OWQX1mZg7Q+rP6JLZY/P3OK3KpP6s41AxV7W8+SNx\npE2uz1ja0Ezaz978+T1SfxdeIeW7EIP6QVjJbPv0zvF7VJvIMi2+LwB7C7GkPfez2nbm3zfz17iL\n+vEBT/Z7sw71lxUX4HnteHtzeCwhRAEjybUQ1mUJqhLYB1UVvKotv4j6H71JDW2ZJXdRia5JZVKS\n6ew6i6qYlzG7FUMlMGmNRyU4vqikujUqsTZVyX9DVdwroYaDm2/hGKCGSTuCai8oBbxP9v4ty+n5\nX0Mlk7UtrMts/wuktNKY1OTRRDcr0n7WzqgkMSvHiubRJDxtXJlJ7+LHs6gk2fy7UAL4vywcMwFV\nPX8G1ZveiZSKtzlL3/MEUif86cnu98aY5t7cPWAl6r/DPjxasRdCFGKSXAthXZagKmWDUBdzmQSj\n+qRNPdcfoRIdSyJRvb92qMS41WPEMxf4jJSWhPKkP7ZycVS18haqB3mK2boKqGEFnVE9z3Goi8jS\nO84dVJJcH9VekB05Pf8kYCEwHZWQ26EuSnRE/chJQv1FwZJfUT8seqMqra9psa832yarI3YEo9oo\nTH3Pn6FaTtJWxi35G5WMjkL1yPcgdcvP41iKGvKwPeq9KYJqqTBP5tM7xzaolhA71Gf7EMuffzAw\nFpVgF0ed+zKyNlpOdr435j/6LqN+vJRMs80S1OfQhfT/WxNCFEKSXAthXc6g2kOKoS4AM/kU9Wfp\nA9ptr7bMxLz6NhqVBMWgRuRYQ2ppK3UZVWUDtDh+Q42ksAtVmbZkBqqf+hoqyfvV7Ni2qKTpAqrF\noSXpJz8TtLhvoy7yXJZJjGnXZff80772QdSoH9dRF+TZoBK2/6A+mxtAE+04pmNdR1Vjx6POf4L2\n/IbZsY1pHqcXx1bgQ9TFfBcBV9QFklnxEJVQ99dielU7jrnMqvDpxXke9QNpMmrkjLOo87XJwr4V\nUZXgW6jqcgiWE9aF2vIdqBFl7pJ6TO/MPruMvjfpxXYMldSfQn1epjaXv1BJ/T5kHGwhhBBCCCEe\n2++kXGwphBBCCCGEyKHGqEq2c2YbCiGEEEIIIdK3GDUZkKWLLoUQhdxjT3lrb29/OyEhoUTmWwqR\nO+zt7e8kJCSkvdhICCGEECLPPXZyDRiNxpyO4iXE47OxsYEn810WQgghhHgsMlqIEMLa1UMNr3cb\nNWOgEEIIoRtJroUQ1u5d1PB0JYHZT+B4U1FD1t1B9dXuRg0NmF0OqOnCT6OGbGudyfZlUcMCxqJm\nseydZn071LBwccA2UsYWN/kCNczfNVJPjw5qXOjt2r5HtWOZex01zGOsFkMZs3VOqCHwbqEmoRmb\nyXkIIUShJsm1EMLa1USNjZwTdhaWGVHjGpdATR7yOypJzokdqBn8LpH5+NHfomb+q4Ca5GYOaopt\nUBMDrULNKlgGNY75crN9h6LGmPbQbp21ZSbBqPGYy2rH+Ek7JqhZEedqr1kRNXb0d2b7TkVNjlMD\nNdnLu0CHTM5FCCHEYzAWRP369TP+9NNPeofxCGdnZ71DyHfI+dTdwvptQ804GI9qCzFNbb0ENZFJ\nFCqZNPXk90dN/jEdVeH92MIxp5J6ApMGqMpzOQvbZtU5Mp4J0hm4T+qp1RejJqkBGAL8abauGCoJ\nrqs9/xs1a6fJANSEPmjb3CP1kHF/kJJ8f4aaXdGklhaLafsLwHNm66ehknUhhBAWSOU6HdpFcjli\nNBox5tJFno8TlxAFUFtgJ/AWqi3kBDALVXV2RbVivIlKNk18gZOoCvFnmRzfUdv/JCoZB1XBjcng\nltXZEs3VRf1IOGG2bD+qqox2v99s3V1tW9P6BmnWH0iz7ylUS0hWjn0KlVzXRVXJK2dwbCGEEGlY\nfXIdFRWFm5sbQ4YMoWHDhnTo0IF79+49sl1cXBwvvfQSXl5euLu7s3LlSgBcXFx477338PDwoEmT\nJpw8eTJ5nx07dvDss8/y9NNPs2pVygzBX375Jb6+vnh6ejJ16tTkOOrVq0e/fv1wd3fn3LlzFrcz\nN2/ePN59993k50FBQbz9tprJd/r06bi7u+Pu7k5AQMAj+4aEhNC5c+fk5yNHjmTx4sXJ5zR58mQM\nBgM+Pj6Eh4fTvn17ateuzbx58zI8DyGslOlXpx3wGjAJlUyeAb4G+pptexHVgpGEquha8ioqUb6L\nqgi/aLbuLCrpTO+2LAfxF0dV3s3dQf1ISG/97TTrb6VZVzyddaZjm9Y7W1hvOrZpm7THluFXhRAi\nHVafXAOcOHGCkSNHcujQIUqXLp0qETbZtGkTVatWJTIykoMHD9Khg2oZtLGxoXTp0hw4cICRI0cy\nZswYQFWfL126xF9//cX69euZOHEiAL/99hsnTpwgLCyMiIgI9u3bx86dO5PjeOuttzh06BDHjh1L\ndzuTnj17smbNmuTnK1asoHfv3uzbt4+goCDCwsLYvXs38+fPZ//+/WTExsYmuaptY2NDzZo1iYiI\noFWrVvTv3581a9awe/dupkyZkul5CGGFTH8qKoe6kPCM2bqzQFWz5+eycLzlqES5InAIePsJxJiR\nWFTl3VwpUhLqO+msv5PO/qW0ZRkd23zfUumsNx0j7bHvIIQQwiL73Dy4zbTHaK2YkvW2CldXVzw8\nPABo1KgRUVFRj2zj4eHBhAkTmDhxIp06daJFixbJ63r3Vhfl9+rVi7Fj1YXwNjY2dOvWDQA3Nzcu\nX74MqKT0t99+w2AwAKoifuLECapXr07NmjXx9fXNcLuWLVMGHShXrhy1atUiNDSU2rVrc+zYMZo3\nb05AQAA9evSgaNGiAPTo0YMdO3bg6emZ5fekS5cuALi7uxMXF4ezszPOzs44OTlx69atLMUnhBW6\nhhrpwwU1KgaoNo7zZttk9o+LkZRK+HVUv/MBYAaqZaIGcDiD/YeQ/Z7k46h/j2uT0hriafY6h4F+\nZts7oy4yNF/vhbrQ0bTvIbN1tVBV6Fiz9d+brTf/x+VpVDvMcVT1P1o79u8Wji2EECKNXE2us5Mg\nPw4nJ6fkx3Z2dsTHx3P+/Hk6deqEjY0Nw4cPZ8iQIURERLBhwwY++OAD2rVrx4cffvjIscx7mh0d\nHZMfm/dQT5o0iSFDhqTaLyoqCmdn51TLLG2XVq9evVixYgX169enR48eyTGYv57RaHyk19re3p6k\npKTk5/Hx8anWm94TW1vbVOdha2tLQkJCluMTwkqY/gNJBFYA/0H1Sj+FGjruyxwcy+Q48AvwDjAc\nVQnPaluEk9nxnIAiWG5FiQNWoy6wHAR4o0b8aKatX4M6hx7ARmAKamzv49r6JcA4bZ2N9tjUT3Zc\n23YK8CGqxaUhavQRgB9QFz+2ACKAT7R1cWbH/gCVuFfW4jNP9IUQQpgpEG0hllSrVo3IyEgiIiIY\nMmQI0dHRFClShDfeeIMJEyYQERGRvO3y5cuT75s3b57hcTt06MDChQuJi1P/37lw4QJXr17N8Xbd\nu3dn7dq1BAcH06uXug6qZcuWrF27lvj4eOLi4li7du0jFeWaNWty5MgRHjx4wM2bN9m2bZvFeC1d\nWGljY5Pl+ISwEuZf9LdRieEp1MWOPwCLzLbLSuU67TZfopL1CtmM6x9U33YVYLMWl2l86smoZNhk\nBFAUNcrJUmAYKdX3a0BP1I+GG4APqS+cnIf6AXAQVWX/Bfif2fpe2j43tGP0RFXlQQ1jOAz1Pl3W\nYhhhtu8U1AWdZ1BjZX8B/JaN90AIIQqVrFSuSwMLUFeHG4GBqEkV8o20VV1LI2ocPHiQd955B1tb\nWxwcHJg7d27yupiYGDw9PSlSpAjBwcEWj2N6/Pzzz3P06FGaNVMFpRIlSrB06dJUPc8ZbVe+fPlU\ncZUuXZoGDRpw9OhRfHx8ADAYDPTv3z+5xWTw4MHJLSGm16hevTqvvvoqDRs2xNXVFW9v73Tfm+yc\nR9r4hLACbdI8v0nqCxjNLdZuGZlmYVkYqYeyyyqXDNalHakkBuiewfZbAbcM1r+n3Sw5w6Pvk7lg\n0m9leQD4azchhMhvFgIvoQoT7toyX9SkYg6okZhGAHu0dZNQuWwiMIqUYkEjIAj1F8aNwGhtuRPq\nL3jeqKLEa6S+ridHFmtBgErG0174ouMIx4/PxcXFeP36db3DEI8BGedaCCGEKKxaAgbUX+5MQkiZ\n7OoF1F/dQA1bGolKul1Q17iYKpBhqKQcVHLdUXs8gpSJtV4jCyNCZdYWUkoLeqH2PIFHh2yyajJu\ntBBCCCGE1dqJ+sufuWhSisGlUZNhgZrJNhh14XsUKrlugrqepAQqwQZVqe6mPe5Cyl88VwHtMgso\ns7YQV+Aqql/REzV97mhUD2GBcOrUKb1DEEIIIYQQT85E1Ky2X6EKyaaLw6uQurX5PGqo1oekHlXq\nAilDuFYlZQhXU5G5LOoaFosyS67tUT0mI1G9KjO0gD/KZD8h8pq0hgghhBAF30nUsKUZCUT1U68B\nXkF1YDyfy3Ely6wt5Lx2MzWB/4RKtoXIV4zalPN5fZsyZYpur13Yb/Ley/tfmG/y/st7X1hvqLH4\nM+OLSqxB5a6mXuoLQHWz7aqh8twL2uO0y037mEZ5Ml17mG7VGjJPri+hSuF1tefPkfHkCUIIIYQQ\nQujpBNBae9yWlDkBfkYNTeqIan2ug+qzvoSaEbcJ6gLHvsA6s31MY/u/jBq5KUNZGYrvbdT4p46o\nUvyALOwjhBBCCCFEbgtGJdLlUAXhj1Az5X6LGkYvXnsOalz/Fdq9aYg+U1vpCNRQfEVRo4Vs0pYH\noma0/Rc1FJ/5HAMWZSW53g80zsJ2Vq948eLExsZmvmEaL730EsHBwZQsWTIXonp8QUFB7Nu3j1mz\nZuXJ67m4uBAeHk7ZsmXz5PX05Ofnp3cIhZa89/qS919f8v7rR977fKd3OsubpLP8Mx6dZwDUoB3u\nFpbfB17NTkBPYhw6o9YDY/VKlCjBnTt39A7jiVu8eDF79+7Ns+Ta1dWVffv25VlynXa6eCGEEEIU\nTNoQyvl6HOUCMf350qVLadKkCQaDgWHDhpGUlASoSvQHH3yAl5cXzZo148qVKwBcvnyZ7t274+Xl\nhZeXF7t3p55w0mg08s477+Du7o6HhwcrVqwAIDo6mlatWmEwGHB3d+evv/4CVKX2xo0bREVF4ebm\nxpAhQ2jYsCEdOnTg3r17AOzZswcPDw8MBkPysdMKCQnBz8+PV155BTc3N/r06ZPuOX/xxRd4eHjg\n5eXF5MmTAfVret++fQBcu3YNV1fX5PM5d+4cbdq0oW7dunz88ceZvncmmzZt4tVXU36whYSE0Llz\nZwCCg4Px8PDA3d2diRMnPhJjVFRUqvP86quvmDZtWnKs48aNo3Hjxri5ubFnzx66d+9O3bp1+fDD\nD7McnxBCCCFEfmL1yfXRo0dZsWIFf//9NxEREdja2vLDDz8AcPfuXZo1a0ZkZCStWrVi/vz5AIwa\nNYo2bdoQGRlJeHg4DRo0SHXM1atXs3//fg4cOMDvv//OO++8w6VLl/jxxx/p2LEjERER7N+//5Ep\nyQFOnDjByJEjOXToEKVLl2bVqlUADBgwgPnz5xMREYG9vX26k9dERkYSEBDAkSNHOHXqVHICb+7X\nX3/l559/JiwsjMjISN59993kONI7blhYGKtXr+bAgQOsXLmSffv2ZfjemTz33HOEhoYSHx8PwPLl\ny+nduzcXL15k4sSJbN++ncjISPbs2cO6dessvXQy8/hsbGxwcnJiz549DB8+nK5duzJ37lwOHTpE\nUFAQMTExWYpPCCGEECI/yUrPdY49zuSHWf0r/9atW9m3bx8+Pj4AxMfHU6lSJQAcHR156aWXAGjU\nqBFbtmwBYPv27SxduhQAW1vbR3ql//zzT15//XVsbGyoUKECrVu3Zs+ePfj6+jJw4EAePnxIt27d\nkpNrc66urnh4eCS/ZlRUFLdu3SI2NpYmTVT7z+uvv8769estno+vry9VqlQBwMvLi6ioKJ599tlH\nznngwIEUKVIEgNKlS2f6PrVv354yZcoA0KNHD/7880/s7OzSfe9M7O3t6dixIz///DM9e/Zk48aN\nfPXVV/z++++0adOGp556CoA33niDHTt20LVr1wzjMG/f6NKlCwANGzakYcOGVKxYEYBatWpx9uxZ\ndu7cmWl8QgghhBD5Sa4m13nVBtuvXz8+++zR3nQHB4fkx7a2tiQkJCQ/z6hH11IPr42NDS1btmTn\nzp2sX7+e/v37M27cOPr27ZtqOycnp+THdnZ2yRVfcxm9dtr9ExISCAsLY+jQoQDJLR2WjmFvb5/c\nNmFqR7HEaDQmV5DTe+/M9erVi9mzZ1O2bFkaN26Ms7PzI++R+TEtxQMqOTbfxnSutra2qc7b/LPK\nSnxCCCGEEPmF1beFtGvXjp9++omrV68CcOPGDc6ePZvpPnPmzAEgMTGR27dvp1rfsmVLli9fTlJS\nElevXmXHjh34+vpy9uxZypcvz6BBg/D39yciIiJLMZYqVYoSJUoQFqamrF+2bFm2ztHX15eIiAgi\nIiLo3Lkzzz//PIsWLUpO3GNiYgDV+713714Afvrpp1TH2LJlCzExMcTHx7Nu3TpatGiR5feudevW\nhIeHM3/+fHr1UiPQNG7cmD/++IPr16+TmJjIsmXLaN26dar9KlasyJUrV7hx4wb3799Pt1pviY2N\nTY4+WyGEEEIIPVl9cu3m5sann35K+/bt8fT0pH379ly6dAlI3Qtt3u8bEBDA9u3b8fDwwMfHh6NH\nj6bavnv37nh4eODp6Um7du348ssvqVChAiEhIXh5eeHt7c3KlSsZPXq0xdcxZ3oeGBjI4MGDMRgM\n3L17l1KlSj1yLpZ6pi31UHfo0IEuXbrg4+ODwWDg66+/BmDChAnMmTMHb29vrl+/nqq/2dfXl549\ne+Lp6cnLL7+Mt7d3hu+dOVtbWzp16sSmTZvo1KkTAJUrV+a///0vbdq0wcvLCx8fn+QLHU2v6+Dg\nwEcffYSvry/t27d/pLc9o/OGjD9bIYQQQoj8SIbiyyNxcXE4OzsD8N///pfLly/zzTff6BxVwSBD\n8QkhhBCFgzUMxZerPdcixYYNG/j8889JSEjAxcWFoKAgvUMSQgghhBBPmFSuhdWTyrUQQghR8N29\nC87O+b9ybfU910IIIYQQouCbMsXi4oXAZeBgmuVvA0eBQ8AXZssnAf8Cx4D2Zssbacf4FwgwW+4E\nLNeW7wZqZhanJNdCCCGEECJfi4yExYstrloEdEyzrA3QBfAAGgJfacsbAK9p9x2B70ipgs8B/IE6\n2s10TH/gurbsG1In6hY9ds91RrMNCpEXTJPjCCGEEKLgSUyEwYPh889h0KBHVu8EXNIsGw58DjzU\nnl/V7rsCwdryKOAE0AQ4A5QAwrTtlgDdgE2oJN1UM18FzM4s3sdOrhMSEqTfVQghhBBC5IrvvoNi\nxWDgQIvJtSV1gFbAZ8A9YAKwF6iCau0wOQ9URSXb582WX9CWo92f0x4nALeAssCN9F5cRgsRQggh\nhBD50sqVIbz3XggDB8K0aVnezR4oAzQFGgMrgFq5E6HlFxdCFBKJSYkcv36c8OhwDl05hJ2tHaWc\nSlHSqSQlnUpSqojZY215CacS2NvKPxVCCCHy3o8/+vHOO37JifW0rGXY54HV2uM9QBJQDlWRrm62\nXTVt2wva47TL0dbVAC6i8uZSZFC1BkmuhSiw7iXc49CVQ0RERxBxSd0OXj5IpeKVMFQ24FHBA4Ar\ncVc4ceMEt+7f4vb929y+fzvV4zv371DEvkiGCfgjj7VtzJeVcCqBrY1cQy2EECJr1q6FI0cgODj7\nuwJtgT+AuoAjcA34GfgRmI5q96iD6rM2ArdR/ddhQF9gpnasn4F+qHaSl4Gtmb24jHMtRAFw+/5t\nIi9FJifS4dHhnLhxgjpP1cFQyaBulQ14VvSkVJFS2Tq20Wgk7mGcSrrv3Xok+TYte2R5mm3iHsbh\n7OCc7QQ97fLijsXlImohhCjg7tyBZ56BJUvAzy9luYUZGoOB1sBTwBXgI2Apaog+L+ABMB4I0baf\nDAxE9U+PBjZryxsBQUBRYCMwSlvuBHwPGFCjhvRCXQyZLkmuhbAyl2MvEx4dnlyNjoiO4FLsJdwr\nuqdKpBtWaEgR+yJ6h5ssyZjEnft30k2+M0rQzdfHJ8RTwrEE5YqVo3n15rRzbUdb17ZUL1U98yCE\nEEJYhdGj4fZtWLQo9XJrmP48q8FFocrliagrKn3N1klyLUQuMBqNnL55OlVbR0R0BPcS7mGobEhO\npL0re1P3qbrY2drpHXKeSEhK4M79O0THRrPzzE62RW1j2+ltlC5SmrYubWnr2pY2rm2o4FxB71CF\nEELkwJ490LkzHD4MTz2Vel1BSq5Po8rllhq4JbkW4jElJCVw7NqxVIl05KVInB2cMVQ24F3JOzmh\nrlGqhrRFpJFkTOLQlUNsO60S7R1ndlCjVA3auqpku1XNVpQuUlrvMIUQQmQiIQEaN4Zx46Bv30fX\nF7Tk2gfVa5KWJNdCZEP8w3gOXjlIRHREcnvH4auHqVqiKt6VvZPbOgyVDJR3Lq93uFYpISmB8Ohw\ntp3extbTW9l9fjdu5dySk+0WNVpQzKGY3mEKIYRI4+uv4ddfYcsWsFRHKkjJ9SnUoNmJwDxgvtk6\nSa6FSMfNezcfaes4FXOKeuXqPXKhYQmnEnqHW2DdT7jP7vO7VWU7ahsR0RE0qtIouY2kSbUmONo5\n6h2mEEIUalFR4OMDu3ZBnTqWtylIyXVlIBooD2wB3kZNNwlgnDJlSvKGfn5++Jlf1ilEIWA0GomO\njX4kkb569yoeFT1S9Uc3KN8AJ3snvUMu1GIfxPLX2b/Yenor205v4/j14zSv3jy5sm2oZCg0PexC\nCJEfGI3QqRM0bw7vv5/+dgUpuTY3BYgFvtaeS+VaFDpJxiS2nNzCH2f+SG7tSExKTG7nMLV31C5b\nW5I0KxATH8MfZ/5I7tm+cOcCrWu2Tk62nyn/jPS5CyFELlq5EqZOhYgIcMzgD4kFJbkuBtgBdwBn\n4DdgmnYPklyLQuTO/TsERQYxK2wWxR2L07Ve1+SEulrJapKAFRCXYi+x/fT25DaS2AexKtHW2khq\nlakln7UQQjwhN2+qMa2XL4cWLTLetqAk167AGu2xPfAD8LnZekmuRYF3OuY0s8JmsXj/Ytq4tGFM\n0zE8W/1ZSbAKiaibUSrZjtrG1lNbcbBzSJVsVy1ZVe8QhRDCao0YAYmJMG9e5tsWlOQ6M5JciwLJ\naDSy48w4vNXrAAAgAElEQVQOZoTOYOeZnQzwGsBI35HULF1T79CEjoxGI/9c/ye5hWR71HbKFyuf\n3ELi5+JHuWLl9A5TCCGswq5d0LOnGtO6TJnMt5fkWggrdC/hHsEHgwkIDeBewj1GNxlNX8++FHcs\nrndoIh9KMiZx4PKB5GH//jz7J66lXWnr2pZ2ru1oWbMlJZ1K6h2mEELkOw8fgrc3fPABvPZa1vax\nkFwvBF5CTX3unmbz8cCXQDlS5mqZhJr+PBE1xbmpzdk0/XkR1PTno7XlTsASwBs1JPVrwJkMY8za\nqWRIkmtRIETfiWbO3jnM2zcPQyUDY5qOof3T7bG1sdU7NGFFHiY+ZO/Fvcn92mEXwmhYoWFyC0nz\n6s0p6lBU7zCFEEJ3n38OO3bAxo2Wx7S2xEJy3RI10MYSUifX1VFDR9cjZSLEBsCPQGOgKvA7UAcw\nAmHASO1+IzAT2ASMABpq968B3YFeGcaYtVPJkCTXwqrtvbiXgNAA1h9fT++GvXnb923cyrvpHZYo\nIO4l3GPXuV3Jw/4duHwA36q+tH+6PX09+kq/thCiUDp5Epo0UVOdu7pmfb902kJcgF9InVyvBD4B\n1pGSXE8CkoAvtG02AVNRlehtgOl//r0AP2CYts0UIBR17aFpaOp02Wf9dIQoOBKSElhzdA0zQmdw\n/vZ5RjYeycyOMylTNAsNX0JkQxH7IrRxbUMb1zaAGnFm59mdrDu2Dvc57jSv3hx/gz+d6nbCwc5B\n52iFECL3GY0wfDi89172Euts6AqcBw6kWV4F2G32/Dyqgv1Qe2xyQVuOdn9Oe5yAmlSxLCltJo+Q\n5FoUKjfib7AgfAGzw2ZTs3RNxjYdS7f63bC3lf8URN4o4VSCF+u8yIt1XmR6h+n8dOQnpu+ezvAN\nw3nT8038Df7UK1dP7zCFECLX/PgjXL4MY8bkyuGLAZOB582W5ekFkJJRiELhyNUjzAydyfLDy+lc\ntzNrXltDoyqN9A5LFHLOjs708+pHP69+HLt2jIURC2kd1Jo6T9XB3+DPKw1ewdnRWe8whRDiiblx\nAyZMgHXrwCELf6wLCQkhJCQkOy/xNKpNZL/2vBqwD2iCqkhXN9u2GqpifUF7nHY52roawEVU3lyK\nDKrWID3XogBLMiax+cRmZoTOIPJSJMMaDWOYzzAql6isd2hCpOth4kPWH19PYEQgf5/7m1efeRV/\ngz8+VXxkXHUhhNUbNAiKFoVZs3K2fzZ6rk1O8+gFjb6kXNBYG3VBYyhq9JAwYAOpL2h0B4ajerG7\nIRc0isIm9kEsiyMXMzNsJsUcijG6yWh6NexFEfsieocmRLZcuH2BoMggFkYupLhjcfwN/vTx6EPZ\nomX1Dk0IIbJtxw54/XU4cgRK5nCEUgvJdTDQGngKNRzfR8Ais/WnAB9Sqs2TUUPxJaCG29usLTcN\nxVcUNVrIKG25E/A9YEANxdcLiMowxmyekyWSXIt8IepmFLPDZrMochGta7ZmTNMxtKzRUqp9wuol\nGZMIiQohMCKQDcc38EKdF/A3+NPWta0MFSmEsAr374OnJ3z2GfTokfPjyCQyQuQyo9HIzrM7CQgN\nICQqhP6e/RnpOxLXMrlz+bEQersRf4MfD/7IgvAF3Lp/i4FeAxlgGEC1ktUy31kIIXTy8cewbx+s\nXZv1Ma0tkeRaiFxyP+E+yw4tIyA0gNgHsYxqMop+nv0o4VRC79CEyBNGo5Hw6HAWhC9g+eHlNK3W\nlEHeg+hUtxOOdo56hyeEEMn++QeefRYiIqB69cy3z4gk10I8YZdiLzF371zm7p2LR0UPxjQdQ8fa\nHeVP46JQu/vwLquOrGJBxAKOXTvGmx5v4u/tT/1y9fUOTQhRyBmN0LYtdO36ZIbek+RaiCckPDqc\ngNAAfv7nZ15t8CqjmozimQrP6B2WEPnO8evHWRixkMX7F/N0mafxN/jz6jOvypB+QghdBAXB7NkQ\nGgp2do9/PEmuhXgMCUkJrDu2jhmhM4i6GcVbjd9isPdgnir2lN6hCZHvPUx8yMZ/NxIYEcifZ//k\n5QYvM8h7EI2rNJaLfIUQeeLqVWjYEH79Fby9n8wxJbkWIgdi4mMIjAhkdthsqpSowpimY+hev7tM\nDS1EDl28c5HFkYsJjAikqENRBhkG0cejj/xQFULkqjffhPLl4euvn9wxJbkWIhuOXTvGzNCZBB8K\n5qU6LzG6yWgaV22sd1hCFBhJxiR2nNlBYEQgv/zzCx1qd2CQYRDtarWT6xaEEE/U1q0wcCAcPgzF\niz+540pyLUQmjEYjv538jRmhM9h3cR9DGw1leOPhVClRRe/QhCjQYuJj+PHgjwRGBHIj/gYDDQMZ\n4DWA6qUe81J+IUShFx8PHh7wzTfQqdOTPbYk10Kkw2g08uPBH/l056c42jkyusloejfsTVGHonqH\nJkShEx4dTmB4IMsOL8O3qi/+Bn+61OsiQ/oJIXLkgw/U8HsrVz75Y0tyLYQFR64eYcSGEdy+f5uv\n2n9FG5c2coGVEPlA/MN4Vh1dRWBEIEeuHqGPex/8vf1pUL6B3qEJIazE4cPg5wf790OVXPgjtDUk\n11ltsrMDIoBfcjEWUcDFPYhj0u+TaB3UmpcbvMyewXto69pWEmsh8omiDkXp49GH7f2289fAv3Cy\nd+K5Jc/RPLA5CyMWEvsgVu8QhRD5WFISDB0K06blTmKdjoXAZeCg2bIvgaPAfmA1UMps3STgX+AY\n0N5seSPtGP8CAWbLnYDl2vLdQM3MAspqcj0aOAJIiVrkyM///Mwz3z3D2dtnOTDsACN9R2Jn+wQG\nvBRC5IraZWvzWbvPODv2LJNaTGLdP+uo/k11Bv88mN3ndyN/sRRCpLVgASQmwrBhefqyi4COaZb9\nBjwDeALHUQk1QAPgNe2+I/AdKVXwOYA/UEe7mY7pD1zXln0DfJFZQFkpGVYDgoD/AOOAzmnWS1uI\nSFfUzShG/TqK49eP8+2L39KuVju9QxJC5FD0nWiW7F/CgogFFHMoxtimY+ndsDdO9k56hyaE0Nml\nS+DuDtu2qfvckk5biAuqu8LSK3cHegJ9UEl2EikJ8iZgKnAG2Aa4act7AX7AMG2bKUAoYA9EA+Uz\nijErletvgHe0YITIkgeJD/h85+f4/M+HJlWbsH/YfkmshbBylUtU5r0W73F85HG+fP5Llh1ahkuA\nC5/88QlX467qHZ4QQkdjx4K/f+4m1jk0ENioPa4CnDdbdx6oamH5BW052v057XECcAsom9EL2mcS\nUCfgCqrf2i+9jaZOnZr82M/PDz+/dDcVhcD209sZsXEET5d5mj2D9+BaxlXvkIQQT5CNjQ3tn25P\n+6fbc/jKYWbsnkHd2XV5pcErjGk6Ri6AFKKQ2bQJwsIgMPDJHzskJISQkJCc7v4+8AD48YkFlAWZ\ntYV8BvRFZepFgJLAKuBNs22kLUQAcCn2EhN+m8DOszuZ2XEmXep1kYsVhSgkrsRdYe7euczZOwev\nSl6MazqO52o9J/8GCFHAxcWpKc7nzYP27TPf/nFloy2kPzAYaAfc05ZN1O7/q92bWj7OANtJaQvp\nDbQChpPSOrKbJ9QWMhmoDrii+k+2kTqxFoLEpERmh83GfY471UpW48iII3St31X+pypEIVLBuQIf\ntf6I06NP82qDVxn/23g85noQGB7IvYR7mR9ACGGVPv4YmjfPm8Q6GzqiWpq7kpJYA/yMymcdUblt\nHSAMuATcBpqgEve+wDqzffppj18Gtmb24tnJfloD44EuaZZL5boQ23NhD8M2DKOEYwm+e+k7+XOw\nEAJQE0VtPb2V6bumEx4dzjCfYYxoPIIKzhX0Dk0I8YTs3w/PPw8HD0LFinnzmhYq18GoHLUcaki+\nKagLFx2BG9o2u4AR2uPJqD7sBNRoeJu15Y1QA3gURfVoj9KWOwHfAwbUqCG9gKgMY8z+aT1CkutC\nKCY+hve3vc+aY2v4v+f+jz4efaRSLYSw6OjVo8zYPYMVR1bQo34PxjYbS8MKDfUOSwjxGBITVcV6\n8GAYNCjvXrcgTSIjBKCqUUv2L6HBd6pCfWTEEfp69pXEWgiRLrfybszrPI9/3/6XWmVq0f779rT/\nvj2bTmyS8bKFsFJz5oCTEwwcqHck+Y9UrkWWHb5ymBEbRxD3II45L82hcdXGeockhLBC9xPus+zQ\nMr7Z/Q0PEh8wtulY+nj0oahDUb1DE0JkwYUL4OkJO3eCm1vm2z9J1lC5luRaZCruQRyf7PiEwIhA\npraeyjCfYTK7ohDisRmNRrZHbeeb3d8QdiGMoY2GMqLxCCoVr6R3aEKIDPTsCc88oy5mzGvWkFxL\nW4hIl9FoZO2xtTT4rgEX7lzg4PCDvOX7liTWQognwsbGhraubfml9y/s6L+Da3ev4fatGwPWDeDA\n5QN6hyeEsODnn+HQIZg8We9I8i+pXAuLTsecZtSmUZy4cYLvXvyONq5t9A5JCFEIXL97nf/t+x+z\n98zGrZwbY5uO5YU6L2BrI7UgIfR2546qWC9eDG10SgusoXItybVI5X7Cfb7e9TXTd01nfLPxjG8+\nHkc7R73DEkIUMg8SH7Di8Aqm75rO3Yd3GdN0DG96vkkxh2J6hyZEoTV2LMTEQFCQfjFIci2sytZT\nW3lr41vUfaouM1+YiUtpF71DEkIUckajkT/O/ME3u7/h73N/M7TRUN5q/BaVS1TWOzQhCpV9++Cl\nl1RLSLly+sUhybWwCtF3ohn/23j+Pvc3M19Q05YLIUR+8+/1fwkIDeCHgz/QuW5nxjYdi6GyQe+w\nhCjwEhKgSRMYPRre1HmebmtIrqWJrRBLTEpkVugsPOZ6ULNUTQ6POCyJtRAi36rzVB1mvzibk6NO\n8kz5Z+gc3Jk2i9vwyz+/kGRM0js8IQqsWbOgdGno21fvSKyDVK4LqbALYQxbP4xSRUrx7YvfyrTl\nQgir8zDxISuPrGT6runcvn+bMU3H0M+zH86OznqHJkSBcfYseHvDrl1Qp47e0UjlWuRDMfExDFs/\njK7LujKu2Ti2vblNEmshhFVysHPgdffX2TN4D4FdAtlyags1Z9Rk0u+TuHD7gt7hCWH1jEZ46y0Y\nMyZ/JNbpWAhcBg6aLSsLbAGOA78Bpc3WTQL+BY4B7c2WN9KO8S8QYLbcCViuLd8N1MwsIEmuCwmj\n0cjiyMU0+K4B9rb2HH3rKH08+si05UIIq2djY0PLmi1Z89oadg/aTdzDONznuNNndR/2Xdynd3hC\nWK3Vq+HkSXj3Xb0jydAioGOaZRNRyXVdYKv2HKAB8Jp23xH4jpQq+BzAH6ij3UzH9Aeua8u+Ab7I\nLCBpCykEDl85zPANw4lPiGfOS3PwqeKjd0hCCJGrbt67yfx985kVNgvXMq6MbTqWznU7yyRYQmTR\nrVtqTOtly6BFC72jSZFOW4gL8Avgrj0/BrRGVbQrASFAfVTVOomUBHkTMBU4A2wDTJO59wL8gGHa\nNlOAUMAeiAbKZxSjVK4LsNgHsby75V3aLG5D74a92e2/WxJrIUShULpIad559h1OjjrJcJ/hfLbz\nM+rNrses0FnEPojVOzwh8r3Jk+HFF/NXYp0NFVGJNdp9Re1xFeC82XbngaoWll/QlqPdn9MeJwC3\nUG0n6bLPadQi/zJNWz5602j8XPw4OPwgFYtXzHxHIYQoYBzsHOjVsBevPfMaf5/7m+m7pzPtj2kM\nNAxkpO9IapSqoXeIQuQ7u3fDmjVw+LDekUBISAghISGPcwijdsszklwXMKdiTvH2r29zOuY0S7ov\nwc/FT++QhBBCdzY2Njxb41merfEsp2JOMTtsNl5zvWhXqx1jmoyhefXmcg2KEMDDhzBkCHz9NZQp\no3c04Ofnh5+fX/LzadOmZWU3UzvIJaAycEVbfgGobrZdNVTF+oL2OO1y0z41gIuovLkUcCOjF5e2\nkALifsJ9Pt3xKb7zfWlVoxWRwyIlsRZCCAtqlanF9A7TiRoTRcsaLem3th++C3z54cAPPEh8oHd4\nQuhq+nSoUgV69dI7ksfyM9BPe9wPWGu2vBfgCLiiLlIMQyXht4EmqH7uvsA6C8d6GXWBZIbkgsYC\n4PdTv/PWxrdwK+dGQMcAapbOdJQYIYQQmsSkRDb+u5EZoTM4du0Yw32GM7TRUMo7Z3jNkhAFzqlT\n4OsLe/aAq6ve0Vhm4YLGYNTFi+VQFeuPUInxClTFOQp4FbipbT8ZGIjqnx4NbNaWNwKCgKLARmCU\nttwJ+B4woEYN6aUdM/0Yc3JiaUhyrZNLsZcYu3ksu87tYtYLs+hcr7PeIQkhhFU7cPkAM0Nnsuro\nKnq69WR0k9G4V3TPfEchrJzRCC+8AG3b5u+h9wrKJDJFUMOPRAJHgM9zNSKRKaPRyA8HfsBzricu\npVw48tYRSayFEOIJ8KjowYIuCzg+8jgupV3osLQD7Za0kynWRYG3bBlER8PYsXpHYv2ymvkXA+6i\nGrn/BCZo9yCV6zx1KfYSw9YP42TMSRZ1XSRD6wkhRC56kPiAlYdX8s3ub7h1/xajfEfR36s/JZxK\n6B2aEE/MjRtqTOu1a6FJE72jyVhBqVyDSqxBNYDbkclVkuLJM69WN6zQkL2D90piLYQQuczRzpE3\nPN5gz+A9BHUNYsfZHbgEuDBu8zhOx5zWOzwhnoiJE6Fnz/yfWFuLrGb+tkA48DRqekjzbhypXOey\n6DvRDNswjFMxpwjqGkSjKo30DkkIIQqtMzfP8O2eb1kYsZBWNVsxusloWtVsJUP5Cau0cyf07q3G\ntC5VSu9oMmcNlevsBlcKdVXlRNRUkgDGKVOmJG+QdjxCkXNGo5EfDv7A+N/GM8R7CB+0+gAneye9\nwxJCCIGaBXfJ/iXMDJ1JUYeijGkyhl4Ne8m/08Jq3L8PBgN88omqXFuDgphcA3wIxANfac+lcp0L\nTNXq0zGnWdR1kVSrhRAin0oyJrH5xGZmhM5g/6X9DPMZxnCf4TIzrsj3Pv0UwsJg3Tqwlj+8WENy\nnZWe63JAae1xUeB5ICLXIirkjEYjSw8sxWueFx4VPNg7ZK8k1kIIkY/Z2tjyQp0X2NxnM9v6beNS\n7CXqf1uf/mv7ExEt/7sU+dPx4zBjBsyebT2JtbXIytvpDixGJeK2qIG0vzRbL5XrJyT6TjRD1w8l\n6mYUQd2C8K7srXdIQgghcuD63evMD5/P7LDZ1C5bm9FNRtOlXhfsbO30Dk0IjEZo1w46d7a+ofes\noXItk8jkA6Zq9fjfxjPMZxgftPoARztHvcMSQgjxmB4mPmT10dXMCJ3B5djLvO37NgMNAylVxAqu\nHBMF1uLFMHMmhIaCvb3e0WSPJNciUxfvXGTY+mFSrRZCiAJu9/ndBIQGsPnEZvp49GFUk1HULltb\n77BEIXPtGjRsCBs3grcVphzpJNeTgD5AEnAQGAA4A8uBmjw6Bfok1BToiahpzn/TlpumQC+CmgJ9\ndE5izOo41+IJMxqNfL//e7zmemGoZGDvkL2SWAshRAHWtFpTgnsGc2D4AYo7FqdZYDO6BHdh66mt\nSJFK5JUJE+D1160zsU6HCzAY8Ea1MtsBvVAj220B6gJbtecADYDXtPuOwHekJOtzAH+gjnbrmJOA\npHKtg4t3LjJ0/VDO3jpLUNcgDJUNeockhBAij919eJelB5YSEBqArY0tY5qM4XX31ynqUFTv0EQB\nZDTCd9/B//2fGtO6eHG9I8oZC5XrssAuoClwB1gDzARmAa2By0Al1BDS9VFV6yTgC23/TcBU4Ayw\nDXDTlvcC/IBh2Y1RKtd5yGg0smT/ErzmeuFdyZs9g/dIYi2EEIVUMYdiDGk0hEPDDzG9/XRWH1uN\nS4ALH277kIt3LuodnihA7t0Df3+YNw+2bbPexDodN4CvgbPARVTrxxagIiqxRrs3jY1ZBThvtv95\noKqF5Re05dlmZW3s1su8Wr25z2ZJqoUQQgCqEvf808/z/NPP88+1f5gVNouG3zXkxTovMqbpGHyq\n+OgdorBi585Bjx7w9NOwaxc4O+sdUfaEhIQQEhKS0SZPA2NQ7SG3gJWo/mtzRu2WJ6QtJJeZqtXv\nbHmH4T7Deb/V+zISiBBCiAzFxMcQGBHIrLBZVC9ZnTFNx9CtfjfsbaUmJrIuJERNbT5+vLoVhPGs\nLbSFvIaag2WQ9rwvqkWkLdAGuARUBraj2kJMvdf/1e43AVNQbSHbSWkL6Y1qK8l2W4gk17no4p2L\nDPllCOdvn2dR10VSrRZCCJEtCUkJrDu2jhmhMzh76ywjG49kkPcgyhQto3doIh8zGtVQe59/DkuX\nwnPP6R3Rk2MhufYEfgAaA/dQo32EoUYJuY7qrZ6ImhBxIupCxh8BX1Tbx+9AbVRlOxQ1ekgYsAHV\nu70p2zFmdwcLJLlOw7xaPaLxCCa3nCzVaiGEEI9l78W9BIQGsP74enq69cTf4E/Tak1NyYYQANy9\nC0OHwqFDsGYNuLjoHdGTlc5QfO8C/VAXKoajqtglgBVADR4dim8yaii+BNRwe5u15aah+IqihuIb\nlaMYc7JTGpJcm7lw+wJD1w/l/O3zBHULwquSl94hCSGEKEAuxV5iceRiAiMCcbBzwN/gT1+PvpR3\nLq93aEJnUVGqv7pBA/jf/6BYMb0jevKsYRIZGS3kCTEajQRFBmGYZ6BxlcaEDQ6TxFoIIcQTV6l4\nJd5r8R7/jPyHOS/NYf/l/dSZVYdXVr7C5hObSUxK1DtEoYOtW6FpU+jXD77/vmAm1tZCKtdPwIXb\nFxiyfggXbl+QarUQQog8d+veLYIPBbMgfAFX4q4w0DCQAV4DqFm6pt6hiVxmNML06fDVVxAcDH5+\nekeUu6yhci3J9WMwGo0s3r+Yd7e8y1uN32JSy0nSWy2EEEJXkZciCQwPJPhQMI2qNMLf4E/Xel1x\nsnfSOzTxhMXFwaBB8O+/sHo11Kihd0S5T5LrAsxUrb545yJBXYPwrOSpd0hCCCFEsviH8aw5tobA\niEAOXj7IG+5v4O/tT8MKDfUOTTwBp05Bt25qGvM5c6BoIZnY0xqSa+m5ziaj0ciiiEV4zfPCt4ov\nYYPCJLEWQgiR7xR1KMrr7q+z9c2t7B60G2dHZzou7UjTBU2Zv28+d+7f0TtEkUObN0OzZmpUkEWL\nCk9ibS2kcp0N52+fZ8gvQ4iOjZZqtRBCCKuTkJTA5hObCYwIZHvUdrrX784g70E0q9ZMhvSzAkYj\nfPGFGsN6+XJo2VLviPKeNVSuJbnOAtNIIO/+/i5v+77NpBaTcLBz0DssIYQQIscux15myf4lLIhY\ngK2NLYMMg+jr2ZcKzhX0Dk1YEBsLAwao6cxXrYKqVfWOSB+SXBcA52+fZ/Avg7kUe0mq1UIIIQoc\no9HIX+f+IjAikDVH1/BcrefwN/jT/un22Nna6R2eQF2w2L27agWZPRucCvG1qZJcWzGj0ciiyEW8\n9/t7jPIdxcQWE6VaLYQQokC7ff82yw4tY0H4AqJjoxngNYABXgNwLeOqd2iF1oYNMHAgfPIJDBmi\ndzT6k+TaSpmq1ZdjLxPULQiPih56hySEEELkqQOXDxAYHsgPB3/AUNnAIMMgutXvJkP65ZGkJPjP\nf2DePFi5UlWthXUk11kZLaQ6sB04DBwih/OsWwOj0cjCiIUY5hloXq05oYNCJbEWQghRKHlU9CDg\nhQDOjzuPv8GfBRELqPZNNUb/OpqDlw/qHV6Bdvu2msZ80ybYs0cS6ywoDfwEHAWOAE2AssAW4Djw\nm7aNySTgX+AY0N5seSPgoLYuIKfBZCXzr6TdIoHiwD6gm3YCUEAq1+dunWPI+iFSrRZCCCHScTrm\nNIsiF7EochGVi1dmkPcgejXsRUmnknqHVmAcO6b6q9u0gRkzwFHmpkslncr1YuAPYCFgDzgD7wPX\ngP8D3gPKABOBBsCPQGOgKvA7UAcwAmHASO1+IzAT2JTdGLNSub6ESqwBYlFJdZXsvlB+ZTQaCQwP\nxPt/3lKtFkIIITLgWsaVj9t8TNToKKb5TWPzyc3UnFGTAesG8OfZPykIxTY9rVsHrVrBhAnw3XeS\nWGdRKaAlKrEGSABuAV1QSTfafTftcVcgGHgIRAEnUJXuykAJVGINsMRsn2yxz+b2LoABCM3Ji+U3\np2JOMWLDCK7evcrWN7dKUi2EEEJkgZ2tHS/UeYEX6rzAlbgrLNm/hMG/DMZoNOJv8OdNzzepWLyi\n3mFajaQkmDZNTQizfj34+uodkVVxBa4CiwBPVIfFGKAicFnb5rL2HFSBeLfZ/udRFeyH2mOTC9ry\nbMtOcl0c1c8yGlXBTjZ16tTkx35+fvj5+eUkljwT+yCWz3d+zrx98xjfbDwTmk+QkUCEEEKIHKjg\nXIEJzScwvtl4dp3fxYLwBdT/tj5tXNrgb/CnQ+0O2Ntmt5ZXeNy8CX37qj7rPXugovwmSSUkJISQ\nkJCMNrEHvFHtHHuAGaj2D3NG7ZYnsnq1pQOwHvgVFbQ5q+m5NhqNBB8K5r3f36NVzVZ88dwXVCtZ\nTe+whBBCiALl9v3bLD+0nMCIQM7fPk9/r/4MNAykVplaeoeWrxw5At26QceO8PXX4CB1vkxZ6Lmu\nBOxCVbABWqAuWKwFtEG1N1dGDc5Rn5TE+7/a/SZgCnBG28ZNW94baA0My3aMWdxmMXAdGGthvVUk\n1+HR4Yz6dRTxCfHM7DiTZ2s8q3dIQgghRIF36MohAsMDWXpwKR4VPfA3+NOlXheKOxbXOzRdrVoF\nw4fDV1/Bm2/qHY31SOeCxh3AINTIIFOBYtry68AXqIS6NKkvaPQl5YLG2qjKdihqVLwwYAM5vKAx\nK8l1Cy3oA6SU1CeZvVi+Tq6vxl3l/W3v8/M/P/NJm08YaBgoM04JIYQQeex+wn3W/bOORZGL+Pvc\n37RxaUMPtx50qtuJskXL6h1enklMhA8/hB9/VAl2o0Z6R2Rd0kmuPYEFgCNwEhgA2AErgBqoCxdf\nBSrOqLsAABMwSURBVG5q208GBqIufhwNbNaWNwKCgKKo0UJyNPx0gZ1E5mHiQ77d8y3/2fkf+rj3\nYYrfFEoXKZ35jkIIIYTIVTHxMWz4dwOrj65m6+mtNKnahB5uPeharyuVS1TWO7xcc+MGvP46PHgA\ny5dD+fJ6R2R9rGESmQKZXG85uYXRm0ZTrWQ1ZnScQYPyDfQOSQghhBAWxD2IY/PJzaw+upoN/27g\nmfLP0MOtB93rdy9Q064fOKDGr+7WDb74AuzlGs8ckeQ6j52KOcW4zeM4eOUg09tPp0u9LqYPQQgh\nhBD53IPEB2w7vY3VR1ez9thaqpasSo/6PejZoCdu5dys9v/py5fDyJEQEKAq1yLnJLnOI6ah9ebu\nm8v4ZuMZ12wcReyL6BqTEEIIIXIuMSmRv879xeqjq1l9dDVFHYrSo34Perj1wKeKj1Uk2gkJMGmS\n6q1eswY8PfWOyPpJcp3LzIfWa12zNV889wVVS+ZovG8hhBBC5FNGo5F90ftYfXQ1q46uIv5hPN3r\nd6eHWw9a1GiRLwcquH4devUCGxsIDoanntI7ooJBkutcJEPrCSGEEIWP0Wjk6LWjyRXt87fP07Ve\nV3q49aCta1uc7J30DpGICOjRA157Df7zH7DLf7m/1ZLkOheYD633adtPGeA1IF/+YhVCCCFE7jsd\nc5o1x9aw+uhqDl89zIt1XqRH/R50rN0RZ0fnPI/nhx/4//buPTrK+s7j+DuEBHKBJBAIuUFaMYBK\nUDAIoifRbrva1bLi8dKu1tZTtHvWipcVvK709LSnsNuj22N7jqJc6opr11IQpYioCVTkFiRBMOGi\nJCSQIIgCQUkmmf3j98xkJpkkk8nMPJOZz+ucOfPMM/PMfOc5uXzmN7/n+/DAA/CHP8Ctt4b95aOe\nwnUQqbWeiIiI9OTYmWOsqVnDX6v/ytb6rVz7rWuZM9H00s5Iygjpa7e2wvz5sHatmV89eXJIXy5m\nKVwHiVrriYiISF+c+voUb+5/k1XVq3jvs/eYkTeDORPnMHvibMakjgnqax0/bqaAJCWZkeuM0Ob4\nmKZw3U9qrSciIiL91dzSzPqD61lVvYp1B9ZxyehLmDNxDjdNuomC9IJ+PffOnXDzzXDnnfDLX2p+\ndagpXAdIrfVEREQkFM47zrt7aa+pWUN+Wr67xd+kUZP69FzLl8Mjj8Dzz5sDGCX0egjX8cBOoB64\nERgBvAaMo+vpzx/DnP68DXOK8w3Wetfpz4diTn8+L6AaA9mok6CFa1drvfnvzKe0oFSt9URERCRk\nHO0OPqizemlXryI1MdUdtKdmT+322/LWVnjoIdiwwcyvvkizVcOmh3D9ECYcDwN+ACwGTljXC4AM\n4FHgImAlUAzkAhuBCwEnsB24z7peB/weWN/nGvu6gQ9BCddqrSciIiJ2cTqd7Dy6091L+3zbeXcv\n7Vn5s9ydyRobTReQ9HR4+WVIS7O58BjTTbjOw4w4/xoTsm8EqoESoAkYA5QBEzGj1u3AImvb9cBC\noBZ4D3B9fXE7UAr8vK812n5me7XWExEREbvFxcVRnFtMcW4xv/nOb9j3+T5WfbKKeevncfTMUb6X\ndQeJe+7h7ZWFzJ0bx1NPwaBBdlctlmeAR4DhHuuyMMEa6zrLWs4Btno8rh4zgt1qLbs0WOv7zLZw\n3bm1XvV91WqtJyIiIraLi4vj4tEXMynzYma2PsUzz53l9XcHM3zKRppn/yvbL0zivz4sobSglKnZ\nUxk8yPaxylh2A3Ac+Agz0uyL07qEhS0/Da7Wevlp+Wz6yaY+H0AgIiIiEiqNjeZgxSVLIDUV7r03\nlZUrIC3tBj5vvoJNtZsory1n7tq5HP7yMDPzZlJaUErJuBIuz7mchPgEu99C1CgrK6OsrKynh1yJ\nmWP9fcyBiMOBl+mYDtIIZGMCOJgR6XyP7fMwI9YN1rLn+oZAag7rnGu11hMREZFI1N4OGzfCCy/A\nu++a9nr33APFxdBTVDl57iSb6zZTdriM8tpyDn1xiBl5M9xhuzi3mMT4xPC9kSjXSyu+EuDfMXOu\nFwMnMXOrHwXS8T6gcTodBzSOx4xsb8N0D9kOvEUkH9Doaq33fMXzPDzzYR6c+aBa64mIiIjtjh2D\nZcvMKHV6Otx7L/zoRzB8eO/b+nLq61NsrttM+eFyymrL2H9yP1fkXkHJODONZHrudIYMHhLcNxFD\n/AjXD2NGskcAfwbG0rUV3+OYVnwOTLu9t631rlZ8SZhuIfcHVGMgG3XSbbh2Op2s3LOSBRsXqLWe\niIiIRIS2NnjnHTNK/f77cMstZpR62rSeR6kD8eU3X/L3ur9Tfric8tpy9n2+j+LcYkrHlVJSUMKM\nvBkacOyDmD6JjFrriYiISCRpaDCj1C++CKNGmUB9++0wbFj4ajh9/jQf1H3gnkby8fGPmZYzzT2y\nPSNvBskJyeEraICJlnC9FPgnzETwyT7u9wrXaq0nIiIikaKtDdavN6PUmzfDbbfB3LkwdardlRln\nzp9hy5EtlNeWU3a4jKqmKi7LvswdtmfmzSQlMcXuMiNGtITrq4GzwJ/oIVx3bq33dOnTaq0nIiIi\ntjhyBJYuhZdeguxsM0p9222m+0cka25p9grbuxt3U5RV5D5ActbYWaQmRvibCKFoCdcABcBaugnX\nGw5ucLfWe/Yfn1VrPREREQk7hwP+9jczSv3BB/DDH5pR6ksvtbuywJ1rPcfW+q3uaSQVRyu4ZPQl\n7pHtWWNnMXxIgEdfDkAxE66//d/fVms9ERERsUVdnRmhfuklyM83o9S33gopUTib4uvWr9nWsM3d\njWRHww4uGnWRO2xfNfYq0oZG7znZYyZcP/HUE+6zE5WWllJaWhqU4kRERER8cTjgrbfMKPXWraZ9\n3ty5UFRkd2Xh9Y3jG7Y3bHd3I9nWsI3CkYXubiRXj72ajKQMu8sMmpgJ1/6eREZERESkPw4fNiPU\nS5dCQYEZpb7lFkhWgw0AWtpa2NGwwz2N5MP6Dxk/YrzXyHZmcqbdZQZM4VpERESkn1pb4c03zSj1\njh1wxx1mlPrii+2uLPK1tLVQcbTCfYDkh/UfMixxGEVZRUzJmmKux0yhcGShexZCJIuWcP0q5ow3\nIzHt+P4DWOZxv8K1iIiIBN1nn5me1MuWwfjxZpT65pshKcnuygYup9NJ7Ve1VDVVUdlYSdVxc11/\nup5JoyZ5h+6sKYxMHml3yV6iJVz3RuFaREREgqK1Fd54w4xS79oFd95pRqknqRFZSDW3NPPx8Y9N\n6G6qpKqpiqqmKlISU9xh2xW4C0cWkhCfYEudCtciIiIifjh40IxSL18OEyeaUeo5c2CozgxuG6fT\nSd1Xde6w7bo+8tURJmZO7DK1JBxzuX2E63zMuVhGA07gBeD3wAjgNWAccBi4FfjS2uYx4G6gDbgf\n2GCtnwYsB4YC64B5AdUYyEadKFyLiIhIn7W0wOrVZpS6qgp+/GP42c9MuJbI1dzSzN7P95ppJR6h\nOzkhmSljplA02oTtoqwiJoycENRRbh/heox12Q2kAhXAPwM/BU4Ai4EFQAbwKHARsBIoBnKBjcCF\nmGC+HbjPul6HCenr+1xjn99VVwrXIiIi4rcDB2DJElixwhyUeM89cNNNMGSI3ZVJoFyj3J5hu7Kp\nkiNfHWFC5gSvedxFWUWMShkV0Ov4MS1kNfCcdSkBmjDhuwyYiBm1bgcWWY9fDywEaoH3ANcEpNuB\nUuDnfa0x8g8LFRERkQHL6YRDh6Ciwly2bDHh+q67YPNmKCy0u0IJhri4OMalj2Nc+jhunHCje/25\n1nMdc7kbK1lTs4bKxkqSE5K7TCsJwih3AXAZsA3IwgRrrOssazkH2OqxTT1mBLvVWnZpsNb3mcK1\niIiIBEV7u3eQrqiAjz6C4cNh2jRzefJJuOYajVLHiuSEZKbnTmd67nT3OqfTyZHTR9zTStbUrOFX\nm35F3Vd1TMic4BW6z9acZfe23f68VCrwF8w86TOd7nNal7BQuBYREZE+a283ByF2DtLp6R1BesEC\nmDoVRgU2A0CiVFxcHGPTxjI2bWyXUe69x/e6p5WsqVlDVVMVQ4cNdYftbiRggvXLmGkh0DEdpBHI\nxrSTBjMine+xbR5mxLrBWvZc3xDQ+wtko04051pERCSKtbebqRydg/SIER1Beto0E6QzB+7J/yQC\nuUa5XdNKnix5ErzzaxywAjgJPOixfrG1bhHmQMZ0vA9onE7HAY3jMSPb2zDdQ7YDb6EDGkVERKS/\n2tpg//6OEL1rlwnSmZldg/TIyDq/iMQAHwc0XgVsAqromPrxGCYg/xkYS9dWfI9jWvE5MNNI3rbW\nu1rxJWG6hdwfUI2BbNSJwrWIiMgA1NYGNTXeI9K7d8Po0V2D9IgRdlcropPIiIiISIRoa4Pqau8g\nXVkJWVldg3RGht3VivimcC0iIiJh53D4DtLZ2V2DdHq63dWK+E/hWkRERELK4YBPPvEO0lVVkJPT\nNUinpdldrUj/KFyLiIhIUDgccPw4HD0Ke/Z0BOk9eyAvzztIX3qpgrREJ4VrERER6ZHDAU1NJjQf\nO9b99YkTpjtHdrY5ZbhnkB4+3O53IRIeCtciIiIxqrXVv9B88qRpc5eTY4Jzd9dZWTBYp36TGKdw\nLSIiEmVaW6GxsffQ/MUX5syEvYXm0aMVmkX8pXAtIiIyQJw/b0KzZ0D2FZpPnTKB2J/QHB9v97sS\niS4K1yIiImHkdEJzM5w9C2fOdFw63z5xomtwPn3av9A8apRCs4hdFK5FRER60N5uwrCvANyX267l\n5mYYMgSGDTOX1FTfyyNHdg3NmZkKzSKRLlrC9XXAs0A88CKwqNP9Ctc2Kisro7S01O4yYpb2v320\n78PL6TRdLVpazPSJd9/dwuTJVwYUgD1vnzsHSUndh+Debne+LyUlNuYv6+ffPtr39uomXPeWVcOq\ntz9B8cBzwD8ADcAO4A3gkxDXJX7SL7m9tP/tE2373uk0B8q5wmtLi/eyr3W93R/INt3d39ICgwaZ\nUeHERHA4JpOd3XPozcnpPSCnpGi0OBDR9vM/kGjfR5yIy6q9hevpwEHgsHX7f4HZKFyLRAWn03wt\n77r2XO7PumA9T0/PXVNTyOrV0NZmRlTb2rpefK3vy2OD8Rw9PbZzgB08uCO8uq67W/bn/pQUyMgI\nfHvPdQkJ3iF44cLfsXDhQtt+dkVELBGXVXsL17nAEY/b9cAV3g9Zx/XXB7coT6GeceLP8/tbQzAf\n5+9zffrpXWza1Pu23T2fv48Nxfae63wtD4R1J0/+gldeMcuui+u+UK0L1nO5DBoEcXHe18FaF8rn\nPnDgcs6dM4E0Pr7rxZ/1CQkwdGjfnqM/r9d5nWeQTUgw70tERPrEj6waXr3Nub4ZM49lrnX7DkzB\nv/B4zEHgguCXJiIiIiLi5RAw3uO2P1k1rHobuW4A8j1u52M+EXgaj4iIiIhI+PmTVSPKYMwnhAIg\nEdgNTLKzIBERERERy4DMqtcDNZjpH4/ZXIuIiIiIiCdlVRERERER6eo6oBo4ACywuZZYkw+8D+wF\nPgbut7ecmBQPfASstbuQGJQOvI5ptbQPmGFvOTHnMczfnj3ASmCIveVEtaVAE2Zfu4wA3gH2Axsw\nvw8SGr72/39i/vZUAquANBvqihW+9r/Lw0A75vchasRjht8LgAQGyByXKDIGuNRaTsV8HaL9H14P\nAa9gmtVLeK0A7raWB6N/buFUAHxKR6B+DbjLtmqi39XAZXiHi8XAfGt5AfDbcBcVQ3zt/+8CrsaZ\nv0X7P5R87X8wA4zrgc+IsnA9E/PGXB61LmKP1cB37C4ihuQBG4Fr0Mh1uKVhwp3YYwTmw3wG5oPN\nWsyZ0SR0CvAOF9VAlrU8xrotoVOA75FTgJuA/wlfKTGpgK77//+AIiI0XPfnlAW+mnbn9q8cCVAB\n5pPdNpvriCXPAI9gvpKS8PoW8DmwDNgFLAGSba0otnwB/A6oA44CX2I+aEr4ZGG+Kse6zurhsRJa\ndwPr7C4ixszGZM4quwvpTn/CdYjPnSh+SsXMPZ0HnLW5llhxA3AcM9+6txMxSfANBqYCf7Sum9G3\nZuF0AfAA5kN9DuZv0L/YWVCMc6L/x3Z5AmjBHHcg4ZEMPA487bEu4v4P9ydcD7im3VEoAfgL5iup\n1TbXEkuuBH6A+TrqVeBa4E+2VhRb6q3LDuv265iQLeFxObAFOAk4MAd0XWlrRbGnCTMdBCAb82Ff\nwusnwPfRB8twuwDzwb4S8z84D6gARttYU1ANyKbdUSQOE+iesbuQGFeC5lzbYRNQaC0vBBbZV0rM\nmYLpUJSE+Tu0Avg3WyuKfgV0PaDR1aHrUXRAXagV4L3/r8N0y8m0pZrYU0D3c94jcs51f6lpt32u\nwsz33Y2ZnvAR5hdewqsEdQuxwxTMyLVaYdljPh2t+FZgvkWT0HgVM7e9BXOc008xYWIjasUXDp33\n/92Y9sO1dPzv/aNt1UU/1/4/T8fPv6dPicJwLSIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIi\nIiIiIiIiIiIiIiIiIiIisen/AYvdCAIbugkDAAAAAElFTkSuQmCC\n", 197 | "text/plain": [ 198 | "" 199 | ] 200 | }, 201 | "metadata": {}, 202 | "output_type": "display_data" 203 | } 204 | ], 205 | "source": [ 206 | "pl.figure(figsize=(12, 3))\n", 207 | "\n", 208 | "ax1 = pl.subplot(111)\n", 209 | "ax1.set_title('Volume as a function of dimensionality\\nfor R=%f' % R)\n", 210 | "ax1.plot(dims, S_n, c='g', label='n-sphere volume')\n", 211 | "ax2 = ax1.twinx()\n", 212 | "ax2.plot(dims, C_n, c='b', label='enclosing n-cube volume')\n", 213 | "h1, l1 = ax1.get_legend_handles_labels()\n", 214 | "h2, l2 = ax2.get_legend_handles_labels()\n", 215 | "ax1.legend(h1+h2, l1+l2, loc=(0.0, 0.9), fontsize=10)" 216 | ] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "metadata": {}, 221 | "source": [ 222 | "Interestingly, the volume of the n-sphere decreases after a number of dimensions (this depends on its\n", 223 | "radius). This is explained on [Wikipedia](https://en.wikipedia.org/wiki/Volume_of_an_n-ball#High_dimensions)" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": 7, 229 | "metadata": { 230 | "collapsed": false 231 | }, 232 | "outputs": [ 233 | { 234 | "data": { 235 | "text/plain": [ 236 | "[]" 237 | ] 238 | }, 239 | "execution_count": 7, 240 | "metadata": {}, 241 | "output_type": "execute_result" 242 | }, 243 | { 244 | "data": { 245 | "image/png": "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\n", 246 | "text/plain": [ 247 | "" 248 | ] 249 | }, 250 | "metadata": {}, 251 | "output_type": "display_data" 252 | } 253 | ], 254 | "source": [ 255 | "pl.figure(figsize=(12, 3))\n", 256 | "\n", 257 | "ax1 = pl.subplot(111)\n", 258 | "ax1.set_title('Ratio of n-sphere to n-cube volume\\n for R=%f' % R)\n", 259 | "ax1.plot(dims, S_n/C_n)" 260 | ] 261 | } 262 | ], 263 | "metadata": { 264 | "kernelspec": { 265 | "display_name": "Python 2", 266 | "language": "python", 267 | "name": "python2" 268 | }, 269 | "language_info": { 270 | "codemirror_mode": { 271 | "name": "ipython", 272 | "version": 2 273 | }, 274 | "file_extension": ".py", 275 | "mimetype": "text/x-python", 276 | "name": "python", 277 | "nbconvert_exporter": "python", 278 | "pygments_lexer": "ipython2", 279 | "version": "2.7.10" 280 | } 281 | }, 282 | "nbformat": 4, 283 | "nbformat_minor": 0 284 | } 285 | -------------------------------------------------------------------------------- /strava/analytics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "107\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "import json\n", 20 | "with open('activities.json') as f:\n", 21 | " activities = json.load(f)\n", 22 | "print len(activities)" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 12, 28 | "metadata": { 29 | "collapsed": false 30 | }, 31 | "outputs": [ 32 | { 33 | "name": "stdout", 34 | "output_type": "stream", 35 | "text": [ 36 | "69 swims\n" 37 | ] 38 | } 39 | ], 40 | "source": [ 41 | "swims = filter(lambda a: a['type'].lower() == 'swim', activities)\n", 42 | "print len(swims), 'swims'" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 48, 48 | "metadata": { 49 | "collapsed": false 50 | }, 51 | "outputs": [ 52 | { 53 | "name": "stdout", 54 | "output_type": "stream", 55 | "text": [ 56 | " distance elapsed_time\n", 57 | "start_date \n", 58 | "2014-09-03T16:00:00Z 2000 3600\n", 59 | "2014-09-08T16:25:00Z 2000 3000\n", 60 | "2014-09-10T16:47:29Z 2000 2820\n", 61 | "2014-09-17T18:22:09Z 2000 2760\n", 62 | "2014-09-20T15:19:51Z 2500 3900\n", 63 | "2014-09-24T16:18:00Z 2000 3000\n", 64 | "2014-09-29T16:26:00Z 1700 2520\n", 65 | "2014-10-02T16:42:00Z 1500 2100\n", 66 | "2014-10-06T16:13:00Z 1700 2400\n", 67 | "2014-10-08T17:05:00Z 1500 2520\n" 68 | ] 69 | } 70 | ], 71 | "source": [ 72 | "import pandas as pd\n", 73 | "data = pd.DataFrame.from_records(swims).sort('start_date')\n", 74 | "data = data.set_index('start_date')\n", 75 | "print data[['distance', 'elapsed_time']].iloc[:10]" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 49, 81 | "metadata": { 82 | "collapsed": false 83 | }, 84 | "outputs": [ 85 | { 86 | "name": "stdout", 87 | "output_type": "stream", 88 | "text": [ 89 | " distance elapsed_time speed\n", 90 | "start_date \n", 91 | "2014-09-03T16:00:00Z 2000 3600 0.555556\n", 92 | "2014-09-08T16:25:00Z 2000 3000 0.666667\n", 93 | "2014-09-10T16:47:29Z 2000 2820 0.709220\n", 94 | "2014-09-17T18:22:09Z 2000 2760 0.724638\n", 95 | "2014-09-20T15:19:51Z 2500 3900 0.641026\n", 96 | "2014-09-24T16:18:00Z 2000 3000 0.666667\n", 97 | "2014-09-29T16:26:00Z 1700 2520 0.674603\n", 98 | "2014-10-02T16:42:00Z 1500 2100 0.714286\n", 99 | "2014-10-06T16:13:00Z 1700 2400 0.708333\n", 100 | "2014-10-08T17:05:00Z 1500 2520 0.595238\n" 101 | ] 102 | } 103 | ], 104 | "source": [ 105 | "data['speed'] = data['distance'] / data['elapsed_time']\n", 106 | "print data[['distance', 'elapsed_time', 'speed']].iloc[:10]" 107 | ] 108 | }, 109 | { 110 | "cell_type": "markdown", 111 | "metadata": {}, 112 | "source": [ 113 | "http://pandas.pydata.org/pandas-docs/stable/computation.html#moving-rolling-statistics-moments" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 62, 119 | "metadata": { 120 | "collapsed": false 121 | }, 122 | "outputs": [ 123 | { 124 | "data": { 125 | "text/plain": [ 126 | "" 127 | ] 128 | }, 129 | "execution_count": 62, 130 | "metadata": {}, 131 | "output_type": "execute_result" 132 | }, 133 | { 134 | "data": { 135 | "image/png": "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\nRU5ErkJggg==\n", 136 | "text/plain": [ 137 | "" 138 | ] 139 | }, 140 | "metadata": {}, 141 | "output_type": "display_data" 142 | } 143 | ], 144 | "source": [ 145 | "import pylab as pl\n", 146 | "%matplotlib inline\n", 147 | "\n", 148 | "ts = data['speed']\n", 149 | "ts.plot(rot=90)\n", 150 | "pd.rolling_mean(ts, 10).plot(style='g', rot=90)" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 64, 156 | "metadata": { 157 | "collapsed": false 158 | }, 159 | "outputs": [ 160 | { 161 | "data": { 162 | "text/plain": [ 163 | "" 164 | ] 165 | }, 166 | "execution_count": 64, 167 | "metadata": {}, 168 | "output_type": "execute_result" 169 | }, 170 | { 171 | "data": { 172 | "image/png": "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\n", 173 | "text/plain": [ 174 | "" 175 | ] 176 | }, 177 | "metadata": {}, 178 | "output_type": "display_data" 179 | } 180 | ], 181 | "source": [ 182 | "data['distance'].plot()\n", 183 | "pd.rolling_mean(data['distance'], 10).plot(style='g', rot=90)" 184 | ] 185 | } 186 | ], 187 | "metadata": { 188 | "kernelspec": { 189 | "display_name": "Python 2", 190 | "language": "python", 191 | "name": "python2" 192 | }, 193 | "language_info": { 194 | "codemirror_mode": { 195 | "name": "ipython", 196 | "version": 2 197 | }, 198 | "file_extension": ".py", 199 | "mimetype": "text/x-python", 200 | "name": "python", 201 | "nbconvert_exporter": "python", 202 | "pygments_lexer": "ipython2", 203 | "version": "2.7.9" 204 | } 205 | }, 206 | "nbformat": 4, 207 | "nbformat_minor": 0 208 | } 209 | -------------------------------------------------------------------------------- /strava/strava_get_activities.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Strava analytics with ipython" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "First step is to get an oauth token from strava.\n", 15 | "\n", 16 | "Create a strava application\n", 17 | "\n", 18 | "To do that, navigate to [this link](https://www.strava.com/oauth/authorize?client_id=5966&response_type=code&redirect_uri=http://localhost&approval_prompt=auto&scope=view_private), changing the client id to match your app and authorize the strava test app. After you have clicked authorize, you will be redirected to a page that does not exist, but you can get the code from the URL :\n", 19 | "\n", 20 | "http://localhost/token_exchange.php?state=&code=XXXX\n", 21 | "\n", 22 | "Strava API docs :\n", 23 | "http://strava.github.io/api/v3/oauth/" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 88, 29 | "metadata": { 30 | "collapsed": true 31 | }, 32 | "outputs": [], 33 | "source": [ 34 | "strava_oauth_code = \"7ba92a5340010f4035b2f897a7c93d6a9a331b53\"" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 89, 40 | "metadata": { 41 | "collapsed": true 42 | }, 43 | "outputs": [], 44 | "source": [ 45 | "import requests\n", 46 | "\n", 47 | "payload = {\n", 48 | " 'client_id':\"5966\",\n", 49 | " 'client_secret':\"b8869c83423df058bbd72319cef18bd46123b251\",\n", 50 | " 'code':strava_oauth_code\n", 51 | "}\n", 52 | "resp = requests.post(\"https://www.strava.com/oauth/token\", params=payload)\n", 53 | "assert resp.status_code == 200\n", 54 | "\n", 55 | "access_token = resp.json()['access_token']\n", 56 | "headers = {\n", 57 | " 'Authorization': \"Bearer \" + access_token\n", 58 | "}" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 90, 64 | "metadata": { 65 | "collapsed": false 66 | }, 67 | "outputs": [ 68 | { 69 | "data": { 70 | "text/plain": [ 71 | "u'cc984d50e8c49b8cfd251d20ca9321d81c396d9d'" 72 | ] 73 | }, 74 | "execution_count": 90, 75 | "metadata": {}, 76 | "output_type": "execute_result" 77 | } 78 | ], 79 | "source": [ 80 | "access_token" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 91, 86 | "metadata": { 87 | "collapsed": false 88 | }, 89 | "outputs": [ 90 | { 91 | "name": "stdout", 92 | "output_type": "stream", 93 | "text": [ 94 | "Julien Rebetez\n" 95 | ] 96 | } 97 | ], 98 | "source": [ 99 | "resp = requests.get(\"https://www.strava.com/api/v3/athlete\", headers=headers)\n", 100 | "assert resp.status_code == 200\n", 101 | "athlete = resp.json()\n", 102 | "print athlete['firstname'], athlete['lastname']" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 101, 108 | "metadata": { 109 | "collapsed": false 110 | }, 111 | "outputs": [ 112 | { 113 | "name": "stdout", 114 | "output_type": "stream", 115 | "text": [ 116 | "107 activities total\n" 117 | ] 118 | } 119 | ], 120 | "source": [ 121 | "def get_activities(page):\n", 122 | " params = {\n", 123 | " 'per_page': 50,\n", 124 | " 'page':page\n", 125 | " }\n", 126 | "\n", 127 | " resp = requests.get(\"https://www.strava.com/api/v3/athlete/activities\",\n", 128 | " params=params, headers=headers)\n", 129 | " assert resp.status_code == 200\n", 130 | " activities = resp.json()\n", 131 | " return activities\n", 132 | "\n", 133 | "def get_all_activities():\n", 134 | " all_activities = []\n", 135 | " page = 1\n", 136 | " while True:\n", 137 | " activities = get_activities(page)\n", 138 | " page += 1\n", 139 | " if len(activities) == 0:\n", 140 | " break\n", 141 | " all_activities += activities\n", 142 | " return all_activities\n", 143 | " \n", 144 | "activities = get_all_activities()\n", 145 | "print len(activities), ' activities total'" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 103, 151 | "metadata": { 152 | "collapsed": true 153 | }, 154 | "outputs": [], 155 | "source": [ 156 | "import json\n", 157 | "with open('activities.json', 'w') as f:\n", 158 | " json.dump(activities, f)" 159 | ] 160 | } 161 | ], 162 | "metadata": { 163 | "kernelspec": { 164 | "display_name": "Python 2", 165 | "language": "python", 166 | "name": "python2" 167 | }, 168 | "language_info": { 169 | "codemirror_mode": { 170 | "name": "ipython", 171 | "version": 2 172 | }, 173 | "file_extension": ".py", 174 | "mimetype": "text/x-python", 175 | "name": "python", 176 | "nbconvert_exporter": "python", 177 | "pygments_lexer": "ipython2", 178 | "version": "2.7.9" 179 | } 180 | }, 181 | "nbformat": 4, 182 | "nbformat_minor": 0 183 | } 184 | --------------------------------------------------------------------------------