diff --git a/docs/_images/icons/aixd_GenSampleEval.png b/docs/_images/icons/aixd_GenSampleEval.png deleted file mode 100644 index 668b839..0000000 Binary files a/docs/_images/icons/aixd_GenSampleEval.png and /dev/null differ diff --git a/docs/_images/icons/aixd_GenSelect.png b/docs/_images/icons/aixd_GenSelect.png deleted file mode 100644 index c40957c..0000000 Binary files a/docs/_images/icons/aixd_GenSelect.png and /dev/null differ diff --git a/docs/_images/icons/aixd_PlotContoursRequest.png b/docs/_images/icons/aixd_PlotContoursRequest.png new file mode 100644 index 0000000..2985924 Binary files /dev/null and b/docs/_images/icons/aixd_PlotContoursRequest.png differ diff --git a/docs/documentation.rst b/docs/documentation.rst index cd4ef5e..be8a524 100644 --- a/docs/documentation.rst +++ b/docs/documentation.rst @@ -171,7 +171,7 @@ Retrieves one sample from the dataset (at a given or random index) and instantia **Outputs** -- **sample** -- Summary of the retrieved sample. +- **sample_summary** -- Summary of the retrieved sample. DatasetSummary -------------- @@ -203,53 +203,16 @@ Runs a generation campaing to create new designs using the trained model. **Inputs** -- **requested_values** *(str)* -- List of requested values, each formatted as a string with the following format: 'variable_name:value'. +- **requested_values** *[List of (str)]* -- List of requested values, each formatted as a string with the following format: 'variable_name:value'. - **n_designs** *(int)* -- Number of designs to generate. -- **run** *(none)* -- Set to True to start the generation process. +- **generate** *(bool)* -- Set to True to start the generation process. +- **clear** *(bool)* -- Forget the previously generated designs. +- **pick_previous** *(bool)* -- Iterate backward through the list of generated designs, instantiate the previous sample. +- **pick_next** *(bool)* -- Iterate forward through the list of generated designs, instantiate the next sample. **Outputs** -- **predicions** -- List of generated designs. - -GenSampleEval -------------- -.. image:: _images/icons/aixd_GenSampleEval.png - :align: left - :height: 24 - :width: 24 - -Compares the requested values with the predicted and the actual values for a current design. - - -**Inputs** - -- **request** *(none)* -- Requested values. -- **predicted** *(none)* -- Predicted values (the generated sample). -- **real** *(none)* -- Actual values (the current design). - -**Outputs** - -- **comparison** -- Table with the comparison of the requested, predicted and actual values. - -GenSelect ---------- -.. image:: _images/icons/aixd_GenSelect.png - :align: left - :height: 24 - :width: 24 - -Selects one of the designs generated from the trained model. - - -**Inputs** - -- **predictions** *[List of (none)]* -- List of generated designs. -- **select** *(int)* -- Index of the selected design. - -**Outputs** - -- **sample_summary** -- Summary of the selected design. -- **generated_sample** -- Sample. +- **sample_summary** -- Selected sample. ModelDimensions --------------- @@ -376,6 +339,27 @@ Plots the distribution contours for each pair of variables from the data in the - **img** -- Bitmap image if output_type is 'static', otherwise None. +PlotContoursRequest +------------------- +.. image:: _images/icons/aixd_PlotContoursRequest.png + :align: left + :height: 24 + :width: 24 + +Plots the requested and predicted values against the distribution contours for each pair of the corresponding variables. + + +**Inputs** + +- **request** *[List of (str)]* -- List of requested values, each formatted as a string with the following format: 'variable_name:value'. +- **output_type** *(str)* -- Plot type: 'static' creates a bitmap image, 'interactive' launches an interactive plot in a browser. +- **plot** *(bool)* -- Set to True to (re-)create the plot. +- **scale** *(float)* -- Resize factor for the static plot. + +**Outputs** + +- **img** -- Bitmap image if output_type is 'static', otherwise None. + PlotCorrelations ---------------- .. image:: _images/icons/aixd_PlotCorrelations.png diff --git a/src/aixd_grasshopper/api.py b/src/aixd_grasshopper/api.py index dfdf90c..f9cd518 100644 --- a/src/aixd_grasshopper/api.py +++ b/src/aixd_grasshopper/api.py @@ -164,6 +164,18 @@ def get_dataobject_names_from_block(): return response +@app.route("/get_dataobject_types", methods=["POST"]) +def get_dataobject_types(): + data = request.data + data = json.loads(data) + session_id = data["session_id"] + sc = SessionController.create(session_id) + + result = sc.get_dataobject_types() + response = json.dumps(result, cls=DataEncoder) + return response + + @app.route("/plot_distrib_attributes", methods=["POST"]) def plot_distrib_attributes(): data = request.data @@ -206,6 +218,22 @@ def plot_contours(): return response +@app.route("/plot_contours_request", methods=["POST"]) +def plot_contours_request(): + data = request.data + data = json.loads(data) + session_id = data["session_id"] + sc = SessionController.create(session_id) + + output_type = data["output_type"] + requested_values = data["request"] + n_samples = data["n_samples"] + + result = sc.plot_contours_request(request=requested_values, n_samples=n_samples, output_type=output_type) + response = json.dumps(result, cls=DataEncoder) + return response + + @app.route("/design_parameters", methods=["GET"]) def get_design_parameters(): data = request.args diff --git a/src/aixd_grasshopper/components/aixd_DataObjectsNames/code.py b/src/aixd_grasshopper/components/aixd_DataObjectsNames/code.py index 1dfd8bc..a3c90b1 100644 --- a/src/aixd_grasshopper/components/aixd_DataObjectsNames/code.py +++ b/src/aixd_grasshopper/components/aixd_DataObjectsNames/code.py @@ -1,4 +1,7 @@ -# this code has been inspired by a forum post by "chanley" (2018.11.02) https://discourse.mcneel.com/t/can-i-instantiate-specific-component-on-the-canvas-with-a-script-python/74204/8 +# flake8: noqa + +# this code has been inspired by a forum post by "chanley" (2018.11.02) +# https://discourse.mcneel.com/t/can-i-instantiate-specific-component-on-the-canvas-with-a-script-python/74204/8 import Grasshopper import System.Drawing as sd @@ -9,21 +12,22 @@ comp = ghenv.Component ghdoc = comp.OnPingDocument() + def make_Panel(NickName, UserText, Pivot, Bounds): try: Panel = Grasshopper.Kernel.Special.GH_Panel() Panel.NickName = NickName Panel.UserText = UserText Panel.Properties.Colour = sd.Color.White - #Panel.Properties.Font = sd.Font("Trebuchet MS", 10) + # Panel.Properties.Font = sd.Font("Trebuchet MS", 10) Panel.Properties.Multiline = False Panel.Properties.DrawIndices = False Panel.Properties.DrawPaths = False - ghdoc.AddObject(Panel,False,ghdoc.ObjectCount+1) + ghdoc.AddObject(Panel, False, ghdoc.ObjectCount + 1) Panel.Attributes.Pivot = Pivot Panel.Attributes.Bounds = Bounds - except Exception, ex: - ghenv.Component.AddRuntimeMessage(Grasshopper.Kernel.GH_RuntimeMessageLevel.Warning,str(ex)) + except Exception(ex): + ghenv.Component.AddRuntimeMessage(Grasshopper.Kernel.GH_RuntimeMessageLevel.Warning, str(ex)) x = ghenv.Component.Attributes.DocObject.Attributes.Bounds.Right @@ -36,20 +40,21 @@ def make_Panel(NickName, UserText, Pivot, Bounds): errors = "" if not datablock: - datablock = [ "design_parameters","performance_attributes","inputML","outputML"] #all datablock names + datablock = ["design_parameters", "performance_attributes", "inputML", "outputML"] # all datablock names if get_names: - + for i, datablock_nickname in enumerate(datablock): panel_title = datablock_nickname - response = get_dataobject_names_from_block(session_id(),datablock_nickname) - text_items = response['names'] + response = get_dataobject_names_from_block(session_id(), datablock_nickname) + text_items = response["names"] if text_items: - text_str= "\n".join(text_items) - pt = sd.PointF(x + gap + i*gap, y + i*gap) - rect = sd.RectangleF(0,0,w,h) + text_str = "\n".join(text_items) + pt = sd.PointF(x + gap + i * gap, y + i * gap) + rect = sd.RectangleF(0, 0, w, h) make_Panel(panel_title, text_str, pt, rect) else: - errors+=response['msg']+"\n" + errors += response["msg"] + "\n" -if errors: ghenv.Component.AddRuntimeMessage(Grasshopper.Kernel.GH_RuntimeMessageLevel.Warning,str(errors)) +if errors: + ghenv.Component.AddRuntimeMessage(Grasshopper.Kernel.GH_RuntimeMessageLevel.Warning, str(errors)) diff --git a/src/aixd_grasshopper/components/aixd_DatasetOneSample/code.py b/src/aixd_grasshopper/components/aixd_DatasetOneSample/code.py index b106f84..522a2ff 100644 --- a/src/aixd_grasshopper/components/aixd_DatasetOneSample/code.py +++ b/src/aixd_grasshopper/components/aixd_DatasetOneSample/code.py @@ -1,117 +1,25 @@ # flake8: noqa -import Grasshopper +from scriptcontext import sticky as st from aixd_grasshopper.gh_ui import get_one_sample from aixd_grasshopper.gh_ui_helper import component_id +from aixd_grasshopper.gh_ui_helper import instantiate_sample, sample_summary from aixd_grasshopper.gh_ui_helper import session_id +cid = component_id(session_id(), ghenv.Component, "GetOneSample") + + if not item: item = -1 if get: - sample = get_one_sample(session_id(), item) - - -# TODO - clean up: - - -# ------------------------------------------------------------------------------- -def find_component_by_nickname(ghdoc, component_nickname): - found = [] - # all_objects = ghdoc.Objects - all_objects = ghenv.Component.OnPingDocument().Objects - for obj in all_objects: - - if obj.Attributes.PathName == component_nickname: - # if obj.NickName == component_nickname: - found.append(obj) - - if not found: - print("No ghcomponent found with a nickname {}.".format(component_nickname)) - return - if len(found) > 1: - print("{len(found)} ghcomponents found with the nickname {} - will return None.".format(component_nickname)) - return - return found[0] - - -def set_value(component, val): - component.Script_ClearPersistentData() - component.AddPersistentData(val) - component.ExpireSolution(True) - - -def set_values(component, vals): - """ - Data type of vals must match the type of the component. - See TYPES list. - """ - ghtype = TYPES[component.TypeName] - - component.Script_ClearPersistentData() - if not isinstance(vals, list): - vals = [vals] - for v in vals: - component.PersistentData.Append(ghtype(v)) - component.ExpireSolution(True) - - -TYPES = { - "Arc": Grasshopper.Kernel.Types.GH_Arc, - "Boolean": Grasshopper.Kernel.Types.GH_Boolean, - "Box": Grasshopper.Kernel.Types.GH_Box, - "Brep": Grasshopper.Kernel.Types.GH_Brep, - "Circle": Grasshopper.Kernel.Types.GH_Circle, - "ComplexNumber": Grasshopper.Kernel.Types.GH_ComplexNumber, - "Curve": Grasshopper.Kernel.Types.GH_Curve, - "Guid": Grasshopper.Kernel.Types.GH_Guid, - "Integer": Grasshopper.Kernel.Types.GH_Integer, - "Interval": Grasshopper.Kernel.Types.GH_Interval, - "Interval2D": Grasshopper.Kernel.Types.GH_Interval2D, - "Line": Grasshopper.Kernel.Types.GH_Line, - "Mesh": Grasshopper.Kernel.Types.GH_Mesh, - "Number": Grasshopper.Kernel.Types.GH_Number, - "Plane": Grasshopper.Kernel.Types.GH_Plane, - "Point": Grasshopper.Kernel.Types.GH_Point, - "Rectangle": Grasshopper.Kernel.Types.GH_Rectangle, - "String": Grasshopper.Kernel.Types.GH_String, - "SubD": Grasshopper.Kernel.Types.GH_SubD, - "Surface": Grasshopper.Kernel.Types.GH_Surface, - "Vector": Grasshopper.Kernel.Types.GH_Vector, - "Text": Grasshopper.Kernel.Types.GH_String, -} - - -# ------------------------------------------------------------------------------- - - -if sample: - - for dp_name, dp_vals in sample["design_parameters"].items(): - # print dp_name, dp_vals - component_name = "GENERATED_{}".format(dp_name) - component = find_component_by_nickname(ghdoc, component_name) - print("{}: {}".format(component_name, dp_vals)) - - if not dp_vals: - print("empty values for {}".format(comp)) - else: - set_values(component, dp_vals) - + st[cid] = get_one_sample(session_id(), item) -# ------------------------------------------------------------------------------- -if sample: - txt = "" - txt += "Design Parameters:\n\n" - for name, values in sample["design_parameters"].items(): - txt += "{}: {}\n".format(name, values) - txt += "\n" - txt += "Performance Attributes:\n\n" - for name, values in sample["performance_attributes"].items(): - txt += "{}: {}\n".format(name, values) +ghdoc = ghenv.Component.OnPingDocument() - sample = txt - trigger = False +if cid in st.keys(): + sample = st[cid] + instantiate_sample(ghdoc, sample) -# sample = None + sample_summary = sample_summary(sample) diff --git a/src/aixd_grasshopper/components/aixd_DatasetOneSample/metadata.json b/src/aixd_grasshopper/components/aixd_DatasetOneSample/metadata.json index 7681d8c..47a62c7 100644 --- a/src/aixd_grasshopper/components/aixd_DatasetOneSample/metadata.json +++ b/src/aixd_grasshopper/components/aixd_DatasetOneSample/metadata.json @@ -1,6 +1,6 @@ { "name": "DatasetOneSample", - "nickname": "OneSample", + "nickname": "DatasetOneSample", "category": "AIXD", "subcategory": "2 Dataset", "description": "Retrieves one sample from the dataset (at a given or random index) and instantiates it in the parametric model.", @@ -25,7 +25,7 @@ "outputParameters": [ { - "name": "sample", + "name": "sample_summary", "description": "Summary of the retrieved sample." } ] diff --git a/src/aixd_grasshopper/components/aixd_GenSampleEval/code.py b/src/aixd_grasshopper/components/aixd_GenSampleEval/code.py deleted file mode 100644 index a6f1c93..0000000 --- a/src/aixd_grasshopper/components/aixd_GenSampleEval/code.py +++ /dev/null @@ -1,40 +0,0 @@ -# flake8: noqa -summarytxt = {} # key =obj name, values= [req, pred, real] - -request_dict = {} -for rv in request: - k, v = rv.split(":") - v = float(v) - request_dict[k] = v -# print request_dict - - -real = [list(b) for b in real.Branches] -real_dict = {k: v for k, v in real} - -predicted_dict = predicted.dict["performance_attributes"] -keys = predicted_dict.keys() - - -txt = "{:>12} |".format("name") -for k in keys: - txt += "{:>12} |".format(k) -txt += "\n" - -txt += "\n{:>12} |".format("request") -for k in keys: - if k in request_dict: - txt += "{:>12.2f} |".format(request_dict[k]) - else: - txt += "{:>12} |".format("*") - -txt += "\n{:>12} |".format("pred") -for k in keys: - txt += "{:>12.2f} |".format(predicted_dict[k]) - -txt += "\n{:>12} |".format("real") -for k in keys: - txt += "{:>12.2f} |".format(real_dict[k]) - - -comparison = txt diff --git a/src/aixd_grasshopper/components/aixd_GenSampleEval/icon.png b/src/aixd_grasshopper/components/aixd_GenSampleEval/icon.png deleted file mode 100644 index 668b839..0000000 Binary files a/src/aixd_grasshopper/components/aixd_GenSampleEval/icon.png and /dev/null differ diff --git a/src/aixd_grasshopper/components/aixd_GenSampleEval/metadata.json b/src/aixd_grasshopper/components/aixd_GenSampleEval/metadata.json deleted file mode 100644 index 80619be..0000000 --- a/src/aixd_grasshopper/components/aixd_GenSampleEval/metadata.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "GenSampleEval", - "nickname": "GenSampleEval", - "category": "AIXD", - "subcategory": "4 Generator", - "description": "Compares the requested values with the predicted and the actual values for a current design.", - "exposure": 2, - "ghpython": { - "isAdvancedMode": false, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "request", - "description": "Requested values.", - "typeHintID": "none", - "scriptParamAccess": 0 - }, - { - "name": "predicted", - "description": "Predicted values (the generated sample).", - "typeHintID": "none", - "scriptParamAccess": 0 - }, - { - "name": "real", - "description": "Actual values (the current design).", - "typeHintID": "none", - "scriptParamAccess": 0 - } - ], - - "outputParameters": [ - { - "name": "comparison", - "description": "Table with the comparison of the requested, predicted and actual values." - } - ] - } -} diff --git a/src/aixd_grasshopper/components/aixd_GenSelect/code.py b/src/aixd_grasshopper/components/aixd_GenSelect/code.py deleted file mode 100644 index 8363d7c..0000000 --- a/src/aixd_grasshopper/components/aixd_GenSelect/code.py +++ /dev/null @@ -1,106 +0,0 @@ -# flake8: noqa -# TODO: outsource the value setting methods -# TODO: turn orange if index out of range - - -# from aixd_grasshopper.rhino_inside import find_component_by_nickname -import Grasshopper -from ghpythonlib.treehelpers import list_to_tree - -dp_pred = predictions[select].dict["design_parameters"] -pa_pred = predictions[select].dict["performance_attributes"] - -generated_sample = predictions[select] -print(dp_pred) -print(pa_pred) -print(ghdoc.Objects.Count) - - -def find_component_by_nickname(ghdoc, component_nickname): - found = [] - # all_objects = ghdoc.Objects - all_objects = ghenv.Component.OnPingDocument().Objects - for obj in all_objects: - - if obj.Attributes.PathName == component_nickname: - # if obj.NickName == component_nickname: - found.append(obj) - - if not found: - print("No ghcomponent found with a nickname {}.".format(component_nickname)) - return - if len(found) > 1: - print("{len(found)} ghcomponents found with the nickname {} - will return None.".format(component_nickname)) - return - return found[0] - - -def set_value(component, val): - component.Script_ClearPersistentData() - component.AddPersistentData(val) - component.ExpireSolution(True) - - -def set_values(component, vals): - """ - Data type of vals must match the type of the component. - See TYPES list. - """ - ghtype = TYPES[component.TypeName] - - component.Script_ClearPersistentData() - if not isinstance(vals, list): - vals = [vals] - for v in vals: - component.PersistentData.Append(ghtype(v)) - component.ExpireSolution(True) - - -TYPES = { - "Arc": Grasshopper.Kernel.Types.GH_Arc, - "Boolean": Grasshopper.Kernel.Types.GH_Boolean, - "Box": Grasshopper.Kernel.Types.GH_Box, - "Brep": Grasshopper.Kernel.Types.GH_Brep, - "Circle": Grasshopper.Kernel.Types.GH_Circle, - "ComplexNumber": Grasshopper.Kernel.Types.GH_ComplexNumber, - "Curve": Grasshopper.Kernel.Types.GH_Curve, - "Guid": Grasshopper.Kernel.Types.GH_Guid, - "Integer": Grasshopper.Kernel.Types.GH_Integer, - "Interval": Grasshopper.Kernel.Types.GH_Interval, - "Interval2D": Grasshopper.Kernel.Types.GH_Interval2D, - "Line": Grasshopper.Kernel.Types.GH_Line, - "Mesh": Grasshopper.Kernel.Types.GH_Mesh, - "Number": Grasshopper.Kernel.Types.GH_Number, - "Plane": Grasshopper.Kernel.Types.GH_Plane, - "Point": Grasshopper.Kernel.Types.GH_Point, - "Rectangle": Grasshopper.Kernel.Types.GH_Rectangle, - "String": Grasshopper.Kernel.Types.GH_String, - "SubD": Grasshopper.Kernel.Types.GH_SubD, - "Surface": Grasshopper.Kernel.Types.GH_Surface, - "Vector": Grasshopper.Kernel.Types.GH_Vector, - "Text": Grasshopper.Kernel.Types.GH_String, -} - - -for dp_name, dp_vals in dp_pred.items(): - # print dp_name, dp_vals - component_name = "GENERATED_{}".format(dp_name) - component = find_component_by_nickname(ghdoc, component_name) - print("{}: {}".format(component_name, dp_vals)) - - if not dp_vals: - print("empty values for {}".format(comp)) - else: - set_values(component, dp_vals) - - txt = "Generated & Predicted\n" - txt += "---------------------\n\n" - txt += "Design Parameters:\n\n" - for name, values in dp_pred.items(): - txt += "{}: {}\n".format(name, values) - txt += "\n" - txt += "Performance Attributes:\n\n" - for name, values in pa_pred.items(): - txt += "{}: {}\n".format(name, values) - - sample_summary = txt diff --git a/src/aixd_grasshopper/components/aixd_GenSelect/icon.png b/src/aixd_grasshopper/components/aixd_GenSelect/icon.png deleted file mode 100644 index c40957c..0000000 Binary files a/src/aixd_grasshopper/components/aixd_GenSelect/icon.png and /dev/null differ diff --git a/src/aixd_grasshopper/components/aixd_GenSelect/metadata.json b/src/aixd_grasshopper/components/aixd_GenSelect/metadata.json deleted file mode 100644 index f794771..0000000 --- a/src/aixd_grasshopper/components/aixd_GenSelect/metadata.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "GenSelect", - "nickname": "GenSelect", - "category": "AIXD", - "subcategory": "4 Generator", - "description": "Selects one of the designs generated from the trained model.", - "exposure": 2, - "ghpython": { - "isAdvancedMode": false, - "iconDisplay": 0, - "inputParameters": [ - { - "name": "predictions", - "description": "List of generated designs.", - "typeHintID": "none", - "scriptParamAccess": 1 - }, - { - "name": "select", - "description": "Index of the selected design.", - "typeHintID": "int", - "scriptParamAccess": 0 - } - ], - - "outputParameters": [ - { - "name": "sample_summary", - "description": "Summary of the selected design." - }, - { - "name": "generated_sample", - "description": "Sample." - } - ] - } -} diff --git a/src/aixd_grasshopper/components/aixd_Generator/code.py b/src/aixd_grasshopper/components/aixd_Generator/code.py index d090bfd..ba904a3 100644 --- a/src/aixd_grasshopper/components/aixd_Generator/code.py +++ b/src/aixd_grasshopper/components/aixd_Generator/code.py @@ -1,55 +1,58 @@ # flake8: noqa -# reformat request data -# original input should be a list of strings -# each string should have a form: attribute_name:value - -if not n_designs or n_designs < 1: - n_designs = 1 - -request_ok = True -try: - request_dict = {} - for rv in requested_values: - rv = rv.strip() - k, v = rv.split(":") - v = float(v) - request_dict[k] = v - print(request_dict) - request_ok = True - -except: - request_ok = False - # raise ValueError("Request could not be processed") - -# ------------------------------------------------------------------------------- +from scriptcontext import sticky as st +from aixd_grasshopper.gh_ui import get_dataobject_types +from aixd_grasshopper.gh_ui import request_designs +from aixd_grasshopper.gh_ui_helper import component_id +from aixd_grasshopper.gh_ui_helper import instantiate_sample +from aixd_grasshopper.gh_ui_helper import sample_summary as sample_summary_f +from aixd_grasshopper.gh_ui_helper import session_id +from aixd_grasshopper.gh_ui_helper import reformat_request -class wrapper: - def __init__(self, dict): - self.dict = dict +cid = component_id(session_id(), ghenv.Component, "run_generation") +item = component_id(session_id(), ghenv.Component, "pick_sample") - def __repr__(self): - return "Generated design" +""" +requested_values: a multiline string with variable_name:values tuples. +""" +if not n_designs or n_designs < 1: + n_designs = 1 # ------------------------------------------------------------------------------- -from scriptcontext import sticky as st - -from aixd_grasshopper.gh_ui import request_designs -from aixd_grasshopper.gh_ui_helper import component_id -from aixd_grasshopper.gh_ui_helper import session_id +if clear and cid in st.keys(): + del st[cid] + st[item] = None + ghenv.Component.Message = "no samples" -cid = component_id(session_id(), ghenv.Component, "Generator") +if generate and requested_values: + variable_types = get_dataobject_types(session_id())["dataobject_types"] -if run and request_ok: + request_dict = reformat_request(requested_values, variable_types) + st[item] = 0 ghenv.Component.Message = "Running" st[cid] = request_designs(session_id(), request_dict, n_designs) ghenv.Component.Message = "Finished" +if pick_previous: + st[item] -= 1 +if pick_next: + st[item] += 1 + if cid in st.keys(): - predictions = [wrapper(x) for x in result] - ghenv.Component.Message = "Ready" + samples = st[cid]["generated"] + n = len(samples) + i = st[item] % n + + ghenv.Component.Message = "sample {}/{}".format(i + 1, n) + + sample_dict = samples[i] + ghdoc = ghenv.Component.OnPingDocument() + instantiate_sample(ghdoc, sample_dict) # sends design parameter values to the parametric model + + # --- output --- + sample_summary = "Predicted/Generated:\n--------------------\n\n" + sample_summary_f(sample_dict) diff --git a/src/aixd_grasshopper/components/aixd_Generator/metadata.json b/src/aixd_grasshopper/components/aixd_Generator/metadata.json index fb877cf..baebdba 100644 --- a/src/aixd_grasshopper/components/aixd_Generator/metadata.json +++ b/src/aixd_grasshopper/components/aixd_Generator/metadata.json @@ -13,7 +13,7 @@ "name": "requested_values", "description": "List of requested values, each formatted as a string with the following format: 'variable_name:value'.", "typeHintID": "str", - "scriptParamAccess": 0 + "scriptParamAccess": 1 }, { "name": "n_designs", @@ -22,18 +22,35 @@ "scriptParamAccess": 0 }, { - "name": "run", + "name": "generate", "description": "Set to True to start the generation process.", - "typeHintID": "none", + "typeHintID": "bool", + "scriptParamAccess": 0 + }, + { + "name": "clear", + "description": "Forget the previously generated designs.", + "typeHintID": "bool", + "scriptParamAccess": 0 + }, + { + "name": "pick_previous", + "description": "Iterate backward through the list of generated designs, instantiate the previous sample.", + "typeHintID": "bool", + "scriptParamAccess": 0 + }, + { + "name": "pick_next", + "description": "Iterate forward through the list of generated designs, instantiate the next sample.", + "typeHintID": "bool", "scriptParamAccess": 0 } ], - "outputParameters": [ { - "name": "predicions", - "description": "List of generated designs." + "name": "sample_summary", + "description": "Selected sample." } ] } -} +} \ No newline at end of file diff --git a/src/aixd_grasshopper/components/aixd_PlotContours/metadata.json b/src/aixd_grasshopper/components/aixd_PlotContours/metadata.json index 8138aff..681994d 100644 --- a/src/aixd_grasshopper/components/aixd_PlotContours/metadata.json +++ b/src/aixd_grasshopper/components/aixd_PlotContours/metadata.json @@ -1,6 +1,6 @@ { "name": "PlotContours", - "nickname": "PlotCountours", + "nickname": "PlotContours", "category": "AIXD", "subcategory": "5 Plotter", "description": "Plots the distribution contours for each pair of variables from the data in the dataset.", diff --git a/src/aixd_grasshopper/components/aixd_PlotContoursRequest/code.py b/src/aixd_grasshopper/components/aixd_PlotContoursRequest/code.py new file mode 100644 index 0000000..d73b370 --- /dev/null +++ b/src/aixd_grasshopper/components/aixd_PlotContoursRequest/code.py @@ -0,0 +1,26 @@ +# flake8: noqa +from scriptcontext import sticky as st +from aixd_grasshopper.gh_ui import plot_contours_request +from aixd_grasshopper.gh_ui import get_dataobject_types +from aixd_grasshopper.gh_ui_helper import session_id +from aixd_grasshopper.gh_ui_helper import component_id +from aixd_grasshopper.gh_ui_helper import convert_str_to_bitmap +from aixd_grasshopper.gh_ui_helper import reformat_request + + +cid = component_id(session_id, ghenv.Component, "create_dataset_object") + +n_samples = 3 + +if plot: + variable_types = get_dataobject_types(session_id())["dataobject_types"] + request_dict = reformat_request(request, variable_types) + print request_dict + st[cid] = plot_contours_request(session_id(), request_dict, n_samples, output_type) # if output_type interactive: will launch the plotly fig in browser + +if cid in st.keys(): + print st[cid] + #TODO: add error msg here + if output_type == "static" and 'imgstr' in st[cid].keys(): + imgstr = st[cid]['imgstr'] + img = convert_str_to_bitmap(imgstr, scale) \ No newline at end of file diff --git a/src/aixd_grasshopper/components/aixd_PlotContoursRequest/icon.png b/src/aixd_grasshopper/components/aixd_PlotContoursRequest/icon.png new file mode 100644 index 0000000..2985924 Binary files /dev/null and b/src/aixd_grasshopper/components/aixd_PlotContoursRequest/icon.png differ diff --git a/src/aixd_grasshopper/components/aixd_PlotContoursRequest/metadata.json b/src/aixd_grasshopper/components/aixd_PlotContoursRequest/metadata.json new file mode 100644 index 0000000..bb5d807 --- /dev/null +++ b/src/aixd_grasshopper/components/aixd_PlotContoursRequest/metadata.json @@ -0,0 +1,44 @@ +{ + "name": "PlotContoursRequest", + "nickname": "PlotContReq", + "category": "AIXD", + "subcategory": "5 Plotter", + "description": "Plots the requested and predicted values against the distribution contours for each pair of the corresponding variables.", + "exposure": 2, + "ghpython": { + "isAdvancedMode": false, + "iconDisplay": 0, + "inputParameters": [ + { + "name": "request", + "description": "List of requested values, each formatted as a string with the following format: 'variable_name:value'.", + "typeHintID": "str", + "scriptParamAccess": 1 + }, + { + "name": "output_type", + "description": "Plot type: 'static' creates a bitmap image, 'interactive' launches an interactive plot in a browser.", + "typeHintID": "str", + "scriptParamAccess": 0 + }, + { + "name": "plot", + "description": "Set to True to (re-)create the plot.", + "typeHintID": "bool", + "scriptParamAccess": 0 + }, + { + "name": "scale", + "description": "Resize factor for the static plot.", + "typeHintID": "float", + "scriptParamAccess": 0 + } + ], + "outputParameters": [ + { + "name": "img", + "description": "Bitmap image if output_type is 'static', otherwise None." + } + ] + } +} \ No newline at end of file diff --git a/src/aixd_grasshopper/controller.py b/src/aixd_grasshopper/controller.py index 9ed0ad4..6af9749 100644 --- a/src/aixd_grasshopper/controller.py +++ b/src/aixd_grasshopper/controller.py @@ -11,7 +11,7 @@ import torch from aixd.data.data_blocks import DesignParameters from aixd.data.data_blocks import PerformanceAttributes -from aixd.data.data_objects import DataInt +from aixd.data.data_objects import DataInt, DataBool, DataCategorical, DataReal from aixd.data.dataset import Dataset from aixd.data.utils_data import reformat_dataframe_to_dataframeflat from aixd.data.utils_data import reformat_dataframeflat_to_dict @@ -229,6 +229,33 @@ def get_dataobject_names_from_block(self, datablock_nickname): return {"msg": "", "names": self.datamodule.output_ml_dblock.names_list} return {"msg": f"Wrong block nickname: {datablock_nickname}.", "names": []} + def get_dataobject_types(self): + """ + Returns names of the data types of the dataobjects in the dataset. + """ + if not self.dataset: + error = "Dataset is not loaded." + raise ValueError(error) + + all_dataobjects = self.dataset.data_objects + # cannot use this because boolean are declared as categorical + # dataobject_types = {d.name: d.type for d in all_dataobjects} + + dataobject_types = {} + for d in all_dataobjects: + if isinstance(d, DataInt): + dataobject_types[d.name] = "integer" + elif isinstance(d, DataReal): + dataobject_types[d.name] = "real" + elif isinstance(d, DataCategorical): + dataobject_types[d.name] = "categorical" + elif isinstance(d, DataBool): + dataobject_types[d.name] = "boolean" + else: + dataobject_types[d.name] = "unsupported" + + return {"msg": "", "dataobject_types": dataobject_types} + def get_design_parameters(self): # TODO: rename if not self.dataset: @@ -287,6 +314,22 @@ def plot_contours(self, dataobjects, output_type): fig = plotter.contours2d(block=block, attributes=dataobjects) return _fig_output(fig, output_type) + def plot_contours_request(self, request, n_samples, output_type): + """ + request: dictionary where keys are the names of dataobjects (usually performance attributes), and values the requested target value(s). + """ + if not self.dataset: + raise ValueError("Dataset is not loaded.") + if not self.model: + raise ValueError("Model is not loaded.") + + plotter = Plotter(datamodule=self.datamodule, output=None) + gen = Generator(model=self.model, datamodule=self.datamodule, over_sample=10) + _, detailed_results = gen.generation(request=request, n_samples=n_samples, format_out="dict_list") + + fig = plotter.generation_scatter([detailed_results], n_samples=n_samples) + return _fig_output(fig, output_type) + def model_setup(self, model_type, inputML, outputML, latent_dim, layer_widths, batch_size): # TODO: set defaults here if missing? if not self.dataset: @@ -309,7 +352,7 @@ def model_setup(self, model_type, inputML, outputML, latent_dim, layer_widths, b self.datamodule = datamodule save_dir = self.dataset_path - + if model_type == "CAE": model = CondAEModel.from_datamodule( datamodule, layer_widths=layer_widths, latent_dim=latent_dim, save_dir=save_dir @@ -320,7 +363,7 @@ def model_setup(self, model_type, inputML, outputML, latent_dim, layer_widths, b ) else: raise ValueError("Model type not recognized. Choose 'CAE' or 'CVAE'.") - + self.model = model self.model_is_trained = False @@ -378,7 +421,7 @@ def model_load(self, model_type, checkpoint_path, checkpoint_name): model = CondVAEModel.load_model_from_checkpoint(checkpoint_filepath) else: raise ValueError("Model type not recognized. Choose 'CAE' or 'CVAE'.") - + self.model = model self.model_is_trained = True self.datamodule = self._datamodule_from_dataset() @@ -455,7 +498,7 @@ def request_designs(self, request, n_samples=1): if not self.dataset: raise ValueError("Dataset is not loaded.") if not self.model: - raise ValueError("NN model is not loaded.") + raise ValueError("Model is not loaded.") gen = Generator(model=self.model, datamodule=self.datamodule, over_sample=100) new_designs = gen.generation(request=request, n_samples=n_samples, format_out="dict_list")[0] diff --git a/src/aixd_grasshopper/gh_ui.py b/src/aixd_grasshopper/gh_ui.py index 5d6c96d..62c18e8 100644 --- a/src/aixd_grasshopper/gh_ui.py +++ b/src/aixd_grasshopper/gh_ui.py @@ -40,6 +40,11 @@ def get_dataobject_names_from_block(session_id, datablock_nickname): return http_post_request(action="get_dataobject_names_from_block", data=data) +def get_dataobject_types(session_id): + data = {"session_id": session_id} + return http_post_request(action="get_dataobject_types", data=data) + + def get_one_sample(session_id, i): data = {"session_id": session_id, "item": i} return http_post_request(action="get_one_sample", data=data) @@ -55,6 +60,11 @@ def plot_contours(session_id, attributes, output_type): return http_post_request(action="plot_contours", data=data) +def plot_contours_request(session_id, request, n_samples, output_type): + data = {"session_id": session_id, "request": request, "n_samples": n_samples, "output_type": output_type} + return http_post_request(action="plot_contours_request", data=data) + + def plot_correlations(session_id, attributes, output_type): data = {"session_id": session_id, "attributes": attributes, "output_type": output_type} return http_post_request(action="plot_correlations", data=data) @@ -71,7 +81,12 @@ def model_train(session_id, epochs, wb): def model_load(session_id, model_type, checkpoint_name, checkpoint_path): - data = {"session_id": session_id, "model_type": model_type, "checkpoint_name": checkpoint_name, "checkpoint_path": checkpoint_path} + data = { + "session_id": session_id, + "model_type": model_type, + "checkpoint_name": checkpoint_name, + "checkpoint_path": checkpoint_path, + } return http_post_request(action="model_load", data=data) diff --git a/src/aixd_grasshopper/gh_ui_helper.py b/src/aixd_grasshopper/gh_ui_helper.py index 52a7f0f..7e5eaad 100644 --- a/src/aixd_grasshopper/gh_ui_helper.py +++ b/src/aixd_grasshopper/gh_ui_helper.py @@ -169,6 +169,33 @@ def ghparam_get_values(component, compute=False): return [x.Value for x in component.VolatileData[0]] +def sample_summary(sample_dict): + txt = "" + txt += "Design Parameters:\n\n" + for name, values in sample_dict["design_parameters"].items(): + txt += "{}: {}\n".format(name, values) + txt += "\n" + txt += "Performance Attributes:\n\n" + for name, values in sample_dict["performance_attributes"].items(): + txt += "{}: {}\n".format(name, values) + return txt + + +def instantiate_sample(ghdoc, sample_dict): + """ + Apply design parameter values to the parametric model. + """ + + for dp_name, dp_vals in sample_dict["design_parameters"].items(): + component_name = "GENERATED_{}".format(dp_name) + component = find_component_by_nickname(ghdoc, component_name) + + if not dp_vals: + print("No values for {}!".format(dp_name)) + else: + ghparam_set_values(component, dp_vals) + + def convert_str_to_bitmap(base64_imgstr, scale=1.0): """Get image from string and rescale.""" @@ -181,3 +208,44 @@ def convert_str_to_bitmap(base64_imgstr, scale=1.0): size = Size(bitmap.Width * scale, bitmap.Height * scale) bitmap = Bitmap(bitmap, size) return bitmap + + +def recast_type(value, typename): + if typename == "real": + return float(value) + if typename == "integer": + return int(value) + if typename == "categorical": + return str(value) + if typename == "bool": + return bool(value) + + +def reformat_request(request_string, variable_types): + """ + Reformats the request string into a dictionary with the correct types. + variable_types: dictionary with variable names as keys and types ('real', 'int', 'categorical', 'bool') as values, + used to restore the data type. + """ + request_dict = {} + + for rv in request_string: + rv = rv.strip() + + # split into name:value(s) + k, v = rv.split(":") + + if k not in variable_types.keys(): + raise ValueError( + "'{0}' is not a valid variable name. There is not variable with this name in the dataset.".format(k) + ) + + # check if a list or a single value + if v[0] == "[" and v[-1] == "]": + v = v[1:-1] + v = v.split(",") + v = [recast_type(vi, variable_types[k]) for vi in v] + else: + v = recast_type(v, variable_types[k]) + request_dict[k] = v + return request_dict \ No newline at end of file