From 755ea51887733b1776d715916da29468859cd698 Mon Sep 17 00:00:00 2001 From: Norman Packard Date: Thu, 4 Apr 2019 14:30:53 -0700 Subject: [PATCH 1/3] cleaning up notebook interface a little revising notebook... revising notebook again rewrite of 03_SimpleTutorial.ipynb + changes in daptics_client.py adding some error reporting changed loop to use esd-factorial-8x5 to avoid space size error add documentation string for wait_for_current_task() Corrections for latest GG feedback Eliminate output, enhance comments Edits to documentation. More edits to documentation. More documentation edits, typo fixes, etc. More documentation about free zone and loop automation. Commented out mixture example, edited loop documentation, set all execution_count to null. Added back login_data. Loop with free ESD. demo parameter now hardcoded in create_session. is_demo removed from function call within loop. Capitalization. Capitalization. --- python_client/01_README.ipynb | 2 +- python_client/02_Terminology.ipynb | 2 +- python_client/03_SimpleTutorial.ipynb | 734 ++++++++++++++++++-------- python_client/daptics_client.py | 151 ++++-- python_client/esd-factorial-3x5.csv | 6 +- python_client/esd-factorial-4x8.csv | 4 + python_client/esd-factorial-8x5.csv | 16 +- python_client/esd-mixture-5.csv | 10 +- 8 files changed, 658 insertions(+), 267 deletions(-) create mode 100644 python_client/esd-factorial-4x8.csv diff --git a/python_client/01_README.ipynb b/python_client/01_README.ipynb index a4e2475..c1393d9 100644 --- a/python_client/01_README.ipynb +++ b/python_client/01_README.ipynb @@ -57,7 +57,7 @@ "Start the notebook in the tutorial directory:\n", "\n", "```\n", - "cd tutorials\n", + "cd python_client\n", "jupyter notebook\n", "```\n", "\n", diff --git a/python_client/02_Terminology.ipynb b/python_client/02_Terminology.ipynb index 4d918dd..5b228f9 100644 --- a/python_client/02_Terminology.ipynb +++ b/python_client/02_Terminology.ipynb @@ -248,7 +248,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.7.2" } }, "nbformat": 4, diff --git a/python_client/03_SimpleTutorial.ipynb b/python_client/03_SimpleTutorial.ipynb index 9d987c7..6d3fed2 100644 --- a/python_client/03_SimpleTutorial.ipynb +++ b/python_client/03_SimpleTutorial.ipynb @@ -31,31 +31,42 @@ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Connect and start a session" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 1. Create a DapticsClient object and connect to the API server.\n", + "\n", + "Before running this project, please make sure that your Jupyter Python environment supports Python 3, and has these required packages installed:\n", + "* chardet\n", + "* urllib3 \n", + "* requests\n", + "* gql\n", + "\n", + "You will also need a validated user account on the Daptics API server. You can create an account by [registering](https:daptics.ai/register) at https://daptics.ai\n", + "\n", + "See the 01_README.ipynb notebook in this folder for more information.\n", + "\n", + "The following cell creates a DapticsClient object and connect to the API server.\n" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Step 1. Create a DapticsClient object and connect to the API server.\n", - "\n", - "# Before running this project, please make sure that your Jupyter\n", - "# Python environment has these required packages installed:\n", - "#\n", - "# chardet\n", - "# urllib3 \n", - "# requests\n", - "# gql\n", - "#\n", - "# You will also need a validated user account on the Daptics API server.\n", - "# You can create an account by clicking the `Get Started for Free` button at \n", - "# https://daptics.ai\n", - "#\n", - "# See the 01_README.ipynb notebook in this folder for more information.\n", - "\n", "# Import the daptics_client module\n", "# Requirements are Python 3, and the `gql` and `requests` libraries\n", "from daptics_client import DapticsClient\n", + "from time import sleep\n", "\n", "# Create a client object and try to connect to the daptics API server.\n", "# The client constructor takes a URL argument that specifies the scheme,\n", @@ -68,13 +79,29 @@ "\n", "# The `connect` method will attempt to connect to the /api path on the\n", "# API server and obtain the GraphQL schema.\n", - "daptics.connect()\n", + "daptics.connect()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Details:__ after connecting, the daptics object should have a `gql` attribute.\n", + "You can look at the data stored in the `gql` attribute by printing `daptics.gql.__dict__`.\n", + "In this data, you can see that the gql library has introspected all the GraphQL type, \n", + "query and mutation information exposed by the API." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 2. Log in to the API server to obtain an access token.\n", + "\n", + "The `login` method takes two string arguments, the user's `email` and `password`.\n", "\n", - "# Show the result. After connecting, the daptics object should have a `gql` attribute.\n", - "# Let's look at the data stored in the `gql` attribute by printing its `__dict__`.\n", - "# We can see that the gql library has introspected all the GraphQL type, \n", - "# query and mutation information exposed by the API.\n", - "print(daptics.gql.__dict__)" + "__Note:__ Use the real email address and password you used when you created\n", + "your account on the daptics.ai website." ] }, { @@ -83,51 +110,94 @@ "metadata": {}, "outputs": [], "source": [ - "# Step 2. Log in to the API server to obtain an access token.\n", - "\n", - "# The `login` method takes two string arguments, the user's\n", - "# `email` and `password`.\n", + "email = 'YOUR_EMAIL@YOUR_DOMAIN'\n", + "password = 'YOUR_PASSWORD'\n", + "login_data = daptics.login(email, password)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "scrolled": true + }, + "source": [ + "## Step 3. Create a daptics session on the server.\n", "\n", - "# Note: Use the real email address and password you used when you created\n", - "# your account on the daptics.ai website.\n", + "The `create_session` method takes three arguments:\n", + "* `name` - the name for the session (which must be unique for your account)\n", + "* `description` - a required, short description\n", + "* `is_demo` - a boolean value that determines whether the session will run \n", + "in demo mode (if `True`) or in standard mode (if `False`)\n", "\n", - "email = 'YOUR_EMAIL@YOUR_DOMAIN.com'\n", - "password = 'YOUR_PASSWORD'\n", - "data = daptics.login(email, password)\n", + "The session will be created for the `userId` saved from the login method.\n", "\n", - "# Show the result. The `daptics` object will remember the access token\n", - "# and use it for all subsequent API requests. The `daptics` object\n", - "# will also remember the `userId` that corresponds to the email \n", - "# address.\n", - "print(data)" + "Note: *each session name must be a new, unique name*. So, every time the following cellis executed, the session name must be changed." ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": true - }, + "metadata": {}, "outputs": [], "source": [ - "# Step 3. Create a daptics session on the server.\n", - "\n", - "# The `create_session` method takes three arguments:\n", - "# `name` - the name for the session (which must be unique for your account)\n", - "# `description` - a required, short description\n", - "# `is_demo` - a boolean value that determines whether the session will run in demo mode\n", - "# (if True) or in standard mode (if False)\n", - "# The session will be created for the `userId` saved from the login method.\n", - "name = 'Practice Session'\n", + "name = 'Practice Session 36'\n", "description = 'This is a practice session'\n", - "is_demo = False\n", - "data = daptics.create_session(name, description, is_demo)\n", - "\n", - "# Show the result. The `daptics` object will remember the `sessionId`.\n", - "# The new session is set up with some default values for the experimental space.\n", - "# We can read those values here, but the daptics object will save them in an `initial_params`\n", - "# member variable. We will change them in the next step.\n", - "print(data)" + "daptics.create_session(name, description);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Please note**: if you want to start a new session, you may execute the cell above, changing the name. It is not necessary to restart the Python kernel." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Experimental Space Definition" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 4. Specify the _Experimental Space Definition_.\n", + "\n", + "The _Experimental Space Definition_ (ESD) may be defined by a \n", + "CSV file that defines the names and constraints for each parameter. There are currently two different types of experiments, `mixture` and `factorial` (see tutorial notebook `02_Terminology`):\n", + "\n", + "For `mixture` experiments, each row has exactly four columns:\n", + "* the parameter name (a string)\n", + "* the parameter type, set to `unit` for mixture experiments\n", + "* the minimum number of units for the parameter (an integer)\n", + "* the maximum number of units for the parameter (an integer)\n", + "\n", + "For `factorial` experiments, each row has at least four columns, \n", + "but can have many more:\n", + "* the parameter name (a string)\n", + "* the parameter type, set to either `numerical` or `categorical`\n", + "* the first possible value for the parameter (a number)\n", + "* the second possible value for the parameter (a different number)\n", + "* ...etc\n", + "\n", + "Each variable may have up to 20 values specified. \n", + "\n", + "You should add blank padding columns for rows that have\n", + "fewer parameter values, so that the CSV file has the same number of \n", + "columns on each row.\n", + "\n", + "Your ESD file should be placed in the directory that \n", + "the Jupyter notebook server process was started. The current location of this tutorial is \n", + "`../daptics-api/python_client/`; the tutorial file should appear in the `HOME` tab of the Jupyter notebook.\n", + "\n", + "### Please note: running this tutorial will be free of charge for any ESDs that fit in the Daptics [Free zone](https://daptics.ai/pdt_pricing).\n", + "For this tutorial, we will use a free ESD, specified in the `esd-factorial-4x8.csv` file\n", + "installed in `../daptics-api/python_client/`.\n", + "To read your own ESD file, you would substitute your file name\n", + "for `esd-factorial-4x8.csv`.\n", + "The contents of the ESD file read as follows:\n" ] }, { @@ -136,63 +206,50 @@ "metadata": {}, "outputs": [], "source": [ - "# Step 4. Upload an expermental space CSV file to the current directory.\n", - "\n", - "# The CSV file defines the names and constraints for each parameter.\n", - "\n", - "# For `mixture` experiments, each row has exactly three columns:\n", - "# the parameter name (a string)\n", - "# the minimum number of units for the parameter (an integer)\n", - "# the maximum number of units for the parameter (an integer)\n", - "\n", - "# For `factorial` experiments, each row has at least three columns, \n", - "# but can have many more:\n", - "# the parameter name (a string)\n", - "# the first possible value for the parameter (a number)\n", - "# the next possible value for the parameter (a different number)\n", - "# ...etc\n", - "# To avoid parsing bugs, add blank padding columns for rows that have\n", - "# fewer parameter values, so that the CSV file has the same number of \n", - "# columns on each row.\n", - "\n", - "# Go to the Home page for your Jupyter server and upload the \n", - "# CSV file for a `factorial` experiment, and name it `factorial_space.csv'.\n", - "# Then return to this notebook and execute this cell just to make\n", - "# sure the file can be read.\n", - "fname = 'esd-mixture-5.csv'\n", + "fname = 'esd-factorial-4x8.csv'\n", "with open(fname, 'r') as f:\n", " print(f.read())" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 5. Define the experimental space parameters for the session.\n", + "\n", + "The `save_experimental_and_space_parameters_csv` method requires two\n", + "arguments:\n", + "* `params` - a dict of parameters that specify\n", + " * `'space'`: a dict to define the space type, either \n", + " * `{'type': 'factorial'}` or \n", + " * `{'type': 'mixture', 'totalunits' : N}`, where `N` is the total number of units that all the \n", + " mixture variables must sum to.\n", + " * `'populationSize'`: how many experiments will be designed for each generation, and \n", + " * `'replicates'`: how many replicates of an experiment will be performed \n", + " (Note: to perform each experiment in the design just once, `replicates` should be zero.)\n", + "* `fname` - The path to the _Experimental Space Definition_ file discussed above. " + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Step 5. Define the experimental space parameters for the session.\n", - "\n", - "# The `save_experimental_and_space_parameters_csv` method requires two\n", - "# arguments:\n", - "# params - a dict of metaparameters that specify\n", - "# how many experiments will be designed for each generation, and\n", - "# define the space type, either `factorial` or `mixture`.\n", - "# fname - The path to the CSV file with the parameter values we uploaded \n", - "# in the previous cell. \n", - "\n", - "# Here are is an example for a \"mixture\" space, which must include a \n", + "# Here is an example for a \"mixture\" space, which must include a \n", "# `totalUnits` number:\n", - "params = {\n", - " 'space': { \n", - " 'type': 'mixture', \n", - " 'totalUnits': 15\n", - " },\n", - " 'populationSize': 30, # or self.daptics.initial_params['populationSize']\n", - " 'replicates': 2 # or self.daptics.initial_params['replicates']\n", - "}\n", - "fname = 'esd-mixture-5.csv'\n", - "\n", - "# And here is an example for a \"factorial\" space, which doesn't need the\n", + "#params = {\n", + "# 'space': { \n", + "# 'type': 'mixture', \n", + "# 'totalUnits': 15\n", + "# },\n", + "# 'populationSize': 30, \n", + "# 'replicates': 2 \n", + "#}\n", + "#fname = 'esd-mixture-5.csv'\n", + "\n", + "\n", + "# OR: here is an example for a \"factorial\" space, which doesn't need the\n", "# `totalUnits` parameter:\n", "params = {\n", " 'space': { \n", @@ -201,15 +258,25 @@ " 'populationSize': 30,\n", " 'replicates': 2\n", "}\n", - "fname = 'esd-factorial-3x5.csv'\n", + "fname = 'esd-factorial-4x8.csv'\n", "\n", "# When we call this method, the session will validate our parameters\n", "# and if the validation succeeds, a potentially long-running process\n", "# to set up the exploration space will be started.\n", "task = daptics.save_experimental_and_space_parameters_csv(params, fname)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 6. poll the task and process results\n", + "\n", + "Execution of the cell above launches a task to validate the _Experimental Space Definition_ and set up the space.\n", + "The following call will poll this task. This call may be repeated, ruturning `status = running` while the task is still in progress. When the task is done (`status=success`), the results are then processed, and the task is removed from the queue. subsequent calls to `poll_for_current_task()` will result in an error because no task will be found.\n", "\n", - "# Show the result. \n", - "print(task)" + "**Please note**: when the task changes to `status=success`, the results are not processed *until `poll_for_current_task()` is called*. The session function `wait_for_current_task()` simply wraps the loop in the following cell in a function, waiting for the task to finish, and the final call to `poll_for_current_task()` that yields `status=success` also processes the results.\n" ] }, { @@ -218,14 +285,18 @@ "metadata": {}, "outputs": [], "source": [ - "# Step 6. Wait a few seconds, then check to see if the task has\n", - "# completed successfully. \n", - "\n", - "# Check the status of any running or completed `space` tasks.\n", - "task = daptics.poll_for_current_task(task_type='space')\n", - "\n", - "# Show the result. Repeat until `status` is `success` (or `failure`).\n", - "print(task)" + "# Poll the task. Repeat until task disappears, when `status` is `success` (or `failure`).\n", + "while True:\n", + " task = daptics.poll_for_current_task() \n", + " if task['currentTask'] is not None:\n", + " print(\"status = \",task['currentTask']['status'])\n", + " sleep(0.5)\n", + " else:\n", + " print(\"No current task found!\")\n", + " break\n", + " \n", + "# OR-----------------\n", + "# daptics.wait_for_current_task()" ] }, { @@ -234,9 +305,57 @@ "metadata": {}, "outputs": [], "source": [ - "# Step 7. If space was validated, let's get it and save it to \n", - "# another CSV file to check it.\n", + "daptics.wait_for_current_task()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 7. Print session, save validated space.\n", "\n", + "If the _Experimental Space Definition_ was successfully validated, we may see it by printing out the current session state:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "daptics.print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The validated _Experimental Space Definition_ object may be retrieved with the following call:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "space = daptics.get_validated_experimental_space()\n", + "space" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "and it may be exported to a CSV file (whose contents should be the same as the _Experimental Space Definition_ CSV file):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "fname = 'validated_space.csv'\n", "space = daptics.export_validated_experimental_space_csv(fname)\n", "\n", @@ -245,7 +364,32 @@ " print(f.read())\n", "\n", "# Show the other space parameters.\n", - "print(space)" + "# print(space)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Initial Experiments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 8. Specify initial experiments (*optional*)\n", + "\n", + "Daptics modeling may be initialized with any available data from initial experiments done previously. The data file for initial experiments must have a certain form: one row of header, followed by one row for each experiment. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 8-a. Initialize with your data\n", + "\n", + "The data file may be initialized with the following call:" ] }, { @@ -254,8 +398,6 @@ "metadata": {}, "outputs": [], "source": [ - "# Step 8. Export a file to our directory named `initial_experiments.csv`\n", - "\n", "fname = 'initial_experiments.csv'\n", "columns = daptics.export_initial_experiments_template_csv(fname)\n", "\n", @@ -273,29 +415,16 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "# Step 8 (alternate). Create a file named `initial_experiments.csv`\n", - "# manually, and type in the required parameter names and the\n", - "# `Response` column, separated by commas.\n", + "The file `initial_experiments.csv` has been created in the Jupyter Notebook home directory (i.e. the directory where the Jupyter Notebook server was launched). You may now fill the file by adding rows to the CSV file, one row for each experiment, containing a value for each of the experimental variables (the specification of the experiment) followed by that experiment's response.\n", "\n", - "# Go to the Home page for your Jupyter server and create a CSV file\n", - "# containing just a header row with columns for each of the parameters \n", - "# in your experimental space, and a special column always named\n", - "# `Response`. For our example, the row should look like this:\n", + "You may construct the initial experiments file from scratch, but **it must have the correct column names in the header row**. \n", "\n", - "# param1,param2,param3,param4,Response\n", + "### Step 8-b (alternative to step 8-a). Initialize with random experiments and responses:\n", "\n", - "# Upload the file to your home directory and make sure it's named\n", - "# `initial_experiments.csv`.\n", - "# Then return to this notebook and execute this cell just to make\n", - "# sure the file can be read.\n", - "fname = 'initial_experiments.csv'\n", - "with open(fname, 'r') as f:\n", - " print(f.read())" + "The following cell creates random experiments with random responses, just as an example for processing initial experiments. " ] }, { @@ -304,13 +433,6 @@ "metadata": {}, "outputs": [], "source": [ - "# Step 8 (alternate). Create 20 randomized initial experiments using\n", - "# the client's `random_experiments_with_responses` method. \n", - "\n", - "# First we need to get the experimental space that we validated in\n", - "# Steps 6 and 7. (Or you could just use `daptics.validated_params['space']`).\n", - "# Now generate 20 \"extra\" experiments. We do not have a generated design yet,\n", - "# so we pass `None` for the `design` argument.\n", "\n", "space = daptics.get_validated_experimental_space()\n", "design = None\n", @@ -318,38 +440,43 @@ "random_experiments = daptics.random_experiments_with_responses(space, design, num_extras)\n", "\n", "# Now let's save these to a file using the `export_csv` utility method.\n", - "\n", "fname = 'initial_experiments.csv'\n", "task = daptics.export_csv(random_experiments, fname)\n", "\n", "# Show the contents of the file we just saved.\n", - "\n", "with open(fname, 'r') as f:\n", " print(f.read())" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 9 (*run only following step 8*). Process Initial Experiments \n", + "\n", + "Submit the initial experiments file created in the \n", + "previous step to create the first experimental design.\n", + "\n" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Step 9. Submit the initial experiments file downloaded in the \n", - "# previous step to create the first exprimental design.\n", - "# In this tutorial, we will not submit any initial experiments, but\n", - "# this \"empty\" file must still be submitted. \n", - "\n", - "# If you actually do have some initial or calibrating experiments \n", - "# to submit, go to the Home page for your Jupyter server, and edit \n", - "# the `initial_experiments.csv` file, adding the parameter values \n", - "# and experimental response for each experiment on a separate line. \n", - "# Then return to this step in the notebook to submit the file.\n", - "\n", "fname = 'initial_experiments.csv'\n", "task = daptics.save_initial_experiments_csv(fname)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 10. Poll the task, print results\n", "\n", - "# Show the result. \n", - "print(task)" + "As in Step 6, the following call will poll the task launched above to process initial experiments and create the first design. This call may be repeated, ruturning status = running while the task is still in progress. When the task is done (status=success), the results are then processed, and the task is removed from the queue. Subsequent calls to poll_for_current_task() will result in an error because no task will be found." ] }, { @@ -358,14 +485,16 @@ "metadata": {}, "outputs": [], "source": [ - "# Step 10. Wait a few seconds, then check to see if the task has\n", - "# completed successfully. \n", - "\n", - "# Check the status of any running or completed `generate` tasks.\n", - "task = daptics.poll_for_current_task(task_type='generate')\n", - "\n", - "# Show the result. Repeat until `status` is `success` (or `failure`).\n", - "print(task)" + "# Poll the task. Repeat until task disappears, when `status` is `success` (or `failure`).\n", + "daptics.wait_for_current_task()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the following cell, when you print the state of the session,\n", + "you should now see the Design printed after the Experimental Space Definition. Note that the Design printed has no responses in the final column." ] }, { @@ -374,13 +503,37 @@ "metadata": {}, "outputs": [], "source": [ - "# Step 11. If the first generation design was created, save it to \n", - "# a CSV file, so we can add responses. We always need to \n", - "# specify the design generation number to retrieve a design.\n", - "\n", - "gen = 1\n", - "fname = 'gen1_design.csv'\n", - "design = daptics.export_generated_design_csv(gen, fname)\n", + "daptics.print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Process the experimental design" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 11. Save design to CSV\n", + "Once the current generation design is created, save it to \n", + "a CSV file.\n", + "The call `daptics.export_generated_design_csv()` saves the last generated design." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# name file for current generation:\n", + "fname = 'gen'+str(daptics.gen)+'_design.csv'\n", + "# e.g. fname = 'gen1_design.csv'\n", + "print(fname)\n", + "design = daptics.export_generated_design_csv(fname)\n", "\n", "# Show the contents of the exported file, which will contain\n", "# the designed parameter values and a blank `Response` column\n", @@ -388,8 +541,27 @@ "with open(fname, 'r') as f:\n", " print(f.read())\n", "\n", - "# Show the design as well.\n", - "print(design)" + "# Show the design (in Python) as well.\n", + "print(\"Design file = \",fname)\n", + "design" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 12. Specify experimental responses" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 12-a. Specify your experimental responses\n", + "\n", + "The previous cell has written the designed experiments to a file named\n", + "`genN_experiments.csv` (with N corresponding to the current generation):\n", + "\n" ] }, { @@ -398,18 +570,36 @@ "metadata": {}, "outputs": [], "source": [ - "# Step 12. Copy the designed experiments to a file named\n", - "# `gen1_experiments.csv`, either using a spreadsheet editor,\n", - "# or the \"Duplicate\" file command in Jupyter Notebooks. \n", - "# Then fill in a numerical (floating point)\n", - "# response value for each experiment row. \n", - "\n", - "# After saving this file, return to this notebook to \n", - "# show the response file contents.\n", + "fname = 'gen'+str(daptics.gen)+'_design.csv'\n", + "# e.g. fname = 'gen1_design.csv'\n", + "print(\"Current design = \",fname)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Either using a spreadsheet or a text editor,\n", + "fill in a numerical (floating point) response value for each experiment row, \n", + "in the last entry of each row.\n", "\n", - "fname = 'gen1_experiments.csv'\n", - "with open(fname, 'r') as f:\n", - " print(f.read())\n" + "After saving this file, return to this notebook to \n", + "show the response file contents." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 12-b (alternative to step 12-a). Specify random responses\n", + "Create random response values for the\n", + "design we just generated using the client's \n", + "`random_experiments_with_responses` method. \n", + "\n", + "We need to have the experimental space and the recently\n", + "generated design for the call to `random_experiments_with_responses`.\n", + "We do not want any extra experiments, so we pass `0` for the \n", + "`num_extras` argument." ] }, { @@ -418,30 +608,43 @@ "metadata": {}, "outputs": [], "source": [ - "# Step 12 (alternate). Create randomized response values for the\n", - "# design we just generated using the client's \n", - "# `random_experiments_with_responses` method. \n", - "\n", - "# We need to have the experimental space and the recently\n", - "# generated design for the call to `random_experiments_with_responses`.\n", - "# We do not want any extra experiments, so we pass `0` for the \n", - "# `num_extras` argument.\n", - "\n", - "gen = 1\n", "space = daptics.get_validated_experimental_space()\n", - "design = daptics.get_generated_design(gen)\n", + "design = daptics.get_generated_design()\n", "num_extras = 0\n", - "random_experiments = daptics.random_experiments_with_responses(space, design, num_extras)\n", - "\n", - "# Now let's save these to a file using the `export_csv` utility method.\n", - "\n", - "fname = 'gen1_experiments.csv'\n", + "random_experiments = daptics.random_experiments_with_responses(space, design, num_extras)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's save these to a file using the `export_csv` utility method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fname = 'gen'+str(daptics.gen)+'_experiments.csv'\n", + "# e.g. fname = 'gen1_experiments.csv'\n", "task = daptics.export_csv(random_experiments, fname)\n", "\n", "# Show the contents of the file we just saved.\n", + "# with open(fname, 'r') as f:\n", + "# print(f.read())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 13. \n", "\n", - "with open(fname, 'r') as f:\n", - " print(f.read())" + "Now save these experiments to the session.\n", + "This will start another `generate` task, just as we did\n", + "in Step 9." ] }, { @@ -450,15 +653,40 @@ "metadata": {}, "outputs": [], "source": [ - "# Step 13. Now save these experiments to the session.\n", - "# This will start another `generate` task, just as we did\n", - "# in Step 9.\n", - "\n", - "fname = 'gen1_experiments.csv'\n", + "fname = 'gen'+str(daptics.gen)+'_experiments.csv'\n", + "# e.g. fname = 'gen1_experiments.csv'\n", "task = daptics.save_experiment_responses_csv(fname)\n", - "\n", - "# Show the result. \n", - "print(task)" + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Poll the task. Repeat until task disappears, when `status` is `success` (or `failure`).\n", + "daptics.wait_for_current_task()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "From this point on, you can repeat steps 11 through\n", + "13 until you are satisfied with your experimental\n", + "campaign, or until all the possible experimental\n", + "parameter combinations have been exhausted." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Full loop\n", + "The following loop illustrates how the execution of steps 1–13 can be \n", + "fully automated. This approach may be helpful, for example,\n", + "if you generate your responses by means of simulations." ] }, { @@ -467,10 +695,82 @@ "metadata": {}, "outputs": [], "source": [ - "# From this point on, you can repeat steps 10 through\n", - "# 13 until you are satisfied with your experimental\n", - "# campaign, or until all the possible experimental\n", - "# parameter combinations have been exhausted." + "###################################################################\n", + "# Connect (step 1)\n", + "api_host = 'http://inertia.protolife.com:8080'\n", + "daptics = DapticsClient(api_host)\n", + "daptics.connect()\n", + "# Login (step 2) - uncomment and fill in correct information\n", + "#email = 'YOUR_EMAIL@YOUR_DOMAIN'\n", + "#password = 'YOUR_PASSWORD'\n", + "#daptics.login(email, password);\n", + "\n", + "# start a session (step 3)\n", + "# NB: session name must be changed each time this cell is executed.\n", + "name = 'Session 10'\n", + "description = 'This is a full loop session'\n", + "daptics.create_session(name, description);\n", + "\n", + "###################################################################\n", + "# Experimental space:\n", + "# (steps 4-7 above)\n", + "params = {\n", + " 'space': { \n", + " 'type': 'factorial'\n", + " },\n", + " 'populationSize': 30,\n", + " 'replicates': 2\n", + "}\n", + "fname = 'esd-factorial-4x8.csv'\n", + "# Save and validate the space:\n", + "task = daptics.save_experimental_and_space_parameters_csv(params, fname)\n", + "daptics.wait_for_current_task()\n", + "print(\"Experimental space done\")\n", + "\n", + "###################################################################\n", + "# Initial experiments:\n", + "# (steps 8-10)\n", + "space = daptics.get_validated_experimental_space()\n", + "design = None\n", + "num_extras = 20\n", + "random_experiments = daptics.random_experiments_with_responses(space, design, num_extras)\n", + "fname = 'initial_experiments.csv'\n", + "task = daptics.export_csv(random_experiments, fname)\n", + "task = daptics.save_initial_experiments_csv(fname)\n", + "daptics.wait_for_current_task()\n", + "print(\"Initial experiments saved.\")\n", + "# Save design:\n", + "# name file for current generation:\n", + "fname = 'gen'+str(daptics.gen)+'_design.csv'\n", + "# e.g. fname = 'gen1_design.csv'\n", + "print(\"Saving design to: \",fname)\n", + "design = daptics.export_generated_design_csv(fname)\n", + "\n", + "#######################################################################\n", + "# Loop:\n", + "# (steps 12-13 -- repeated for 5 generations)\n", + "for n in range(5):\n", + " ###################################################################\n", + " # Fill design with random responses\n", + " space = daptics.get_validated_experimental_space()\n", + " design = daptics.get_generated_design()\n", + " num_extras = 0\n", + " # Normally, you would fill the design with your experimental responses here.\n", + " # For this tutorial we execute the following line \n", + " # providing random numbers as a surrogate for the experimental responses.\n", + " random_experiments = daptics.random_experiments_with_responses(space, design, num_extras)\n", + " fname = 'gen'+str(daptics.gen)+'_experiments.csv'\n", + " # e.g. fname = 'gen1_experiments.csv'\n", + " task = daptics.export_csv(random_experiments, fname)\n", + " # save responses and generate next design\n", + " task = daptics.save_experiment_responses_csv(fname)\n", + " daptics.wait_for_current_task()\n", + " print(\"Done with design. Now gen = \",daptics.gen)\n", + " fname = 'gen'+str(daptics.gen)+'_design.csv'\n", + " # e.g. fname = 'gen1_design.csv'\n", + " print(\"Saving design to: \",fname)\n", + " design = daptics.export_generated_design_csv(fname) \n", + "print(\"Loop completed\") " ] } ], @@ -490,7 +790,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.5.2" + "version": "3.7.2" } }, "nbformat": 4, diff --git a/python_client/daptics_client.py b/python_client/daptics_client.py index 994af7c..4b0e4de 100644 --- a/python_client/daptics_client.py +++ b/python_client/daptics_client.py @@ -42,7 +42,8 @@ import requests import requests.auth import time - +import sys +from time import sleep class TokenAuth(requests.auth.AuthBase): """A callable authentication object for the Python "requests" moudule. @@ -162,6 +163,47 @@ def __init__(self, host): self.validated_params = None self.design = None + def print(self): + print("host = ",self.host) + print("user_id = ",self.user_id) + print("session_id = ",self.session_id) + print("current_task = ",self.current_task) + print("gen = ",self.gen) + if self.validated_params is not None: + print("Experimental Space Definition:") + for x in self.validated_params: + if x == 'space': + sp = self.validated_params[x] + for y in sp: + if y == 'table': + print('\t ESD Data:') + print("\t\t",sp['table']['colHeaders']) + for z in sp['table']['data']: + zz = [z[i] for i in range(len(z)) if z[i] != ''] + print('\t\t',zz) + else: + print("\t",y,": ",sp[y]) + else: + print("\t",x,": ",self.validated_params[x]) + else: + print("Experimental Space Definition validated_params = None") + + if self.design is not None: + print("Design:") + print(self.design['table']['colHeaders']) + for dd in self.design['table']['data']: + print(dd) + else: + print("design: None") + + def call_api(self, doc, vars, timeout=None): + data = None + try: + data = self.gql.execute(doc, variable_values=vars,timeout = timeout) + except Exception as e: + data = {'error': str(e)} + return data + def save(self, fname): """Save the user and session id to a JSON file. @@ -270,7 +312,7 @@ def login(self, email, password): self.user_id = data['login']['user']['userId'] return data - def create_session(self, name, description, demo): + def create_session(self, name, description): """Create a new PDT session. Parameters @@ -279,8 +321,6 @@ def create_session(self, name, description, demo): The unique name for the session among the authenticated user's sessions. description : str A description for the session. - is_demo : bool - If True, this will be a demo session with simulated designs and responses. Returns ------- @@ -293,10 +333,9 @@ def create_session(self, name, description, demo): 'userId': self.user_id, 'name': name, 'description': description, - 'demo': demo + 'demo': False # If True, this will be a demo session with simulated designs and responses. } } - # The 'createSession' mutation will add a new session to the backend's database, # copy runtime files to a fresh Rserve session directory on the Rserve filesystem, # start the session and return initial session information. @@ -316,7 +355,13 @@ def create_session(self, name, description, demo): } } """) - data = self.gql.execute(doc, variable_values=vars) + # data = self.gql.execute(doc, variable_values=vars) + data = self.call_api(doc,vars) + if 'error' in data: + print("Problem creating session!") + print("Error: {}".format(data['error'])) + print("Hint: session name may have already been taken, in which case choose another one.") + return None if 'createSession' in data and data['createSession'] is not None: self.session_id = data['createSession']['sessionId'] self.initial_params = data['createSession']['params'] @@ -1054,7 +1099,14 @@ def poll_for_current_task(self, task_type=None): } } """) - data = self.gql.execute(doc, variable_values=vars) + + data = self.call_api(doc, vars) + + if 'error' in data: + print("Problem finding current task!") + print("Error: {}".format(data['error'])) + return None + if 'currentTask' in data and data['currentTask'] is not None \ and 'status' in data['currentTask'] \ and 'type' in data['currentTask']: @@ -1066,11 +1118,11 @@ def poll_for_current_task(self, task_type=None): self.current_task = data['currentTask'] elif status == 'canceled': # Message will be in response error - # TESTME: will we ever get here, or will exception be thown first? + # TESTME: will we ever get here, or will exception be thrown first? self.current_task = None elif status == 'error': # Message will be in response error - # TESTME: will we ever get here, or will exception be thown first? + # TESTME: will we ever get here, or will exception be thrown first? self.current_task = None elif status == 'success': # Fetch non-error result @@ -1088,6 +1140,37 @@ def poll_for_current_task(self, task_type=None): self.current_task['pollRetries'] = retries + 1 return data + def wait_for_current_task(self): + """Wraps poll_for_current_task() in a loop + + Returns + _______ + None for normal exit. + task['currentTask']['errors'] if ever task['currentTask']['status'] == 'error'. + """ + # Poll the task. Repeat until task disappears, when `status` is `success` (or `failure`). + cnt = 0 + while True: + task = self.poll_for_current_task() + if task['currentTask'] is not None: + sys.stdout.write("\r") + if task['currentTask']['status'] == 'error': + sys.stdout.write("\n") + print("Error! Messages are:") + # print(task) + for ee in task['currentTask']['errors'][0]: + print(ee,'\t',task['currentTask']['errors'][0][ee]) + print("------------ end of error messages.") + return + mystr = "status = "+str(task['currentTask']['status'])+" -- "+str(cnt)+" seconds." + sys.stdout.write(mystr) + sleep(1) + cnt = cnt+1 + else: + sys.stdout.write("\n") + print("Error: no client found!") + break + def get_validated_experimental_space(self, timeout=5*60): """Utility method to retrieve the validated experimental space from the session. If the session was restarted and the experimental space @@ -1114,25 +1197,24 @@ def get_validated_experimental_space(self, timeout=5*60): tmax = time.time() if timeout is not None and timeout > 0: tmax += timeout - while True: - data = self.poll_for_current_task('space') - if 'currentTask' in data and data['currentTask'] is not None: - type_ = data['currentTask']['type'] - if type_ == 'space': - status = data['currentTask']['status'] - if status == 'success' and self.validated_params is not None: - return self.validated_params['space'] - elif status != 'new' and status != 'running': - raise TaskFailedError('space') - else: - raise TaskTypeError('space') + if 'currentTask' in data and data['currentTask'] is not None: + type_ = data['currentTask']['type'] + if type_ == 'space': + status = data['currentTask']['status'] + if status == 'success' and self.validated_params is not None: + return self.validated_params['space'] + elif status != 'new' and status != 'running': + raise TaskFailedError('space') else: - raise NoCurrentTaskError() - if tmax < time.time(): - raise TaskTimeoutError() - time.sleep(1.0) + raise TaskTypeError('space') + else: + raise NoCurrentTaskError() + if tmax < time.time(): + raise TaskTimeoutError() + time.sleep(1.0) - def get_generated_design(self, gen, timeout=30*60): + + def get_generated_design(self, gen=None, timeout=30*60): """Utility method to retrieve a design generation from the session. If the session was restarted and the design for the specified generation number is available, it will be in the `design` @@ -1155,6 +1237,8 @@ def get_generated_design(self, gen, timeout=30*60): with empty responses with `colHeaders`, and `data` keys. """ + if gen == None: + gen = self.gen if self.validated_params is None or gen < 0: raise SessionParametersNotValidatedError() @@ -1339,21 +1423,22 @@ def export_initial_experiments_template_csv(self, fname, timeout=5*60): space = self.get_validated_experimental_space(timeout) col_headers = self.experiments_table_column_names(space) - self.export_csv({'colHeaders': colHeaders, 'data':[]}, fname) + self.export_csv({'colHeaders': col_headers, 'data':[]}, fname) return col_headers - def export_generated_design_csv(self, gen, fname, timeout=30*60): + def export_generated_design_csv(self, fname, gen = None, timeout=30*60): """Retrieves a design generation from the session, adn writes the experiments table (with empty responses) to a CSV file on disk. Parameters ---------- - gen : int - The generation number for the design to be retrieved. - fname : str The filesystem path where the file will be written. + gen : int + The generation number for the design to be retrieved. Default current + generation. + timeout : int The maximum number of seconds that the client will poll the session to retrieve the generated design. The default is 1800 (30 minutes). @@ -1364,6 +1449,8 @@ def export_generated_design_csv(self, gen, fname, timeout=30*60): with empty responses with `colHeaders`, and `data` keys. """ + if gen is None: + gen = self.gen design = self.get_generated_design(gen, timeout) self.export_csv(design['table'], fname) return design diff --git a/python_client/esd-factorial-3x5.csv b/python_client/esd-factorial-3x5.csv index 729131d..0c9aeac 100644 --- a/python_client/esd-factorial-3x5.csv +++ b/python_client/esd-factorial-3x5.csv @@ -1,3 +1,3 @@ -V11,numerical,0,1,2,3,4 -V12,numerical,0,1,2,3,4 -V13,numerical,0,1,2,3,4 +param1,numerical,0,1,2,3,4 +param2,numerical,0,1,2,3,4 +param3,numerical,0,1,2,3,4 diff --git a/python_client/esd-factorial-4x8.csv b/python_client/esd-factorial-4x8.csv new file mode 100644 index 0000000..633c511 --- /dev/null +++ b/python_client/esd-factorial-4x8.csv @@ -0,0 +1,4 @@ +param1,numerical,0,1,2,3,4,5,6,7 +param2,numerical,0,1,2,3,4,5,6,7 +param3,numerical,0,1,2,3,4,5,6,7 +param4,numerical,0,1,2,3,4,5,6,7 diff --git a/python_client/esd-factorial-8x5.csv b/python_client/esd-factorial-8x5.csv index e579f38..33304b8 100644 --- a/python_client/esd-factorial-8x5.csv +++ b/python_client/esd-factorial-8x5.csv @@ -1,8 +1,8 @@ -V11,numerical,0,1,2,3,4 -V12,numerical,0,1,2,3,4 -V13,numerical,0,1,2,3,4 -t11,numerical,5,10,15,20,25 -t12,numerical,5,10,15,20,25 -V21,numerical,0,1,2,3,4 -V22,numerical,0,1,2,3,4 -V23,numerical,0,1,2,3,4 +param1,numerical,0,1,2,3,4 +param2,numerical,0,1,2,3,4 +param3,numerical,0,1,2,3,4 +param4,numerical,5,10,15,20,25 +param5,numerical,5,10,15,20,25 +param6,numerical,0,1,2,3,4 +param7,numerical,0,1,2,3,4 +param8,numerical,0,1,2,3,4 diff --git a/python_client/esd-mixture-5.csv b/python_client/esd-mixture-5.csv index 93e1e68..c4d1363 100644 --- a/python_client/esd-mixture-5.csv +++ b/python_client/esd-mixture-5.csv @@ -1,5 +1,5 @@ -p1,unit,0,5 -p2,unit,0,5 -p3,unit,1,5 -p4,unit,1,5 -p5,unit,1,5 +param1,unit,0,5 +param2,unit,0,5 +param3,unit,1,5 +param4,unit,1,5 +param5,unit,1,5 From 919314512f06974182c3997dce5823ff22d0c123 Mon Sep 17 00:00:00 2001 From: Peter Zingg Date: Tue, 16 Apr 2019 16:58:12 -0700 Subject: [PATCH 2/3] nbstripouted --- python_client/03_SimpleTutorial.ipynb | 4 +--- python_client/04_GetAnalytics.ipynb | 8 ++------ python_client/05_RestartSession.ipynb | 4 +--- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/python_client/03_SimpleTutorial.ipynb b/python_client/03_SimpleTutorial.ipynb index 6d3fed2..1d7c2f2 100644 --- a/python_client/03_SimpleTutorial.ipynb +++ b/python_client/03_SimpleTutorial.ipynb @@ -117,9 +117,7 @@ }, { "cell_type": "markdown", - "metadata": { - "scrolled": true - }, + "metadata": {}, "source": [ "## Step 3. Create a daptics session on the server.\n", "\n", diff --git a/python_client/04_GetAnalytics.ipynb b/python_client/04_GetAnalytics.ipynb index a202853..a50a15e 100644 --- a/python_client/04_GetAnalytics.ipynb +++ b/python_client/04_GetAnalytics.ipynb @@ -104,9 +104,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": true - }, + "metadata": {}, "outputs": [], "source": [ "# Step 3. Create a daptics session on the server.\n", @@ -120,9 +118,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": true - }, + "metadata": {}, "outputs": [], "source": [ "# Step 4. Save and validate the default experimental space parameters.\n", diff --git a/python_client/05_RestartSession.ipynb b/python_client/05_RestartSession.ipynb index 8329075..bebf496 100644 --- a/python_client/05_RestartSession.ipynb +++ b/python_client/05_RestartSession.ipynb @@ -124,9 +124,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": true - }, + "metadata": {}, "outputs": [], "source": [ "# Step 4. Reconnect to a daptics session on the server.\n", From b0c3873e77b0de005daea1ad6ad4972c8ff5af57 Mon Sep 17 00:00:00 2001 From: Peter Zingg Date: Tue, 16 Apr 2019 17:02:48 -0700 Subject: [PATCH 3/3] version bump to 0.6.0 --- python_client/daptics_client.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python_client/daptics_client.py b/python_client/daptics_client.py index 4b0e4de..cf2ed8d 100644 --- a/python_client/daptics_client.py +++ b/python_client/daptics_client.py @@ -9,7 +9,7 @@ On the web at https://daptics.ai By email at support@daptics.ai -Daptics API Version 0.5.1 +Daptics API Version 0.6.0 Copyright (c) 2019 Daptics Inc. Permission is hereby granted, free of charge, to any person obtaining a copy @@ -187,7 +187,7 @@ def print(self): print("\t",x,": ",self.validated_params[x]) else: print("Experimental Space Definition validated_params = None") - + if self.design is not None: print("Design:") print(self.design['table']['colHeaders']) @@ -195,7 +195,7 @@ def print(self): print(dd) else: print("design: None") - + def call_api(self, doc, vars, timeout=None): data = None try: @@ -1142,7 +1142,7 @@ def poll_for_current_task(self, task_type=None): def wait_for_current_task(self): """Wraps poll_for_current_task() in a loop - + Returns _______ None for normal exit. @@ -1151,7 +1151,7 @@ def wait_for_current_task(self): # Poll the task. Repeat until task disappears, when `status` is `success` (or `failure`). cnt = 0 while True: - task = self.poll_for_current_task() + task = self.poll_for_current_task() if task['currentTask'] is not None: sys.stdout.write("\r") if task['currentTask']['status'] == 'error':