diff --git a/Optimization of control rules/Optimization_control_rules.ipynb b/Optimization of control rules/Optimization_control_rules.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..4216078769bfca7a5ab9bd8777e7a7865cffb882 --- /dev/null +++ b/Optimization of control rules/Optimization_control_rules.ipynb @@ -0,0 +1,817 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dc6fe0c6", + "metadata": {}, + "source": [ + "### Import of libraries" + ] + }, + { + "cell_type": "markdown", + "id": "9f940e4e", + "metadata": {}, + "source": [ + "General libraries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ebbcf530", + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-28T14:21:34.156926700Z", + "start_time": "2024-07-28T14:21:33.780745600Z" + } + }, + "outputs": [], + "source": [ + "import typing\n", + "import datetime\n", + "import shutil\n", + "import subprocess\n", + "from multiprocessing.connection import Client\n", + "from time import sleep\n", + "from warnings import warn\n", + "from pathlib import Path\n", + "import functools\n", + "import threading\n", + "from multiprocessing.pool import ThreadPool\n", + "import traceback\n", + "import numpy as np\n", + "import pandas as pd\n", + "import nevergrad as ng\n", + "from IPython.utils import io as ipyio\n", + "import os\n", + "from scipy.ndimage import gaussian_filter1d" + ] + }, + { + "cell_type": "markdown", + "id": "d260d7da", + "metadata": {}, + "source": [ + "Libraries for the interface with MIKE+" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ebb5c530", + "metadata": {}, + "outputs": [], + "source": [ + "from mikeplus import DataTableAccess\n", + "from mikeplus.engines import Engine1D\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "from mikeio1d import Res1D\n", + "from mikeio1d.query import QueryDataStructure\n", + "from mikeio1d.query import QueryDataNode" + ] + }, + { + "cell_type": "markdown", + "id": "7de86ed5", + "metadata": {}, + "source": [ + "### Setting variables" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34c9b436", + "metadata": {}, + "outputs": [], + "source": [ + "# a folder where used files are saved\n", + "# note this should be at the same folder depth as the original folder, so that relative paths lead to the same files\n", + "model_folder = Path(\"verteilte Steuerung/\")\n", + "# path of .mupp file, relative to model_folder\n", + "db_path_rel = \"240502_Modell Rodenkirchen.mupp\"\n", + "# Number of simulations allowed for optimization\n", + "optimization_budget = 60" + ] + }, + { + "cell_type": "markdown", + "id": "003d8aff", + "metadata": {}, + "source": [ + "Names of the input Excel files" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "afb44b21", + "metadata": {}, + "outputs": [], + "source": [ + "parameter_xlsx = \"config_parameter.xlsx\"\n", + "event_xlsx = \"config_events.xlsx\"\n", + "structure_xlsx = \"structures.xlsx\"" + ] + }, + { + "cell_type": "markdown", + "id": "32eb5a4f", + "metadata": {}, + "source": [ + "Further Variables" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ffa64b6", + "metadata": {}, + "outputs": [], + "source": [ + "# delete result files(as specified by event config) before a sim, assures that no old file is reread in the case that a simulation does not run\n", + "DELETE_RESFILE_BEFORE_SIM = True\n", + "# output heaps of additional data, may be useful for debugging, Default = False\n", + "VERBOSE_OUTPUT = False\n", + "# attempt simulation at most so many times, used when a simulation fails\n", + "MAX_SIMULATION_TRY = 1\n", + "# Consider a simulation as failed and retry if so many values in the result compared to the reference are missing\n", + "MAX_MISSING_RATIO = 0.6\n", + "# delete the temp folder at end, Default = True\n", + "DELETE_TEMP = True\n", + "# time string for output folder\n", + "time_path_part = datetime.datetime.now().strftime(\"%Y-%m-%dT%H.%M.%S\")\n", + "# set path of temp and output folder\n", + "temp_folder = Path(\"tmp_\" + time_path_part)\n", + "# output folder\n", + "out_folder = Path(\"out_\" + time_path_part)\n", + "# wheter output from mike simulation get shown on error\n", + "show_sim_out_on_error = True\n", + "# set the id of the sensors and the timezone\n", + "timezone = \"UTC+01:00\"\n", + "#Number of seats for the Engine, limis amount of simulation we can run at once\n", + "engine_seats = 4\n", + "optimizers = [\"BayesOptimBO\"] " + ] + }, + { + "cell_type": "markdown", + "id": "b9dceb58", + "metadata": {}, + "source": [ + "### Preparation of the database" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b04a0f9f", + "metadata": {}, + "outputs": [], + "source": [ + "def file_to_df(path: Path) -> pd.DataFrame:\n", + " \"\"\"\n", + " Tries to read the file at path as a pandas DataFrame.\n", + " Supports Excel, CSV, and .res1d files.\n", + " \"\"\"\n", + " if path.suffix == \".res1d\":\n", + " res1d = Res1D(str(path)) # Create a Res1D instance with the file\n", + " df = res1d.read_all() # Read all data from the .res1d file\n", + " return df\n", + " elif path.suffix == \".xlsx\":\n", + " return pd.read_excel(path)\n", + " elif path.suffix == \".csv\":\n", + " return pd.read_csv(path)\n", + " else:\n", + " raise NotImplementedError(f\"No method for {path.suffix} implemented\")" + ] + }, + { + "cell_type": "markdown", + "id": "14138d91", + "metadata": {}, + "source": [ + "Creating an output folder and a temporary folder" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa253ce4", + "metadata": {}, + "outputs": [], + "source": [ + "out_folder.mkdir(exist_ok=True,parents=True)\n", + "assert not temp_folder.exists(), \"Make sure not to mess with existing folders\"\n", + "shutil.copytree(model_folder,temp_folder)\n", + "db_path = temp_folder / db_path_rel\n", + "\n", + "# print the path of the temp and out folder\n", + "temp_folder.absolute()\n", + "out_folder.absolute()" + ] + }, + { + "cell_type": "markdown", + "id": "8283a2e5", + "metadata": {}, + "source": [ + "Read the excel config_events" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0efc3c47", + "metadata": {}, + "outputs": [], + "source": [ + "events = file_to_df(temp_folder / event_xlsx).set_index('Event', drop=False)\n", + "# check if start time of events is before the end time.\n", + "assert (events[\"Start\"] <= events[\"End\"]).all(\n", + " axis=None\n", + "), \"Event end needs to be after start.\"\n", + "# add timezone. Use same as sensor_date\n", + "events[\"Start\"]=events[\"Start\"].dt.tz_localize(timezone)\n", + "events[\"End\"]=events[\"End\"].dt.tz_localize(timezone)\n", + "# check if there are duplicated events\n", + "assert events.index.is_unique, \"Need unique event\"\n", + "assert events.drop_duplicates(subset=\"Event\")[\"SimulationName\"].is_unique, \"Need exactly one simulation name per event\"\n", + "# Conversion of the SimulationName column to the String data type, if it is not already there\n", + "events[\"SimulationName\"] = events[\"SimulationName\"].astype(str)\n", + "events" + ] + }, + { + "cell_type": "markdown", + "id": "3db96632", + "metadata": {}, + "source": [ + "Read the excel config_parameter" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d1e12ad", + "metadata": {}, + "outputs": [], + "source": [ + "param_specs = file_to_df(temp_folder / parameter_xlsx)\n", + "\n", + "# give params_specs a sensible string index, this is later used to pass optimization parameters as a dictionary\n", + "param_specs.index = pd.Index(\n", + " param_specs[[\"Table\", \"Column\", \"Muid\"]].agg(\";\".join, axis=\"columns\"),\n", + " name=\"ParamKey\",\n", + ")\n", + "assert (\n", + " param_specs.index.is_unique\n", + "), \"Index needs to be unique. Otherwise indicates dupplicates in the parameters.\"\n", + "param_specs" + ] + }, + { + "cell_type": "markdown", + "id": "0c455656", + "metadata": {}, + "source": [ + "Read the excel strucutres" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b60324a6", + "metadata": {}, + "outputs": [], + "source": [ + "structures = file_to_df(temp_folder / structure_xlsx)\n", + "structures_list = structures.tolist()\n", + "structures_list" + ] + }, + { + "cell_type": "markdown", + "id": "21eab72c", + "metadata": {}, + "source": [ + "Open Mike+ database and start engine" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe607e54", + "metadata": {}, + "outputs": [], + "source": [ + "dta = DataTableAccess(db_path)\n", + "# open the database from MIKE+\n", + "dta.open_database()\n", + "# check if the database is open.\n", + "dta.is_database_open()\n", + "# Create the Engine1D object that will be used to run MIKE 1D simulations\n", + "engine = Engine1D(dta.datatables)" + ] + }, + { + "cell_type": "markdown", + "id": "a67a5823", + "metadata": {}, + "source": [ + "Threading is used to run multiple simulations with differnt engines" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "371fc664", + "metadata": {}, + "outputs": [], + "source": [ + "engines = {}\n", + "\n", + "def init_engine():\n", + " engines[threading.current_thread()] = Engine1D(dta.datatables)\n", + " print(f'initialized engine for {threading.current_thread()}')\n", + "\n", + "# Use threadpool from the multiprocessing package, this has a rather simple interface\n", + "sim_executor = ThreadPool(processes=engine_seats,initializer=init_engine)" + ] + }, + { + "cell_type": "markdown", + "id": "40f2b6da", + "metadata": {}, + "source": [ + "Functions for the simulation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a51fa7f3", + "metadata": {}, + "outputs": [], + "source": [ + " def calculate_discharge_for_structure(res1d, structure):\n", + " \"\"\"\n", + " Calculates the discharge for a given structure from the loaded Res1D object.\n", + " \n", + " Parameters\n", + " ----------\n", + " res1d : Res1D\n", + " The loaded Res1D object containing simulation results.\n", + " structure : str\n", + " The name of the structure for which discharge is to be calculated.\n", + " \n", + " Returns\n", + " -------\n", + " float or None\n", + " The total discharge in liters, or None if an error occurs.\n", + " \"\"\"\n", + " try:\n", + " values = res1d.structures[structure].Discharge.read()\n", + " total_discharge = np.sum(values) * 60 * 1000\n", + " return total_discharge\n", + " except KeyError:\n", + " print(f\"Structure {structure} not found in the Res1D data.\")\n", + " return None\n", + " except AttributeError:\n", + " print(f\"Discharge data not available for structure {structure}.\")\n", + " return None\n", + " except Exception as e:\n", + " print(f\"An error occurred while calculating discharge for {structure}: {e}\")\n", + " return None " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f3ab9f06", + "metadata": {}, + "outputs": [], + "source": [ + "def check_flood(res1d) -> bool:\n", + " \"\"\"\n", + " Checks whether there is flooding in the system by checking all nodes of the .res1d file\n", + " are checked for the WaterVolumeAboveGround value.\n", + "\n", + " Parameters:\n", + " -----------\n", + " res1d_file_path: str\n", + " The file path to the .res1d file containing the simulation results.\n", + "\n", + " Returns:\n", + " --------\n", + " bool\n", + " Retruns True, if a flooding is detected otherwise False.\n", + " \"\"\"\n", + " Flood = False\n", + " node_names = res1d.nodes\n", + " flooded_nodes = []\n", + "\n", + " # Iterate over all nodes and retrieve the WaterVolumeAboveGround values\n", + " for node in node_names:\n", + " query_node = QueryDataNode(quantity=\"WaterVolumeAboveGround\", name=node)\n", + " \n", + " try:\n", + " water_level_values = query_node.get_values(res1d)\n", + " \n", + " if water_level_values is None or len(water_level_values) == 0:\n", + " raise ValueError(f\"WaterVolumeAboveGround for Node '{node}' doesn't exist.\")\n", + " \n", + " if (water_level_values > 0).any(): \n", + " Flood = True\n", + " flooded_nodes.append(node)\n", + " print(f\"Flood detected for Node: {node}\")\n", + " \n", + " except Exception as e:\n", + " print(f\"Fehler beim Abrufen der Werte für Node {node}: {e}\")\n", + "\n", + " if Flood:\n", + " print(\"Flood detected in the system!\")\n", + " print(\"Flooded nodes:\", flooded_nodes)\n", + " else:\n", + " print(\"No flood detected in the system.\")\n", + " \n", + " return Flood" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "654c4b86", + "metadata": {}, + "outputs": [], + "source": [ + "def single_sim_run(ev):\n", + " '''\n", + " Runs a single simulation for event ev. Returns a sting message on successfull finish.\n", + " Intended for unsage with ThreadPool.map(). Use map to complete all simulations and read results after that\n", + " '''\n", + " sim_name = events.loc[events[\"Event\"] == ev, \"SimulationName\"].iloc[0]\n", + " \n", + " print(f\"Running simulation {sim_name} in {threading.current_thread()}.\")\n", + " engines[threading.current_thread()].run(sim_name)\n", + " return f\"Completed sim for {ev} in {threading.current_thread()}\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b821fda2", + "metadata": {}, + "outputs": [], + "source": [ + "def simulation_run(**kwparams):\n", + " '''\n", + " Takes parameters as arguments, runs simulations as defined by events with them and returns the results as a series with Multiinex [event, time].\n", + " Note that kwparams need keys that match the indeyx of the DataFrame param_specs\n", + " '''\n", + " assert set(kwparams.keys()) == set(\n", + " param_specs.index\n", + " ), \"parameters need to match param_specs\" \n", + " \n", + " for k in kwparams.keys():\n", + " tab = param_specs.loc[k, \"Table\"]\n", + " col = param_specs.loc[k, \"Column\"]\n", + " muid = param_specs.loc[k, \"Muid\"]\n", + " val = kwparams[k]\n", + "\n", + " if pd.notna(param_specs.loc[k, \"Expression\"]):\n", + " expression = param_specs.loc[k, \"Expression\"].format(ConditionValue=val)\n", + " dta.set_value(tab, muid, col, expression)\n", + " print(f\"Set value for {tab}.{muid}.{col} to expression: {expression}\")\n", + "\n", + " else:\n", + " dta.set_value(tab, muid, col, val)\n", + " print(f\"Set value for {tab}.{muid}.{col} to value: {val}\")\n", + "\n", + " if DELETE_RESFILE_BEFORE_SIM: \n", + " for l in events.index:\n", + " delete_result_file(l) \n", + " \n", + " # Now run each simulation on its own thread\n", + " finish_msgs = sim_executor.map(single_sim_run,events[\"Event\"].unique())\n", + " print(finish_msgs)\n", + " \n", + " results_table = pd.DataFrame(columns=['Event', 'Result'])\n", + " total_result = 0\n", + " \n", + " for l in events.index:\n", + " result_file_path = temp_folder / events.loc[l, \"ResultFile\"]\n", + " result_file_path_str = str(temp_folder / events.loc[l, \"ResultFile\"])\n", + " \n", + " # Check if the result file exists\n", + " if not Path(result_file_path).exists():\n", + " print(f\"Result file does not exist: {result_file_path}\")\n", + " continue\n", + "\n", + " try:\n", + " res1d = Res1D(str(Path(result_file_path))) # Create Res1D instance for each file\n", + " flood = check_flood(res1d)\n", + " \n", + " if flood == True: # Give a penelty for flooding\n", + " results_ev = 10e100\n", + " \n", + " else:\n", + " # Calculate discharge values\n", + " discharge_values = [calculate_discharge_for_structure(res1d, structure) for structure in structures_list]\n", + " # Entferne None-Werte, falls es Fehler bei der Berechnung gab\n", + " discharge_values = [value for value in discharge_values if value is not None]\n", + " # Berechne die Gesamtzahl der Discharge-Werte\n", + " results_ev = np.sum(discharge_values) if discharge_values else 0\n", + " print(f\"Result for result file {result_file_path}: {results_ev}\")\n", + " # Update total result\n", + " total_result += results_ev\n", + " except Exception as e:\n", + " print(f\"Error processing result file {result_file_path}: {e}\")\n", + " traceback.print_exc()\n", + " results_ev = 0\n", + " results_table = pd.concat([results_table, pd.DataFrame([{'Event': events.loc[l, \"Event\"], 'Result': results_ev}])], ignore_index=True)\n", + " print(f\"Total result: {total_result}\") \n", + " print(results_table)\n", + " \n", + " return total_result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "012fb1ff", + "metadata": {}, + "outputs": [], + "source": [ + "def delete_result_file(event):\n", + " '''\n", + " Deletes existing result file before a simulation corresponding to event.\n", + " '''\n", + " file_path = str(temp_folder / events.loc[event, \"ResultFile\"])\n", + " try:\n", + " Path(file_path).unlink(missing_ok=True)\n", + " except FileNotFoundError:\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "id": "19d16489", + "metadata": {}, + "source": [ + "### Test simulation run for all events with the default parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dffeeb37", + "metadata": {}, + "outputs": [], + "source": [ + "# test run with default parameters\n", + "# the ** in the call unrolls the series into keyword parameters\n", + "param_specs.dtypes\n", + "_test_res = simulation_run(**param_specs[\"Default\"])\n", + "display(\"_test_res:\")\n", + "display(_test_res)" + ] + }, + { + "cell_type": "markdown", + "id": "baf6b68a", + "metadata": {}, + "source": [ + "### Preparations for optimisation" + ] + }, + { + "cell_type": "markdown", + "id": "3d54b910", + "metadata": {}, + "source": [ + "Define optimization parameters as needed by nevergrade" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0dbba878", + "metadata": {}, + "outputs": [], + "source": [ + "ng_params = {}\n", + "for i in param_specs.index:\n", + " vmin = param_specs.at[i, \"Min\"]\n", + " vmax = param_specs.at[i, \"Max\"]\n", + " vdef = param_specs.at[i, \"Default\"]\n", + " ngp = ng.p.Scalar(init=vdef, lower=vmin, upper=vmax)\n", + " ng_params[i] = ngp\n", + "# now turn the list into an instrumentation\n", + "# the instrumentation is used to tell the optimizer how the paremeters are passed to our function\n", + "instrumentation = ng.p.Instrumentation(**ng_params)\n", + "instrumentation" + ] + }, + { + "cell_type": "markdown", + "id": "6cb4a183", + "metadata": {}, + "source": [ + "Define constarins for the parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62e0a803", + "metadata": {}, + "outputs": [], + "source": [ + "def constraint_pid_setpoints(params_val):\n", + " \"\"\"Constraint: SetPoints should be less than or equal to the CSO-reduction-301 value\"\"\"\n", + " cso_reduction = params_val[1][\"msm_RTCRule;Condition;CSO-reduction-301\"]\n", + " setpoint_0301 = params_val[1][\"msm_RTCAction;PIDSetPoint;HUB0301_CSO_red\"]\n", + " \n", + " # Ensure both setpoints are <= CSO-reduction-301\n", + " return cso_reduction - setpoint_0301\n", + "\n", + "# Register the constraint\n", + "instrumentation.register_cheap_constraint(constraint_pid_setpoints)" + ] + }, + { + "cell_type": "markdown", + "id": "7d5b751f", + "metadata": {}, + "source": [ + "Define callback funtions for the optimizers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4a870f3", + "metadata": {}, + "outputs": [], + "source": [ + "def params_losses_callback():\n", + " \"\"\"returns two lists and a callback function.\n", + " When the callback function is executed it puts the parameters and loss into the lists.\n", + " !!Do not register this function itself as a callback, but the third returned parameter!!\n", + " \"\"\"\n", + " # create two lists to store the params and the corresponding losses\n", + " params = []\n", + " losses = []\n", + "\n", + " def callback(optim, par, loss):\n", + " params.append(par)\n", + " losses.append(loss)\n", + "\n", + " # returns two lists and the function\n", + " return params, losses, callback" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aea52f70", + "metadata": {}, + "outputs": [], + "source": [ + "def optim_run(optim_name):\n", + " '''\n", + " Function to start the optimizer.\n", + " input = name of the optimizer\n", + " output = dataframe with details from optimization model\n", + " '''\n", + " # set the optimizer based on the optim_name and instrumentation from params_spec\n", + " optimizer = ng.optimizers.registry[optim_name](\n", + " parametrization=instrumentation, budget=optimization_budget, num_workers=1\n", + " )\n", + " # show progressbar. updated when the optimizer gets the result of a step\n", + " optimizer.register_callback(\"tell\", ng.callbacks.ProgressBar())\n", + " # put params and loss in list\n", + " param_list, loss_list, plcb = params_losses_callback()\n", + " optimizer.register_callback(\"tell\", plcb)\n", + " # get recommendation of parameters after run is finished\n", + " reccomendation = optimizer.minimize(simulation_run, verbosity=2)\n", + " optim_run_df = pd.concat(\n", + " [\n", + " pd.DataFrame(data=[p.value[1] for p in param_list]),\n", + " pd.DataFrame({\"Optimizer\": optimizer.name, \"Loss\": loss_list}),\n", + " ],\n", + " axis=\"columns\",\n", + " )\n", + " # also add a line for the reccomendation\n", + " rec_params = reccomendation.value[1]\n", + " optim_run_df.loc[\"reccomendation\", rec_params.keys()] = rec_params\n", + " optim_run_df.loc[\"reccomendation\", \"Loss\"] = reccomendation.loss\n", + " optim_run_df.loc[\"reccomendation\", \"Optimizer\"] = optimizer.name\n", + " # save the results of the optimization as a csv and excel file\n", + " optim_run_df.to_csv(out_folder / f\"optim_run{optimizer.name}.csv\")\n", + " optim_run_df.to_excel(out_folder / f\"optim_run{optimizer.name}.xlsx\")\n", + "\n", + " return optim_run_df" + ] + }, + { + "cell_type": "markdown", + "id": "312ebf1f", + "metadata": {}, + "source": [ + "### Run optimisation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0517d4fe", + "metadata": {}, + "outputs": [], + "source": [ + "# choose optimizers from nevergrad\n", + "# https://en.wikipedia.org/wiki/CMA-ES\n", + "# create a list with optimizers\n", + "# it is also possible to use multiple optimizers and compare the results\n", + "# create dictionary to store the rundata for each optimizer\n", + "optim_run_dfs = {}\n", + "for o in optimizers:\n", + " # start the optimization\n", + " optim_run_dfs[o] = optim_run(o)\n", + " # display results\n", + " display(optim_run_dfs[o])\n", + " optim_run_dfs[o].plot(subplots=True, figsize=(5, 2 * len(param_specs)))\n", + "\n", + "# optim_run_df already has optimizer included, use .values instead of passing the dict, so that the optimizer is not added again.\n", + "optim_run_df = pd.concat(optim_run_dfs.values())\n", + "optim_run_df.to_csv(out_folder / \"optim_run.csv\")\n", + "\n", + "# set the index if the optim_run_df\n", + "optim_run_df.index.name = \"Step\"\n", + "optim_run_df = optim_run_df.reset_index().set_index([\"Optimizer\", \"Step\"])" + ] + }, + { + "cell_type": "markdown", + "id": "64de11d1", + "metadata": {}, + "source": [ + "Print the results of the Optimization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7162ffac", + "metadata": {}, + "outputs": [], + "source": [ + "# print the recommendations one last time\n", + "for key, df in optim_run_df.groupby(level=\"Optimizer\"):\n", + " display(key,df.loc[(key, \"reccomendation\"), param_specs.index])" + ] + }, + { + "cell_type": "markdown", + "id": "06dd3f0e", + "metadata": {}, + "source": [ + "Close database" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93579781", + "metadata": {}, + "outputs": [], + "source": [ + "dta.close_database()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:anaconda3-entfrachten]", + "language": "python", + "name": "conda-env-anaconda3-entfrachten-py" + }, + "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.9.20" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}