From edee1156b52e71b5962fec17d19c9e158172977f Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 23 Jul 2021 09:30:54 -0400 Subject: [PATCH 01/69] Started functions for generating parameter sweep plots from pickled results. --- stochss/handlers/util/parameter_sweep_1d.py | 61 +++++++++++----- stochss/handlers/util/parameter_sweep_2d.py | 59 +++++++++------- stochss/handlers/util/stochss_job.py | 78 +++++++++++++++++++-- 3 files changed, 149 insertions(+), 49 deletions(-) diff --git a/stochss/handlers/util/parameter_sweep_1d.py b/stochss/handlers/util/parameter_sweep_1d.py index e1744532a8..51f34c007e 100644 --- a/stochss/handlers/util/parameter_sweep_1d.py +++ b/stochss/handlers/util/parameter_sweep_1d.py @@ -143,26 +143,51 @@ def get_plotly_traces(self, keys): return trace_list - def plot(self, keys=None): - ''' - Plot the results based on the keys using matplotlib - - Attributes - ---------- - key : list - Identifiers for the results data - ''' - if len(keys) > 2: - results = self.results[keys[0]][keys[1]][keys[2]] + # def plot(self, keys=None): + # ''' + # Plot the results based on the keys using matplotlib + + # Attributes + # ---------- + # key : list + # Identifiers for the results data + # ''' + # if len(keys) > 2: + # results = self.results[keys[0]][keys[1]][keys[2]] + # else: + # results = self.results[keys[0]][keys[1]] + + # matplotlib.pyplot.subplots(figsize=(8, 8)) + # matplotlib.pyplot.title(f"Parameter Sweep - Variable: {keys[0]}") + # matplotlib.pyplot.errorbar(self.param['range'], results[:, 0], results[:, 1]) + # matplotlib.pyplot.xlabel(self.param['parameter'], + # fontsize=16, fontweight='bold') + # matplotlib.pyplot.ylabel("Population", fontsize=16, fontweight='bold') + + + classmethod + def plot(cls, results, species, param, mapper="final", reducer="avg"): + func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, + "var": numpy.var, "final": lambda res: res[-1]} + map_results = [[func_map[mapper](traj[species]) for traj in result] for result in results] + if len(map_res[0]) > 1: + data = [[func_map[reducer](map_result), numpy.std(map_result)] for map_result in map_results] + visible = True else: - results = self.results[keys[0]][keys[1]] + data = [[map_result[0], 0] for map_result in map_results] + visible = False + + error_y = dict(type="data", array=data[:, 1], visible=visible) + trace_list = [plotly.graph_objs.Scatter(x=self.param['range'], + y=data[:, 0], error_y=error_y)] + + title = f"Parameter Sweep - Variable: {species}" + layout = plotly.graph_objs.Layout(title=dict(text=title, x=0.5), + xaxis=dict(title=f"{param}"), + yaxis=dict(title="Population")) - matplotlib.pyplot.subplots(figsize=(8, 8)) - matplotlib.pyplot.title(f"Parameter Sweep - Variable: {keys[0]}") - matplotlib.pyplot.errorbar(self.param['range'], results[:, 0], results[:, 1]) - matplotlib.pyplot.xlabel(self.param['parameter'], - fontsize=16, fontweight='bold') - matplotlib.pyplot.ylabel("Population", fontsize=16, fontweight='bold') + fig = dict(data=trace_list, layout=layout) + return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) def run(self, job_id, verbose=False): diff --git a/stochss/handlers/util/parameter_sweep_2d.py b/stochss/handlers/util/parameter_sweep_2d.py index b9718cc710..40037d49c8 100644 --- a/stochss/handlers/util/parameter_sweep_2d.py +++ b/stochss/handlers/util/parameter_sweep_2d.py @@ -140,32 +140,39 @@ def get_plotly_traces(self, keys): return trace_list - def plot(self, keys=None): - ''' - Plot the results based on the keys using matplotlib - - Attributes - ---------- - key : list - Identifiers for the results data - ''' - if len(keys) <= 2: - results = self.results[keys[0]][keys[1]] - else: - results = self.results[keys[0]][keys[1]][keys[2]] - - _, axis = matplotlib.pyplot.subplots(figsize=(8, 8)) - matplotlib.pyplot.imshow(results) - axis.set_xticks(numpy.arange(results.shape[1])+0.5, minor=False) - axis.set_yticks(numpy.arange(results.shape[0])+0.5, minor=False) - matplotlib.pyplot.title(f"Parameter Sweep - Variable: {keys[0]}") - axis.set_xticklabels(self.params[0]['range'], minor=False, rotation=90) - axis.set_yticklabels(self.params[1]['range'], minor=False) - axis.set_xlabel(self.params[0]['parameter'], fontsize=16, fontweight='bold') - axis.set_ylabel(self.params[1]['parameter'], fontsize=16, fontweight='bold') - divider = mpl_toolkits.axes_grid1.make_axes_locatable(axis) - cax = divider.append_axes("right", size="5%", pad=0.2) - _ = matplotlib.pyplot.colorbar(ax=axis, cax=cax) + # def plot(self, keys=None): + # ''' + # Plot the results based on the keys using matplotlib + + # Attributes + # ---------- + # key : list + # Identifiers for the results data + # ''' + # if len(keys) <= 2: + # results = self.results[keys[0]][keys[1]] + # else: + # results = self.results[keys[0]][keys[1]][keys[2]] + + # _, axis = matplotlib.pyplot.subplots(figsize=(8, 8)) + # matplotlib.pyplot.imshow(results) + # axis.set_xticks(numpy.arange(results.shape[1])+0.5, minor=False) + # axis.set_yticks(numpy.arange(results.shape[0])+0.5, minor=False) + # matplotlib.pyplot.title(f"Parameter Sweep - Variable: {keys[0]}") + # axis.set_xticklabels(self.params[0]['range'], minor=False, rotation=90) + # axis.set_yticklabels(self.params[1]['range'], minor=False) + # axis.set_xlabel(self.params[0]['parameter'], fontsize=16, fontweight='bold') + # axis.set_ylabel(self.params[1]['parameter'], fontsize=16, fontweight='bold') + # divider = mpl_toolkits.axes_grid1.make_axes_locatable(axis) + # cax = divider.append_axes("right", size="5%", pad=0.2) + # _ = matplotlib.pyplot.colorbar(ax=axis, cax=cax) + + + @classmethod + def plot(cls, results, species, params, mapper="final", reducer="avg"): + func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, + "var": numpy.var, "final": lambda res: res[-1]} + map_results = def run(self, job_id, verbose=False): diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index b9beeaa818..b430227b7d 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -125,6 +125,57 @@ def __get_extract_dst_path(self, mdl_file): return os.path.join(wkgp_path, mdl_file) + def __get_filtered_1d_results(self, f_keys): + results = self.__get_pickled_results() + f_results = [] + for key, result in results.items(): + passed = True + for f_key in f_keys: + if f_key not in key: + passed = False + break + if passed: + f_results.append(result) + return f_results + + + def __get_filtered_2d_results(self, f_keys, param): + results = self.__get_pickled_results() + f_results = [] + for value in param['range']: + p_key = f"{param['name']}:{value}" + p_results = [] + for key, result in results.items(): + if p_key in key: + passed = True + for f_key in f_keys: + if f_key not in key: + passed = False + break + if passed: + p_results.append(result) + + + @classmethod + def __get_fixed_keys_and_dims(cls, settings, fixed): + p_len = len(settings['parameterSweepSettings']['parameters']) + dims = p_len - len(fixed.keys()) + if dims <= 0: + message = "Too many fixed parameters were provided. At least one variable parameter is required." + raise StochSSJobResultsError(message) + if dims > 2: + message = "Not enough fixed parameters were provided. Variable parameters cannot exceed 2." + raise StochSSJobResultsError(message) + f_keys = [f"{name}:{value}" for name, value in fixed.keys()] + return dims, f_keys + + + def __get_pickled_results(self): + path = os.path.join(self.get_results_path(full=True), "results.p") + with open(path, "rb") as results_file: + return pickle.load(results_file) + + def __is_csv_dir(self, file): if "results_csv" not in file: return False @@ -335,13 +386,11 @@ def get_plot_from_results(self, plt_key, plt_data, plt_type): Type of plot to generate. ''' self.log("debug", f"Key identifying the plot to generate: {plt_type}") - path = os.path.join(self.get_results_path(full=True), "results.p") try: self.log("info", "Loading the results...") - with open(path, "rb") as results_file: - result = pickle.load(results_file) - if plt_key is not None: - result = result[plt_key] + results = self.__get_pickled_results() + if plt_key is not None: + result = result[plt_key] self.log("info", "Generating the plot...") if plt_type == "mltplplt": fig = result.plotplotly(return_plotly_figure=True, multiple_graphs=True) @@ -366,6 +415,25 @@ def get_plot_from_results(self, plt_key, plt_data, plt_type): raise PlotNotAvailableError(message, traceback.format_exc()) from err + def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): + ''' Generate and return the parameter sweep plot form the time series results. ''' + settings = self.load_settings() + dims, f_keys = self.__get_fixed_keys_and_dims(settings, fixed) + params = list(filter(lambda param: param['name'] not in fixed.keys(), settings['parameterSweepSettings']['parameters'])) + var_params = list(map(lambda param: param['name'], params)) + if dims == 1: + kwargs['param'] = var_params[0] + kwargs['results'] = self.__get_filtered_1d_results(f_keys) + fig = ParameterSweep1D.plot(**kwargs) + else: + kwargs['params'] = var_params + kwargs['results'] = self.__get_filtered_2d_results(f_keys, var_params[0]) + fig = ParameterSweep2D.plot(**kwargs) + if add_config: + fig['config'] = {"responsive": True} + return fig + + def get_results_path(self, full=False): ''' Return the path to the results directory From fd629a789b1f3acc4049553bf83b2eb71aa07c14 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 23 Jul 2021 14:25:03 -0400 Subject: [PATCH 02/69] Completeed the plot functions for parameter sweep plots. --- stochss/handlers/util/parameter_sweep_1d.py | 31 ++++++++-- stochss/handlers/util/parameter_sweep_2d.py | 29 +++++++++- stochss/handlers/util/stochss_job.py | 63 ++++++++++++--------- 3 files changed, 86 insertions(+), 37 deletions(-) diff --git a/stochss/handlers/util/parameter_sweep_1d.py b/stochss/handlers/util/parameter_sweep_1d.py index 51f34c007e..011897c1c1 100644 --- a/stochss/handlers/util/parameter_sweep_1d.py +++ b/stochss/handlers/util/parameter_sweep_1d.py @@ -16,13 +16,14 @@ along with this program. If not, see . ''' +import json import copy import logging import traceback import numpy import plotly -import matplotlib +# import matplotlib log = logging.getLogger("stochss") @@ -165,25 +166,43 @@ def get_plotly_traces(self, keys): # matplotlib.pyplot.ylabel("Population", fontsize=16, fontweight='bold') - classmethod + @classmethod def plot(cls, results, species, param, mapper="final", reducer="avg"): + ''' + Plot the results with error bar from time series results. + + Attributes + ---------- + results : list + List of GillesPy2 results objects. + species : str + Species of interest name. + param : dict + StochSS sweep parameter dictionary. + mapper : str + Key indicating the feature extraction function to use. + reducer : str + Key indicating the ensemble aggragation function to use. + ''' func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, "var": numpy.var, "final": lambda res: res[-1]} map_results = [[func_map[mapper](traj[species]) for traj in result] for result in results] - if len(map_res[0]) > 1: - data = [[func_map[reducer](map_result), numpy.std(map_result)] for map_result in map_results] + if len(map_results[0]) > 1: + data = [[func_map[reducer](map_result), + numpy.std(map_result)] for map_result in map_results] visible = True else: data = [[map_result[0], 0] for map_result in map_results] visible = False + data = numpy.array(data) error_y = dict(type="data", array=data[:, 1], visible=visible) - trace_list = [plotly.graph_objs.Scatter(x=self.param['range'], + trace_list = [plotly.graph_objs.Scatter(x=param['range'], y=data[:, 0], error_y=error_y)] title = f"Parameter Sweep - Variable: {species}" layout = plotly.graph_objs.Layout(title=dict(text=title, x=0.5), - xaxis=dict(title=f"{param}"), + xaxis=dict(title=f"{param['name']}"), yaxis=dict(title="Population")) fig = dict(data=trace_list, layout=layout) diff --git a/stochss/handlers/util/parameter_sweep_2d.py b/stochss/handlers/util/parameter_sweep_2d.py index 40037d49c8..396e93505c 100644 --- a/stochss/handlers/util/parameter_sweep_2d.py +++ b/stochss/handlers/util/parameter_sweep_2d.py @@ -22,8 +22,8 @@ import numpy import plotly -import matplotlib -import mpl_toolkits +# import matplotlib +# import mpl_toolkits log = logging.getLogger("stochss") @@ -170,9 +170,32 @@ def get_plotly_traces(self, keys): @classmethod def plot(cls, results, species, params, mapper="final", reducer="avg"): + ''' + Plot the results with error bar from time series results. + + Attributes + ---------- + results : list + List of GillesPy2 results objects. + species : str + Species of interest name. + param : dict + StochSS sweep parameter dictionary. + mapper : str + Key indicating the feature extraction function to use. + reducer : str + Key indicating the ensemble aggragation function to use. + ''' func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, "var": numpy.var, "final": lambda res: res[-1]} - map_results = + data = [] + for p_results in results: + map_results = [[func_map[mapper](traj[species]) for traj in result] + for result in p_results] + if len(map_results[0]) > 1: + red_results = [func_map[reducer](map_result) for map_result in map_results] + data.append(red_results) + data = numpy.array(data) def run(self, job_id, verbose=False): diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index b430227b7d..74e4233bce 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -29,10 +29,13 @@ from .stochss_base import StochSSBase from .stochss_folder import StochSSFolder from .stochss_model import StochSSModel +from .parameter_sweep_1d import ParameterSweep1D +# from .parameter_sweep_2d import ParameterSweep2D from .stochss_spatial_model import StochSSSpatialModel from .stochss_errors import StochSSJobError, StochSSJobNotCompleteError, \ StochSSFileNotFoundError, StochSSFileExistsError, \ - FileNotJSONFormatError, PlotNotAvailableError + FileNotJSONFormatError, PlotNotAvailableError, \ + StochSSJobResultsError class StochSSJob(StochSSBase): ''' @@ -70,7 +73,7 @@ def __create_new_job(self, mdl_path, settings=None): path = self.get_path(full=True) try: os.mkdir(path) - os.mkdir(self.get_results_path(full=True)) + os.mkdir(self.__get_results_path(full=True)) info = {"source_model":mdl_path, "wkfl_model":None, "type":self.type, "start_time":None} self.update_info(new_info=info, new=True) open(os.path.join(path, "logs.txt"), "w").close() @@ -154,6 +157,8 @@ def __get_filtered_2d_results(self, f_keys, param): break if passed: p_results.append(result) + f_results.append(p_results) + return f_results @classmethod @@ -161,25 +166,39 @@ def __get_fixed_keys_and_dims(cls, settings, fixed): p_len = len(settings['parameterSweepSettings']['parameters']) dims = p_len - len(fixed.keys()) if dims <= 0: - message = "Too many fixed parameters were provided. At least one variable parameter is required." + message = "Too many fixed parameters were provided." + message += "At least one variable parameter is required." raise StochSSJobResultsError(message) if dims > 2: - message = "Not enough fixed parameters were provided. Variable parameters cannot exceed 2." + message = "Not enough fixed parameters were provided." + message += "Variable parameters cannot exceed 2." raise StochSSJobResultsError(message) - f_keys = [f"{name}:{value}" for name, value in fixed.keys()] + f_keys = [f"{name}:{value}" for name, value in fixed.items()] return dims, f_keys def __get_pickled_results(self): - path = os.path.join(self.get_results_path(full=True), "results.p") + path = os.path.join(self.__get_results_path(full=True), "results.p") with open(path, "rb") as results_file: return pickle.load(results_file) + def __get_results_path(self, full=False): + ''' + Return the path to the results directory + + Attributes + ---------- + full : bool + Indicates whether or not to get the full path or local path + ''' + return os.path.join(self.get_path(full=full), "results") + + def __is_csv_dir(self, file): if "results_csv" not in file: return False - path = os.path.join(self.get_results_path(), file) + path = os.path.join(self.__get_results_path(), file) if not os.path.isdir(path): return False return True @@ -288,7 +307,7 @@ def get_csv_path(self, full=False): Attributes ---------- ''' - res_path = self.get_results_path(full=full) + res_path = self.__get_results_path(full=full) if not os.path.exists(res_path): message = f"Could not find the results directory: {res_path}" raise StochSSFileNotFoundError(message, traceback.format_exc()) @@ -388,7 +407,7 @@ def get_plot_from_results(self, plt_key, plt_data, plt_type): self.log("debug", f"Key identifying the plot to generate: {plt_type}") try: self.log("info", "Loading the results...") - results = self.__get_pickled_results() + result = self.__get_pickled_results() if plt_key is not None: result = result[plt_key] self.log("info", "Generating the plot...") @@ -419,33 +438,21 @@ def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): ''' Generate and return the parameter sweep plot form the time series results. ''' settings = self.load_settings() dims, f_keys = self.__get_fixed_keys_and_dims(settings, fixed) - params = list(filter(lambda param: param['name'] not in fixed.keys(), settings['parameterSweepSettings']['parameters'])) - var_params = list(map(lambda param: param['name'], params)) + params = list(filter(lambda param: param['name'] not in fixed.keys(), + settings['parameterSweepSettings']['parameters'])) if dims == 1: - kwargs['param'] = var_params[0] + kwargs['param'] = params[0] kwargs['results'] = self.__get_filtered_1d_results(f_keys) fig = ParameterSweep1D.plot(**kwargs) else: - kwargs['params'] = var_params - kwargs['results'] = self.__get_filtered_2d_results(f_keys, var_params[0]) - fig = ParameterSweep2D.plot(**kwargs) + kwargs['params'] = params + kwargs['results'] = self.__get_filtered_2d_results(f_keys, params[0]) + # fig = ParameterSweep2D.plot(**kwargs) if add_config: fig['config'] = {"responsive": True} return fig - def get_results_path(self, full=False): - ''' - Return the path to the results directory - - Attributes - ---------- - full : bool - Indicates whether or not to get the full path or local path - ''' - return os.path.join(self.get_path(full=full), "results") - - def get_results_plot(self, plt_key, plt_data, fig=None): ''' Get the plotly figure for the results of a job @@ -459,7 +466,7 @@ def get_results_plot(self, plt_key, plt_data, fig=None): ''' self.log("debug", f"Key identifying the requested plot: {plt_key}") self.log("debug", f"Title and axis data for the plot: {plt_data}") - path = os.path.join(self.get_results_path(full=True), "plots.json") + path = os.path.join(self.__get_results_path(full=True), "plots.json") self.log("debug", f"Path to the job result plot file: {path}") try: if fig is None: From f97a89b65984c0bfaf837be1b665fc36ed6ae002 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 23 Jul 2021 16:11:38 -0400 Subject: [PATCH 03/69] Added missing code for the new plot psweep plots. --- stochss/handlers/util/parameter_sweep_2d.py | 16 +++++++++++++--- stochss/handlers/util/stochss_job.py | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/stochss/handlers/util/parameter_sweep_2d.py b/stochss/handlers/util/parameter_sweep_2d.py index 396e93505c..903979be24 100644 --- a/stochss/handlers/util/parameter_sweep_2d.py +++ b/stochss/handlers/util/parameter_sweep_2d.py @@ -179,8 +179,8 @@ def plot(cls, results, species, params, mapper="final", reducer="avg"): List of GillesPy2 results objects. species : str Species of interest name. - param : dict - StochSS sweep parameter dictionary. + params : list + List of StochSS sweep parameter dictionaries. mapper : str Key indicating the feature extraction function to use. reducer : str @@ -191,11 +191,21 @@ def plot(cls, results, species, params, mapper="final", reducer="avg"): data = [] for p_results in results: map_results = [[func_map[mapper](traj[species]) for traj in result] - for result in p_results] + for result in p_results] if len(map_results[0]) > 1: red_results = [func_map[reducer](map_result) for map_result in map_results] data.append(red_results) data = numpy.array(data) + + trace_list = [plotly.graph_objs.Heatmap(z=data, x=params[0]['range'], + y=params[1]['range'])] + + title = f"Parameter Sweep - Variable: {species}" + layout = plotly.graph_objs.Layout(title=dict(text=title, x=0.5), + xaxis=dict(title=f"{params[0]['name']}"), + yaxis=dict(title=f"{params[1]['name']}")) + fig = dict(data=trace_list, layout=layout) + return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) def run(self, job_id, verbose=False): diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 74e4233bce..140fadefc6 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -30,7 +30,7 @@ from .stochss_folder import StochSSFolder from .stochss_model import StochSSModel from .parameter_sweep_1d import ParameterSweep1D -# from .parameter_sweep_2d import ParameterSweep2D +from .parameter_sweep_2d import ParameterSweep2D from .stochss_spatial_model import StochSSSpatialModel from .stochss_errors import StochSSJobError, StochSSJobNotCompleteError, \ StochSSFileNotFoundError, StochSSFileExistsError, \ @@ -447,7 +447,7 @@ def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): else: kwargs['params'] = params kwargs['results'] = self.__get_filtered_2d_results(f_keys, params[0]) - # fig = ParameterSweep2D.plot(**kwargs) + fig = ParameterSweep2D.plot(**kwargs) if add_config: fig['config'] = {"responsive": True} return fig From bed3d07544e4e95c4967ddb76289df0b5de07d51 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 26 Jul 2021 08:28:00 -0400 Subject: [PATCH 04/69] Added json import. Pylint fixes. --- stochss/handlers/util/parameter_sweep_2d.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stochss/handlers/util/parameter_sweep_2d.py b/stochss/handlers/util/parameter_sweep_2d.py index 903979be24..f4e1e85dc8 100644 --- a/stochss/handlers/util/parameter_sweep_2d.py +++ b/stochss/handlers/util/parameter_sweep_2d.py @@ -16,6 +16,7 @@ along with this program. If not, see . ''' +import json import copy import logging import traceback @@ -196,10 +197,10 @@ def plot(cls, results, species, params, mapper="final", reducer="avg"): red_results = [func_map[reducer](map_result) for map_result in map_results] data.append(red_results) data = numpy.array(data) - + trace_list = [plotly.graph_objs.Heatmap(z=data, x=params[0]['range'], y=params[1]['range'])] - + title = f"Parameter Sweep - Variable: {species}" layout = plotly.graph_objs.Layout(title=dict(text=title, x=0.5), xaxis=dict(title=f"{params[0]['name']}"), From 6f7aa6efd83f7b959a90b938a7d2a2245b7dd6f1 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 26 Jul 2021 17:10:36 -0400 Subject: [PATCH 05/69] Added else function to handle single trajectory heatmap data. --- stochss/handlers/util/parameter_sweep_2d.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stochss/handlers/util/parameter_sweep_2d.py b/stochss/handlers/util/parameter_sweep_2d.py index f4e1e85dc8..162e488230 100644 --- a/stochss/handlers/util/parameter_sweep_2d.py +++ b/stochss/handlers/util/parameter_sweep_2d.py @@ -195,6 +195,8 @@ def plot(cls, results, species, params, mapper="final", reducer="avg"): for result in p_results] if len(map_results[0]) > 1: red_results = [func_map[reducer](map_result) for map_result in map_results] + else: + red_results = [map_result[0] for map_result in map_results] data.append(red_results) data = numpy.array(data) From c7304d02db3c309170d398ea535d2959b95205f5 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 27 Jul 2021 11:45:32 -0400 Subject: [PATCH 06/69] Refactored the way plot data is generated so its compatable with the new plot functions. --- client/views/workflow-results.js | 100 ++++++++++++++----------------- 1 file changed, 45 insertions(+), 55 deletions(-) diff --git a/client/views/workflow-results.js b/client/views/workflow-results.js index 95dc9fba28..bb1c656820 100644 --- a/client/views/workflow-results.js +++ b/client/views/workflow-results.js @@ -141,22 +141,19 @@ module.exports = View.extend({ let self = this; this.cleanupPlotContainer(type); let data = this.getPlotData(type); - if(type === "psweep" && Boolean(this.plots[data.plt_key])) { - this.plotFigure(this.plots[data.plt_key], type); - }else if(type === "ts-psweep" && Boolean(this.plots[data.plt_type + data.plt_key])) { - this.plotFigure(this.plots[data.plt_type + data.plt_key], type); + let storageKey = JSON.stringify(data); + data['plt_data'] = this.getPlotLayoutData(); + if(Boolean(this.plots[storageKey])) { + let renderTypes = ['psweep', 'ts-psweep', 'ts-psweep-mp', 'mltplplt']; + if(renderTypes.includes(type)) { + this.plotFigure(this.plots[storageKey], type); + } }else{ let queryStr = "?path=" + this.model.directory + "&data=" + JSON.stringify(data); let endpoint = path.join(app.getApiPath(), "workflow/plot-results") + queryStr; app.getXHR(endpoint, { success: function (err, response, body) { - if(type === "psweep") { - self.plots[data.plt_key] = body; - }else if(type === "ts-psweep"){ - self.plots[data.plt_type + data.plt_key] = body; - }else{ - self.plots[type] = body; - } + self.plots[storageKey] = body; self.plotFigure(body, type); }, error: function (err, response, body) { @@ -170,28 +167,25 @@ module.exports = View.extend({ getPlotData: function (type) { let data = {}; if(type === 'psweep'){ - let key = this.getPsweepKey(); - data['plt_key'] = key; + data['sim_type'] = "ParameterSweep"; + data['data_keys'] = {}; + data['plt_key'] = this.getPlotKey(type); }else if(type === "ts-psweep" || type === "ts-psweep-mp") { - if(type === "ts-psweep-mp"){ - data['plt_type'] = "mltplplt"; - }else{ - data['plt_type'] = this.tsPlotData['type']; - } - let key = this.getTSPsweepKey() - data['plt_key'] = key; - }else if(type === "mltplplt"){ - data['plt_type'] = type; - data['plt_key'] = null; - }else{ + data['sim_type'] = "GillesPy2_PS"; + data['data_keys'] = this.getDataKeys(true); + data['plt_key'] = type === "ts-psweep-mp" ? "mltplplt" : this.tsPlotData['type']; + }else { + data['sim_type'] = "GillesPy2"; + data['data_keys'] = {}; data['plt_key'] = type; } + return data + }, + getPlotLayoutData: function () { if(Object.keys(this.plotArgs).length){ - data['plt_data'] = this.plotArgs; - }else{ - data['plt_data'] = null; + return this.plotArgs; } - return data + return null }, getPlotForEnsembleAggragator: function (e) { this.model.settings.resultsSettings.reducer = e.target.value; @@ -208,17 +202,17 @@ module.exports = View.extend({ this.model.settings.parameterSweepSettings.speciesOfInterest = species; this.getPlot('psweep') }, - getPsweepKey: function () { - let speciesOfInterest = this.model.settings.parameterSweepSettings.speciesOfInterest.name; - let featureExtractor = this.model.settings.resultsSettings.mapper; - let key = speciesOfInterest + "-" + featureExtractor - let realizations = this.model.settings.simulationSettings.realizations; - let algorithm = this.model.settings.simulationSettings.algorithm; - if(algorithm !== "ODE" && realizations > 1){ - let ensembleAggragator = this.model.settings.resultsSettings.reducer; - key += ("-" + ensembleAggragator); + getPlotKey: function (type) { + if(type === "psweep") { + let realizations = this.model.settings.simulationSettings.realizations; + let algorithm = this.model.settings.simulationSettings.algorithm; + let plt_key = { + species: this.model.settings.parameterSweepSettings.speciesOfInterest.name, + mapper: this.model.settings.resultsSettings.mapper, + reducer: algorithm !== "ODE" && realizations > 1 ? this.model.settings.resultsSettings.reducer : null + } + return plt_key; } - return key; }, getTSPlotForType: function (e) { this.tsPlotData['type'] = e.target.value; @@ -226,20 +220,15 @@ module.exports = View.extend({ $(this.queryByHook("multiple-plots")).css("display", display); this.getPlot("ts-psweep"); }, - getTSPsweepKey: function () { - let self = this; - let strs = Object.keys(this.tsPlotData.parameters).map(function (key) { - return key + ":" + self.tsPlotData.parameters[key] - }); - let key = strs.join(","); - return key; + getDataKeys: function (full) { + if(full) { + return this.tsPlotData.parameters; + } }, handleCollapsePlotContainerClick: function (e) { app.changeCollapseButtonText(this, e); let type = e.target.dataset.type; - if(!this.plots[type]){ - this.getPlot(type); - } + this.getPlot(type); }, handleConvertToNotebookClick: function (e) { let self = this; @@ -260,7 +249,8 @@ module.exports = View.extend({ }, handleDownloadJSONClick: function (e) { let type = e.target.dataset.type; - let jsonData = this.plots[type]; + let storageKey = JSON.stringify(this.getPlotData(type)) + let jsonData = this.plots[storageKey]; let dataStr = JSON.stringify(jsonData); let dataURI = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); let exportFileDefaultName = type + '-plot.json'; @@ -399,24 +389,24 @@ module.exports = View.extend({ }, setTitle: function (e) { this.plotArgs['title'] = e.target.value - for (var type in this.plots) { - let fig = this.plots[type] + for (var storageKey in this.plots) { + let fig = this.plots[storageKey] fig.layout.title.text = e.target.value this.plotFigure(fig, type) } }, setXAxis: function (e) { this.plotArgs['xaxis'] = e.target.value - for (var type in this.plots) { - let fig = this.plots[type] + for (var storageKey in this.plots) { + let fig = this.plots[storageKey] fig.layout.xaxis.title.text = e.target.value this.plotFigure(fig, type) } }, setYAxis: function (e) { this.plotArgs['yaxis'] = e.target.value - for (var type in this.plots) { - let fig = this.plots[type] + for (var storageKey in this.plots) { + let fig = this.plots[storageKey] fig.layout.yaxis.title.text = e.target.value this.plotFigure(fig, type) } From 0e3178165d7fbd04a32839bee696e65d154b447d Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 27 Jul 2021 13:50:49 -0400 Subject: [PATCH 07/69] Refactored the get plot from results function to work with the new data pass from the front end. --- stochss/handlers/util/stochss_job.py | 42 +++++++++++++++++----------- stochss/handlers/workflows.py | 16 +++++------ 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 140fadefc6..a8cb293ba9 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -391,41 +391,40 @@ def get_notebook_data(self): return {"kwargs":kwargs, "type":wkfl_type} - def get_plot_from_results(self, plt_key, plt_data, plt_type): + def get_plot_from_results(self, data_keys, plt_key, add_config=False): ''' Get the plotly figure for the results of a job Attributes ---------- + data_keys : dict + Dictionary of param names and values used to identify the correct data. plt_key : str - Indentifier for the requested plot figure - plt_data : dict - Title and axes data for the plot - plt_type : str Type of plot to generate. ''' - self.log("debug", f"Key identifying the plot to generate: {plt_type}") + self.log("debug", f"Key identifying the plot to generate: {plt_key}") try: self.log("info", "Loading the results...") result = self.__get_pickled_results() - if plt_key is not None: - result = result[plt_key] + if data_keys: + key = [f"{name}:{value}" for name, value in data_keys.items()] + key = ','.join(key) + result = result[key] self.log("info", "Generating the plot...") - if plt_type == "mltplplt": + if plt_key == "mltplplt": fig = result.plotplotly(return_plotly_figure=True, multiple_graphs=True) - elif plt_type == "stddevran": + elif plt_key == "stddevran": fig = result.plotplotly_std_dev_range(return_plotly_figure=True) else: - if plt_type == "stddev": + if plt_key == "stddev": result = result.stddev_ensemble() - elif plt_type == "avg": + elif plt_key == "avg": result = result.average_ensemble() fig = result.plotplotly(return_plotly_figure=True) - if plt_type != "mltplplt": + if add_config and plt_key != "mltplplt": fig["config"] = {"responsive":True} self.log("info", "Loading the plot...") - fig = json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) - return self.get_results_plot(plt_key=None, plt_data=plt_data, fig=fig) + return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) except FileNotFoundError as err: message = f"Could not find the results pickle file: {str(err)}" raise StochSSFileNotFoundError(message, traceback.format_exc()) from err @@ -435,7 +434,16 @@ def get_plot_from_results(self, plt_key, plt_data, plt_type): def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): - ''' Generate and return the parameter sweep plot form the time series results. ''' + ''' + Generate and return the parameter sweep plot form the time series results. + + Attributes + ---------- + fixed : dict + Dictionary for parameters that remain at a fixed value. + kwarps : dict + Dictionary of keys used for post proccessing the results. + ''' settings = self.load_settings() dims, f_keys = self.__get_fixed_keys_and_dims(settings, fixed) params = list(filter(lambda param: param['name'] not in fixed.keys(), @@ -450,7 +458,7 @@ def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): fig = ParameterSweep2D.plot(**kwargs) if add_config: fig['config'] = {"responsive": True} - return fig + return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) def get_results_plot(self, plt_key, plt_data, fig=None): diff --git a/stochss/handlers/workflows.py b/stochss/handlers/workflows.py index a7ec157b8b..f02412777e 100644 --- a/stochss/handlers/workflows.py +++ b/stochss/handlers/workflows.py @@ -233,17 +233,17 @@ async def get(self): path = self.get_query_argument(name="path") log.debug("The path to the workflow: %s", path) body = json.loads(self.get_query_argument(name='data')) - if body['plt_data'] == "None": - body['plt_data'] = None log.debug("Plot args passed to the plot: %s", body) try: - wkfl = StochSSJob(path=path) - if "plt_type" in body.keys(): - fig = wkfl.get_plot_from_results(**body) - wkfl.print_logs(log) + job = StochSSJob(path=path) + if body['sim_type'] in ("GillesPy2", "GillesPy2_PS"): + fig = job.get_plot_from_results(data_keys=body['data_keys'], + plt_key=data['plt_key'], add_config=True) + job.print_logs(log) else: - log.info("Loading the plot...") - fig = wkfl.get_results_plot(**body) + fig = job.get_get_psweep_plot_from_results(fixed=body['data_keys'], + kwargs=body['plt_key'], add_config=True) + job.print_logs(log) log.debug("Plot figure: %s", fig) self.write(fig) except StochSSAPIError as err: From c21481e9432b1ecd6bb3093d1eacf218e1ccc60c Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 27 Jul 2021 16:37:24 -0400 Subject: [PATCH 08/69] Refactored the get psweep plot from result function to work with the new plot. Refactored the layout update to not load the figure. --- stochss/handlers/util/stochss_job.py | 58 ++++++++++++++-------------- stochss/handlers/workflows.py | 1 + 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index a8cb293ba9..754f01c1d9 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -444,42 +444,48 @@ def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): kwarps : dict Dictionary of keys used for post proccessing the results. ''' + self.log("debug", f"Key identifying the plot to generate: {kwargs}") settings = self.load_settings() - dims, f_keys = self.__get_fixed_keys_and_dims(settings, fixed) - params = list(filter(lambda param: param['name'] not in fixed.keys(), - settings['parameterSweepSettings']['parameters'])) - if dims == 1: - kwargs['param'] = params[0] - kwargs['results'] = self.__get_filtered_1d_results(f_keys) - fig = ParameterSweep1D.plot(**kwargs) - else: - kwargs['params'] = params - kwargs['results'] = self.__get_filtered_2d_results(f_keys, params[0]) - fig = ParameterSweep2D.plot(**kwargs) - if add_config: - fig['config'] = {"responsive": True} - return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) + try: + self.log("info", "Loading the results...") + dims, f_keys = self.__get_fixed_keys_and_dims(settings, fixed) + params = list(filter(lambda param: param['name'] not in fixed.keys(), + settings['parameterSweepSettings']['parameters'])) + if dims == 1: + kwargs['param'] = params[0] + kwargs['results'] = self.__get_filtered_1d_results(f_keys) + self.log("info", "Generating the plot...") + fig = ParameterSweep1D.plot(**kwargs) + else: + kwargs['params'] = params + kwargs['results'] = self.__get_filtered_2d_results(f_keys, params[0]) + self.log("info", "Generating the plot...") + fig = ParameterSweep2D.plot(**kwargs) + if add_config: + fig['config'] = {"responsive": True} + self.log("info", "Loading the plot...") + return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) + except FileNotFoundError as err: + message = f"Could not find the results pickle file: {str(err)}" + raise StochSSFileNotFoundError(message, traceback.format_exc()) from err + except KeyError as err: + message = f"The requested plot is not available: {str(err)}" + raise PlotNotAvailableError(message, traceback.format_exc()) from err - def get_results_plot(self, plt_key, plt_data, fig=None): + def update_fig_layout(self, fig=None, plt_data=None): ''' Get the plotly figure for the results of a job Attributes ---------- - plt_key : str - Indentifier for the requested plot figure + fig : dict + Plotly figure to be updated plt_data : dict Title and axes data for the plot ''' - self.log("debug", f"Key identifying the requested plot: {plt_key}") self.log("debug", f"Title and axis data for the plot: {plt_data}") - path = os.path.join(self.__get_results_path(full=True), "plots.json") - self.log("debug", f"Path to the job result plot file: {path}") try: - if fig is None: - with open(path, "r") as plot_file: - fig = json.load(plot_file)[plt_key] if plt_data is None: return fig for key in plt_data.keys(): @@ -488,12 +494,6 @@ def get_results_plot(self, plt_key, plt_data, fig=None): else: fig['layout'][key]['title']['text'] = plt_data[key] return fig - except FileNotFoundError as err: - message = f"Could not find the plots file: {str(err)}" - raise StochSSFileNotFoundError(message, traceback.format_exc()) from err - except json.decoder.JSONDecodeError as err: - message = f"The plots file is not JSON decodable: {str(err)}" - raise FileNotJSONFormatError(message, traceback.format_exc()) from err except KeyError as err: message = f"The requested plot is not available: {str(err)}" raise PlotNotAvailableError(message, traceback.format_exc()) from err diff --git a/stochss/handlers/workflows.py b/stochss/handlers/workflows.py index 7745ffef7a..ac01584314 100644 --- a/stochss/handlers/workflows.py +++ b/stochss/handlers/workflows.py @@ -245,6 +245,7 @@ async def get(self): fig = job.get_get_psweep_plot_from_results(fixed=body['data_keys'], kwargs=body['plt_key'], add_config=True) job.print_logs(log) + fig = job.update_fig_layout(fig=fig, plt_data=body['plt_data']) log.debug("Plot figure: %s", fig) self.write(fig) except StochSSAPIError as err: From f1f669a56d900dd5fc0ce026bf5020a624a24fa7 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Jul 2021 11:07:04 -0400 Subject: [PATCH 09/69] Fixed issue with setting plot title and axes labels. --- client/views/workflow-results.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/views/workflow-results.js b/client/views/workflow-results.js index bb1c656820..f993e928fc 100644 --- a/client/views/workflow-results.js +++ b/client/views/workflow-results.js @@ -225,6 +225,12 @@ module.exports = View.extend({ return this.tsPlotData.parameters; } }, + getType: function (storageKey) { + let plotData = JSON.parse(storageKey) + if(plotData.sim_type === "GillesPy2") { return plotData.plt_key } + if(plotData.sim_type === "GillesPy2_PS") { return "ts-psweep"} + return psweep + }, handleCollapsePlotContainerClick: function (e) { app.changeCollapseButtonText(this, e); let type = e.target.dataset.type; @@ -390,6 +396,7 @@ module.exports = View.extend({ setTitle: function (e) { this.plotArgs['title'] = e.target.value for (var storageKey in this.plots) { + let type = this.getType(storageKey); let fig = this.plots[storageKey] fig.layout.title.text = e.target.value this.plotFigure(fig, type) @@ -398,6 +405,7 @@ module.exports = View.extend({ setXAxis: function (e) { this.plotArgs['xaxis'] = e.target.value for (var storageKey in this.plots) { + let type = this.getType(storageKey); let fig = this.plots[storageKey] fig.layout.xaxis.title.text = e.target.value this.plotFigure(fig, type) @@ -406,6 +414,7 @@ module.exports = View.extend({ setYAxis: function (e) { this.plotArgs['yaxis'] = e.target.value for (var storageKey in this.plots) { + let type = this.getType(storageKey); let fig = this.plots[storageKey] fig.layout.yaxis.title.text = e.target.value this.plotFigure(fig, type) From 26e72e5746ee877561b887377d415556551d9340 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Jul 2021 11:07:58 -0400 Subject: [PATCH 10/69] Fixed issues with the job plot api handler. --- stochss/handlers/workflows.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stochss/handlers/workflows.py b/stochss/handlers/workflows.py index ac01584314..2043c20868 100644 --- a/stochss/handlers/workflows.py +++ b/stochss/handlers/workflows.py @@ -239,11 +239,11 @@ async def get(self): job = StochSSJob(path=path) if body['sim_type'] in ("GillesPy2", "GillesPy2_PS"): fig = job.get_plot_from_results(data_keys=body['data_keys'], - plt_key=data['plt_key'], add_config=True) + plt_key=body['plt_key'], add_config=True) job.print_logs(log) else: - fig = job.get_get_psweep_plot_from_results(fixed=body['data_keys'], - kwargs=body['plt_key'], add_config=True) + fig = job.get_psweep_plot_from_results(fixed=body['data_keys'], + kwargs=body['plt_key'], add_config=True) job.print_logs(log) fig = job.update_fig_layout(fig=fig, plt_data=body['plt_data']) log.debug("Plot figure: %s", fig) From 393f2c7af4664dbf0a62d98e41f1639b3af0ef31 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Jul 2021 11:17:22 -0400 Subject: [PATCH 11/69] Removed plot storage function from the ensemble simulation job. --- stochss/handlers/util/ensemble_simulation.py | 27 ++------------------ 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/stochss/handlers/util/ensemble_simulation.py b/stochss/handlers/util/ensemble_simulation.py index d2697e06db..854068a727 100644 --- a/stochss/handlers/util/ensemble_simulation.py +++ b/stochss/handlers/util/ensemble_simulation.py @@ -91,28 +91,6 @@ def __store_pickled_results(cls, results): return False - @classmethod - def __store_result_plots(cls, results): - try: - plots = {"trajectories":results.plotplotly(return_plotly_figure=True)} - if len(results) > 1: - plots['stddevran'] = results.plotplotly_std_dev_range(return_plotly_figure=True) - std_res = results.stddev_ensemble() - plots['stddev'] = std_res.plotplotly(return_plotly_figure=True) - avg_res = results.average_ensemble() - plots['avg'] = avg_res.plotplotly(return_plotly_figure=True) - for _, plot in plots.items(): - plot["config"] = {"responsive":True} - with open('results/plots.json', 'w') as plots_file: - json.dump(plots, plots_file, cls=plotly.utils.PlotlyJSONEncoder, - indent=4, sort_keys=True) - except Exception as err: - message = f"Error storing result plots: {err}\n{traceback.format_exc()}" - log.error(message) - return message - return False - - def __update_timespan(self): if "timespanSettings" in self.settings.keys(): keys = self.settings['timespanSettings'].keys() @@ -163,9 +141,8 @@ def run(self, preview=False, verbose=True): pkl_err = self.__store_pickled_results(results=results) if verbose: log.info("Storing the polts of the results") - plt_err = self.__store_result_plots(results=results) - if pkl_err and plt_err: + if pkl_err: message = "An unexpected error occured with the result object" - trace = f"{pkl_err}\n{plt_err}" + trace = str(pkl_err) raise StochSSJobResultsError(message, trace) return None From 0f0f7408fa3c3e0e18372e262ed58e3e2162ef48 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Jul 2021 11:26:17 -0400 Subject: [PATCH 12/69] Removed the plot storage function from the parameter sweep job. Removed unused imports. --- stochss/handlers/util/ensemble_simulation.py | 2 - stochss/handlers/util/parameter_sweep.py | 42 +------------------- 2 files changed, 2 insertions(+), 42 deletions(-) diff --git a/stochss/handlers/util/ensemble_simulation.py b/stochss/handlers/util/ensemble_simulation.py index 854068a727..441e6b0bd7 100644 --- a/stochss/handlers/util/ensemble_simulation.py +++ b/stochss/handlers/util/ensemble_simulation.py @@ -18,13 +18,11 @@ import os -import json import pickle import logging import traceback import numpy -import plotly from gillespy2 import TauHybridSolver from .stochss_job import StochSSJob diff --git a/stochss/handlers/util/parameter_sweep.py b/stochss/handlers/util/parameter_sweep.py index 43251bfabd..6b67f21d7c 100644 --- a/stochss/handlers/util/parameter_sweep.py +++ b/stochss/handlers/util/parameter_sweep.py @@ -25,7 +25,6 @@ import traceback import numpy -import plotly from gillespy2 import TauHybridSolver @@ -136,42 +135,6 @@ def __store_pickled_results(cls, job): return False - @classmethod - def __store_result_plots(cls, job): - try: - mappers = ["min", "max", "avg", "var", "final"] - if "solver" in job.settings.keys(): - solver_name = job.settings['solver'].name - else: - solver_name = job.model.get_best_solver().name - if "ODE" not in solver_name and job.settings['number_of_trajectories'] > 1: - keys = list(itertools.product(job.list_of_species, mappers, - ["min", "max", "avg", "var"])) - else: - keys = list(itertools.product(job.list_of_species, mappers)) - plot_figs = {} - for key in keys: - key = list(key) - trace_list = job.get_plotly_traces(keys=key) - plt_data = {'title':f"Parameter Sweep - Variable: {key[0]}"} - job.get_plotly_layout_data(plt_data=plt_data) - layout = plotly.graph_objs.Layout(title=dict(text=plt_data['title'], x=0.5), - xaxis=dict(title=plt_data['xaxis_label']), - yaxis=dict(title=plt_data['yaxis_label'])) - - fig = dict(data=trace_list, layout=layout, config={"responsive": True}) - plot_figs['-'.join(key)] = fig - - with open('results/plots.json', 'w') as plots_file: - json.dump(plot_figs, plots_file, cls=plotly.utils.PlotlyJSONEncoder, - indent=4, sort_keys=True) - except Exception as err: - message = f"Error storing result plots: {err}\n{traceback.format_exc()}" - log.error(message) - return message - return False - - @classmethod def __store_results(cls, job): try: @@ -246,8 +209,7 @@ def run(self, verbose=True): log.info("Storing the polts of the results") res_err = self.__store_results(job=job) self.__store_csv_results(job=job) - plt_err = self.__store_result_plots(job=job) - if pkl_err and res_err and plt_err: - self.__report_result_error(trace=f"{res_err}\n{pkl_err}\n{plt_err}") + if pkl_err and res_err: + self.__report_result_error(trace=f"{res_err}\n{pkl_err}") elif pkl_err: self.__report_result_error(trace=pkl_err) From d82a7334fb95ad46e7cc128da234f3a5125a6adb Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Jul 2021 12:12:44 -0400 Subject: [PATCH 13/69] Added a fixed session var to the sweep parameter model. Added checkbox to sweep parameter view to allow users set fixed parameters for ploting. Added function to set the initial fixed value to false and only show the fixed checkbox for psweep plots. --- client/models/sweep-parameter.js | 3 +++ client/templates/includes/sweepParameterRange.pug | 6 ++++++ client/views/sweep-parameter-range.js | 9 +++++++++ 3 files changed, 18 insertions(+) diff --git a/client/models/sweep-parameter.js b/client/models/sweep-parameter.js index 288c68c832..0850841e88 100644 --- a/client/models/sweep-parameter.js +++ b/client/models/sweep-parameter.js @@ -29,6 +29,9 @@ module.exports = State.extend({ steps: 'any', hasChangedRange: 'boolean' }, + session: { + fixed: 'boolean' + }, derived: { elementID: { deps: ["collection"], diff --git a/client/templates/includes/sweepParameterRange.pug b/client/templates/includes/sweepParameterRange.pug index eff6534ec5..e2c7663b15 100644 --- a/client/templates/includes/sweepParameterRange.pug +++ b/client/templates/includes/sweepParameterRange.pug @@ -5,6 +5,12 @@ div div + div.inline.mr-2(data-hook=this.model.elementID + "-fixed-container") + + span(for=this.model.elementID + "-fixed") Fixed: + + input(type="checkbox" id=this.model.elementID + "-fixed" data-hook=this.model.elementID + "-fixed") + div.inline.mr-2=this.model.name + ":" div.inline(style="max-width: 100%") diff --git a/client/views/sweep-parameter-range.js b/client/views/sweep-parameter-range.js index 00faa0a625..11bce31d02 100644 --- a/client/views/sweep-parameter-range.js +++ b/client/views/sweep-parameter-range.js @@ -27,12 +27,15 @@ module.exports = View.extend({ events: function () { let events = {}; events['change [data-hook=' + this.model.elementID + '-slider'] = 'setParameterRangeValue'; + events['change [data-hook=' + this.model.elementID + '-fixed'] = 'setParameterRangeFixed'; events['input [data-hook=' + this.model.elementID + '-slider'] = 'viewParameterRangeValue'; return events; }, initialize: function (attrs, options) { View.prototype.initialize.apply(this, arguments); let self = this; + this.model.fixed = false; + this.showFixed = Boolean(attrs.showFixed) ? attrs.showFixed : false; this.parameter = this.parent.model.model.parameters.filter(function (param) { return param.compID === self.model.paramID })[0]; @@ -41,6 +44,12 @@ module.exports = View.extend({ }, render: function (attrs, options) { View.prototype.render.apply(this, arguments); + if(!this.showFixed){ + $(this.queryByHook(this.model.elementID + "-fixed-container")).css("display", "none"); + } + }, + setParameterRangeFixed: function (e) { + this.model.fixed = e.target.checked; }, setParameterRangeValue: function (e) { var value = this.model.range[e.target.value]; From 0f3ea330ff05ac6c7f5cf555c631c2e4ba272f14 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Jul 2021 13:20:35 -0400 Subject: [PATCH 14/69] Refactored the get data and get data keys functions to get the dictionary of fixed parameters. --- client/views/sweep-parameter-range.js | 3 ++- client/views/workflow-results.js | 33 +++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/client/views/sweep-parameter-range.js b/client/views/sweep-parameter-range.js index 11bce31d02..16ae4a0fd1 100644 --- a/client/views/sweep-parameter-range.js +++ b/client/views/sweep-parameter-range.js @@ -50,6 +50,7 @@ module.exports = View.extend({ }, setParameterRangeFixed: function (e) { this.model.fixed = e.target.checked; + this.parent.getPlot("psweep"); }, setParameterRangeValue: function (e) { var value = this.model.range[e.target.value]; @@ -57,7 +58,7 @@ module.exports = View.extend({ value = value.toFixed(1) } this.parent.tsPlotData.parameters[this.model.name] = value; - this.parent.getPlot("ts-psweep"); + this.parent.getPlot(this.showFixed ? "psweep" : "ts-psweep"); }, viewParameterRangeValue: function (e) { let value = this.model.range[e.target.value]; diff --git a/client/views/workflow-results.js b/client/views/workflow-results.js index f993e928fc..0184eaa139 100644 --- a/client/views/workflow-results.js +++ b/client/views/workflow-results.js @@ -138,9 +138,10 @@ module.exports = View.extend({ $(this.queryByHook(type + "-plot-spinner")).css("display", "block"); }, getPlot: function (type) { + let data = this.getPlotData(type); + if(data === null) { return }; let self = this; this.cleanupPlotContainer(type); - let data = this.getPlotData(type); let storageKey = JSON.stringify(data); data['plt_data'] = this.getPlotLayoutData(); if(Boolean(this.plots[storageKey])) { @@ -168,7 +169,23 @@ module.exports = View.extend({ let data = {}; if(type === 'psweep'){ data['sim_type'] = "ParameterSweep"; - data['data_keys'] = {}; + if(this.model.settings.parameterSweepSettings.parameters.length <= 2) { + data['data_keys'] = {} + }else { + let dataKeys = this.getDataKeys(false); + let paramDiff = this.model.settings.parameterSweepSettings.parameters.length - Object.keys(dataKeys).length + if(paramDiff <= 0) { + $(this.queryByHook("too-many-params")).css("display", "block"); + return null; + } + if(paramDiff > 2) { + $(this.queryByHook("too-few-params")).css("display", "block"); + return null; + } + $(this.queryByHook("too-few-params")).css("display", "none"); + $(this.queryByHook("too-many-params")).css("display", "none"); + data['data_keys'] = dataKeys; + } data['plt_key'] = this.getPlotKey(type); }else if(type === "ts-psweep" || type === "ts-psweep-mp") { data['sim_type'] = "GillesPy2_PS"; @@ -221,9 +238,15 @@ module.exports = View.extend({ this.getPlot("ts-psweep"); }, getDataKeys: function (full) { - if(full) { - return this.tsPlotData.parameters; - } + if(full) { return this.tsPlotData.parameters; } + let self = this; + let parameters = {}; + this.model.settings.parameterSweepSettings.parameters.forEach(function (param) { + if(param.fixed){ + parameters[param.name] = self.tsPlotData[param.name]; + } + }); + return parameters; }, getType: function (storageKey) { let plotData = JSON.parse(storageKey) From bd17f52c2960c38bb2b7b32aab87af225d440286 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Jul 2021 16:22:24 -0400 Subject: [PATCH 15/69] Added parameter sweep visualization to the 3+D parameter sweeps. --- .../includes/parameterScanResults.pug | 173 ++++++++++++++---- client/views/sweep-parameter-range.js | 13 +- client/views/workflow-results.js | 48 ++--- 3 files changed, 171 insertions(+), 63 deletions(-) diff --git a/client/templates/includes/parameterScanResults.pug b/client/templates/includes/parameterScanResults.pug index 7c02d2860e..413d39b894 100644 --- a/client/templates/includes/parameterScanResults.pug +++ b/client/templates/includes/parameterScanResults.pug @@ -4,6 +4,18 @@ div#workflow-results.card.card-body h3.inline.mr-2 Results + div.inline + + ul.nav.nav-tabs + + li.nav-item + + a.nav-link.tab.active(data-toggle="tab" href="#time-series") Time Series + + li.nav-item + + a.nav-link.tab(data-toggle="tab" href="#sweep") Sweep + button.btn.btn-outline-collapse.mb-1(data-toggle="collapse" data-target="#collapse-results" data-hook="collapse-results-btn") - div.collapse.show(id="collapse-results" data-hook="workflow-results") @@ -23,62 +35,149 @@ div#workflow-results.card.card-body td: div(id="xaxis" data-hook="xaxis") td: div(id="yaxis" data-hook="yaxis") - div.card.card-body + div.tab-content + + div.tab-pane.active.card.card-body(id="time-series" data-hook="time-series") + + div + + h5.inline Parameter Sweep + + button.btn.btn-outline-collapse( + data-toggle="collapse" + data-target="#collapse-ts-psweep" + id="collapse-ts-psweep-btn" + data-hook="collapse-ts-psweep-btn" + data-trigger="collapse-plot-container" + data-type="ts-psweep" + ) - + + div.collapse.show(id="collapse-ts-psweep") + + div.row + + div.col-md-9 + + div(data-hook="ts-psweep-plot") + + div.spinner-border.workflow-plot(data-hook="ts-psweep-plot-spinner") + + button.btn.btn-primary.box-shadow(data-hook="ts-psweep-edit-plot" data-target="edit-plot" disabled) Edit Plot + + button.btn.btn-primary.box-shadow(data-hook="multiple-plots" data-type="ts-psweep-mp" style="display: none;" disabled) Multiple Plots + + button.btn.btn-primary.box-shadow( + data-hook="ts-psweep-download-png-custom" + data-target="download-png-custom" + data-type="ts-psweep" + disabled + ) Download PNG + + button.btn.btn-primary.box-shadow( + data-hook="ts-psweep-download-json" + data-target="download-json" + data-type="ts-psweep" + disabled + ) Download JSON + + div.col-md-3 + + div.head(data-hook="plot-type-header") + + h6 Plot Type + + div.mt-2(data-hook="plot-type-select") + + div.head + + h6 Parameters + + div.mt-2(data-hook="ts-parameter-ranges") + + div.tab-pane.mt-3(id="sweep" data-hook="sweep") + + div.inline.horizontal-space + + span.inline(for="species-of-interest") Variable of Interest: + + div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.species) + + div.inline(id="species-of-interest" data-hook="specie-of-interest-list") + + div.inline.horizontal-space + + span.inline(for="feature-extractor") Feature Extraction: + + div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.mapper) + + div.inline(id="feature-extractor" data-hook="feature-extraction-list") + + div.inline.horizontal-space(id="ensemble-aggragator-container" data-hook="ensemble-aggragator-container") + + span.inline(for="ensemble-aggragator") Ensemble Aggragator: + + div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.reducer) + + div.inline(id="ensemble-aggragator" data-hook="ensemble-aggragator-list") + + div.card.card-body - div + div - h5.inline Parameter Sweep + h5.inline Parameter Sweep - button.btn.btn-outline-collapse( - data-toggle="collapse" - data-target="#collapse-ts-psweep" - id="collapse-ts-psweep-btn" - data-hook="collapse-ts-psweep-btn" - data-trigger="collapse-plot-container" - data-type="ts-psweep" - ) - + button.btn.btn-outline-collapse( + data-toggle="collapse" + data-target="#collapse-psweep" + id="collapse-psweep-btn" + data-hook="collapse-psweep-btn" + data-trigger="collapse-plot-container" + data-type="psweep" + ) - - div.collapse.show(id="collapse-ts-psweep") + div.collapse.show(id="collapse-psweep") - div.row + div.row - div.col-md-9 + div.col-md-9 - div(data-hook="ts-psweep-plot") + div(data-hook="psweep-plot") - div.spinner-border.workflow-plot(data-hook="ts-psweep-plot-spinner") + div.spinner-border.workflow-plot(data-hook="psweep-plot-spinner") - button.btn.btn-primary.box-shadow(data-hook="ts-psweep-edit-plot" data-target="edit-plot" disabled) Edit Plot + div.mb-3(data-hook="too-few-params") - button.btn.btn-primary.box-shadow(data-hook="multiple-plots" data-type="ts-psweep-mp" style="display: none;" disabled) Multiple Plots + p.text-danger + | The number of variable parameters cannot exceed 2. Please add another fixed parameters. - button.btn.btn-primary.box-shadow( - data-hook="ts-psweep-download-png-custom" - data-target="download-png-custom" - data-type="ts-psweep" - disabled - ) Download PNG + div.mb-3(data-hook="too-many-params" style="display: none") - button.btn.btn-primary.box-shadow( - data-hook="ts-psweep-download-json" - data-target="download-json" - data-type="ts-psweep" - disabled - ) Download JSON + p.text-danger + | The number of variable parameters cannot be 0. Please remove a fixed parameter. - div.col-md-3 + button.btn.btn-primary.box-shadow(data-hook="psweep-edit-plot" data-target="edit-plot" disabled) Edit Plot - div.head(data-hook="plot-type-header") + button.btn.btn-primary.box-shadow( + data-hook="psweep-download-png-custom" + data-target="download-png-custom" + data-type="psweep" + disabled + ) Download PNG - h6 Plot Type + button.btn.btn-primary.box-shadow( + data-hook="psweep-download-json" + data-target="download-json" + data-type="psweep" + disabled + ) Download JSON - div.mt-2(data-hook="plot-type-select") + div.col-md-3 - div.head + div.head - h6 Parameters + h6 Parameters - div.mt-2(data-hook="parameter-ranges") + div.mt-2(data-hook="ps-parameter-ranges") button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish Presentation diff --git a/client/views/sweep-parameter-range.js b/client/views/sweep-parameter-range.js index 16ae4a0fd1..c7a7fb6677 100644 --- a/client/views/sweep-parameter-range.js +++ b/client/views/sweep-parameter-range.js @@ -36,11 +36,9 @@ module.exports = View.extend({ let self = this; this.model.fixed = false; this.showFixed = Boolean(attrs.showFixed) ? attrs.showFixed : false; - this.parameter = this.parent.model.model.parameters.filter(function (param) { - return param.compID === self.model.paramID - })[0]; let value = this.model.range[0].toString().includes('.') ? this.model.range[0] : this.model.range[0].toFixed(1) this.parent.tsPlotData.parameters[this.model.name] = value; + this.parent.fixedParameters[this.model.name] = value; }, render: function (attrs, options) { View.prototype.render.apply(this, arguments); @@ -57,8 +55,13 @@ module.exports = View.extend({ if(!value.toString().includes(".")) { value = value.toFixed(1) } - this.parent.tsPlotData.parameters[this.model.name] = value; - this.parent.getPlot(this.showFixed ? "psweep" : "ts-psweep"); + if(this.showFixed) { + this.parent.fixedParameters[this.model.name] = value; + this.parent.getPlot("psweep"); + }else{ + this.parent.tsPlotData.parameters[this.model.name] = value; + this.parent.getPlot("ts-psweep"); + } }, viewParameterRangeValue: function (e) { let value = this.model.range[e.target.value]; diff --git a/client/views/workflow-results.js b/client/views/workflow-results.js index 0184eaa139..9145d97c54 100644 --- a/client/views/workflow-results.js +++ b/client/views/workflow-results.js @@ -84,24 +84,20 @@ module.exports = View.extend({ var type = isEnsemble ? "stddevran" : "trajectories"; }else{ this.tsPlotData = {"parameters":{}}; + this.fixedParameters = {}; var type = "ts-psweep"; - if(!isParameterScan) { - this.renderSpeciesOfInterestView(); - this.renderFeatureExtractionView(); - if(isEnsemble) { - this.renderEnsembleAggragatorView(); - }else{ - $(this.queryByHook('ensemble-aggragator-container')).css("display", "none"); - } - this.getPlot("psweep"); - } + this.renderSpeciesOfInterestView(); + this.renderFeatureExtractionView(); if(isEnsemble) { + this.renderEnsembleAggragatorView(); this.renderPlotTypeSelectView(); this.tsPlotData["type"] = "stddevran" }else{ + $(this.queryByHook('ensemble-aggragator-container')).css("display", "none"); $(this.queryByHook('plot-type-header')).css("display", "none"); this.tsPlotData["type"] = "trajectories" } + this.getPlot("psweep"); this.renderSweepParameterView(); } this.getPlot(type); @@ -125,7 +121,7 @@ module.exports = View.extend({ error.css("display", "none"); }, 5000); }, - cleanupPlotContainer: function (type) { + cleanupPlotContainer: function (type, data) { let el = this.queryByHook(type + "-plot"); Plotly.purge(el); $(this.queryByHook(type + "-plot")).empty(); @@ -135,15 +131,18 @@ module.exports = View.extend({ $(this.queryByHook(type + "-download-json")).prop("disabled", true); $(this.queryByHook("multiple-plots")).prop("disabled", true); } - $(this.queryByHook(type + "-plot-spinner")).css("display", "block"); + if(data !== null) { + $(this.queryByHook(type + "-plot-spinner")).css("display", "block"); + } }, getPlot: function (type) { + let self = this; let data = this.getPlotData(type); + this.cleanupPlotContainer(type, data); if(data === null) { return }; - let self = this; - this.cleanupPlotContainer(type); let storageKey = JSON.stringify(data); data['plt_data'] = this.getPlotLayoutData(); + console.log(data) if(Boolean(this.plots[storageKey])) { let renderTypes = ['psweep', 'ts-psweep', 'ts-psweep-mp', 'mltplplt']; if(renderTypes.includes(type)) { @@ -190,7 +189,7 @@ module.exports = View.extend({ }else if(type === "ts-psweep" || type === "ts-psweep-mp") { data['sim_type'] = "GillesPy2_PS"; data['data_keys'] = this.getDataKeys(true); - data['plt_key'] = type === "ts-psweep-mp" ? "mltplplt" : this.tsPlotData['type']; + data['plt_key'] = type === "ts-psweep-mp" ? "mltplplt" : this.tsPlotData.type; }else { data['sim_type'] = "GillesPy2"; data['data_keys'] = {}; @@ -232,8 +231,8 @@ module.exports = View.extend({ } }, getTSPlotForType: function (e) { - this.tsPlotData['type'] = e.target.value; - let display = this.tsPlotData['type'] === "trajectories" ? "inline-block" : "none"; + this.tsPlotData.type = e.target.value; + let display = this.tsPlotData.type === "trajectories" ? "inline-block" : "none"; $(this.queryByHook("multiple-plots")).css("display", display); this.getPlot("ts-psweep"); }, @@ -243,7 +242,7 @@ module.exports = View.extend({ let parameters = {}; this.model.settings.parameterSweepSettings.parameters.forEach(function (param) { if(param.fixed){ - parameters[param.name] = self.tsPlotData[param.name]; + parameters[param.name] = self.fixedParameters[param.name]; } }); return parameters; @@ -252,7 +251,7 @@ module.exports = View.extend({ let plotData = JSON.parse(storageKey) if(plotData.sim_type === "GillesPy2") { return plotData.plt_key } if(plotData.sim_type === "GillesPy2_PS") { return "ts-psweep"} - return psweep + return "psweep" }, handleCollapsePlotContainerClick: function (e) { app.changeCollapseButtonText(this, e); @@ -410,10 +409,17 @@ module.exports = View.extend({ app.registerRenderSubview(this, speciesOfInterestView, "specie-of-interest-list"); }, renderSweepParameterView: function () { - let sweepParameterView = this.renderCollection( + let tsSweepParameterView = this.renderCollection( + this.model.settings.parameterSweepSettings.parameters, + SweepParametersView, + this.queryByHook("ts-parameter-ranges") + ); + let options = {viewOptions: {showFixed: true, parent: this}}; + let psSweepParameterView = this.renderCollection( this.model.settings.parameterSweepSettings.parameters, SweepParametersView, - this.queryByHook("parameter-ranges") + this.queryByHook("ps-parameter-ranges"), + options ); }, setTitle: function (e) { From ca840e6e3c6fa3eaf5ae6eead810278c239f4ca4 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Jul 2021 16:25:07 -0400 Subject: [PATCH 16/69] Fixed result filter issue that was causing the plot data to be mis shaped. --- stochss/handlers/util/stochss_job.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 754f01c1d9..fe295dbaca 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -132,12 +132,7 @@ def __get_filtered_1d_results(self, f_keys): results = self.__get_pickled_results() f_results = [] for key, result in results.items(): - passed = True - for f_key in f_keys: - if f_key not in key: - passed = False - break - if passed: + if self.__is_result_valid(f_keys, key): f_results.append(result) return f_results @@ -149,14 +144,8 @@ def __get_filtered_2d_results(self, f_keys, param): p_key = f"{param['name']}:{value}" p_results = [] for key, result in results.items(): - if p_key in key: - passed = True - for f_key in f_keys: - if f_key not in key: - passed = False - break - if passed: - p_results.append(result) + if p_key in key.split(',') and self.__is_result_valid(f_keys, key): + p_results.append(result) f_results.append(p_results) return f_results @@ -204,6 +193,14 @@ def __is_csv_dir(self, file): return True + @classmethod + def __is_result_valid(cls, f_keys, key): + for f_key in f_keys: + if f_key not in key.split(','): + return False + return True + + def __update_settings(self): settings = self.job['settings']['parameterSweepSettings'] if "parameters" not in settings.keys(): From 99ba1ffe5964b5cda094b3c658653a7753b9a727 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Jul 2021 16:46:58 -0400 Subject: [PATCH 17/69] Fixed render issue with parameter sliders. --- .../includes/parameterSweepResults.pug | 2 +- client/views/workflow-results.js | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/client/templates/includes/parameterSweepResults.pug b/client/templates/includes/parameterSweepResults.pug index ccd407c306..f58e287a3f 100644 --- a/client/templates/includes/parameterSweepResults.pug +++ b/client/templates/includes/parameterSweepResults.pug @@ -92,7 +92,7 @@ div#workflow-results.card.card-body h6 Parameters - div.mt-2(data-hook="parameter-ranges") + div.mt-2(data-hook="ts-parameter-ranges") div.tab-pane.active.mt-3(id="sweep" data-hook="sweep") diff --git a/client/views/workflow-results.js b/client/views/workflow-results.js index 9145d97c54..4c43cc639f 100644 --- a/client/views/workflow-results.js +++ b/client/views/workflow-results.js @@ -121,7 +121,7 @@ module.exports = View.extend({ error.css("display", "none"); }, 5000); }, - cleanupPlotContainer: function (type, data) { + cleanupPlotContainer: function (type) { let el = this.queryByHook(type + "-plot"); Plotly.purge(el); $(this.queryByHook(type + "-plot")).empty(); @@ -131,18 +131,15 @@ module.exports = View.extend({ $(this.queryByHook(type + "-download-json")).prop("disabled", true); $(this.queryByHook("multiple-plots")).prop("disabled", true); } - if(data !== null) { - $(this.queryByHook(type + "-plot-spinner")).css("display", "block"); - } + $(this.queryByHook(type + "-plot-spinner")).css("display", "block"); }, getPlot: function (type) { let self = this; + this.cleanupPlotContainer(type); let data = this.getPlotData(type); - this.cleanupPlotContainer(type, data); if(data === null) { return }; let storageKey = JSON.stringify(data); data['plt_data'] = this.getPlotLayoutData(); - console.log(data) if(Boolean(this.plots[storageKey])) { let renderTypes = ['psweep', 'ts-psweep', 'ts-psweep-mp', 'mltplplt']; if(renderTypes.includes(type)) { @@ -174,10 +171,12 @@ module.exports = View.extend({ let dataKeys = this.getDataKeys(false); let paramDiff = this.model.settings.parameterSweepSettings.parameters.length - Object.keys(dataKeys).length if(paramDiff <= 0) { + $(this.queryByHook(type + "-plot-spinner")).css("display", "none"); $(this.queryByHook("too-many-params")).css("display", "block"); return null; } if(paramDiff > 2) { + $(this.queryByHook(type + "-plot-spinner")).css("display", "none"); $(this.queryByHook("too-few-params")).css("display", "block"); return null; } @@ -414,13 +413,15 @@ module.exports = View.extend({ SweepParametersView, this.queryByHook("ts-parameter-ranges") ); - let options = {viewOptions: {showFixed: true, parent: this}}; - let psSweepParameterView = this.renderCollection( - this.model.settings.parameterSweepSettings.parameters, - SweepParametersView, - this.queryByHook("ps-parameter-ranges"), - options - ); + if(this.model.settings.parameterSweepSettings.parameters.length > 2) { + let options = {viewOptions: {showFixed: true, parent: this}}; + let psSweepParameterView = this.renderCollection( + this.model.settings.parameterSweepSettings.parameters, + SweepParametersView, + this.queryByHook("ps-parameter-ranges"), + options + ); + } }, setTitle: function (e) { this.plotArgs['title'] = e.target.value From 484947d2a5c9dbbabe2271fb456fc4348604d786 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 28 Jul 2021 17:04:30 -0400 Subject: [PATCH 18/69] Fixed loading page error when update project format. --- client/pages/loading-page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/pages/loading-page.js b/client/pages/loading-page.js index 69e573414b..8a83f98360 100644 --- a/client/pages/loading-page.js +++ b/client/pages/loading-page.js @@ -35,7 +35,7 @@ let LoadingPage = PageView.extend({ let urlParams = new URLSearchParams(window.location.search) this.filePath = urlParams.get("path"); this.action = urlParams.get("action"); - this.homeLink = path.append(app.getBasePath(), 'stochss/home'); + this.homeLink = path.join(app.getBasePath(), 'stochss/home'); }, render: function (attrs, options) { PageView.prototype.render.apply(this, arguments); From 2d75a13d013fe351e2717ebebc5d8f9e0dc9a5c9 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 29 Jul 2021 11:25:07 -0400 Subject: [PATCH 19/69] Fixed the copy link button. --- client/app.js | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/client/app.js b/client/app.js index f2ef090d74..af7f9d8934 100644 --- a/client/app.js +++ b/client/app.js @@ -213,28 +213,13 @@ documentSetup = () => { } copyToClipboard = (text) => { - if (window.clipboardData && window.clipboardData.setData) { - // Internet Explorer-specific code path to prevent textarea being shown while dialog is visible. - return window.clipboardData.setData("Text", text); - - } - else if (document.queryCommandSupported && document.queryCommandSupported("copy")) { - var textarea = document.createElement("textarea"); - textarea.textContent = text; - textarea.style.display = 'none'; // Prevent scrolling to bottom of page in Microsoft Edge. - document.body.appendChild(textarea); - textarea.select(); - try { - return document.execCommand("copy"); // Security exception may be thrown by some browsers. - } - catch (ex) { - console.warn("Copy to clipboard failed.", ex); - return false; - } - finally { - document.body.removeChild(textarea); - } - } + if (window.clipboardData && window.clipboardData.setData) { + // Internet Explorer-specific code path to prevent textarea being shown while dialog is visible. + return window.clipboardData.setData("Text", text); + } + else { + navigator.clipboard.writeText(text) + } } module.exports = { From 988c1623bdfd59c7fc8b3b85c56968b00cd8606b Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 29 Jul 2021 12:04:00 -0400 Subject: [PATCH 20/69] Changed the link to a button. Added success and fail messages to inform the user of the copy link status. Added success and fail callbacks to be executed when the copy link promise resolves. --- client/app.js | 4 ++-- client/modals.js | 10 ++++++---- client/pages/file-browser.js | 15 +++++++++++---- client/views/file-browser-view.js | 15 +++++++++++---- client/views/model-state-buttons.js | 15 +++++++++++---- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/client/app.js b/client/app.js index af7f9d8934..7b83d8114d 100644 --- a/client/app.js +++ b/client/app.js @@ -212,13 +212,13 @@ documentSetup = () => { }); } -copyToClipboard = (text) => { +copyToClipboard = (text, success, error) => { if (window.clipboardData && window.clipboardData.setData) { // Internet Explorer-specific code path to prevent textarea being shown while dialog is visible. return window.clipboardData.setData("Text", text); } else { - navigator.clipboard.writeText(text) + navigator.clipboard.writeText(text).then(success, error) } } diff --git a/client/modals.js b/client/modals.js index dcc8da5eac..6f3e68d4e8 100644 --- a/client/modals.js +++ b/client/modals.js @@ -224,7 +224,7 @@ let templates = { ` }, - presentationLinks : (modalID, title, name, headers, links) => { + presentationLinks : (modalID, title, headers, links) => { return `