{
  "id": 198480,
  "title": "Speeding up computation",
  "url": "/competitions/conways-reverse-game-of-life-2020/discussion/198480",
  "author_name": "somedud",
  "post_date": "2020-11-21T12:43:07.972000",
  "votes": 3,
  "comment_count": 7,
  "views": 0,
  "content": "<p>Hi there,</p>\n<p>a novice here.<br>\nI was just watching the competition and toying around with the dataset and it got me curious regarding the improvement of data-preparation.<br>\nA naive approach yielded an error-rate of around 0.17 so I thought it best to compute some more data for e.g. delta=1.</p>\n<p>A very direct approach for the computation would run ~5-6h and not use the GPU.</p>\n<p>I came up with the following approach which runs ~45-60min(if projections pan out) and utilizes ~13-15% of the GPU.</p>\n<p>I understand that the bottle-neck here is computation of a cell's state in the next iteration.<br>\nIs there a way to treat everything as a matrix-/vector-operation and thus utilizing the GPU more gaining more speed?</p>\n<pre><code>import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom fastai.tabular.all import *\nfrom torch.nn import *\n\n\ndef calculate_all_masks(matrixWidth):\n    columnCount = int(matrixWidth ** 2)\n    result = torch.as_tensor(np.zeros((columnCount,columnCount), int), device=device)\n\n    for position in range(0,columnCount):\n        for y in [-1,0,1]:\n            for z in [-1,0,1]:\n                calculated_row = (position + columnCount +y*matrixWidth) % columnCount\n                index_with_column_chosen = int(calculated_row/matrixWidth) * matrixWidth + (calculated_row + 1*z)%matrixWidth\n                result[position][index_with_column_chosen] = 1\n\n    return result\n\ndef calculate_gol_state_for_tensor_with_masks(start, masks, matrixWidth):\n    columnCount = int(matrixWidth ** 2)\n    result = torch.as_tensor(np.zeros((columnCount,), int), device=device)\n    zero_vector = torch.as_tensor(np.zeros((columnCount,),int), device=device).float().unsqueeze(0)\n\n    for current_cells in range(0, columnCount):\n        result[current_cells] = int(\n            math.ceil(\n                torch.cdist((masks[current_cells] &amp; start).float().unsqueeze(0), zero_vector)[0].item() ** 2\n            ) in [3,4]\n        )\n\n    return result\n\ndef calculate_gol_state_with_masks_per_delta(start, mask, matrixWidth, delta):\n    result = start\n\n    for i in range(0,delta):\n        result = calculate_gol_state_for_tensor_with_masks(result, masks, matrixWidth)\n\n    return result\n\ndataset = pd.read_csv('/kaggle/input/conways-reverse-game-of-life-2020/train.csv')        \ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nto_generate_one_delta_from = dataset.loc[dataset['delta'] &gt; 1].reset_index(drop=True)\nmasks = calculate_all_masks(25)\ncalculated_result = torch.as_tensor(np.zeros((len(list(to_generate_one_delta_from.index)), 625), int), device=device)\nstart_positions = torch.as_tensor(to_generate_one_delta_from.filter(regex='^start_').values, device=device)\n\nfor i in range(0, start_positions.shape[0]):\n    calculated_result[i] |= calculate_gol_state_with_masks_per_delta(start_positions[i],  masks, 25, 1)\n    if  ((i % 100) == 0):\n        print(i, end=',')\n</code></pre>\n<p>I am aware that I could deduce the width of the vector/dimension of the metrix from the shap, but chose to be a bit more explicit here for no good reason.</p>\n<p>TL;DR;<br>\nIs there a way to improve the performance of the code above further?</p>\n<p>Cheers</p>\n<p>dud</p>",
  "messages": [
    {
      "id": 1086152,
      "postDate": "2020-11-21T12:43:07.973Z",
      "content": "<p>Hi there,</p>\n<p>a novice here.<br>\nI was just watching the competition and toying around with the dataset and it got me curious regarding the improvement of data-preparation.<br>\nA naive approach yielded an error-rate of around 0.17 so I thought it best to compute some more data for e.g. delta=1.</p>\n<p>A very direct approach for the computation would run ~5-6h and not use the GPU.</p>\n<p>I came up with the following approach which runs ~45-60min(if projections pan out) and utilizes ~13-15% of the GPU.</p>\n<p>I understand that the bottle-neck here is computation of a cell's state in the next iteration.<br>\nIs there a way to treat everything as a matrix-/vector-operation and thus utilizing the GPU more gaining more speed?</p>\n<pre><code>import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom fastai.tabular.all import *\nfrom torch.nn import *\n\n\ndef calculate_all_masks(matrixWidth):\n    columnCount = int(matrixWidth ** 2)\n    result = torch.as_tensor(np.zeros((columnCount,columnCount), int), device=device)\n\n    for position in range(0,columnCount):\n        for y in [-1,0,1]:\n            for z in [-1,0,1]:\n                calculated_row = (position + columnCount +y*matrixWidth) % columnCount\n                index_with_column_chosen = int(calculated_row/matrixWidth) * matrixWidth + (calculated_row + 1*z)%matrixWidth\n                result[position][index_with_column_chosen] = 1\n\n    return result\n\ndef calculate_gol_state_for_tensor_with_masks(start, masks, matrixWidth):\n    columnCount = int(matrixWidth ** 2)\n    result = torch.as_tensor(np.zeros((columnCount,), int), device=device)\n    zero_vector = torch.as_tensor(np.zeros((columnCount,),int), device=device).float().unsqueeze(0)\n\n    for current_cells in range(0, columnCount):\n        result[current_cells] = int(\n            math.ceil(\n                torch.cdist((masks[current_cells] &amp; start).float().unsqueeze(0), zero_vector)[0].item() ** 2\n            ) in [3,4]\n        )\n\n    return result\n\ndef calculate_gol_state_with_masks_per_delta(start, mask, matrixWidth, delta):\n    result = start\n\n    for i in range(0,delta):\n        result = calculate_gol_state_for_tensor_with_masks(result, masks, matrixWidth)\n\n    return result\n\ndataset = pd.read_csv('/kaggle/input/conways-reverse-game-of-life-2020/train.csv')        \ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nto_generate_one_delta_from = dataset.loc[dataset['delta'] &gt; 1].reset_index(drop=True)\nmasks = calculate_all_masks(25)\ncalculated_result = torch.as_tensor(np.zeros((len(list(to_generate_one_delta_from.index)), 625), int), device=device)\nstart_positions = torch.as_tensor(to_generate_one_delta_from.filter(regex='^start_').values, device=device)\n\nfor i in range(0, start_positions.shape[0]):\n    calculated_result[i] |= calculate_gol_state_with_masks_per_delta(start_positions[i],  masks, 25, 1)\n    if  ((i % 100) == 0):\n        print(i, end=',')\n</code></pre>\n<p>I am aware that I could deduce the width of the vector/dimension of the metrix from the shap, but chose to be a bit more explicit here for no good reason.</p>\n<p>TL;DR;<br>\nIs there a way to improve the performance of the code above further?</p>\n<p>Cheers</p>\n<p>dud</p>",
      "rawMarkdown": "Hi there,\n\na novice here.\nI was just watching the competition and toying around with the dataset and it got me curious regarding the improvement of data-preparation.\nA naive approach yielded an error-rate of around 0.17 so I thought it best to compute some more data for e.g. delta=1.\n\nA very direct approach for the computation would run ~5-6h and not use the GPU.\n\nI came up with the following approach which runs ~45-60min(if projections pan out) and utilizes ~13-15% of the GPU.\n\nI understand that the bottle-neck here is computation of a cell's state in the next iteration.\nIs there a way to treat everything as a matrix-/vector-operation and thus utilizing the GPU more gaining more speed?\n\n```\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom fastai.tabular.all import *\nfrom torch.nn import *\n\n\ndef calculate_all_masks(matrixWidth):\n    columnCount = int(matrixWidth ** 2)\n    result = torch.as_tensor(np.zeros((columnCount,columnCount), int), device=device)\n    \n    for position in range(0,columnCount):\n        for y in [-1,0,1]:\n            for z in [-1,0,1]:\n                calculated_row = (position + columnCount +y*matrixWidth) % columnCount\n                index_with_column_chosen = int(calculated_row/matrixWidth) * matrixWidth + (calculated_row + 1*z)%matrixWidth\n                result[position][index_with_column_chosen] = 1\n    \n    return result\n\ndef calculate_gol_state_for_tensor_with_masks(start, masks, matrixWidth):\n    columnCount = int(matrixWidth ** 2)\n    result = torch.as_tensor(np.zeros((columnCount,), int), device=device)\n    zero_vector = torch.as_tensor(np.zeros((columnCount,),int), device=device).float().unsqueeze(0)\n    \n    for current_cells in range(0, columnCount):\n        result[current_cells] = int(\n            math.ceil(\n                torch.cdist((masks[current_cells] & start).float().unsqueeze(0), zero_vector)[0].item() ** 2\n            ) in [3,4]\n        )\n    \n    return result\n\ndef calculate_gol_state_with_masks_per_delta(start, mask, matrixWidth, delta):\n    result = start\n    \n    for i in range(0,delta):\n        result = calculate_gol_state_for_tensor_with_masks(result, masks, matrixWidth)\n    \n    return result\n        \ndataset = pd.read_csv('/kaggle/input/conways-reverse-game-of-life-2020/train.csv')        \ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nto_generate_one_delta_from = dataset.loc[dataset['delta'] > 1].reset_index(drop=True)\nmasks = calculate_all_masks(25)\ncalculated_result = torch.as_tensor(np.zeros((len(list(to_generate_one_delta_from.index)), 625), int), device=device)\nstart_positions = torch.as_tensor(to_generate_one_delta_from.filter(regex='^start_').values, device=device)\n\nfor i in range(0, start_positions.shape[0]):\n    calculated_result[i] |= calculate_gol_state_with_masks_per_delta(start_positions[i],  masks, 25, 1)\n    if  ((i % 100) == 0):\n        print(i, end=',')\n```\n\nI am aware that I could deduce the width of the vector/dimension of the metrix from the shap, but chose to be a bit more explicit here for no good reason.\n\nTL;DR;\nIs there a way to improve the performance of the code above further?\n\nCheers\n\ndud\n",
      "votes": 3
    },
    {
      "id": 1097002,
      "postDate": "2020-11-30T23:36:37.123Z",
      "content": "<p>The \"secret\" of the top leaderboard entries is that they are written in C. Same with the ConnectX competition. Native python is 100x slower than C.</p>\n<p>If you want to stick with Python, then get familiar with numba <a href=\"https://www.kaggle.com/njit\" target=\"_blank\">@njit</a> which compiles to the LLVM (ie C)</p>\n<ul>\n<li><a href=\"https://numba.pydata.org/numba-doc/latest/index.html\" target=\"_blank\">https://numba.pydata.org/numba-doc/latest/index.html</a></li>\n</ul>\n<pre><code>import numpy as np\nfrom numba import njit\n\n@njit()\ndef count_neighbours( input: np.ndarray ) -&gt; np.ndarray:\n  output     = np.zeros(array.shape, dtype=np.int)  \n  directions = [ (1,1), (1,0), (1,-1), (0,1), (1,-1), (-1,1), (-1,0), (-1,-1), ]\n  shape = input.shape\n  for x in range(shape[0]):\n    for y in range(shape[1]):\n      for (dx,dy) in directions:\n        output[x,y] += input[ (x+dx)%shape[0], (y+dy)%shape[1] ]\n  return output \n</code></pre>\n<p>In practice using numba can be a bit fiddly to use:</p>\n<ul>\n<li>It has to statically type everything using C types and will throw compile-time exceptions if it can't figure out deterministic typing for all variables</li>\n<li>It doesn't like object oriented programming - using <a href=\"https://www.kaggle.com/jit\" target=\"_blank\">@jit</a>(nopython=False)  eliminates most of the performance gains</li>\n<li>It doesn't like some common python idioms </li>\n<li>It doesn't like dictionaries unless you use numba.typed.Dict() and be explicit about typing</li>\n<li>It has problems with reflected lists if it can't statically determine type</li>\n<li>It likes numpy arrays (which are already C typed)</li>\n<li>Not all numpy functions are implemented, and you often can't use itertools or other libraries</li>\n<li>There is very high one-time compile cost the first time the function is run</li>\n<li>There is significant overhead in converting python objects to C types and back, so ading <a href=\"https://www.kaggle.com/njit\" target=\"_blank\">@njit</a> to a single small function may actually make things slower - please profile your code and avoid passing in unnecessary data</li>\n<li>It can be tricky to get working sometimes, and some code may be impossible to port</li>\n<li>Used correctly it can provide a 100-1000x performance increase vs native python</li>\n<li>use NUMBA_DISABLE_JIT=1  to disable for debugging</li>\n</ul>",
      "rawMarkdown": "The \"secret\" of the top leaderboard entries is that they are written in C. Same with the ConnectX competition. Native python is 100x slower than C.\n\nIf you want to stick with Python, then get familiar with numba @njit which compiles to the LLVM (ie C)\n- https://numba.pydata.org/numba-doc/latest/index.html\n```\nimport numpy as np\nfrom numba import njit\n\n@njit()\ndef count_neighbours( input: np.ndarray ) -> np.ndarray:\n  output     = np.zeros(array.shape, dtype=np.int)  \n  directions = [ (1,1), (1,0), (1,-1), (0,1), (1,-1), (-1,1), (-1,0), (-1,-1), ]\n  shape = input.shape\n  for x in range(shape[0]):\n    for y in range(shape[1]):\n      for (dx,dy) in directions:\n        output[x,y] += input[ (x+dx)%shape[0], (y+dy)%shape[1] ]\n  return output \n```\n\nIn practice using numba can be a bit fiddly to use:\n- It has to statically type everything using C types and will throw compile-time exceptions if it can't figure out deterministic typing for all variables\n- It doesn't like object oriented programming - using @jit(nopython=False)  eliminates most of the performance gains\n- It doesn't like some common python idioms \n- It doesn't like dictionaries unless you use numba.typed.Dict() and be explicit about typing\n- It has problems with reflected lists if it can't statically determine type\n- It likes numpy arrays (which are already C typed)\n- Not all numpy functions are implemented, and you often can't use itertools or other libraries\n- There is very high one-time compile cost the first time the function is run\n- There is significant overhead in converting python objects to C types and back, so ading @njit to a single small function may actually make things slower - please profile your code and avoid passing in unnecessary data\n- It can be tricky to get working sometimes, and some code may be impossible to port\n- Used correctly it can provide a 100-1000x performance increase vs native python\n- use NUMBA_DISABLE_JIT=1  to disable for debugging",
      "votes": 1,
      "replies": [
        {
          "id": 1097620,
          "postDate": "2020-12-01T06:16:10.977Z",
          "content": "<p>I was actually thinking of restructuring things to harness the GPU better, but did not expect s.th. like numba to be in the mix, as well. I havn't even heard of it before. And by the looks of it, is also able to work with Cuda.<br>\nThank you for this tip.👍</p>",
          "rawMarkdown": "I was actually thinking of restructuring things to harness the GPU better, but did not expect s.th. like numba to be in the mix, as well. I havn't even heard of it before. And by the looks of it, is also able to work with Cuda.\nThank you for this tip.👍"
        }
      ]
    },
    {
      "id": 1087990,
      "postDate": "2020-11-23T08:18:48.787Z",
      "content": "<p>Out of curiosity, what is the idea behind this code? What is it supposed to do?<br>\nIt looks like some kind of magic.</p>",
      "rawMarkdown": "Out of curiosity, what is the idea behind this code? What is it supposed to do?\nIt looks like some kind of magic.",
      "replies": [
        {
          "id": 1088019,
          "postDate": "2020-11-23T08:43:59.600Z",
          "content": "<p>Hi!<br>\nThank you for answering.<br>\nThat escalated quickly.<br>\nWasn't expecting a discussion about the motivation behind it, but I do appreciate it, as I might have made mistakes I am not aware of.</p>\n<p>So let me structure this in 2 parts.<br>\nOne for what the code actually does and another with the motivation behind this.</p>\n<p>Let's start with the motivation and how I viewed the problem.<br>\nThe rules for the Game of Life allow for an easy forward computation, but as soon as you try to reverse it you are faced with a combinatory problem which grows greated with the size of the grid and the number of iterations.<br>\nML is hungry for data.<br>\nI was motivated by the thought that more data allows for my Neural Net to train more and become better at calculating a solution. Every iteration with delta &gt; 1 is a composition of x iterations of delta==1 predictions where x is the actual delta for the dataset to find a solution for.<br>\nIn order get more data quickly I chose the start-positions of the training set as they provide a state that lives up to or past delta == 1.<br>\nThe code above is meant to calculate the first iteration for every row in the dataset with delta &gt; 1.<br>\nI still have an error rate of 0.14 so I must be doing s.th. else wrong, but I am working on it.</p>\n<p>As far as the code is concerned.<br>\nThe state of every cell is only concerned with it's immediat surrounding.<br>\nSo I can mask out all other cells for the purpose of computing a cell's state after one iteration.<br>\nTo speed things up, I precompute a combination of all the masks.<br>\nThen the state of the cell in question is calculated by using bit-wise &amp; on the vector and it will become a matter distance to a 0-vector.<br>\nLength 3 or 4 means the cell lives after the current iteration regardless of it's current state.<br>\nAny other length means  that it is not alive after the current iteration.<br>\n2 or 3 neighbors =&gt; cell get's to live on so the length can either be 3 or 4(3 neighbors(1) and the alive cell(1))<br>\nIf the cell is dead and 3 neighbors are present =&gt; cell gets to live next round and the length has to be 3</p>\n<p>Cheers</p>\n<p>dud</p>",
          "rawMarkdown": "Hi!\nThank you for answering.\nThat escalated quickly.\nWasn't expecting a discussion about the motivation behind it, but I do appreciate it, as I might have made mistakes I am not aware of.\n\nSo let me structure this in 2 parts.\nOne for what the code actually does and another with the motivation behind this.\n\nLet's start with the motivation and how I viewed the problem.\nThe rules for the Game of Life allow for an easy forward computation, but as soon as you try to reverse it you are faced with a combinatory problem which grows greated with the size of the grid and the number of iterations.\nML is hungry for data.\nI was motivated by the thought that more data allows for my Neural Net to train more and become better at calculating a solution. Every iteration with delta > 1 is a composition of x iterations of delta==1 predictions where x is the actual delta for the dataset to find a solution for.\nIn order get more data quickly I chose the start-positions of the training set as they provide a state that lives up to or past delta == 1.\nThe code above is meant to calculate the first iteration for every row in the dataset with delta > 1.\nI still have an error rate of 0.14 so I must be doing s.th. else wrong, but I am working on it.\n\nAs far as the code is concerned.\nThe state of every cell is only concerned with it's immediat surrounding.\nSo I can mask out all other cells for the purpose of computing a cell's state after one iteration.\nTo speed things up, I precompute a combination of all the masks.\nThen the state of the cell in question is calculated by using bit-wise & on the vector and it will become a matter distance to a 0-vector.\nLength 3 or 4 means the cell lives after the current iteration regardless of it's current state.\nAny other length means  that it is not alive after the current iteration.\n2 or 3 neighbors => cell get's to live on so the length can either be 3 or 4(3 neighbors(1) and the alive cell(1))\nIf the cell is dead and 3 neighbors are present => cell gets to live next round and the length has to be 3\n\nCheers\n\ndud\n"
        },
        {
          "id": 1088601,
          "postDate": "2020-11-23T19:29:35.593Z",
          "content": "<p>Interesting idea.</p>\n<p>Although it is still unclear to me how you code solves reverse game of life problem and not direct one, a have few comments:</p>\n<blockquote>\n  <p>The state of every cell is only concerned with its immediate surrounding.</p>\n</blockquote>\n<p>Unfortunately, while working with SAT solvers, I found that adding additional constraint on dead cell surrounded by dead cells to be dead previously (0-length in your terms) often makes problem of finding previous step unsolvable.<br>\nSometimes even 5x5 surrounding is not enough to determine cell state.</p>\n<blockquote>\n  <p>Every iteration with delta &gt; 1 is a composition of x iterations of delta==1 predictions where x is the actual delta for the dataset to find a solution for.</p>\n</blockquote>\n<p>While this is true, only small subset of possible 1st iteration solutions will be solvable on next iteration. <br>\nFinding solutions with delta at least (x-1) is crucial for the competition.</p>",
          "rawMarkdown": "Interesting idea.\n\nAlthough it is still unclear to me how you code solves reverse game of life problem and not direct one, a have few comments:\n\n\n> The state of every cell is only concerned with its immediate surrounding.\n\nUnfortunately, while working with SAT solvers, I found that adding additional constraint on dead cell surrounded by dead cells to be dead previously (0-length in your terms) often makes problem of finding previous step unsolvable.\nSometimes even 5x5 surrounding is not enough to determine cell state.\n\n\n> Every iteration with delta > 1 is a composition of x iterations of delta==1 predictions where x is the actual delta for the dataset to find a solution for.\n\nWhile this is true, only small subset of possible 1st iteration solutions will be solvable on next iteration. \nFinding solutions with delta at least (x-1) is crucial for the competition."
        },
        {
          "id": 1088638,
          "postDate": "2020-11-23T20:08:02.333Z",
          "content": "<p>I think we are talking about s.th. different.<br>\nThis is not ment to solve the reverse Game of Life, but rather provide data for it.<br>\nIt is a forward solver to give the Neural Net more data, and for that I only need the surrounding cells.<br>\nSince it is a forward solver for data-generation and and zeroed out all the non-neighborly cells, I think I actually removed unnecessary input. Looking at my conditions for a living/reanimated cell, I actually end up with one less condition compared to the step-instructions for the classical non-vector approach.</p>\n<p>Do you mind elaborating on </p>\n<blockquote>\n  <p>While this is true, only small subset of possible 1st iteration solutions will be solvable on next iteration.<br>\n  Finding solutions with delta at least (x-1) is crucial for the competition.</p>\n</blockquote>\n<p>Why is that? Could you furnish me with a link?<br>\nAlso, why does it matter? Any valid starting-position resulting in the given game is valid according to the competition's description</p>\n<blockquote>\n  <p>Submissions are solved forward by the appropriate number of steps, so that any correct starting solution will achieve a maximum score. This article contains the stepping function that is used for this competition.</p>\n</blockquote>",
          "rawMarkdown": "I think we are talking about s.th. different.\nThis is not ment to solve the reverse Game of Life, but rather provide data for it.\nIt is a forward solver to give the Neural Net more data, and for that I only need the surrounding cells.\nSince it is a forward solver for data-generation and and zeroed out all the non-neighborly cells, I think I actually removed unnecessary input. Looking at my conditions for a living/reanimated cell, I actually end up with one less condition compared to the step-instructions for the classical non-vector approach.\n\nDo you mind elaborating on \n> While this is true, only small subset of possible 1st iteration solutions will be solvable on next iteration.\nFinding solutions with delta at least (x-1) is crucial for the competition.\n\nWhy is that? Could you furnish me with a link?\nAlso, why does it matter? Any valid starting-position resulting in the given game is valid according to the competition's description\n\n> Submissions are solved forward by the appropriate number of steps, so that any correct starting solution will achieve a maximum score. This article contains the stepping function that is used for this competition.\n\n"
        }
      ]
    },
    {
      "id": 1088018,
      "postDate": "2020-11-23T08:43:40.060Z",
      "rawMarkdown": "",
      "isDeleted": true
    }
  ],
  "comments": [
    {
      "id": 1097002,
      "author_name": "James McGuigan",
      "author_url": "",
      "post_date": "2020-11-30T23:36:37.123000",
      "content": "<p>The \"secret\" of the top leaderboard entries is that they are written in C. Same with the ConnectX competition. Native python is 100x slower than C.</p>\n<p>If you want to stick with Python, then get familiar with numba <a href=\"https://www.kaggle.com/njit\" target=\"_blank\">@njit</a> which compiles to the LLVM (ie C)</p>\n<ul>\n<li><a href=\"https://numba.pydata.org/numba-doc/latest/index.html\" target=\"_blank\">https://numba.pydata.org/numba-doc/latest/index.html</a></li>\n</ul>\n<pre><code>import numpy as np\nfrom numba import njit\n\n@njit()\ndef count_neighbours( input: np.ndarray ) -&gt; np.ndarray:\n  output     = np.zeros(array.shape, dtype=np.int)  \n  directions = [ (1,1), (1,0), (1,-1), (0,1), (1,-1), (-1,1), (-1,0), (-1,-1), ]\n  shape = input.shape\n  for x in range(shape[0]):\n    for y in range(shape[1]):\n      for (dx,dy) in directions:\n        output[x,y] += input[ (x+dx)%shape[0], (y+dy)%shape[1] ]\n  return output \n</code></pre>\n<p>In practice using numba can be a bit fiddly to use:</p>\n<ul>\n<li>It has to statically type everything using C types and will throw compile-time exceptions if it can't figure out deterministic typing for all variables</li>\n<li>It doesn't like object oriented programming - using <a href=\"https://www.kaggle.com/jit\" target=\"_blank\">@jit</a>(nopython=False)  eliminates most of the performance gains</li>\n<li>It doesn't like some common python idioms </li>\n<li>It doesn't like dictionaries unless you use numba.typed.Dict() and be explicit about typing</li>\n<li>It has problems with reflected lists if it can't statically determine type</li>\n<li>It likes numpy arrays (which are already C typed)</li>\n<li>Not all numpy functions are implemented, and you often can't use itertools or other libraries</li>\n<li>There is very high one-time compile cost the first time the function is run</li>\n<li>There is significant overhead in converting python objects to C types and back, so ading <a href=\"https://www.kaggle.com/njit\" target=\"_blank\">@njit</a> to a single small function may actually make things slower - please profile your code and avoid passing in unnecessary data</li>\n<li>It can be tricky to get working sometimes, and some code may be impossible to port</li>\n<li>Used correctly it can provide a 100-1000x performance increase vs native python</li>\n<li>use NUMBA_DISABLE_JIT=1  to disable for debugging</li>\n</ul>",
      "votes": 1,
      "replies": [
        {
          "id": 1097620,
          "author_name": "somedud",
          "author_url": "",
          "post_date": "2020-12-01T06:16:10.977000",
          "content": "<p>I was actually thinking of restructuring things to harness the GPU better, but did not expect s.th. like numba to be in the mix, as well. I havn't even heard of it before. And by the looks of it, is also able to work with Cuda.<br>\nThank you for this tip.👍</p>",
          "votes": 0,
          "replies": []
        }
      ]
    },
    {
      "id": 1087990,
      "author_name": "Serhii Hrynko",
      "author_url": "",
      "post_date": "2020-11-23T08:18:48.787000",
      "content": "<p>Out of curiosity, what is the idea behind this code? What is it supposed to do?<br>\nIt looks like some kind of magic.</p>",
      "votes": 0,
      "replies": [
        {
          "id": 1088019,
          "author_name": "somedud",
          "author_url": "",
          "post_date": "2020-11-23T08:43:59.600000",
          "content": "<p>Hi!<br>\nThank you for answering.<br>\nThat escalated quickly.<br>\nWasn't expecting a discussion about the motivation behind it, but I do appreciate it, as I might have made mistakes I am not aware of.</p>\n<p>So let me structure this in 2 parts.<br>\nOne for what the code actually does and another with the motivation behind this.</p>\n<p>Let's start with the motivation and how I viewed the problem.<br>\nThe rules for the Game of Life allow for an easy forward computation, but as soon as you try to reverse it you are faced with a combinatory problem which grows greated with the size of the grid and the number of iterations.<br>\nML is hungry for data.<br>\nI was motivated by the thought that more data allows for my Neural Net to train more and become better at calculating a solution. Every iteration with delta &gt; 1 is a composition of x iterations of delta==1 predictions where x is the actual delta for the dataset to find a solution for.<br>\nIn order get more data quickly I chose the start-positions of the training set as they provide a state that lives up to or past delta == 1.<br>\nThe code above is meant to calculate the first iteration for every row in the dataset with delta &gt; 1.<br>\nI still have an error rate of 0.14 so I must be doing s.th. else wrong, but I am working on it.</p>\n<p>As far as the code is concerned.<br>\nThe state of every cell is only concerned with it's immediat surrounding.<br>\nSo I can mask out all other cells for the purpose of computing a cell's state after one iteration.<br>\nTo speed things up, I precompute a combination of all the masks.<br>\nThen the state of the cell in question is calculated by using bit-wise &amp; on the vector and it will become a matter distance to a 0-vector.<br>\nLength 3 or 4 means the cell lives after the current iteration regardless of it's current state.<br>\nAny other length means  that it is not alive after the current iteration.<br>\n2 or 3 neighbors =&gt; cell get's to live on so the length can either be 3 or 4(3 neighbors(1) and the alive cell(1))<br>\nIf the cell is dead and 3 neighbors are present =&gt; cell gets to live next round and the length has to be 3</p>\n<p>Cheers</p>\n<p>dud</p>",
          "votes": 0,
          "replies": []
        },
        {
          "id": 1088601,
          "author_name": "Serhii Hrynko",
          "author_url": "",
          "post_date": "2020-11-23T19:29:35.593000",
          "content": "<p>Interesting idea.</p>\n<p>Although it is still unclear to me how you code solves reverse game of life problem and not direct one, a have few comments:</p>\n<blockquote>\n  <p>The state of every cell is only concerned with its immediate surrounding.</p>\n</blockquote>\n<p>Unfortunately, while working with SAT solvers, I found that adding additional constraint on dead cell surrounded by dead cells to be dead previously (0-length in your terms) often makes problem of finding previous step unsolvable.<br>\nSometimes even 5x5 surrounding is not enough to determine cell state.</p>\n<blockquote>\n  <p>Every iteration with delta &gt; 1 is a composition of x iterations of delta==1 predictions where x is the actual delta for the dataset to find a solution for.</p>\n</blockquote>\n<p>While this is true, only small subset of possible 1st iteration solutions will be solvable on next iteration. <br>\nFinding solutions with delta at least (x-1) is crucial for the competition.</p>",
          "votes": 0,
          "replies": []
        },
        {
          "id": 1088638,
          "author_name": "somedud",
          "author_url": "",
          "post_date": "2020-11-23T20:08:02.333000",
          "content": "<p>I think we are talking about s.th. different.<br>\nThis is not ment to solve the reverse Game of Life, but rather provide data for it.<br>\nIt is a forward solver to give the Neural Net more data, and for that I only need the surrounding cells.<br>\nSince it is a forward solver for data-generation and and zeroed out all the non-neighborly cells, I think I actually removed unnecessary input. Looking at my conditions for a living/reanimated cell, I actually end up with one less condition compared to the step-instructions for the classical non-vector approach.</p>\n<p>Do you mind elaborating on </p>\n<blockquote>\n  <p>While this is true, only small subset of possible 1st iteration solutions will be solvable on next iteration.<br>\n  Finding solutions with delta at least (x-1) is crucial for the competition.</p>\n</blockquote>\n<p>Why is that? Could you furnish me with a link?<br>\nAlso, why does it matter? Any valid starting-position resulting in the given game is valid according to the competition's description</p>\n<blockquote>\n  <p>Submissions are solved forward by the appropriate number of steps, so that any correct starting solution will achieve a maximum score. This article contains the stepping function that is used for this competition.</p>\n</blockquote>",
          "votes": 0,
          "replies": []
        }
      ]
    },
    {
      "id": 1088018,
      "author_name": "",
      "author_url": "",
      "post_date": "2020-11-23T08:43:40.060000",
      "content": "",
      "votes": 0,
      "replies": []
    }
  ],
  "raw_markdown_by_id": {
    "1086152": "Hi there,\n\na novice here.\nI was just watching the competition and toying around with the dataset and it got me curious regarding the improvement of data-preparation.\nA naive approach yielded an error-rate of around 0.17 so I thought it best to compute some more data for e.g. delta=1.\n\nA very direct approach for the computation would run ~5-6h and not use the GPU.\n\nI came up with the following approach which runs ~45-60min(if projections pan out) and utilizes ~13-15% of the GPU.\n\nI understand that the bottle-neck here is computation of a cell's state in the next iteration.\nIs there a way to treat everything as a matrix-/vector-operation and thus utilizing the GPU more gaining more speed?\n\n```\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom fastai.tabular.all import *\nfrom torch.nn import *\n\n\ndef calculate_all_masks(matrixWidth):\n    columnCount = int(matrixWidth ** 2)\n    result = torch.as_tensor(np.zeros((columnCount,columnCount), int), device=device)\n    \n    for position in range(0,columnCount):\n        for y in [-1,0,1]:\n            for z in [-1,0,1]:\n                calculated_row = (position + columnCount +y*matrixWidth) % columnCount\n                index_with_column_chosen = int(calculated_row/matrixWidth) * matrixWidth + (calculated_row + 1*z)%matrixWidth\n                result[position][index_with_column_chosen] = 1\n    \n    return result\n\ndef calculate_gol_state_for_tensor_with_masks(start, masks, matrixWidth):\n    columnCount = int(matrixWidth ** 2)\n    result = torch.as_tensor(np.zeros((columnCount,), int), device=device)\n    zero_vector = torch.as_tensor(np.zeros((columnCount,),int), device=device).float().unsqueeze(0)\n    \n    for current_cells in range(0, columnCount):\n        result[current_cells] = int(\n            math.ceil(\n                torch.cdist((masks[current_cells] & start).float().unsqueeze(0), zero_vector)[0].item() ** 2\n            ) in [3,4]\n        )\n    \n    return result\n\ndef calculate_gol_state_with_masks_per_delta(start, mask, matrixWidth, delta):\n    result = start\n    \n    for i in range(0,delta):\n        result = calculate_gol_state_for_tensor_with_masks(result, masks, matrixWidth)\n    \n    return result\n        \ndataset = pd.read_csv('/kaggle/input/conways-reverse-game-of-life-2020/train.csv')        \ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nto_generate_one_delta_from = dataset.loc[dataset['delta'] > 1].reset_index(drop=True)\nmasks = calculate_all_masks(25)\ncalculated_result = torch.as_tensor(np.zeros((len(list(to_generate_one_delta_from.index)), 625), int), device=device)\nstart_positions = torch.as_tensor(to_generate_one_delta_from.filter(regex='^start_').values, device=device)\n\nfor i in range(0, start_positions.shape[0]):\n    calculated_result[i] |= calculate_gol_state_with_masks_per_delta(start_positions[i],  masks, 25, 1)\n    if  ((i % 100) == 0):\n        print(i, end=',')\n```\n\nI am aware that I could deduce the width of the vector/dimension of the metrix from the shap, but chose to be a bit more explicit here for no good reason.\n\nTL;DR;\nIs there a way to improve the performance of the code above further?\n\nCheers\n\ndud\n",
    "1097002": "The \"secret\" of the top leaderboard entries is that they are written in C. Same with the ConnectX competition. Native python is 100x slower than C.\n\nIf you want to stick with Python, then get familiar with numba @njit which compiles to the LLVM (ie C)\n- https://numba.pydata.org/numba-doc/latest/index.html\n```\nimport numpy as np\nfrom numba import njit\n\n@njit()\ndef count_neighbours( input: np.ndarray ) -> np.ndarray:\n  output     = np.zeros(array.shape, dtype=np.int)  \n  directions = [ (1,1), (1,0), (1,-1), (0,1), (1,-1), (-1,1), (-1,0), (-1,-1), ]\n  shape = input.shape\n  for x in range(shape[0]):\n    for y in range(shape[1]):\n      for (dx,dy) in directions:\n        output[x,y] += input[ (x+dx)%shape[0], (y+dy)%shape[1] ]\n  return output \n```\n\nIn practice using numba can be a bit fiddly to use:\n- It has to statically type everything using C types and will throw compile-time exceptions if it can't figure out deterministic typing for all variables\n- It doesn't like object oriented programming - using @jit(nopython=False)  eliminates most of the performance gains\n- It doesn't like some common python idioms \n- It doesn't like dictionaries unless you use numba.typed.Dict() and be explicit about typing\n- It has problems with reflected lists if it can't statically determine type\n- It likes numpy arrays (which are already C typed)\n- Not all numpy functions are implemented, and you often can't use itertools or other libraries\n- There is very high one-time compile cost the first time the function is run\n- There is significant overhead in converting python objects to C types and back, so ading @njit to a single small function may actually make things slower - please profile your code and avoid passing in unnecessary data\n- It can be tricky to get working sometimes, and some code may be impossible to port\n- Used correctly it can provide a 100-1000x performance increase vs native python\n- use NUMBA_DISABLE_JIT=1  to disable for debugging",
    "1087990": "Out of curiosity, what is the idea behind this code? What is it supposed to do?\nIt looks like some kind of magic.",
    "1088018": ""
  }
}