def _calculate_start_point(database, updating_options): """Calculate the starting point. Args: database (sqlalchemy.MetaData): Bound metadata object. updating_options (dict): Specification how to update the plotting data. It contains rollover, update_frequency, update_chunk, jump and stride. Returns: start_point (int): iteration from which to start the dashboard. """ if updating_options["jump"]: last_entry = read_last_rows( database=database, table_name="optimization_iterations", n_rows=1, return_type="list_of_dicts", ) nr_of_entries = last_entry[0]["rowid"] nr_to_go_back = updating_options["rollover"] * updating_options[ "stride"] start_point = max(0, nr_of_entries - nr_to_go_back) else: start_point = 0 return start_point
def _read_optimization_iteration(database, iteration, params_treedef, registry): """Get information about an optimization iteration.""" if iteration >= 0: rowid = iteration + 1 else: last_iteration = read_last_rows( database=database, table_name="optimization_iterations", n_rows=1, return_type="list_of_dicts", ) highest_rowid = last_iteration[0]["rowid"] # iteration is negative here! rowid = highest_rowid + iteration + 1 data = read_specific_row( database, table_name="optimization_iterations", rowid=rowid, return_type="list_of_dicts", ) if len(data) == 0: raise IndexError(f"Invalid iteration requested: {iteration}") else: data = data[0] params = tree_unflatten(params_treedef, data["params"], registry=registry) data["params"] = params return data
def read_optimization_iteration(path_or_database, iteration, include_internals=False): """Get information about an optimization iteration. Args: path_or_database (pathlib.Path, str or sqlalchemy.MetaData) iteration (int): The index of the iteration that should be retrieved. The row_id behaves as Python list indices, i.e. ``0`` identifies the first iteration, ``-1`` the last one, etc. include_internals (bool): Whether internally used quantities like the internal parameter vector and the corresponding derivative etc. are included in the result. Default False. This should only be used by advanced users. Returns: dict: The logged information corresponding to the iteration. The keys correspond to database columns. Raises: KeyError: if the iteration is out of bounds. """ database = load_database(**_process_path_or_database(path_or_database)) start_params = read_start_params(database) if iteration >= 0: rowid = iteration + 1 else: last_iteration = read_last_rows( database=database, table_name="optimization_iterations", n_rows=1, return_type="list_of_dicts", ) highest_rowid = last_iteration[0]["rowid"] rowid = highest_rowid + iteration + 1 data = read_specific_row( database=database, table_name="optimization_iterations", rowid=rowid, return_type="list_of_dicts", ) if len(data) == 0: raise IndexError(f"Invalid iteration requested: {iteration}") else: data = data[0] params = start_params.copy() params["value"] = data.pop("external_params") data["params"] = params to_remove = ["distance_origin", "distance_ones"] if not include_internals: to_remove += ["internal_params", "internal_derivative"] for key in to_remove: if key in data: del data[key] return data
def test_optimization_iteration_table_scalar(tmp_path, iteration_data): path = tmp_path / "test.db" database = load_database(path=path) make_optimization_iteration_table(database, first_eval={"output": 0.5}) append_row(iteration_data, "optimization_iterations", database, path, False) res = read_last_rows(database, "optimization_iterations", 1, "list_of_dicts") assert isinstance(res, list) and isinstance(res[0], dict) res = res[0] assert res["rowid"] == 1 assert_array_equal(res["params"], iteration_data["params"]) for key in ["value", "timestamp"]: assert res[key] == iteration_data[key]
def test_optimization_problem_table(tmp_path, problem_data): path = tmp_path / "test.db" database = load_database(path=path) make_optimization_problem_table(database) append_row(problem_data, "optimization_problem", database, path, False) res = read_last_rows(database, "optimization_problem", 1, "list_of_dicts")[0] assert res["rowid"] == 1 for key, expected in problem_data.items(): if key == "criterion": assert res[key](np.ones(3)) == 3 elif isinstance(expected, np.ndarray): assert_array_equal(res[key], expected) else: assert res[key] == expected
def test_read_last_rows_stride(tmp_path, iteration_data): path = tmp_path / "test.db" database = load_database(path=path) make_optimization_iteration_table(database, first_eval={"output": 0.5}) for i in range(1, 11): # sqlalchemy starts counting at 1 iteration_data["value"] = i append_row(iteration_data, "optimization_iterations", database, path, False) res = read_last_rows( database=database, table_name="optimization_iterations", n_rows=3, return_type="dict_of_lists", stride=2, )["value"] expected = [6.0, 8.0, 10.0] assert res == expected
def test_read_last_rows_with_step(tmp_path, iteration_data): path = tmp_path / "test.db" database = load_database(path=path) make_optimization_iteration_table(database, first_eval={"output": 0.5}) for i in range(1, 11): # sqlalchemy starts counting at 1 iteration_data["value"] = i iteration_data["step"] = i % 2 append_row(iteration_data, "optimization_iterations", database, path, False) res = read_last_rows( database=database, table_name="optimization_iterations", n_rows=20, return_type="dict_of_lists", step=0, ) expected = [2, 4, 6, 8, 10] assert res["rowid"] == expected
def read_start_params(path_or_database): """Load the start parameters DataFrame. Args: path_or_database (pathlib.Path, str or sqlalchemy.MetaData) Returns: params (pd.DataFrame): see :ref:`params`. """ database = load_database(**_process_path_or_database(path_or_database)) optimization_problem = read_last_rows( database=database, table_name="optimization_problem", n_rows=1, return_type="dict_of_lists", ) start_params = optimization_problem["params"][0] return start_params
def dashboard_app( doc, session_data, updating_options, ): """Create plots showing the development of the criterion and parameters. Args: doc (bokeh.Document): Argument required by bokeh. session_data (dict): Infos to be passed between and within apps. Keys of this app's entry are: - last_retrieved (int): last iteration currently in the ColumnDataSource. - database_path (str or pathlib.Path) - callbacks (dict): dictionary to be populated with callbacks. updating_options (dict): Specification how to update the plotting data. It contains rollover, update_frequency, update_chunk, jump and stride. """ # style the Document template_folder = Path(__file__).resolve().parent # conversion to string from pathlib Path is necessary for FileSystemLoader env = Environment(loader=FileSystemLoader(str(template_folder))) doc.template = env.get_template("index.html") # process inputs database = load_database(path=session_data["database_path"]) start_point = _calculate_start_point(database, updating_options) session_data["last_retrieved"] = start_point # build start_params DataFrame registry = get_registry(extended=True) start_params_tree = read_start_params(path_or_database=database) internal_params = tree_just_flatten(tree=start_params_tree, registry=registry) full_names = leaf_names(start_params_tree, registry=registry) optimization_problem = read_last_rows( database=database, table_name="optimization_problem", n_rows=1, return_type="dict_of_lists", ) free_mask = optimization_problem["free_mask"][0] params_groups, short_names = get_params_groups_and_short_names( params=start_params_tree, free_mask=free_mask ) start_params = pd.DataFrame( { "full_name": full_names, "name": short_names, "group": params_groups, "value": internal_params, } ) start_params["id"] = _create_id_column(start_params) group_to_param_ids = _map_group_to_other_column(start_params, "id") group_to_param_names = _map_group_to_other_column(start_params, "name") criterion_history, params_history = _create_cds_for_dashboard(group_to_param_ids) # create elements title_text = """<h1 style="font-size:30px;">estimagic Dashboard</h1>""" title = Row( children=[ Div( text=title_text, sizing_mode="scale_width", ) ], name="title", margin=(5, 5, -20, 5), ) plots = _create_initial_plots( criterion_history=criterion_history, params_history=params_history, group_to_param_ids=group_to_param_ids, group_to_param_names=group_to_param_names, ) restart_button = _create_restart_button( doc=doc, database=database, session_data=session_data, start_params=start_params, updating_options=updating_options, ) button_row = Row( children=[restart_button], name="button_row", ) # add elements to bokeh Document grid = Column(children=[title, button_row, *plots], sizing_mode="stretch_width") doc.add_root(grid) # start the convergence plot immediately restart_button.active = True