{ "info": { "author": "Jinyu Xie", "author_email": "xjygr08@gmail.com", "bugtrack_url": null, "classifiers": [], "description": "# simglucose\nA Type-1 Diabetes simulator implemented in Python for Reinforcement Learning purpose\n\nThis simulator is a python implementation of the FDA-approved [UVa/Padova Simulator (2008 version)](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4454102/) for research purpose only. The simulator includes 30 virtual patients, 10 adolescents, 10 adults, 10 children. \n\n - Note: simglucose only supports python3.\n\n| Animation | CVGA Plot | BG Trace Plot | Risk Index Stats |\n|---------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|\n| ![animation screenshot](https://github.com/jxx123/simglucose/blob/master/screenshots/animate.png) | ![CVGA](https://github.com/jxx123/simglucose/blob/master/screenshots/CVGA.png) | ![BG Trace Plot](https://github.com/jxx123/simglucose/blob/master/screenshots/BG_trace_plot.png) | ![Risk Index Stats](https://github.com/jxx123/simglucose/blob/master/screenshots/risk_index.png) |\n\n \n\n## Main Features\n- Simulation enviroment follows [OpenAI gym](https://github.com/openai/gym) and [rllab](https://github.com/rll/rllab) APIs. It returns observation, reward, done, info at each step, which means the simulator is \"reinforcement-learning-ready\".\n- Supports customized reward function. The reward function is a function of blood glucose measurements in the last hour. By default, the reward at each step is `risk[t-1] - risk[t]`. `risk[t]` is the risk index at time `t` defined in this [paper](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2903980/pdf/dia.2008.0138.pdf). \n- Supports parallel computing. The simulator simulates mutliple patients parallelly using [pathos multiprocessing package](https://github.com/uqfoundation/pathos) (you are free to turn parallel off by setting `parallel=False`).\n- The simulator provides a random scenario generator (`from simglucose.simulation.scenario_gen import RandomScenario`) and a customized scenario generator (`from simglucose.simulation.scenario import CustomScenario`). Commandline user-interface will guide you through the scenario settings.\n- The simulator provides the most basic basal-bolus controller for now. It provides very simple syntax to implement your own controller, like Model Predictive Control, PID control, reinforcement learning control, etc. \n- You can specify random seed in case you want to repeat your experiments.\n- The simulator will generate several plots for performance analysis after simulation. The plots include blood glucose trace plot, Control Variability Grid Analysis (CVGA) plot, statistics plot of blood glucose in different zones, risk indices statistics plot.\n- NOTE: `animate` and `parallel` cannot be set to `True` at the same time in macOS. Most backends of matplotlib in macOS is not thread-safe. Windows has not been tested. Let me know the results if anybody has tested it out.\n\n## Installation\nIt is highly recommended to use `pip` to install `simglucose`, follow this [link](https://pip.pypa.io/en/stable/installing/) to install pip.\n\nAuto installation:\n```bash\npip install simglucose\n```\n\nManual installation: \n```bash\ngit clone https://github.com/jxx123/simglucose.git\ncd simglucose\n```\nIf you have `pip` installed, then\n```bash\npip install -e .\n```\nIf you do not have `pip`, then\n```bash\npython setup.py install\n```\n\nIf [rllab (optional)](https://github.com/rll/rllab) is installed, the package will utilize some functionalities in rllab.\n\nNote: there might be some minor differences between auto install version and manual install version. Use `git clone` and manual installation to get the latest version.\n\n## Quick Start\n### Use simglucose as a simulator and test controllers\nRun the simulator user interface\n```python\nfrom simglucose.simulation.user_interface import simulate\nsimulate()\n```\n\nYou are free to implement your own controller, and test it in the simulator. For example,\n```python\nfrom simglucose.simulation.user_interface import simulate\nfrom simglucose.controller.base import Controller, Action\n\n\nclass MyController(Controller):\n def __init__(self, init_state):\n self.init_state = init_state\n self.state = init_state\n\n def policy(self, observation, reward, done, **info):\n '''\n Every controller must have this implementation!\n ----\n Inputs:\n observation - a namedtuple defined in simglucose.simulation.env. For\n now, it only has one entry: blood glucose level measured\n by CGM sensor.\n reward - current reward returned by environment\n done - True, game over. False, game continues\n info - additional information as key word arguments,\n simglucose.simulation.env.T1DSimEnv returns patient_name\n and sample_time\n ----\n Output:\n action - a namedtuple defined at the beginning of this file. The\n controller action contains two entries: basal, bolus\n '''\n self.state = observation\n action = Action(basal=0, bolus=0)\n return action\n\n def reset(self):\n '''\n Reset the controller state to inital state, must be implemented\n '''\n self.state = self.init_state\n\n\nctrller = MyController(0)\nsimulate(controller=ctrller)\n```\n\nThese two examples can also be found in examples\\ folder.\n\nIn fact, you can specify a lot more simulation parameters through `simulation`:\n```python\nsimulate(sim_time=my_sim_time,\n scenario=my_scenario,\n controller=my_controller,\n start_time=my_start_time,\n save_path=my_save_path,\n animate=False,\n parallel=True)\n```\n### OpenAI Gym usage\n- Using default reward\n```python\nimport gym\n\n# Register gym environment. By specifying kwargs,\n# you are able to choose which patient to simulate.\n# patient_name must be 'adolescent#001' to 'adolescent#010',\n# or 'adult#001' to 'adult#010', or 'child#001' to 'child#010'\nfrom gym.envs.registration import register\nregister(\n id='simglucose-adolescent2-v0',\n entry_point='simglucose.envs:T1DSimEnv',\n kwargs={'patient_name': 'adolescent#002'}\n)\n\nenv = gym.make('simglucose-adolescent2-v0')\n\nobservation = env.reset()\nfor t in range(100):\n env.render(mode='human')\n print(observation)\n # Action in the gym environment is a scalar\n # representing the basal insulin, which differs from\n # the regular controller action outside the gym\n # environment (a tuple (basal, bolus)).\n # In the perfect situation, the agent should be able\n # to control the glucose only through basal instead\n # of asking patient to take bolus\n action = env.action_space.sample()\n observation, reward, done, info = env.step(action)\n if done:\n print(\"Episode finished after {} timesteps\".format(t + 1))\n break\n```\n- Customized reward function\n```python\nimport gym\nfrom gym.envs.registration import register\n\n\ndef custom_reward(BG_last_hour):\n if BG_last_hour[-1] > 180:\n return -1\n elif BG_last_hour[-1] < 70:\n return -2\n else:\n return 1\n\n\nregister(\n id='simglucose-adolescent2-v0',\n entry_point='simglucose.envs:T1DSimEnv',\n kwargs={'patient_name': 'adolescent#002',\n 'reward_fun': custom_reward}\n)\n\nenv = gym.make('simglucose-adolescent2-v0')\n\nreward = 1\ndone = False\n\nobservation = env.reset()\nfor t in range(200):\n env.render(mode='human')\n action = env.action_space.sample()\n observation, reward, done, info = env.step(action)\n print(observation)\n print(\"Reward = {}\".format(reward))\n if done:\n print(\"Episode finished after {} timesteps\".format(t + 1))\n break\n```\n\n### rllab usage\n```python\nfrom rllab.algos.ddpg import DDPG\nfrom rllab.envs.normalized_env import normalize\nfrom rllab.exploration_strategies.ou_strategy import OUStrategy\nfrom rllab.policies.deterministic_mlp_policy import DeterministicMLPPolicy\nfrom rllab.q_functions.continuous_mlp_q_function import ContinuousMLPQFunction\nfrom rllab.envs.gym_env import GymEnv\nfrom gym.envs.registration import register\n\nregister(\n id='simglucose-adolescent2-v0',\n entry_point='simglucose.envs:T1DSimEnv',\n kwargs={'patient_name': 'adolescent#002'}\n)\n\nenv = GymEnv('simglucose-adolescent2-v0')\nenv = normalize(env)\n\npolicy = DeterministicMLPPolicy(\n env_spec=env.spec,\n # The neural network policy should have two hidden layers, each with 32 hidden units.\n hidden_sizes=(32, 32)\n)\n\nes = OUStrategy(env_spec=env.spec)\n\nqf = ContinuousMLPQFunction(env_spec=env.spec)\n\nalgo = DDPG(\n env=env,\n policy=policy,\n es=es,\n qf=qf,\n batch_size=32,\n max_path_length=100,\n epoch_length=1000,\n min_pool_size=10000,\n n_epochs=1000,\n discount=0.99,\n scale_reward=0.01,\n qf_learning_rate=1e-3,\n policy_learning_rate=1e-4\n)\nalgo.train()\n```\n\n## Advanced Usage\nYou can create the simulation objects, and run batch simulation. For example,\n```python\nfrom simglucose.simulation.env import T1DSimEnv\nfrom simglucose.controller.basal_bolus_ctrller import BBController\nfrom simglucose.sensor.cgm import CGMSensor\nfrom simglucose.actuator.pump import InsulinPump\nfrom simglucose.patient.t1dpatient import T1DPatient\nfrom simglucose.simulation.scenario_gen import RandomScenario\nfrom simglucose.simulation.scenario import CustomScenario\nfrom simglucose.simulation.sim_engine import SimObj, sim, batch_sim\nfrom datetime import timedelta\nfrom datetime import datetime\n\n# specify start_time as the beginning of today\nnow = datetime.now()\nstart_time = datetime.combine(now.date(), datetime.min.time())\n\n# --------- Create Random Scenario --------------\n# Specify results saving path\npath = './results'\n\n# Create a simulation environment\npatient = T1DPatient.withName('adolescent#001')\nsensor = CGMSensor.withName('Dexcom', seed=1)\npump = InsulinPump.withName('Insulet')\nscenario = RandomScenario(start_time=start_time, seed=1)\nenv = T1DSimEnv(patient, sensor, pump, scenario)\n\n# Create a controller\ncontroller = BBController()\n\n# Put them together to create a simulation object\ns1 = SimObj(env, controller, timedelta(days=1), animate=False, path=path)\nresults1 = sim(s1)\nprint(results1)\n\n# --------- Create Custom Scenario --------------\n# Create a simulation environment\npatient = T1DPatient.withName('adolescent#001')\nsensor = CGMSensor.withName('Dexcom', seed=1)\npump = InsulinPump.withName('Insulet')\n# custom scenario is a list of tuples (time, meal_size)\nscen = [(7, 45), (12, 70), (16, 15), (18, 80), (23, 10)]\nscenario = CustomScenario(start_time=start_time, scenario=scen)\nenv = T1DSimEnv(patient, sensor, pump, scenario)\n\n# Create a controller\ncontroller = BBController()\n\n# Put them together to create a simulation object\ns2 = SimObj(env, controller, timedelta(days=1), animate=False, path=path)\nresults2 = sim(s2)\nprint(results2)\n\n\n# --------- batch simulation --------------\n# Re-initialize simulation objects\ns1.reset()\ns2.reset()\n\n# create a list of SimObj, and call batch_sim\ns = [s1, s2]\nresults = batch_sim(s, parallel=True)\nprint(results)\n```\n\nRun analysis offline\n```python\nfrom simglucose.analysis.report import report\nimport pandas as pd\nimport os\nimport glob\n\n# the path where results are saved\npath = os.path.join('.', 'results', '2017-12-31_17-46-32')\nos.chdir(path)\n# find all csv with pattern *#*.csv, e.g. adolescent#001.csv\nfilename = glob.glob('*#*.csv')\nname = [_f[:-4] for _f in filename] # get the filename without extension\ndf = pd.concat([pd.read_csv(f, index_col=0) for f in filename], keys=name)\nreport(df)\n```\n## Release Notes\n### 9/10/2018\n- Controller `policy` method gets access to all the current patient state through `info['patient_state']`.\n### 2/26/2018\n- Support customized reward function.\n### 1/10/2018\n- Added workaround to select patient when make gym environment: register gym environment by passing kwargs of patient_name.\n### 1/7/2018\n- Added OpenAI gym support, use `gym.make('simglucose-v0')` to make the enviroment.\n- Noticed issue: the patient name selection is not available in gym.make for now. The patient name has to be hard-coded in the constructor of `simglucose.envs.T1DSimEnv`.\n\n## Reporting issues\nShoot me any bugs, enhancements or even discussion by [creating issues](https://github.com/jxx123/simglucose/issues/new).\n\n## How to contribute\nThe following instruction is originally from the [contribution instructions of sklearn](https://github.com/scikit-learn/scikit-learn/blob/master/CONTRIBUTING.md).\n\nThe preferred workflow for contributing to simglucose is to fork the\n[main repository](https://github.com/jxx123/simglucose) on\nGitHub, clone, and develop on a branch. Steps:\n\n1. Fork the [project repository](https://github.com/jxx123/simglucose)\n by clicking on the 'Fork' button near the top right of the page. This creates\n a copy of the code under your GitHub user account. For more details on\n how to fork a repository see [this guide](https://help.github.com/articles/fork-a-repo/).\n\n2. Clone your fork of the simglucose repo from your GitHub account to your local disk:\n\n ```bash\n $ git clone git@github.com:YourLogin/simglucose.git\n $ cd simglucose\n ```\n\n3. Create a ``feature`` branch to hold your development changes:\n\n ```bash\n $ git checkout -b my-feature\n ```\n\n Always use a ``feature`` branch. It's good practice to **never work on the ``master`` branch**!\n\n4. Develop the feature on your feature branch. Add changed files using ``git add`` and then ``git commit`` files:\n\n ```bash\n $ git add modified_files\n $ git commit\n ```\n\n to record your changes in Git, then push the changes to your GitHub account with:\n\n ```bash\n $ git push -u origin my-feature\n ```\n\n5. Follow [these instructions](https://help.github.com/articles/creating-a-pull-request-from-a-fork)\nto create a pull request from your fork. This will send an email to the committers.\n\n(If any of the above seems like magic to you, please look up the\n[Git documentation](https://git-scm.com/documentation) on the web, or ask a friend or another contributor for help.)\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/jxx123/simglucose", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "simglucose", "package_url": "https://pypi.org/project/simglucose/", "platform": "", "project_url": "https://pypi.org/project/simglucose/", "project_urls": { "Homepage": "https://github.com/jxx123/simglucose" }, "release_url": "https://pypi.org/project/simglucose/0.2.1/", "requires_dist": [ "pandas", "numpy", "scipy", "matplotlib", "pathos", "gym (==0.9.4)" ], "requires_python": "", "summary": "A Type-1 Diabetes Simulator as a Reinforcement Learning Environment in OpenAI gym or rllab (python implementation of UVa/Padova Simulator)", "version": "0.2.1" }, "last_serial": 4259786, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "d5dac2cce4ca4e1bea8eb8fbb1439bf6", "sha256": "9cea86d5874307fa2b6dbdd8407fb485ec8b0f88c26d4a7bfbf493cd4a59120c" }, "downloads": -1, "filename": "simglucose-0.1.0.tar.gz", "has_sig": false, "md5_digest": "d5dac2cce4ca4e1bea8eb8fbb1439bf6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29748, "upload_time": "2018-01-01T03:20:20", "url": "https://files.pythonhosted.org/packages/7c/fb/f0c1230743d4e8ebd152c06e3ee0a571a72147c0a81c8f344d882d96e8c8/simglucose-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "7f8874bf7af12af7472cc112c8dd019e", "sha256": "711b13c7e7c74867773f99dac991602b9743fdf90b169e449d5ae9b6c3a3beae" }, "downloads": -1, "filename": "simglucose-0.1.1.tar.gz", "has_sig": false, "md5_digest": "7f8874bf7af12af7472cc112c8dd019e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29759, "upload_time": "2018-01-01T03:34:48", "url": "https://files.pythonhosted.org/packages/0e/d7/6c253ed8375e670b59c8ec31f8e0ebb0ace9115e7d406649c06a238e762b/simglucose-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "89f8cc32fc72011a095e1b36b2e4e7bb", "sha256": "1f7c75023f94dcac2bd0b80fbe332b662a9ac38702c67ebe31b23ee4aba92a7f" }, "downloads": -1, "filename": "simglucose-0.1.2.tar.gz", "has_sig": false, "md5_digest": "89f8cc32fc72011a095e1b36b2e4e7bb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30155, "upload_time": "2018-01-01T15:02:13", "url": "https://files.pythonhosted.org/packages/7b/02/d7b847002e895985523113961a56f085e62738d549892304f966eb2f866b/simglucose-0.1.2.tar.gz" } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "17bfe56b9a5835e15baebe54a18604cb", "sha256": "eb5940ac51ac2e6cafe36aafb2a98e818e70da6322bf73fe11cd67117e6072a2" }, "downloads": -1, "filename": "simglucose-0.1.3.tar.gz", "has_sig": false, "md5_digest": "17bfe56b9a5835e15baebe54a18604cb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 584301, "upload_time": "2018-01-02T04:20:05", "url": "https://files.pythonhosted.org/packages/d3/08/c183f19cd49a51846183fd356899ddfc049cdd6c05a7ced7f0232d912258/simglucose-0.1.3.tar.gz" } ], "0.1.4": [ { "comment_text": "", "digests": { "md5": "2155d295e3755b0c04cbdaee72cb5fbe", "sha256": "b43c71abec913cdb547e6bd290cd30c8e8e121c2cf4f6ee95faf3f1174442806" }, "downloads": -1, "filename": "simglucose-0.1.4.tar.gz", "has_sig": false, "md5_digest": "2155d295e3755b0c04cbdaee72cb5fbe", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 584749, "upload_time": "2018-01-03T05:14:39", "url": "https://files.pythonhosted.org/packages/bb/e5/fb2f694df51c3d7e8f02a3627da485c31e3615dddf323cb86b7468644a31/simglucose-0.1.4.tar.gz" } ], "0.1.5": [ { "comment_text": "", "digests": { "md5": "f375b8738aa385199ce766de2ed003b4", "sha256": "8fed14185625fe28cf7ba57fc8a5c3fd1bfadb31d29ac7fccfec476bded0ce4b" }, "downloads": -1, "filename": "simglucose-0.1.5.tar.gz", "has_sig": false, "md5_digest": "f375b8738aa385199ce766de2ed003b4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 587033, "upload_time": "2018-01-08T04:09:35", "url": "https://files.pythonhosted.org/packages/69/54/d0a37439635d9d77685754e3000b530017aee96504f3a6739a01bab0b445/simglucose-0.1.5.tar.gz" } ], "0.1.6": [ { "comment_text": "", "digests": { "md5": "2ac5a1a3ed54af13a5764d6aa9bbe1c7", "sha256": "1689535114666a43719323feb5d0a86ee335448f8c09bc444e91b9b49c361d02" }, "downloads": -1, "filename": "simglucose-0.1.6.tar.gz", "has_sig": false, "md5_digest": "2ac5a1a3ed54af13a5764d6aa9bbe1c7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 586513, "upload_time": "2018-01-12T02:45:28", "url": "https://files.pythonhosted.org/packages/1b/6d/c397c038ae6dded1463d663ce7675cea8a27e69fd04566ecb8cb14a88893/simglucose-0.1.6.tar.gz" } ], "0.1.7": [ { "comment_text": "", "digests": { "md5": "44b76e63a3769fc1850c8a0ade466c68", "sha256": "49a7adfd812af0ca415d83ca06feff9faae98207fcf9e3f3fa99d4b411628f97" }, "downloads": -1, "filename": "simglucose-0.1.7.tar.gz", "has_sig": false, "md5_digest": "44b76e63a3769fc1850c8a0ade466c68", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 589151, "upload_time": "2018-01-29T02:39:12", "url": "https://files.pythonhosted.org/packages/ea/c9/779234250287af24a0b53ac828c005619a698d7794870cd88b05ad3c53a0/simglucose-0.1.7.tar.gz" } ], "0.1.8": [ { "comment_text": "", "digests": { "md5": "783b140da77f30638372ea98a2820dcc", "sha256": "32da8fbc4f8e87a49d2b23b60d80f5fd418a564b074f2eb1f9ad389b2be37da6" }, "downloads": -1, "filename": "simglucose-0.1.8.tar.gz", "has_sig": false, "md5_digest": "783b140da77f30638372ea98a2820dcc", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 588930, "upload_time": "2018-02-14T05:23:46", "url": "https://files.pythonhosted.org/packages/70/58/7cd27e5284633e95ad65283827e6e8e328b279e660c5119179a86153e2b5/simglucose-0.1.8.tar.gz" } ], "0.1.9": [ { "comment_text": "", "digests": { "md5": "b2837733e6d14d72e3584c8dbcfb10cf", "sha256": "23f2a4005eed4c66c07ed4870b3b5eaae5765e341240565019abc7ac88c3baaa" }, "downloads": -1, "filename": "simglucose-0.1.9.tar.gz", "has_sig": false, "md5_digest": "b2837733e6d14d72e3584c8dbcfb10cf", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 589061, "upload_time": "2018-02-27T03:46:08", "url": "https://files.pythonhosted.org/packages/a5/fc/5dd7745181034f36b460ce8c05ceed76bd354ababcbc74352784bfb23eec/simglucose-0.1.9.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "d16ef030336ab25d0011cd91c641aabf", "sha256": "5dbe0229b552491776f75a4281548a822089dccc2d63da515df398623e9682ed" }, "downloads": -1, "filename": "simglucose-0.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "d16ef030336ab25d0011cd91c641aabf", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 35492, "upload_time": "2018-09-11T02:34:56", "url": "https://files.pythonhosted.org/packages/6a/21/b73c1394b255bb60de2a9da3a113180f4fc5f107d7b426cfc7ad016fe80b/simglucose-0.2.0-py3-none-any.whl" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "f8bab5e2169087b05b742f787b3c3bfa", "sha256": "3b5ec0df53e69592492217330246469295455226b72336859433284f9c97a07e" }, "downloads": -1, "filename": "simglucose-0.2.1-py3-none-any.whl", "has_sig": false, "md5_digest": "f8bab5e2169087b05b742f787b3c3bfa", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 40466, "upload_time": "2018-09-11T02:48:09", "url": "https://files.pythonhosted.org/packages/e1/25/65287b014ef84ccb0c0173cb04c2b3e1068022444dc8f5619e18cad3dc71/simglucose-0.2.1-py3-none-any.whl" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "f8bab5e2169087b05b742f787b3c3bfa", "sha256": "3b5ec0df53e69592492217330246469295455226b72336859433284f9c97a07e" }, "downloads": -1, "filename": "simglucose-0.2.1-py3-none-any.whl", "has_sig": false, "md5_digest": "f8bab5e2169087b05b742f787b3c3bfa", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 40466, "upload_time": "2018-09-11T02:48:09", "url": "https://files.pythonhosted.org/packages/e1/25/65287b014ef84ccb0c0173cb04c2b3e1068022444dc8f5619e18cad3dc71/simglucose-0.2.1-py3-none-any.whl" } ] }