683 lines
18 KiB
Plaintext
683 lines
18 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"**Make sure you run this at the begining**"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import os\n",
|
|
"import sys\n",
|
|
"import math\n",
|
|
"import numpy as np\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"\n",
|
|
"# Append template path to sys path\n",
|
|
"sys.path.append(os.getcwd() + \"/template\") "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from utils.load_data import load_data\n",
|
|
"from utils.visualize_tsp import plotTSP\n",
|
|
"\n",
|
|
"from tsp import TSP_Bench_ONE\n",
|
|
"from tsp import TSP_Bench_PATH\n",
|
|
"from tsp import TSP_Bench_ALL"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Workshop Starts Here"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"<img src=\"images/tsp.jpg\" alt=\"TSP\" style=\"width: 900px;\"/>"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"<img src=\"images/solutions.png\" alt=\"solutions\" style=\"width: 900px;\"/>"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Get familiar with your dataset"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"There are problems at different levels. **3 easy, 2 medium, 1 difficult**."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": false
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"for root, _, files in os.walk('./template/data'):\n",
|
|
" if(files):\n",
|
|
" for f in files:\n",
|
|
" print(str(root) + \"/\" + f)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"ulysses16 = np.array(load_data(\"./template/data/easy/ulysses16.tsp\"))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"ulysses16[:]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"plt.scatter(ulysses16[:, 0], ulysses16[:, 1])\n",
|
|
"for i in range(0, 16):\n",
|
|
" plt.annotate(i, (ulysses16[i, 0], ulysses16[i, 1]+0.5))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Naive Solution: In Order"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"simple_sequence = list(range(0, 16))\n",
|
|
"print(simple_sequence)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"plotTSP([simple_sequence], ulysses16, num_iters=1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Naive Solution: Random Permutation"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"random_permutation = np.random.permutation(16).tolist()\n",
|
|
"print(random_permutation)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"plotTSP([random_permutation], ulysses16, num_iters=1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Best Solution"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"best_ulysses16 = [0, 13, 12, 11, 6, 5, 14, 4, 10, 8, 9, 15, 2, 1, 3, 7]\n",
|
|
"plotTSP([best_ulysses16], ulysses16, num_iters=1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Calculate Fitness (Sum of all Distances)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def dist(node_0, node_1, coords):\n",
|
|
" \"\"\"\n",
|
|
" Euclidean distance between two nodes.\n",
|
|
" \"\"\"\n",
|
|
" coord_0, coord_1 = coords[node_0], coords[node_1]\n",
|
|
" return math.sqrt((coord_0[0] - coord_1[0]) ** 2 + (coord_0[1] - coord_1[1]) ** 2)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"print(\"Coordinate of City 0:\", ulysses16[0])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"print(\"Coordinate of City 1:\", ulysses16[1])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"print(\"Distance Between\", dist(0, 1, ulysses16))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def fitness(solution, coords):\n",
|
|
" N = len(coords)\n",
|
|
" cur_fit = 0\n",
|
|
" for i in range(len(solution)):\n",
|
|
" cur_fit += dist(solution[i % N], solution[(i + 1) % N], coords)\n",
|
|
" return cur_fit"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"print (\"Order Fitness:\\t\", fitness(simple_sequence, ulysses16))\n",
|
|
"print (\"Random Fitness:\\t\", fitness(random_permutation, ulysses16))\n",
|
|
"print (\"Best Fitness:\\t\", fitness(best_ulysses16, ulysses16))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Naive Random Model"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import math\n",
|
|
"import random\n",
|
|
"from model.base_model import Model\n",
|
|
"import numpy as np\n",
|
|
"\n",
|
|
"class MyRandomModel(Model):\n",
|
|
" def __init__(self):\n",
|
|
" super().__init__()\n",
|
|
"\n",
|
|
" def init(self, nodes):\n",
|
|
" \"\"\"\n",
|
|
" Put your initialization here.\n",
|
|
" \"\"\"\n",
|
|
" super().init(nodes)\n",
|
|
"\n",
|
|
" def fit(self, max_it=1000):\n",
|
|
" \"\"\"\n",
|
|
" Put your iteration process here.\n",
|
|
" \"\"\"\n",
|
|
" random_solutions = []\n",
|
|
" for i in range(0, max_it):\n",
|
|
" solution = np.random.permutation(self.N).tolist()\n",
|
|
" random_solutions.append(solution)\n",
|
|
" self.fitness_list.append(self.fitness(solution))\n",
|
|
"\n",
|
|
" self.best_solution = random_solutions[self.fitness_list.index(min(self.fitness_list))]\n",
|
|
" return self.best_solution, self.fitness_list"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"tsp_file = './template/data/easy/ulysses16.tsp'"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyRandomModel)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"plt.plot(fitness_list, 'o-')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Ant Colony Optimization"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"https://github.com/rochakgupta/aco-tsp"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import math\n",
|
|
"import random\n",
|
|
"from model.base_model import Model\n",
|
|
"\n",
|
|
"class MyACOModel(Model):\n",
|
|
" def __init__(self):\n",
|
|
" super().__init__()\n",
|
|
"\n",
|
|
" class Edge:\n",
|
|
" def __init__(self, a, b, weight, initial_pheromone):\n",
|
|
" self.a = a\n",
|
|
" self.b = b\n",
|
|
" self.weight = weight\n",
|
|
" self.pheromone = initial_pheromone\n",
|
|
"\n",
|
|
" class Ant:\n",
|
|
" def __init__(self, alpha, beta, num_nodes, edges):\n",
|
|
" self.alpha = alpha\n",
|
|
" self.beta = beta\n",
|
|
" self.num_nodes = num_nodes\n",
|
|
" self.edges = edges\n",
|
|
" self.tour = None\n",
|
|
" self.distance = 0.0\n",
|
|
"\n",
|
|
" def _select_node(self):\n",
|
|
" roulette_wheel = 0.0\n",
|
|
" unvisited_nodes = [node for node in range(self.num_nodes) if node not in self.tour]\n",
|
|
" heuristic_total = 0.0\n",
|
|
" for unvisited_node in unvisited_nodes:\n",
|
|
" heuristic_total += self.edges[self.tour[-1]][unvisited_node].weight\n",
|
|
" for unvisited_node in unvisited_nodes:\n",
|
|
" roulette_wheel += pow(self.edges[self.tour[-1]][unvisited_node].pheromone, self.alpha) * \\\n",
|
|
" pow((heuristic_total / self.edges[self.tour[-1]][unvisited_node].weight), self.beta)\n",
|
|
" random_value = random.uniform(0.0, roulette_wheel)\n",
|
|
" wheel_position = 0.0\n",
|
|
" for unvisited_node in unvisited_nodes:\n",
|
|
" wheel_position += pow(self.edges[self.tour[-1]][unvisited_node].pheromone, self.alpha) * \\\n",
|
|
" pow((heuristic_total / self.edges[self.tour[-1]][unvisited_node].weight), self.beta)\n",
|
|
" if wheel_position >= random_value:\n",
|
|
" return unvisited_node\n",
|
|
"\n",
|
|
" def find_tour(self):\n",
|
|
" self.tour = [random.randint(0, self.num_nodes - 1)]\n",
|
|
" while len(self.tour) < self.num_nodes:\n",
|
|
" self.tour.append(self._select_node())\n",
|
|
" return self.tour\n",
|
|
"\n",
|
|
" def get_distance(self):\n",
|
|
" self.distance = 0.0\n",
|
|
" for i in range(self.num_nodes):\n",
|
|
" self.distance += self.edges[self.tour[i]][self.tour[(i + 1) % self.num_nodes]].weight\n",
|
|
" return self.distance\n",
|
|
"\n",
|
|
" def init(self, nodes):\n",
|
|
" super().init(nodes)\n",
|
|
"\n",
|
|
" # Set hypter-parameters\n",
|
|
" mode=['ACS']\n",
|
|
" colony_size=10\n",
|
|
" elitist_weight=1.0\n",
|
|
" min_scaling_factor=0.001\n",
|
|
" alpha=1.0\n",
|
|
" beta=3.0\n",
|
|
" rho=0.1\n",
|
|
" pheromone_deposit_weight=1.0\n",
|
|
" initial_pheromone=1.0\n",
|
|
" labels = None\n",
|
|
"\n",
|
|
" self.mode = str(mode[0]) \n",
|
|
" self.colony_size = colony_size\n",
|
|
" self.elitist_weight = elitist_weight\n",
|
|
" self.min_scaling_factor = min_scaling_factor\n",
|
|
" self.rho = rho\n",
|
|
" self.pheromone_deposit_weight = pheromone_deposit_weight\n",
|
|
" self.num_nodes = len(nodes)\n",
|
|
" self.nodes = nodes\n",
|
|
"\n",
|
|
" if labels is not None:\n",
|
|
" self.labels = labels\n",
|
|
" else:\n",
|
|
" self.labels = range(1, self.num_nodes + 1)\n",
|
|
" self.edges = [[None] * self.num_nodes for _ in range(self.num_nodes)]\n",
|
|
" for i in range(self.num_nodes):\n",
|
|
" for j in range(i + 1, self.num_nodes):\n",
|
|
" self.edges[i][j] = self.edges[j][i] = self.Edge(i, j, math.sqrt(\n",
|
|
" pow(self.nodes[i][0] - self.nodes[j][0], 2.0) + pow(self.nodes[i][1] - self.nodes[j][1], 2.0)),\n",
|
|
" initial_pheromone)\n",
|
|
" self.ants = [self.Ant(alpha, beta, self.num_nodes, self.edges) for _ in range(self.colony_size)]\n",
|
|
" self.global_best_tour = None\n",
|
|
" self.global_best_distance = float(\"inf\")\n",
|
|
"\n",
|
|
" def _add_pheromone(self, tour, distance, weight=1.0):\n",
|
|
" pheromone_to_add = self.pheromone_deposit_weight / distance\n",
|
|
" for i in range(self.num_nodes):\n",
|
|
" self.edges[tour[i]][tour[(i + 1) % self.num_nodes]].pheromone += weight * pheromone_to_add\n",
|
|
"\n",
|
|
" def _acs(self, max_it):\n",
|
|
" for step in range(0, max_it):\n",
|
|
" # print('[step]', step)\n",
|
|
" for ant in self.ants:\n",
|
|
" self._add_pheromone(ant.find_tour(), ant.get_distance())\n",
|
|
" if ant.distance < self.global_best_distance:\n",
|
|
" self.global_best_tour = ant.tour\n",
|
|
" self.global_best_distance = ant.distance\n",
|
|
" self.fitness_list.append(ant.distance)\n",
|
|
" for i in range(self.num_nodes):\n",
|
|
" for j in range(i + 1, self.num_nodes):\n",
|
|
" self.edges[i][j].pheromone *= (1.0 - self.rho)\n",
|
|
"\n",
|
|
" def _elitist(self, max_it):\n",
|
|
" for step in range(0, max_it):\n",
|
|
" # print('[step]', step)\n",
|
|
" for ant in self.ants:\n",
|
|
" self._add_pheromone(ant.find_tour(), ant.get_distance())\n",
|
|
" if ant.distance < self.global_best_distance:\n",
|
|
" self.global_best_tour = ant.tour\n",
|
|
" self.global_best_distance = ant.distance\n",
|
|
" self.fitness_list.append(ant.distance)\n",
|
|
" self._add_pheromone(self.global_best_tour, self.global_best_distance, weight=self.elitist_weight)\n",
|
|
" for i in range(self.num_nodes):\n",
|
|
" for j in range(i + 1, self.num_nodes):\n",
|
|
" self.edges[i][j].pheromone *= (1.0 - self.rho)\n",
|
|
"\n",
|
|
" def fit(self, max_it=1000):\n",
|
|
" \"\"\"\n",
|
|
" Execute simulated annealing algorithm.\n",
|
|
" \"\"\"\n",
|
|
" # Initialize with the greedy solution.\n",
|
|
" if self.mode == 'ACS':\n",
|
|
" self._acs(max_it)\n",
|
|
" elif self.mode == 'Elitist':\n",
|
|
" self._elitist(max_it)\n",
|
|
" else:\n",
|
|
" print(\"Un supported\")\n",
|
|
" # print('Sequence : <- {0} ->'.format(' - '.join(str(self.labels[i]) for i in self.global_best_tour)))\n",
|
|
" # print('Total distance travelled to complete the tour : {0}\\n'.format(round(self.global_best_distance, 2)))\n",
|
|
"\n",
|
|
" return self.global_best_tour, self.fitness_list"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"tsp_file = './template/data/easy/ulysses16.tsp'"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyACOModel, timeout=300)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Your Smart Model"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import math\n",
|
|
"import random\n",
|
|
"from model.base_model import Model\n",
|
|
"\n",
|
|
"class MyModel(Model):\n",
|
|
" def __init__(self):\n",
|
|
" super().__init__()\n",
|
|
"\n",
|
|
" def init(self, nodes):\n",
|
|
" \"\"\"\n",
|
|
" Put your initialization here.\n",
|
|
" \"\"\"\n",
|
|
" super().init(nodes)\n",
|
|
"\n",
|
|
" self.log(\"Nothing to initialize in your model now\")\n",
|
|
"\n",
|
|
" def fit(self, max_it=1000):\n",
|
|
" \"\"\"\n",
|
|
" Put your iteration process here.\n",
|
|
" \"\"\"\n",
|
|
" self.best_solution = np.random.permutation(self.N).tolist()\n",
|
|
" self.fitness_list.append(self.fitness(self.best_solution))\n",
|
|
"\n",
|
|
" return self.best_solution, self.fitness_list"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Test your Model"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"tsp_problem = './template/data/easy/ulysses16.tsp'"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyModel)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Test All Dataset"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"tsp_path = './template'\n",
|
|
"for root, _, files in os.walk(tsp_path + '/data'):\n",
|
|
" if(files):\n",
|
|
" for f in files:\n",
|
|
" print(str(root) + \"/\" + f)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def plot_results(best_solutions, times, title):\n",
|
|
" fig = plt.figure()\n",
|
|
" nodes = [len(s) for s in best_solutions]\n",
|
|
" data = np.array([[node, time] for node, time in sorted(zip(nodes, times))])\n",
|
|
" plt.plot(data[:, 0], data[:, 1], 'o-')\n",
|
|
" fig.suptitle(title, fontsize=20)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"print(\"Random Search\")\n",
|
|
"best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyRandomModel)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"plot_results(best_solutions, times, \"Random Model\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"print(\"Ant Colony Optimization\")\n",
|
|
"best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyACOModel)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"plot_results(best_solutions, times, \"Ant Colony Optimization\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.8.5"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|